From 41e18055b7d6240f18ff6826ebdb5356a9db3f3d Mon Sep 17 00:00:00 2001 From: Raruto Date: Thu, 2 Feb 2023 15:33:19 +0100 Subject: [PATCH 01/48] refactor `src/app/gui/queryresults/queryresultsservice.js` - add FIXME comments (eg. please "add function description") - code cleanup (eg. tons of weird ternary conditions, short-circuiting logic and missing brackets!) - full function rewrite `QueryResultsService::updateLayerResultFeatures(layer)` - notable function rewrite `QueryResultsService::_digestFeaturesForLayers (featuresForLayers)` --- package.json | 1 + .../gui/queryresults/queryresultsservice.js | 2534 ++++++++++------- 2 files changed, 1537 insertions(+), 998 deletions(-) diff --git a/package.json b/package.json index b0dbc69a9..8b8ada1dd 100755 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "homepage": "https://github.com/g3w-suite/g3w-client.git", "dependencies": { "regenerator-runtime": "^0.13.9", + "util-deprecate": "^1.0.2", "shp-write": "^0.3.2", "vue": "2.6.12", "vue-color": "^2.8.1", diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index b7cbdc1d4..9b30aaffa 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -1,5 +1,5 @@ import GUI from 'services/gui'; -import {G3W_FID, LIST_OF_RELATIONS_TITLE, LIST_OF_RELATIONS_ID} from 'constant'; +import { G3W_FID, LIST_OF_RELATIONS_TITLE, LIST_OF_RELATIONS_ID } from 'constant'; import ProjectsRegistry from 'store/projects'; import DataRouterService from 'services/data'; import CatalogLayersStoresRegistry from 'store/catalog-layers'; @@ -7,10 +7,9 @@ import DownloadFormats from 'components/QueryResultsActionDownloadFormats.vue'; import QueryPolygonCsvAttributesComponent from 'components/QueryResultsActionQueryPolygonCSVAttributes.vue'; import ApplicationService from 'services/application'; -const {base, inherit, noop, downloadFile, throttle, getUniqueDomId, copyUrl } = require('core/utils/utils'); -const { createFeatureFromFeatureObject } = require('core/utils/geo'); -const {getAlphanumericPropertiesFromFeature, createFeatureFromGeometry, createFeatureFromBBOX, createFeatureFromCoordinates} = require('core/utils/geo'); -const {t} = require('core/i18n/i18n.service'); +const { base, inherit, noop, downloadFile, throttle, getUniqueDomId, copyUrl } = require('core/utils/utils'); +const { getAlphanumericPropertiesFromFeature, createFeatureFromFeatureObject, createFeatureFromGeometry, createFeatureFromBBOX, createFeatureFromCoordinates } = require('core/utils/geo'); +const { t } = require('core/i18n/i18n.service'); const Layer = require('core/layers/layer'); const G3WObject = require('core/g3wobject'); const VectorLayer = require('core/layers/vectorlayer'); @@ -18,13 +17,30 @@ const PrintService = require('core/print/printservice'); const RelationsPage = require('gui/relations/vue/relationspage'); const PickCoordinatesInteraction = require('g3w-ol/interactions/pickcoordinatesinteraction'); -//used to get and set vue reactivity to queryresultservice +const deprecate = require('util-deprecate'); + +/** + * Get and set vue reactivity to QueryResultsService + * + * @type {Vue} + */ const VM = new Vue(); function QueryResultsService() { + + /** + * @FIXME add description + */ this.printService = new PrintService(); + + /** + * @FIXME add description + */ this._currentLayerIds = []; + /** + * @FIXME add description + */ ProjectsRegistry.onafter('setCurrentProject', project => { this._project = project; this._setRelations(project); @@ -32,165 +48,325 @@ function QueryResultsService() { this.state.download_data = false; this.plotLayerIds = []; }); + + /** + * @FIXME add description + */ this.unlistenerlayeractionevents = []; + + /** + * @FIXME add description + */ this._actions = { 'zoomto': QueryResultsService.zoomToElement, 'highlightgeometry': this.highlightGeometry.bind(this), 'clearHighlightGeometry': this.clearHighlightGeometry.bind(this) }; + + /** + * @FIXME add description + */ this._relations = []; + + /** + * @FIXME add description + */ this._atlas = []; + + /** + * @FIXME add description + */ this.plotLayerIds = []; - const project = this._project = ProjectsRegistry.getCurrentProject(); - // userful to set right order for query result based on toc order layers + + /** + * @FIXME add description + */ + this._project = ProjectsRegistry.getCurrentProject(); + + /** + * Keep the right order for query result based on TOC order layers + */ this._projectLayerIds = this._project.getConfigLayers().map(layer => layer.id); - // set reactive state + + /** + * Set reactive state + */ this.state = { + + /** + * @FIXME add description + */ zoomToResult: true, + + /** + * @FIXME add description + */ components: [], + + /** + * @FIXME add description + */ layers: [], + + /** + * @FIXME add description + */ changed: false, + + /** + * @FIXME add description + */ query: null, - type: 'ows', // or api in case of search + + /** + * 'ows' = default + * 'api' = search + */ + type: 'ows', + + /** + * An action is an object that contains: + * + * ``` + * { + * "id": (required) Unique action Id + * "download": wether action is download or not + * "class": (required) fontawsome classname to show icon + * "state": need to be reactive. Used for example to toggled state of action icon + * "hint": Tooltip text + * "init": Method called when action is loaded + * "clear": Method called before clear the service. Used for example to clear unwatch + * "change": Method called when feature of layer is changed + * "cbk": (required) Method called when action is cliccked + * } + * ``` + **/ layersactions: {}, - actiontools:{}, // addd action tools (for features) - currentactiontools:{}, // current action tools contain component of a specific action (for example download) - currentactionfeaturelayer:{}, // contain current action that expose component vue (it useful to comprare id other action is toggled and expose component) + + /** + * Add action tools (for features) + */ + actiontools:{}, + + /** + * Current action tools contain component + * of a specific action (eg. download) + */ + currentactiontools:{}, + + /** + * Contains current action that expose vue component + * (useful for comparing the id other action is + * triggered and exposing the component) + */ + currentactionfeaturelayer:{}, + + /** + * @FIXME add description + */ layeractiontool: {}, + + /** + * @FIXME add description + */ layersFeaturesBoxes:{}, - layerscustomcomponents:{} // used to show a custom component for a layer + + /** + * Used to show a custom component for a layer + */ + layerscustomcomponents:{} // + }; + + /** + * @FIXME add description + */ this.init = function() { this.clearState(); }; - // Is a vector layer used by query resul to show eventually query resuesta as coordnates, bbox, polygon, etc .. - const color = 'blue'; - const stroke = new ol.style.Stroke({ - color, - width: 3 - }); - const fill = new ol.style.Fill({ - color - }); + + /** + * Vector layer used by query result to show query + * request as coordinates, bbox, polygon, etc .. + * + * @type {ol.layer.Vector} + */ this.resultsQueryLayer = new ol.layer.Vector({ - style: new ol.style.Style({ - stroke, - image: new ol.style.Circle({ - fill, - radius: 6 - }), - }), - source: new ol.source.Vector() + source: new ol.source.Vector(), + style: new ol.style.Style({ stroke: new ol.style.Stroke({ width: 3, color: 'blue' }), image: new ol.style.Circle({ radius: 6, fill: new ol.style.Fill({ color: 'blue' }) }) }), }); + /** + * @FIXME add description + */ this._vectorLayers = []; + + /** + * @FIXME add description + */ this._addFeaturesLayerResultInteraction = { - id: null, // reference to current layer - interaction: null, // interaction bind to layer, - mapcontrol: null, // add current toggled map control if toggled + + /** + * Reference to current layer + */ + id: null, + + /** + * Interaction bind to layer, + */ + interaction: null, + + /** + * Add current toggled map control if toggled + */ + mapcontrol: null, + + /** + * @FIXME add description + */ toggleeventhandler: null + }; + + /** + * @FIXME add description + */ this.setters = { + /** - * Method call when response is handled by Data Provider + * Setter method called when response is handled by DataProvider + * * @param queryResponse * @param options: add is used to know if is a new query request or add/remove query request */ setQueryResponse(queryResponse, options={add:false}) { - const {add} = options; - // in case of new request results reset the query otherwise maintain the previous request - if (!add) { + // in case of new request results reset the + // query otherwise maintain the previous request + if (!options.add) { this.clearState(); this.state.query = queryResponse.query; this.state.type = queryResponse.type; } - const {data} = queryResponse; - const layers = this._digestFeaturesForLayers(data); - this.setLayersData(layers, options); + this.setLayersData( + this._digestFeaturesForLayers(queryResponse.data), + options + ); }, /** - * method to add layer and feature for response + * Setter method called when adding layer and feature for response + * * @param layers * @param options */ setLayersData(layers, options={add:false}) { - const {add} = options; - if (!add){ - // here set the right order of result layers based on toc + if (!options.add) { + // set the right order of result layers based on TOC this._currentLayerIds = layers.map(layer => layer.id); this._orderResponseByProjectLayers(layers); } - layers.forEach(layer => { - // in case of a new request query - if (!add) this.state.layers.push(layer); - //get features from add pick layer - else this.updateLayerResultFeatures(layer); - }); - this.setActionsForLayers(layers, {add}); + // get features from add pick layer in case of a new request query + layers.forEach(layer => { options.add ? this.updateLayerResultFeatures(layer) : this.state.layers.push(layer); }); + this.setActionsForLayers(layers, {add: options.add}); this.state.changed = true; }, + /** - * Method + * @FIXME add description + * * @param component */ addComponent(component) { this._addComponent(component) }, + /** - * + * @FIXME add description + * + * @param actions + * @param layers */ addActionsForLayers(actions, layers) {}, + /** - * + * @FIXME add description + * * @param element */ postRender(element) {}, + /** - * + * @FIXME add description */ closeComponent() {}, + /** - * + * @FIXME add description + * * @param layer */ - changeLayerResult(layer){ + changeLayerResult(layer) { this._changeLayerResult(layer); }, + /** - * + * @FIXME add description */ - activeMapInteraction(){}, + activeMapInteraction() {}, + /** - * setter hook to relation table + * Setter method related to relation table */ - editFeature({layer, feature}={}){}, + editFeature({layer, feature}={}) {}, + /** - * Method to listen open/close feature info data content. + * Setter method called when opening/closing feature info data content. + * * @param open * @param layer * @param feature * @param container */ - openCloseFeatureResult({open, layer, feature, container}={}){} + openCloseFeatureResult({open, layer, feature, container}={}) {} + }; + base(this); - this.addLayersPlotIds = function(layerIds=[]) { - this.plotLayerIds = layerIds; - }; + /** + * @FIXME add description + */ + this.addLayersPlotIds = function(layerIds=[]) { this.plotLayerIds = layerIds; }; - this.getPlotIds = function(){ - return this.plotLayerIds; - }; + /** + * @FIXME add description + */ + this.getPlotIds = function() { return this.plotLayerIds; }; - this.findPlotId = function(id){ - return this.plotLayerIds.find(plotId => plotId == id); - }; + /** + * @FIXME add description + */ + this.findPlotId = function(id) { return this.plotLayerIds.find(plotId => plotId == id); }; + + + /** + * @FIXME add description + */ + this._setRelations(this._project); + + /** + * @FIXME add description + */ + this._setAtlasActions(this._project); - this._setRelations(project); - this._setAtlasActions(project); + /** + * @FIXME add description + */ this._addVectorLayersDataToQueryResponse(); + + /** + * @FIXME add description + */ this._asyncFnc = { todo: noop, zoomToLayerFeaturesExtent: { @@ -200,14 +376,18 @@ function QueryResultsService() { async: false } }; - GUI.onbefore('setContent', (options)=>{ - const {perc} = options; + + /** + * @FIXME add description + */ + GUI.onbefore('setContent', (options)=> { this.mapService = this.mapService || ApplicationService.getApplicationService('map'); - if (perc === 100 && GUI.isMobile()) { + if (100 === options.perc && GUI.isMobile()) { this._asyncFnc.zoomToLayerFeaturesExtent.async = true; this._asyncFnc.goToGeometry.async = true; } }); + } // Make the public service en Event Emitter @@ -216,224 +396,205 @@ inherit(QueryResultsService, G3WObject); const proto = QueryResultsService.prototype; /** - * Method to register for plugin or other component of application to add custom component on result for each layer feature or layer - * @param id unique id identification - * @param layerId Layer id of layer + * Register for plugin or other component of application to add + * custom component on result for each layer feature or layer + * + * @param id unique id identification + * @param layerId Layer id of layer * @param component custom component - * @param type feature or layer + * @param type feature or layer + * @param position */ -proto.registerCustomComponent = function({id=getUniqueDomId(), layerId, component, type='feature', position='after'}={}){ - if (this.state.layerscustomcomponents[layerId] === undefined) +proto.registerCustomComponent = function({id=getUniqueDomId(), layerId, component, type='feature', position='after'}={}) { + if (undefined === this.state.layerscustomcomponents[layerId]) { this.state.layerscustomcomponents[layerId] = { - layer: { - before: [], - after: [] - }, - feature: { - before: [], - after: [] - } + layer: { before: [], after: [] }, + feature: { before: [], after: [] } }; - this.state.layerscustomcomponents[layerId][type][position].push({ - id, - component - }); + } + this.state.layerscustomcomponents[layerId][type][position].push({ id, component }); return id; }; /** - * To check position + * Check position + * * @param id * @param layerId * @param type */ -proto.unRegisterCustomComponent = function({id, layerId, type, position}){ - if (position) this.state.layerscustomcomponents[layerId][type][position] = this.state.layerscustomcomponents[layerId][type][position].filter(({id:componentId}) => componentId !== id); - else Object.keys(this.state.layerscustomcomponents[layerId][type]).forEach(position =>{ +proto.unRegisterCustomComponent = function({id, layerId, type, position}) { + if (position) { this.state.layerscustomcomponents[layerId][type][position] = this.state.layerscustomcomponents[layerId][type][position].filter(({id:componentId}) => componentId !== id); - }) + } else { + Object.keys(this.state.layerscustomcomponents[layerId][type]) + .forEach(position => { + this.state.layerscustomcomponents[layerId][type][position] = this.state.layerscustomcomponents[layerId][type][position].filter(({id:componentId}) => componentId !== id); + }) + } }; /** - * Method to add a feature to current layer result + * Add a feature to current layer result + * * @param layer * @param feature */ -proto.addFeatureLayerToResult = function(layer, feature){ +proto.addFeatureLayerToResult = function(layer, feature) { this.state.layersFeaturesBoxes[this.getBoxId(layer, feature)].collapsed = true; }; /** - * Method to remove a feature from current layer result + * Remove a feature from current layer result + * * @param layer * @param feature */ -proto.removeFeatureLayerFromResult = function(layer, feature){ - const {id, external} = layer; - this.updateLayerResultFeatures({ - id, - external, - features: [feature] - }) +proto.removeFeatureLayerFromResult = function(layer, feature) { + this.updateLayerResultFeatures({ id: layer.id, external: layer.external, features: [feature] }); }; /** - * Method wrapper for download + * Wrapper for download + * + * @param downloadFnc + * @param options */ - -proto.downloadApplicationWrapper = async function(downloadFnc, options={}){ +proto.downloadApplicationWrapper = async function(downloadFnc, options={}) { const download_caller_id = ApplicationService.setDownload(true); GUI.setLoadingContent(true); try { await downloadFnc(options); - } catch(err){ - GUI.showUserMessage({ - type: 'alert', - message: err || 'server_error', - textMessage: err ? true : false - }) + } catch(err) { + GUI.showUserMessage({ type: 'alert', message: err || 'server_error', textMessage: !!err }) } ApplicationService.setDownload(false, download_caller_id); GUI.setLoadingContent(false); }; /** - * Based on layer response check if features layer are to add or remove to current state.layers results - * @param layer + * Based on layer response check if features layer need to + * be added or removed to current `state.layers` results + * + * @param {Array} layer * @since v3.8.0 */ -proto.updateLayerResultFeatures = function(layer){ - //extract features from layer object - let {features=[]} = layer; - // get layer from current state.layers showed on result - const findLayer = this.state.layers.find(_layer => _layer.id === layer.id); - // if get features and find layer - if (findLayer && features.length){ - // get id external layer or not (external is a layer added by mapcontrol addexternlayer) - const {external} = findLayer; - // is array of idexes od features that we has to remove from state.layer because is already loaded - const removeFeatureIndexes = []; - // get id of the features - const features_ids = features.map(feature => !external ? feature.attributes[G3W_FID]: feature.id); - // loop nad filter the features that we had to remove) - findLayer.features = findLayer.features.filter(feature => { - const indexFindFeature = features_ids.indexOf(!external ? feature.attributes[G3W_FID]: feature.id); - // check if need to filter or not - const filtered = indexFindFeature === -1; - if (!filtered){ - removeFeatureIndexes.push(indexFindFeature); - const featureRemoved = features[indexFindFeature]; - this.state.layersFeaturesBoxes[this.getBoxId(layer, feature)].collapsed = true; - setTimeout(()=> delete this.state.layersFeaturesBoxes[this.getBoxId(layer, featureRemoved)]); - } else this.state.layersFeaturesBoxes[this.getBoxId(layer, feature)].collapsed = true; - return filtered; - }); - // filter features to add - features = features.filter((feature, index) => removeFeatureIndexes.indexOf(index) === -1); - // check if new feature ha to be added - if (features.length) { - const newlayerfeatures = [...findLayer.features, ...features]; - findLayer.features = newlayerfeatures; +proto.updateLayerResultFeatures = function(responseLayer) { + const layer = this._getLayer(responseLayer.id), // get layer from current `state.layers` showed on result + features = this._getLayerFeatures(responseLayer), // extract features from layer object + external = this._getExternalLayer(responseLayer.id); // get id of external layer or not (`external` is a layer added by mapcontrol addexternlayer) + + if (layer && features.length) { + const _featuresToAdd = this._featuresToAdd(features, external); // filter the features that we had to add + const _featuresToRemove = this._featuresToRemove(features, external); // filter the features that we had to remove (because they are already loaded in `state.layers`) + + /** + * @TODO check if the first loop `features.forEach` is redundant, + * it can be replaced by `_featuresToAdd.forEach` ? + */ + features.forEach(feature => this._toggleLayerFeatureBox(layer, feature, true)); + // _featuresToAdd.forEach(feature => this._toggleLayerFeatureBox(layer, feature, true)); + _featuresToRemove.forEach(feature => this._removeLayerFeatureBox(layer, feature)); + + // new layer features + layer.features = [ ..._featuresToRemove, ..._featuresToAdd ]; + + // in case of removed features + if (1 === _featuresToRemove.length) { + this._toggleLayerFeatureBox(layer, _featuresToRemove[0], false); } - //in case of removed features - if (findLayer.features.length === 1 && this.state.layersFeaturesBoxes[this.getBoxId(findLayer, findLayer.features[0])]) - // used to do all vue reactive thing before update layers - setTimeout(() => this.state.layersFeaturesBoxes[this.getBoxId(findLayer, findLayer.features[0])].collapsed = false); + // in case no more features on layer remove interaction pickcoordinate to get result from map - this.checkIfLayerHasNoFeatures(findLayer); + this.checkIfLayerHasNoFeatures(layer); + } + // hightlight new feature - this.state.layers.length === 1 && this.highlightFeaturesPermanently(this.state.layers[0]); - this.changeLayerResult(findLayer); -}; + if (1 === this.state.layers.length) { + this.highlightFeaturesPermanently(this.state.layers[0]); + } -/** - * @deprecated since v3.8. Will be deleted in 4.x. Use QueryResultsService::updateLayerResultFeatures(layer) instead - */ -proto.addRemoveFeaturesToLayerResult = proto.updateLayerResultFeatures; + this.changeLayerResult(layer); +}; /** - * Method called when layer result features for example is changed + * Called when layer result features is changed + * * @param layer */ -proto._changeLayerResult = function(layer){ - const layeractions = this.state.layersactions[layer.id]; - // call if present change mthod to action - layeractions.forEach(action => action.change && action.change(layer)); - //reset layer current actions tools - this.resetCurrentActionToolsLayer(layer); +proto._changeLayerResult = function(layer) { + this.state.layersactions[layer.id].forEach(action => action.change && action.change(layer)); // call if present change method to action + this.resetCurrentActionToolsLayer(layer); // reset layer current actions tools }; /** - * Check and do action if layer has no features after delete feature(s + * Check and do action if layer has no features after delete feature(s) + * + * @param layer */ -proto.checkIfLayerHasNoFeatures = function(layer){ - if (layer.features.length === 0) { - // used to do all vue reactive thing before update layers +proto.checkIfLayerHasNoFeatures = function(layer) { + if (layer && 0 === layer.features.length) { + // due to vue reactivity, wait a little bit before update layers setTimeout(() => { - this.state.layers = this.state.layers.filter(_layer => _layer.id !== layer.id); + this.state.layers = this.state.layers.filter(l => l.id !== layer.id); this.clearHighlightGeometry(layer); - this.removeAddFeaturesLayerResultInteraction({ - toggle: true - }); + this.removeAddFeaturesLayerResultInteraction({ toggle: true }); }) } }; /** - * Method to create boxid identify to query result hmtl + * Create boxid identify to query result hmtl + * * @param layer * @param feature * @param relation_index + * * @returns {string} */ -proto.getBoxId = function(layer, feature, relation_index){ - return relation_index !== null && relation_index !== undefined ? `${layer.id}_${feature.id}_${relation_index}` : `${layer.id}_${feature.id}`; +proto.getBoxId = function(layer, feature, relation_index) { + return (null !== relation_index && undefined !== relation_index) ? `${layer.id}_${feature.id}_${relation_index}` : `${layer.id}_${feature.id}`; }; +/** + * @FIXME add description + * + * @param layers + * @param options + */ proto.setActionsForLayers = function(layers, options={add: false}) { - const {add} = options; - if (!add) { - this.unlistenerlayeractionevents = []; - layers.forEach(layer => { - /** - * set eventually layer action tool and need to be reactive - * @type {{}} - */ - this.state.layeractiontool[layer.id] = Vue.observable({ - component: null, - config: null - }); - const currentactiontoolslayer = {}; - const currentationfeaturelayer = {}; - layer.features.forEach((feature, index)=> { - currentactiontoolslayer[index] = null; - currentationfeaturelayer[index] = null; - }); - this.state.currentactiontools[layer.id] = Vue.observable(currentactiontoolslayer); - this.state.currentactionfeaturelayer[layer.id] = Vue.observable(currentationfeaturelayer); - const is_external_layer_or_wms = layer.external || (layer.source ? layer.source.type === 'wms' : false); - if (!this.state.layersactions[layer.id]) this.state.layersactions[layer.id] = []; - /** - * An action is an object contains - * { - * id: Unique action Id => required True - download: if is action download or not => required False - class: calss fontawsome to show icon => required True, - state: need to be reactive. Used for example to toggled state of action icon => required False - hint: Tooltip text => required False - init: Method called when action is loaded => required False - clear: Method called before clear the service. Used for example to clear unwatch => require False - change: Method called when feature of layer is changed - cbk: Method called when action is cliccked => required True - } - * - * } - */ + if (options.add) { + return; + } + + this.unlistenerlayeractionevents = []; + + layers.forEach(layer => { + + // set eventually layer action tool and need to be reactive + this.state.layeractiontool[layer.id] = Vue.observable({ component: null, config: null }); + this.state.currentactiontools[layer.id] = Vue.observable(currentactiontoolslayer); + this.state.currentactionfeaturelayer[layer.id] = Vue.observable(currentationfeaturelayer); + + const currentactiontoolslayer = {}; + const currentationfeaturelayer = {}; + layer.features.forEach((_, idx) => { + currentactiontoolslayer[idx] = null; + currentationfeaturelayer[idx] = null; + }); - //in case of geometry - if (layer.hasgeometry) { - this.state.layersactions[layer.id].push({ + const is_external_layer_or_wms = (layer.external) || (layer.source ? layer.source.type === 'wms' : false); + + if (!this.state.layersactions[layer.id]) { + this.state.layersactions[layer.id] = []; + } + + /** + * Lookup for layer geometry. + */ + if (layer.hasgeometry) { + this.state.layersactions[layer.id] + .push({ id: 'gotogeometry', download: false, mouseover: true, @@ -441,20 +602,27 @@ proto.setActionsForLayers = function(layers, options={add: false}) { hint: 'sdk.mapcontrols.query.actions.zoom_to_feature.hint', cbk: throttle(this.goToGeometry.bind(this)) }); - } - // in case of relations - if (this._relations) { - const relations = this._relations[layer.id] && this._relations[layer.id].filter(relation =>{ - return relation.type === 'MANY'; - }); - if (relations && relations.length) { - const chartRelationIds = []; - relations.forEach(relation => { - const id = this.plotLayerIds.find(id => id === relation.referencingLayer); - id && chartRelationIds.push(id); - }); + } + + /** + * Lookup for layer relations. + */ + if (this._relations && this._relations[layer.id]) { + + const relations = this._relations[layer.id].filter(relation => 'MANY' === relation.type); + const chartRelationIds = []; + + relations.forEach(relation => { + const id = this.plotLayerIds.find(id => id === relation.referencingLayer); + if (id) { + chartRelationIds.push(id); + } + }); - this.state.layersactions[layer.id].push({ + /** @FIXME add description */ + if (relations.length) { + this.state.layersactions[layer.id] + .push({ id: 'show-query-relations', download: false, class: GUI.getFontClass('relation'), @@ -463,83 +631,98 @@ proto.setActionsForLayers = function(layers, options={add: false}) { relations, chartRelationIds }); - const state = this.createActionState({ - layer - }); - chartRelationIds.length && this.state.layersactions[layer.id].push({ + } + + /** @FIXME add description */ + if (chartRelationIds.length) { + this.state.layersactions[layer.id] + .push({ id: 'show-plots-relations', download: false, opened: true, class: GUI.getFontClass('chart'), - state, + state: this.createActionState({ layer }), hint: 'sdk.mapcontrols.query.actions.relations_charts.hint', cbk: throttle(this.showRelationsChart.bind(this, chartRelationIds)) }); - } } - /** - * - * Check if layer has atlas - */ - this.getAtlasByLayerId(layer.id).length && this.state.layersactions[layer.id].push({ - id: `printatlas`, - download: true, - class: GUI.getFontClass('print'), - hint: `sdk.tooltips.atlas`, - cbk: this.printAtlas.bind(this) - }); - const state = this.createActionState({ - layer - }); - if (layer.downloads.length === 1) { - const [format] = layer.downloads; - const cbk = this.downloadFeatures.bind(this, format); - layer[format] = Vue.observable({ - active: false + + } + + /** + * Lookup for layer print atlas. + */ + if (this.getAtlasByLayerId(layer.id).length) { + this.state.layersactions[layer.id] + .push({ + id: `printatlas`, + download: true, + class: GUI.getFontClass('print'), + hint: `sdk.tooltips.atlas`, + cbk: this.printAtlas.bind(this) }); - this.state.layersactions[layer.id].push({ + } + + const state = this.createActionState({ layer }); + + /** + * Lookup for layer downloadable features (single). + */ + if (layer.downloads.length === 1) { + const [format] = layer.downloads; // NB: format == layer.downloads[0] + const cbk = this.downloadFeatures.bind(this, format); + layer[format] = Vue.observable({ active: false }); + this.state.layersactions[layer.id] + .push({ id: `download_${format}_feature`, download: true, state, class: GUI.getFontClass('download'), hint: `sdk.tooltips.download_${format}`, - cbk: (layer, feature, action, index)=>{ + cbk: (layer, feature, action, index) => { action.state.toggled[index] = !action.state.toggled[index]; - if (action.state.toggled[index]) cbk(layer, feature, action, index); - else this.setCurrentActionLayerFeatureTool({ - index, - action, - layer - }) + if (action.state.toggled[index]) { + cbk(layer, feature, action, index); + } else { + this.setCurrentActionLayerFeatureTool({ index, action, layer }) + } } }); - } else if (layer.downloads.length > 1 ){ - // SET CONSTANT TO AVOID TO CHANGE ALL THINGS - const ACTIONTOOLSDOWNLOADFORMATS = DownloadFormats.name; - const downloads = []; - layer.downloads.forEach(format => { + } + + /** + * Lookup for layer downloadable features (multi). + */ + if (layer.downloads.length > 1) { + + const downloads = []; + + layer.downloads + .forEach(format => { downloads.push({ id: `download_${format}_feature`, download: true, - format, + format: format, class: GUI.getFontClass(format), hint: `sdk.tooltips.download_${format}`, cbk: (layer, feature, action, index)=> { - //used to untoggle downloads action + // un-toggle downloads action this.downloadFeatures(format, layer, feature, action, index); - const downloadsaction = this.state.layersactions[layer.id].find(action => action.id === 'downloads'); - if (this.state.query.type !== 'polygon') downloadsaction.cbk(layer, feature, downloadsaction, index); + if ('polygon' !== this.state.query.type) { + const downloadsaction = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); + downloadsaction.cbk(layer, feature, downloadsaction, index); + } } }); }); - this.state.actiontools[ACTIONTOOLSDOWNLOADFORMATS] = this.state.actiontools[ACTIONTOOLSDOWNLOADFORMATS] || {}; - // set config of actionstools - this.state.actiontools[ACTIONTOOLSDOWNLOADFORMATS][layer.id] = { - downloads // ARE DOWNLOAD ACTIONS, - }; - // used to - //check if has download actions - this.state.layersactions[layer.id].push({ + + // set actionstools configs + this.state.actiontools[DownloadFormats.name] = this.state.actiontools[DownloadFormats.name] || {}; + this.state.actiontools[DownloadFormats.name][layer.id] = { downloads }; + + // check if has download actions + this.state.layersactions[layer.id] + .push({ id: `downloads`, download: true, class: GUI.getFontClass('download'), @@ -547,26 +730,27 @@ proto.setActionsForLayers = function(layers, options={add: false}) { toggleable: true, hint: `Downloads`, change({features}) { - features.forEach((feature, index) =>{ - if (this.state.toggled[index] === undefined) VM.$set(this.state.toggled, index, false); - else this.state.toggled[index] = false; - }); + features + .forEach((feature, index) => { + if (undefined === this.state.toggled[index]) { + VM.$set(this.state.toggled, index, false); + } else { + this.state.toggled[index] = false; + } + }); }, cbk: (layer, feature, action, index) => { action.state.toggled[index] = !action.state.toggled[index]; - this.setCurrentActionLayerFeatureTool({ - layer, - index, - action, - component: action.state.toggled[index] ? DownloadFormats : null - }); + this.setCurrentActionLayerFeatureTool({ layer, index, action, component: (action.state.toggled[index] ? DownloadFormats : null) }); } }); - } - /* - Check if si external layer or wms - */ - !is_external_layer_or_wms && this.state.layersactions[layer.id].push({ + } + + /** + * Lookup for not external layer or WMS. + */ + if (false == is_external_layer_or_wms) { + this.state.layersactions[layer.id].push({ id: 'removefeaturefromresult', download: false, mouseover: true, @@ -577,145 +761,148 @@ proto.setActionsForLayers = function(layers, options={add: false}) { hint: 'sdk.mapcontrols.query.actions.remove_feature_from_results.hint', cbk: this.removeFeatureLayerFromResult.bind(this) }); + } - /** - * check if selection is active - */ - if (layer.selection.active !== undefined) { - // selection action - const state = this.createActionState({ - layer - }); - - this.state.layersactions[layer.id].push({ + /** + * Lookup for layer selection status (active). + */ + if (undefined !== layer.selection.active) { + this.state.layersactions[layer.id] + .push({ id: 'selection', download: false, class: GUI.getFontClass('success'), hint: 'sdk.mapcontrols.query.actions.add_selection.hint', - state, - init: ({feature, index, action}={})=>{ - typeof layer.selection.active !== "undefined" && this.checkFeatureSelection({ - layer, - index, - feature, - action - }) + state: this.createActionState({ layer }), + init: ({feature, index, action}={}) => { + if("undefined" !== typeof layer.selection.active) { + this.checkFeatureSelection({ layer, index, feature, action }) + } }, cbk: throttle(this.addToSelection.bind(this)) }); - /* - * In case of external layer don't listen selection event - * */ - this.listenClearSelection(layer, 'selection'); - //end selection action - } - /* - If not wms of external layer show copy link to feature - */ - !is_external_layer_or_wms && layer.hasgeometry && this.state.layersactions[layer.id].push({ - id: 'link_zoom_to_fid', - download: false, - class: GUI.getFontClass('link'), - hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint', - hint_change: { - hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint_change', - duration: 1000 - }, - cbk: this.copyZoomToFidUrl.bind(this) - }); - layer.editable && !layer.inediting && this.state.layersactions[layer.id].push({ - id: 'editing', - class: GUI.getFontClass('pencil'), - hint: 'Editing', - cbk: (layer, feature) => { - this.editFeature({ - layer, - feature - }) - } - }); - }); - this.addActionsForLayers(this.state.layersactions, this.state.layers); - } + // In case of external layer don't listen to `selection` event + this.listenClearSelection(layer, 'selection'); + } + + /** + * Lookup for not external layer or WMS (copy link to feature). + */ + if (!is_external_layer_or_wms && layer.hasgeometry) { + this.state.layersactions[layer.id] + .push({ + id: 'link_zoom_to_fid', + download: false, + class: GUI.getFontClass('link'), + hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint', + hint_change: { + hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint_change', + duration: 1000 + }, + cbk: this.copyZoomToFidUrl.bind(this) + }); + } + + /** + * Lookup for editable layer. + */ + if (layer.editable && !layer.inediting) { + this.state.layersactions[layer.id] + .push({ + id: 'editing', + class: GUI.getFontClass('pencil'), + hint: 'Editing', + cbk: (layer, feature) => { this.editFeature({ layer, feature }) } + }); + } + + }); + this.addActionsForLayers(this.state.layersactions, this.state.layers); }; -proto.createActionState = function({layer, dynamicProperties=['toggled']}){ +/** + * @FIXME add description + */ +proto.createActionState = function({layer, dynamicProperties=['toggled']}) { // check number of download formats - const propertiesObject = dynamicProperties.reduce((accumulator, property) =>{ - accumulator[property] = {}; - return accumulator; - }, {}); - layer.features.map((feature, index)=> { - Object.keys(propertiesObject).forEach(property =>{ - propertiesObject[property][index] = null; - }) - }); + const propertiesObject = dynamicProperties.reduce((accumulator, property) => { accumulator[property] = {}; return accumulator; }, {}); + layer.features.map((_, idx) => { Object.keys(propertiesObject).forEach(property => { propertiesObject[property][idx] = null; }); }); return Vue.observable(propertiesObject); }; /** - * Method to get action referred to layer getting the acion id + * Get action referred to layer getting the action id + * * @param layer layer linked to action - * @param id action id - * @returns {*} + * @param id action id */ -proto.getActionLayerById = function({layer, id}={}){ +proto.getActionLayerById = function({layer, id}={}) { return this.state.layersactions[layer.id].find(action => action.id === id); }; /** * Set current layer action tool in feature + * * @param layer current layer * @param index feature index * @param value component value or null */ -proto.setCurrentActionLayerFeatureTool = function({layer, action, index, component=null}={}){ - if (component){ - if (this.state.currentactiontools[layer.id][index] && action.id !== this.state.currentactionfeaturelayer[layer.id][index].id && this.state.currentactionfeaturelayer[layer.id][index].toggleable) +proto.setCurrentActionLayerFeatureTool = function({layer, action, index, component=null}={}) { + if (component) { + if (this.state.currentactiontools[layer.id][index] && action.id !== this.state.currentactionfeaturelayer[layer.id][index].id && this.state.currentactionfeaturelayer[layer.id][index].toggleable) { this.state.currentactionfeaturelayer[layer.id][index].state.toggled[index] = false; + } this.state.currentactionfeaturelayer[layer.id][index] = action; - } else this.state.currentactionfeaturelayer[layer.id][index] = null; + } else { + this.state.currentactionfeaturelayer[layer.id][index] = null; + } this.state.currentactiontools[layer.id][index] = component; }; -proto.addCurrentActionToolsLayer = function({id, layer, config={}}){ +/** + * @FIXME add description + */ +proto.addCurrentActionToolsLayer = function({id, layer, config={}}) { this.state.actiontools[id] = {}; this.state.actiontools[id][layer.id] = config; }; /** * Reset current action tools on layer when feature layer change + * * @param layer */ -proto.resetCurrentActionToolsLayer = function(layer){ - layer.features.forEach((feature, index)=>{ +proto.resetCurrentActionToolsLayer = function(layer) { + layer.features.forEach((_, idx)=> { if (this.state.currentactiontools[layer.id]) { - if (this.state.currentactiontools[layer.id][index] === undefined) Vue.set(this.state.currentactiontools[layer.id], index, null); - else this.state.currentactiontools[layer.id][index] = null; - this.state.currentactionfeaturelayer[layer.id][index] = null; + if (undefined === this.state.currentactiontools[layer.id][idx]) { + Vue.set(this.state.currentactiontools[layer.id], idx, null); + } else { + this.state.currentactiontools[layer.id][idx] = null; + } + this.state.currentactionfeaturelayer[layer.id][idx] = null; } }) }; /** - * + * @FIXME add description */ -proto.setLayerActionTool = function({layer, component=null, config=null}={}){ +proto.setLayerActionTool = function({layer, component=null, config=null}={}) { this.state.layeractiontool[layer.id].component = component; this.state.layeractiontool[layer.id].config = config; }; /** - * Method copy zoomtofid url + * Copy `zoomtofid` url + * * @param layer * @param feature */ -proto.copyZoomToFidUrl = function(layer, feature, action){ - const fid = feature.attributes[G3W_FID]; +proto.copyZoomToFidUrl = function(layer, feature, action) { const url = new URL(location.href); - const zoom_to_fid = `${layer.id}|${fid}`; - url.searchParams.set('zoom_to_fid', zoom_to_fid); + url.searchParams.set('zoom_to_fid', `${layer.id}|${feature.attributes[G3W_FID]}`); copyUrl(url.toString()); action.hint_changed = true; }; @@ -728,9 +915,7 @@ proto.clear = function() { this.unlistenerEventsActions(); this.mapService.clearHighlightGeometry(); this.resultsQueryLayer.getSource().clear(); - this.removeAddFeaturesLayerResultInteraction({ - toggle: true - }); + this.removeAddFeaturesLayerResultInteraction({ toggle: true }); this.mapService.getMap().removeLayer(this.resultsQueryLayer); this._asyncFnc = null; this._asyncFnc = { @@ -746,140 +931,208 @@ proto.clear = function() { this.closeComponent(); }; -proto.getCurrentLayersIds = function(){ +/** + * @FIXME add description + */ +proto.getCurrentLayersIds = function() { return this._currentLayerIds; }; +/** + * @FIXME add description + */ proto.runAsyncTodo = function() { this._asyncFnc.todo(); }; +/** + * @FIXME add description + */ proto._orderResponseByProjectLayers = function(layers) { - layers.sort((layerA, layerB) => { - const aIndex = this._projectLayerIds.indexOf(layerA.id); - const bIndex = this._projectLayerIds.indexOf(layerB.id); - return aIndex > bIndex ? 1 : -1; - }); + layers.sort((layerA, layerB) => (this._projectLayerIds.indexOf(layerA.id) > this._projectLayerIds.indexOf(layerB.id) ? 1 : -1)); }; +/** + * @FIXME add description + */ proto.setZoomToResults = function(bool=true) { this.state.zoomToResult = bool; }; -proto.highlightFeaturesPermanently = function(layer){ - const {features} = layer; - this.mapService.highlightFeatures(features, { - duration: Infinity - }) +/** + * @FIXME add description + */ +proto.highlightFeaturesPermanently = function(layer) { + this.mapService.highlightFeatures(layer.features, { duration: Infinity }); }; /** * Check if one layer result + * * @returns {boolean} */ -proto.isOneLayerResult = function(){ - return this.state.layers.length === 1; +proto.isOneLayerResult = function() { + return (1 === this.state.layers.length); }; /** - * + * @FIXME add description + * * @param toggle boolean If true toggle true the mapcontrol */ -proto.removeAddFeaturesLayerResultInteraction = function({toggle=false}={}){ - if (this._addFeaturesLayerResultInteraction.interaction) this.mapService.removeInteraction(this._addFeaturesLayerResultInteraction.interaction); +proto.removeAddFeaturesLayerResultInteraction = function({toggle=false}={}) { + if (this._addFeaturesLayerResultInteraction.interaction) { + this.mapService.removeInteraction(this._addFeaturesLayerResultInteraction.interaction); + } + this._addFeaturesLayerResultInteraction.interaction = null; this._addFeaturesLayerResultInteraction.id = null; - // check if map control query map is register and if toggled - toggle && this._addFeaturesLayerResultInteraction.mapcontrol && this._addFeaturesLayerResultInteraction.mapcontrol.toggle(true); + + // check if query map control is toggled and registered + if (toggle && this._addFeaturesLayerResultInteraction.mapcontrol) { + this._addFeaturesLayerResultInteraction.mapcontrol.toggle(true); + } + this._addFeaturesLayerResultInteraction.mapcontrol = null; - this._addFeaturesLayerResultInteraction.toggleeventhandler && this.mapService.off('mapcontrol:toggled', this._addFeaturesLayerResultInteraction.toggleeventhandler); + + if (this._addFeaturesLayerResultInteraction.toggleeventhandler) { + this.mapService.off('mapcontrol:toggled', this._addFeaturesLayerResultInteraction.toggleeventhandler); + } + this._addFeaturesLayerResultInteraction.toggleeventhandler = null; }; /** - * - * Adde feature to Features results + * Adds feature to Features results + * * @param layer */ -proto.addLayerFeaturesToResultsAction = function(layer){ - /** - * Check if layer is current layer to add or clear previous - */ - if (this._addFeaturesLayerResultInteraction.id !== null && this._addFeaturesLayerResultInteraction.id !== layer.id){ +proto.addLayerFeaturesToResultsAction = function(layer) { + + // Check if layer is current layer to add or clear previous + if ( + null !== this._addFeaturesLayerResultInteraction.id && + layer.id !== this._addFeaturesLayerResultInteraction.id + ) { + const layer = this.state.layers.find(layer => layer.id === this._addFeaturesLayerResultInteraction.id); - if (layer) layer.addfeaturesresults.active = false; - //remove previous add result interaction - if (this._addFeaturesLayerResultInteraction.interaction) this.mapService.removeInteraction(this._addFeaturesLayerResultInteraction.interaction); + if (layer) { + layer.addfeaturesresults.active = false; + } + + // remove previous add result interaction + if (this._addFeaturesLayerResultInteraction.interaction) { + this.mapService.removeInteraction(this._addFeaturesLayerResultInteraction.interaction); + } + } + this._addFeaturesLayerResultInteraction.id = layer.id; + layer.addfeaturesresults.active = !layer.addfeaturesresults.active; - if (layer.addfeaturesresults.active) { - this.activeMapInteraction(); // useful o send an event - const {external} = layer; - if (!this._addFeaturesLayerResultInteraction.mapcontrol) this._addFeaturesLayerResultInteraction.mapcontrol = this.mapService.getCurrentToggledMapControl(); - this._addFeaturesLayerResultInteraction.interaction = new PickCoordinatesInteraction(); - this.mapService.addInteraction(this._addFeaturesLayerResultInteraction.interaction, { - close: false - }); - this._addFeaturesLayerResultInteraction.interaction.on('picked', async evt =>{ - const {coordinate: coordinates} = evt; - if (!external) - await DataRouterService.getData('query:coordinates', + + if (!layer.addfeaturesresults.active) { + + this.removeAddFeaturesLayerResultInteraction({ toggle: true }); + + } else { + + this.activeMapInteraction(); // useful to send an event + const external_layer = layer.external; + + if (!this._addFeaturesLayerResultInteraction.mapcontrol) { + this._addFeaturesLayerResultInteraction.mapcontrol = this.mapService.getCurrentToggledMapControl(); + } + + const interaction = this._addFeaturesLayerResultInteraction.interaction = new PickCoordinatesInteraction(); + + this.mapService.addInteraction(interaction, { close: false }); + + interaction.on('picked', async (e) => { + if (external_layer) { + this.setQueryResponse( + { + data: [ + this.getVectorLayerFeaturesFromQueryRequest( + this._vectorLayers.find(vectorLayer => layer.id === vectorLayer.get('id')), + { coordinates } + ) + ], + query: { + coordinates: e.coordinate + } + }, + { add: true } + ); + } else { + await DataRouterService.getData( + 'query:coordinates', { inputs: { - coordinates, + coordinates: e.coordinate, query_point_tolerance: this._project.getQueryPointTolerance(), layerIds: [layer.id], multilayers: false, - }, outputs: { + }, + outputs: { show: { add: true } } - }); - else { - const vectorLayer = this._vectorLayers.find(vectorLayer => layer.id === vectorLayer.get('id')); - const responseObject = this.getVectorLayerFeaturesFromQueryRequest(vectorLayer,{ - coordinates - }); - this.setQueryResponse({ - data: [responseObject], - query: { - coordinates } - }, {add:true}); + ); } }); - const eventHandler = evt => { - if (evt.target.isToggled() && evt.target.isClickMap()) layer.addfeaturesresults.active = false; + + this._addFeaturesLayerResultInteraction.toggleeventhandler = (evt) => { + if (evt.target.isToggled() && evt.target.isClickMap()) { + layer.addfeaturesresults.active = false; + } }; - this._addFeaturesLayerResultInteraction.toggleeventhandler = eventHandler; - this.mapService.once('mapcontrol:toggled', eventHandler); - } else this.removeAddFeaturesLayerResultInteraction({ - toggle: true - }); + + this.mapService.once('mapcontrol:toggled', this._addFeaturesLayerResultInteraction.toggleeventhandler); + + } }; -proto.deactiveQueryInteractions = function(){ - this.state.layers.forEach(layer => { if (layer.addfeaturesresults) layer.addfeaturesresults.active = false}); +/** + * @FIXME add description + */ +proto.deactiveQueryInteractions = function() { + this.state.layers.forEach(layer => { + if (layer.addfeaturesresults) layer.addfeaturesresults.active = false; + }); this.removeAddFeaturesLayerResultInteraction(); }; +/** + * @FIXME add description + * + * @param layer + * @param options + */ proto.zoomToLayerFeaturesExtent = function(layer, options={}) { - const {features} = layer; options.highlight = !this.isOneLayerResult(); - if (this._asyncFnc.zoomToLayerFeaturesExtent.async) - this._asyncFnc.todo = this.mapService.zoomToFeatures.bind(this.mapService, features, options); - else this.mapService.zoomToFeatures(features, options); + if (this._asyncFnc.zoomToLayerFeaturesExtent.async) { + this._asyncFnc.todo = this.mapService.zoomToFeatures.bind(this.mapService, layer.features, options); + } else { + this.mapService.zoomToFeatures(layer.features, options); + } }; +/** + * @FIXME add description + */ proto.clearState = function(options={}) { this.state.layers.splice(0); this.state.query = {}; this.state.querytitle = ""; this.state.changed = false; // clear action if present - Object.values(this.state.layersactions).forEach(layeractions =>layeractions.forEach(action => action.clear && action.clear())); + Object.values(this.state.layersactions) + .forEach(layeractions => + layeractions.forEach(action => action.clear && action.clear()) + ); this.state.layersactions = {}; this.state.actiontools = {}; this.state.layeractiontool = {}; @@ -889,93 +1142,147 @@ proto.clearState = function(options={}) { this.removeAddFeaturesLayerResultInteraction(); }; +/** + * @FIXME add description + */ proto.getState = function() { return this.state; }; +/** + * @FIXME add description + * + * @param state + */ proto.setState = function(state) { this.state = state; }; +/** + * @FIXME add description + * + * @param project + */ proto._setRelations = function(project) { const projectRelations = project.getRelations(); this._relations = projectRelations ? _.groupBy(projectRelations,'referencedLayer'): []; }; +/** + * @param layerId + */ proto.getAtlasByLayerId = function(layerId) { return this._atlas.filter(atlas => atlas.atlas.qgs_layer_id === layerId); }; -proto._setAtlasActions = function(project){ +/** + * @FIXME add description + * + * @param project + */ +proto._setAtlasActions = function(project) { this._atlas = project.getPrint().filter(printconfig => printconfig.atlas) || []; }; +/** + * @FIXME add description + * + * @param querytitle + */ proto.setTitle = function(querytitle) { this.state.querytitle = querytitle || ""; }; +/** + * @FIXME add description + */ proto.reset = function() { this.clearState(); }; /** - * Method that convert response from Data Provider to a Query Result component data structure + * Converts response from DataProvider into a QueryResult component data structure + * * @param featuresForLayers: Array contains for each layer features + * * @returns {[]} - * @private */ proto._digestFeaturesForLayers = function(featuresForLayers) { - let id = 0; featuresForLayers = featuresForLayers || []; + let id = 0; const layers = []; let layerAttributes, layerRelationsAttributes, layerTitle, layerId; + + // converter const _handleFeatureFoLayer = featuresForLayer => { - let formStructure; + const layerObj = { + editable: false, + inediting: false, + downloads: [], + infoformats: [], + filter: {}, + selection: {}, + external: false, + source: undefined, + infoformat: undefined, + formStructure: undefined, + attributes: [], + features: [], + hasgeometry: false, + show: true, + addfeaturesresults: { + active:false + }, + [DownloadFormats.name]: { + active: false + }, + expandable: true, + hasImageField: false, + error: '', + rawdata: null, // rawdata response + loading: false, + }; + + const layer = featuresForLayer.layer; let sourceType; - let source; let extractRelations = false; - let external = false; - let editable = false; - let inediting = false; - const layer = featuresForLayer.layer; - let downloads = []; - let infoformats = []; - let infoformat; - let filter = {}; - let selection = {}; + if (layer instanceof Layer) { - editable = layer.isEditable(); - inediting = layer.isInEditing(); - source = layer.getSource(); - infoformats = layer.getInfoFormats(); // add infoformats property - infoformat = layer.getInfoFormat(); + layerObj.editable = layer.isEditable(); + layerObj.inediting = layer.isInEditing(); + layerObj.source = layer.getSource(); + layerObj.infoformats = layer.getInfoFormats(); + layerObj.infoformat = layer.getInfoFormat(); // set selection filter and relation if not wms - if ([Layer.SourceTypes.WMS, Layer.SourceTypes.WCS, Layer.SourceTypes.WMST].indexOf(layer.getSourceType()) === -1){ - filter = layer.state.filter; - selection = layer.state.selection; - extractRelations = true; + if (-1 === [ + Layer.SourceTypes.WMS, + Layer.SourceTypes.WCS, + Layer.SourceTypes.WMST + ].indexOf(layer.getSourceType()) + ) { + layerObj.filter = layer.state.filter; + layerObj.selection = layer.state.selection; + extractRelations = true; } - downloads = layer.getDownloadableFormats(); - try { - sourceType = layer.getSourceType() - } catch(err){} - // sanitize attributes layer only if is ows - layerAttributes = this.state.type === 'ows' ? layer.getAttributes().map(attribute => { - const sanitizeAttribute = {...attribute}; - sanitizeAttribute.name = sanitizeAttribute.name.replace(/ /g, '_'); - return sanitizeAttribute - }) : layer.getAttributes(); - + layerObj.downloads = layer.getDownloadableFormats(); + try { sourceType = layer.getSourceType() } catch(err) {} layerRelationsAttributes = []; - layerTitle = layer.getTitle(); - layerId = layer.getId(); + layerTitle = layer.getTitle(); + layerId = layer.getId(); + layerAttributes = ('ows' === this.state.type) /* sanitize attributes layer only if is ows */ + ? layer.getAttributes().map(attribute => { + const sanitizeAttribute = {...attribute}; + sanitizeAttribute.name = sanitizeAttribute.name.replace(/ /g, '_'); + return sanitizeAttribute + }) + : layer.getAttributes(); if (layer.hasFormStructure()) { const structure = layer.getLayerEditingFormStructure(); if (this._relations && this._relations.length) { - const getRelationFieldsFromFormStructure = node => { + const getRelationFieldsFromFormStructure = (node) => { if (!node.nodes) { node.name ? node.relation = true : null; } else { @@ -988,211 +1295,223 @@ proto._digestFeaturesForLayers = function(featuresForLayers) { getRelationFieldsFromFormStructure(node); } } - const fields = layer.getFields().filter(field => field.show); // get features show - formStructure = { + layerObj.formStructure = { structure, - fields - } + fields: layer.getFields().filter(field => field.show), // get features show + }; } - } else if (layer instanceof ol.layer.Vector){ - selection = layer.selection; - layerAttributes = layer.getProperties(); - layerRelationsAttributes = []; - layerTitle = layer.get('name'); - layerId = layer.get('id'); - external = true; - } else if (typeof layer === 'string' || layer instanceof String) { - sourceType = Layer.LayerTypes.VECTOR; - const feature = featuresForLayer.features[0]; - layerAttributes = feature ? feature.getProperties() : []; + } else if (layer instanceof ol.layer.Vector) { + layerObj.selection = layer.selection; + layerAttributes = layer.getProperties(); + layerRelationsAttributes = []; + layerTitle = layer.get('name'); + layerId = layer.get('id'); + layerObj.external = true; + } else if ('string' === typeof layer || layer instanceof String) { + const feature = featuresForLayer.features[0]; + const split_layer_name = layer.split('_'); + sourceType = Layer.LayerTypes.VECTOR; + layerAttributes = (feature ? feature.getProperties() : []); layerRelationsAttributes = []; - const split_layer_name = layer.split('_'); - layerTitle = (split_layer_name.length > 4) ? split_layer_name.slice(0, split_layer_name.length -4).join(' '): layer; - layerId = layer; - external = true; + layerId = layer; + layerObj.external = true; + layerTitle = (split_layer_name.length > 4) + ? split_layer_name.slice(0, split_layer_name.length -4).join(' ') + : layer; } - const layerObj = { - title: layerTitle, - id: layerId, - infoformat, - infoformats, - attributes: [], - features: [], - hasgeometry: false, - atlas: this.getAtlasByLayerId(layerId), - source, - downloads, - show: true, - filter, - addfeaturesresults: { - active:false - }, - [DownloadFormats.name]: { - active: false - }, - external, - editable, - inediting, - selection, - expandable: true, - hasImageField: false, - relationsattributes: layerRelationsAttributes, - formStructure, - error: '', - rawdata: null, // rawdata response - loading: false - }; - if (featuresForLayer.rawdata){ + + layerObj.title = layerTitle; + layerObj.id = layerId; + layerObj.atlas = this.getAtlasByLayerId(layerId); + layerObj.relationsattributes = layerRelationsAttributes; + + if (featuresForLayer.rawdata) { layerObj.rawdata = featuresForLayer.rawdata; layers.push(layerObj) } else if (featuresForLayer.features && featuresForLayer.features.length) { - const layerSpecialAttributesName = (layer instanceof Layer) ? layerAttributes.filter(attribute => { - try { - return attribute.name[0] === '_' || Number.isInteger(1*attribute.name[0]) - } catch(e) { - return false - } - }).map(attribute => ({ - alias: attribute.name.replace(/_/, ''), - name: attribute.name - })) : []; - layerSpecialAttributesName.length && featuresForLayer.features.forEach( feature => this._setSpecialAttributesFeatureProperty(layerSpecialAttributesName, feature)); + const layerSpecialAttributesName = + (layer instanceof Layer) + ? layerAttributes.filter(attribute => { + try { + return ('_' === attribute.name[0] || Number.isInteger(1*attribute.name[0])) + } catch(e) { + return false; + } + }).map(attribute => ({ alias: attribute.name.replace(/_/, ''), name: attribute.name })) + : []; + if (layerSpecialAttributesName.length) { + featuresForLayer.features + .forEach(feature => this._setSpecialAttributesFeatureProperty(layerSpecialAttributesName, feature)); + } layerObj.attributes = this._parseAttributes(layerAttributes, featuresForLayer.features[0], sourceType); - layerObj.attributes.forEach(attribute => { - if (formStructure) { - const relationField = layer.getFields().find(field => field.name === attribute.name); // need to check all field also show false - !relationField && formStructure.fields.push(attribute); - } - if (attribute.type === 'image') layerObj.hasImageField = true; - }); - featuresForLayer.features.forEach(feature => { - const {id:fid, geometry, properties:attributes, selection} = this.getFeaturePropertiesAndGeometry(feature); - if (geometry) layerObj.hasgeometry = true; - const featureObj = { - id: layerObj.external ? feature.getId() : fid, - attributes, - geometry, - selection, - show: true - }; - layerObj.features.push(featureObj); - id += 1; - }); - layers.push(layerObj); + layerObj.attributes + .forEach(attribute => { + if (formStructure) { + const relationField = layer.getFields().find(field => field.name === attribute.name); // need to check all field also show false + if (!relationField) { + formStructure.fields.push(attribute); + } + } + if (attribute.type === 'image') { + layerObj.hasImageField = true; + } + }); + featuresForLayer.features + .forEach(feature => { + const props = this.getFeaturePropertiesAndGeometry(feature); + if (props.geometry) { + layerObj.hasgeometry = true; + } + layerObj.features + .push({ + id: layerObj.external ? feature.getId() : props.id, + attributes: props.properties, + geometry: props.geometry, + selection: props.selection, + show: true + }); + id += 1; + }); + layers.push(layerObj); + } else if (featuresForLayer.error) { + layerObj.error = featuresForLayer.error; } - else if (featuresForLayer.error) layerObj.error = featuresForLayer.error; }; featuresForLayers.forEach(featuresForLayer => { - if (!Array.isArray(featuresForLayer)) _handleFeatureFoLayer(featuresForLayer); - else featuresForLayer.forEach(featuresForLayer => _handleFeatureFoLayer(featuresForLayer)); + if (!Array.isArray(featuresForLayer)) { + _handleFeatureFoLayer(featuresForLayer); + } else { + featuresForLayer.forEach(featuresForLayer => _handleFeatureFoLayer(featuresForLayer)); + } }); return layers; }; /** - * Method to set special attributes + * Set special attributes + * * @param layerSpecialAttributesName * @param feature - * @private */ proto._setSpecialAttributesFeatureProperty = function(layerSpecialAttributesName, feature) { - // get feature properties get from server request - const featureAttributes = feature.getProperties(); - // get attributes special keys - const featureAttributesNames = Object.keys(featureAttributes); - if (layerSpecialAttributesName.length) { - layerSpecialAttributesName.forEach(attributeObj =>{ - featureAttributesNames.find(featureAttribute => { - if (featureAttribute === attributeObj.alias) { - feature.set(attributeObj.name, feature.get(featureAttribute)); - return true - } - }) - }); + if (!layerSpecialAttributesName.length) { + return; } + // get attributes special keys from feature properties received by server request + const featureAttributesNames = Object.keys(feature.getProperties()); + layerSpecialAttributesName + .forEach(layerAttr => { + featureAttributesNames + .find(featureAttr => { + if (featureAttr === layerAttr.alias) { + feature.set(layerAttr.name, feature.get(featureAttr)); + return true + } + }) + }); }; /** - * Method to get properties geometry and id from different type of fetaure + * Get `properties`, `geometry` and `id` from different types of feature + * * @param feature + * * @returns {{geometry: (undefined|*|null|ol.Feature), id: *, properties: string[]}|{geometry: *, id: *, properties: *}} */ -proto.getFeaturePropertiesAndGeometry = function(feature){ - if (feature instanceof ol.Feature){ - return { - selection: feature.selection, - properties:feature.getProperties(), - geometry: feature.getGeometry(), - id: feature.getId() - } - } else { - const {selection, properties, geometry, id} = feature; - return { - selection, - properties, - geometry, - id - } - } +proto.getFeaturePropertiesAndGeometry = function(feature) { + const isOlFeature = feature instanceof ol.Feature; + return { + selection: feature.selection, + properties: isOlFeature ? feature.getProperties() : feature.properties, + geometry: isOlFeature ? feature.getGeometry() : feature.geometry, + id: isOlFeature ? feature.getId() : feature.id + }; }; /** - * parse attributre to show on result based on field + * Parse attributes to show on result based on field + * * @param layerAttributes * @param feature * @param sourceType + * * @returns {{name: T, show: boolean, label: T}[]|*} - * @private */ proto._parseAttributes = function(layerAttributes, feature, sourceType) { - const {properties:featureAttributes} = this.getFeaturePropertiesAndGeometry(feature); - let featureAttributesNames = Object.keys(featureAttributes); - featureAttributesNames = getAlphanumericPropertiesFromFeature(featureAttributesNames); + let featureAttributesNames = getAlphanumericPropertiesFromFeature( + Object.keys(this.getFeaturePropertiesAndGeometry(feature).properties) + ); if (layerAttributes && layerAttributes.length) { - const attributes = layerAttributes.filter(attribute => featureAttributesNames.indexOf(attribute.name) > -1); - return attributes; - } else { - const {GDAL, WMS, WCS, WMST} = Layer.SourceTypes; - const showSourcesTypes = [GDAL, WMS, WCS, WMST]; - return featureAttributesNames.map(featureAttributesName => { - return { - name: featureAttributesName, - label: featureAttributesName, - show: featureAttributesName !== G3W_FID && (sourceType === undefined || showSourcesTypes.indexOf(sourceType) !== -1), - type: 'varchar' - } - }) + return layerAttributes.filter(attr => featureAttributesNames.indexOf(attr.name) > -1); } + const {GDAL, WMS, WCS, WMST} = Layer.SourceTypes; + const sourcesTypes = [GDAL, WMS, WCS, WMST]; + return featureAttributesNames.map(featureAttr => ({ + name: featureAttr, + label: featureAttr, + show: (G3W_FID !== featureAttr) && (undefined === sourceType || -1 !== sourcesTypes.indexOf(sourceType)), + type: 'varchar' + })) }; +/** + * @FIXME add description + * + * @param actionId + * @param layer + * @param feature + * @param index + * @param container + */ proto.trigger = async function(actionId, layer, feature, index, container) { - const actionMethod = this._actions[actionId]; - actionMethod && actionMethod(layer, feature, index); - if (layer) { - const layerActions = this.state.layersactions[layer.id]; - if (layerActions) { - const action = layerActions.find(layerAction => layerAction.id === actionId); - action && await this.triggerLayerAction(action,layer,feature, index, container); + if (this._actions[actionId]) { + this._actions[actionId](layer, feature, index); + } + if (layer && this.state.layersactions[layer.id]) { + const action = this.state.layersactions[layer.id].find(layerAction => layerAction.id === actionId); + if (action) { + await this.triggerLayerAction(action, layer, feature, index, container); } } }; -proto.triggerLayerAction = async function(action,layer,feature, index, container) { - action.cbk && await action.cbk(layer,feature, action, index, container); +/** + * @FIXME add description + * + * @param action + * @param layer + * @param feature + * @param index + * @param container + */ +proto.triggerLayerAction = async function(action, layer, feature, index, container) { + if (action.cbk) { + await action.cbk(layer,feature, action, index, container); + } if (action.route) { - let url; - let urlTemplate = action.route; - url = urlTemplate.replace(/{(\w*)}/g,function(m,key){ - return feature.attributes.hasOwnProperty(key) ? feature.attributes[key] : ""; - }); - url && url !== '' && GUI.goto(url); + let url = action.route.replace(/{(\w*)}/g, (m, key) => feature.attributes.hasOwnProperty(key) ? feature.attributes[key] : ""); + if (url && '' !== url) { + GUI.goto(url); + } } }; +/** + * @FIXME add description + * + * @param vectorLayer + */ proto.registerVectorLayer = function(vectorLayer) { - this._vectorLayers.indexOf(vectorLayer) === -1 && this._vectorLayers.push(vectorLayer); + if (-1 === this._vectorLayers.indexOf(vectorLayer)) { + this._vectorLayers.push(vectorLayer); + } }; +/** + * @FIXME add description + * + * @param vectorLayer + */ proto.unregisterVectorLayer = function(vectorLayer) { this._vectorLayers = this._vectorLayers.filter(layer => { this.state.layers = this.state.layers && this.state.layers.filter(layer => layer.id !== vectorLayer.get('id')); @@ -1200,138 +1519,178 @@ proto.unregisterVectorLayer = function(vectorLayer) { }); }; -proto.getVectorLayerFeaturesFromQueryRequest = function(vectorLayer, query={}){ +/** + * @FIXME add description + * + * @param vectorLayer + * @param query + * + * @returns {Object|Boolean} + */ +proto.getVectorLayerFeaturesFromQueryRequest = function(vectorLayer, query={}) { let isVisible = false; - const {coordinates, bbox, geometry} = query; // extract information about query type let features = []; + switch (vectorLayer.constructor) { - case VectorLayer: - isVisible = vectorLayer.isVisible(); - break; - case ol.layer.Vector: - isVisible = vectorLayer.getVisible(); - break; + case VectorLayer: isVisible = vectorLayer.isVisible(); break; + case ol.layer.Vector: isVisible = vectorLayer.getVisible(); break; } - if (!isVisible) return true; - // case query coordinates - if (coordinates && Array.isArray(coordinates)) { - const pixel = this.mapService.viewer.map.getPixelFromCoordinate(coordinates); - this.mapService.viewer.map.forEachFeatureAtPixel(pixel, (feature, layer) => { - features.push(feature); - }, { - layerFilter(layer) { - return layer === vectorLayer; - } - }); - //case bbox - } else if (bbox && Array.isArray(bbox)) { - const geometry = ol.geom.Polygon.fromExtent(bbox); + + if (!isVisible) { + return true; + } + + if ( + query.coordinates && + Array.isArray(query.coordinates) + ) { // case query coordinates + const pixel = this.mapService.viewer.map.getPixelFromCoordinate(query.coordinates); + this.mapService.viewer.map + .forEachFeatureAtPixel( + pixel, + (feature, layer) => { features.push(feature); }, + { layerFilter: (layer) => layer === vectorLayer } + ); + } else if ( + query.bbox && + Array.isArray(query.bbox) + ) { //case bbox + const geometry = ol.geom.Polygon.fromExtent(query.bbox); switch (vectorLayer.constructor) { - case VectorLayer: - features = vectorLayer.getIntersectedFeatures(geometry); - break; - case ol.layer.Vector: - vectorLayer.getSource().getFeatures().forEach(feature => { - geometry.intersectsExtent(feature.getGeometry().getExtent()) && features.push(feature); - }); - break; + case VectorLayer: features = vectorLayer.getIntersectedFeatures(geometry); break; + case ol.layer.Vector: vectorLayer.getSource().getFeatures().forEach(feature => { geometry.intersectsExtent(feature.getGeometry().getExtent()) && features.push(feature); }); break; } - //case geometry - } else if (geometry instanceof ol.geom.Polygon || geometry instanceof ol.geom.MultiPolygon) { + } else if ( + query.geometry instanceof ol.geom.Polygon || + query.geometry instanceof ol.geom.MultiPolygon + ) { //case geometry switch (vectorLayer.constructor) { - case VectorLayer: - features = vectorLayer.getIntersectedFeatures(geometry); - break; - case ol.layer.Vector: - vectorLayer.getSource().getFeatures().forEach(feature => { - geometry.intersectsExtent(feature.getGeometry().getExtent()) && features.push(feature); - }); - break; + case VectorLayer: features = vectorLayer.getIntersectedFeatures(query.geometry); break; + case ol.layer.Vector: vectorLayer.getSource().getFeatures().forEach(feature => { query.geometry.intersectsExtent(feature.getGeometry().getExtent()) && features.push(feature); }); break; } } + return { features, layer: vectorLayer }; + }; +/** + * @FIXME add description + */ proto._addVectorLayersDataToQueryResponse = function() { this.onbefore('setQueryResponse', (queryResponse, options={}) => { const {query={}} = queryResponse; const {add=false}= options; - !add && this._vectorLayers.forEach(vectorLayer => { - const responseObj = this.getVectorLayerFeaturesFromQueryRequest(vectorLayer, query); - if(!queryResponse.data) queryResponse.data = []; - queryResponse.data.push(responseObj); - }) + if(!add) { + this._vectorLayers.forEach(vectorLayer => { + if (!queryResponse.data) { + queryResponse.data = []; + } + queryResponse.data.push( + this.getVectorLayerFeaturesFromQueryRequest(vectorLayer, query) + ); + }) + } }); }; -//function to add custom component in query result +/** + * Add custom component in query result + * + * @param component + */ proto._addComponent = function(component) { this.state.components.push(component) }; -proto._printSingleAtlas = function({atlas={}, features=[]}={}){ - let {name:template, atlas: {field_name}} = atlas; +/** + * @FIXME add description + */ +proto._printSingleAtlas = function({atlas={}, features=[]}={}) { + let { + name: template, + atlas: { field_name } + } = atlas; field_name = field_name || '$id'; - const values = features.map(feature => feature.attributes[field_name === '$id' ? G3W_FID: field_name]); const download_caller_id = ApplicationService.setDownload(true); - return this.printService.printAtlas({ - field: field_name, - values, - template, - download: true - }).then(({url}) =>{ - downloadFile({ - url, - filename: template, - mime_type: 'application/pdf' - }).catch(error=>{ - GUI.showUserMessage({ - type: 'alert', - error - }) - }).finally(()=>{ - ApplicationService.setDownload(false, download_caller_id); - GUI.setLoadingContent(false); - }) - }) + return this.printService + .printAtlas({ + field: field_name, + values: features.map(feature => feature.attributes['$id' === field_name ? G3W_FID: field_name]), + template, + download: true + }) + .then(({url}) => { downloadFile({ url, filename: template, mime_type: 'application/pdf' }) + .catch(error => { GUI.showUserMessage({ type: 'alert', error }) }) + .finally(() => { ApplicationService.setDownload(false, download_caller_id); GUI.setLoadingContent(false); }) + }); }; -proto.showChart = function(ids, container, relationData){ +/** + * @FIXME add description + * + * @param ids + * @param container + * @param relationData + */ +proto.showChart = function(ids, container, relationData) { this.emit('show-chart', ids, container, relationData); }; -proto.hideChart = function(container){ +/** + * @FIXME add description + */ +proto.hideChart = function(container) { this.emit('hide-chart', container); }; -proto.showRelationsChart = function(ids=[], layer, feature, action, index, container){ +/** + * @FIXME add description + * + * @param ids + * @param layer + * @param feature + * @param action + * @param index + * @param container + */ +proto.showRelationsChart = function(ids=[], layer, feature, action, index, container) { action.state.toggled[index] = !action.state.toggled[index]; - if (action.state.toggled[index]){ - const relations = this._relations[layer.id]; - const relationData = { - relations, + if (action.state.toggled[index]) { + this.emit('show-chart', ids, container, { + relations: this._relations[layer.id], fid: feature.attributes[G3W_FID], height: 400 - }; - this.emit('show-chart', ids, container, relationData) - } else this.hideChart(container) + }); + } else { + this.hideChart(container); + } }; -proto.printAtlas = function(layer, feature){ - let {id:layerId, features} = layer; +/** + * @FIXME add description + * + * @param layer + * @param feature + */ +proto.printAtlas = function(layer, feature) { + const features = feature ? [feature]: layer.features; const inputAtlasAttr = 'g3w_atlas_index'; - features = feature ? [feature]: features; - const atlasLayer = this.getAtlasByLayerId(layerId); + const atlasLayer = this.getAtlasByLayerId(layer.id); if (atlasLayer.length > 1) { let inputs=''; atlasLayer.forEach((atlas, index) => { const id = getUniqueDomId(); - inputs += ` - -
`; + inputs += `
`; }); GUI.showModalDialog({ @@ -1341,99 +1700,112 @@ proto.printAtlas = function(layer, feature){ success: { label: "OK", className: "skin-button", - callback: ()=> { + callback: () => { const index = $('input[name="template"]:checked').attr(inputAtlasAttr); - if (index !== null || index !== undefined) { - const atlas = atlasLayer[index]; - this._printSingleAtlas({ - atlas, - features - }) + if (null !== index || undefined !== index) { + this._printSingleAtlas({ atlas: atlasLayer[index], features }); } } } } - }) - } else this._printSingleAtlas({ - atlas: atlasLayer[0], - features - }) + }); + } else { + this._printSingleAtlas({ atlas: atlasLayer[0], features }); + } }; /** - * Method that in case + * @FIXME add description + * * @param layer */ proto.showLayerDownloadFormats = function(layer) { - const layerKey = DownloadFormats.name; - layer[layerKey].active = !layer[layerKey].active; + const name = DownloadFormats.name; + layer[name].active = !layer[name].active; this.setLayerActionTool({ layer, - component: layer[layerKey].active ? DownloadFormats : null, - config: layer[layerKey].active ? this.state.actiontools[layerKey][layer.id] : null + component: layer[name].active ? DownloadFormats : null, + config: layer[name].active ? this.state.actiontools[name][layer.id] : null }) }; /** - * - * @param type - * @param layerId - * @param features - */ -proto.downloadFeatures = async function(type, layer, features=[], action, index){ - const layerId = layer.id; - const {query={}} = this.state; - features = features ? Array.isArray(features) ? features : [features]: features; - const data = { - fids : features.map(feature => feature.attributes[G3W_FID]).join(',') - }; + * @FIXME add description + * + * @param type + * @param layer + * @param features + * @param action + * @param index + */ +proto.downloadFeatures = async function(type, layer, features=[], action, index) { + features = features + ? Array.isArray(features) + ? features + : [features] + : features; + const { + query = {} + } = this.state; + const fids = features.map(feature => feature.attributes[G3W_FID]).join(','); + const data = { fids }; + /** - * is a function that che be called in case of querybypolygon + * A function that che be called in case of querybypolygon + * * @param active */ const runDownload = async (active=false) => { if (features.length > 1) { layer[DownloadFormats.name].active = active; - this.setLayerActionTool({ - layer - }) + this.setLayerActionTool({ layer }); } - const projectLayer = CatalogLayersStoresRegistry.getLayerById(layer.id); const download_caller_id = ApplicationService.setDownload(true); GUI.setLoadingContent(true); try { - await projectLayer.getDownloadFilefromDownloadDataType(type, { - data - }) || Promise.resolve(); - } catch(err){ + await CatalogLayersStoresRegistry.getLayerById(layer.id).getDownloadFilefromDownloadDataType(type, { data }) || Promise.resolve(); + } catch(err) { GUI.notify.error(err || t("info.server_error")); } ApplicationService.setDownload(false, download_caller_id); GUI.setLoadingContent(false); - const downloadsactions = this.state.layersactions[layer.id].find(action => action.id === 'downloads'); - if (features.length > 1) { - if (downloadsactions === undefined) { - layer[type].active = false; - this.setLayerActionTool({ - layer - }) - } - else layer[DownloadFormats.name].active = false; + const downloadsactions = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); + + /** @FIXME add description */ + if (features.length > 1 && undefined === downloadsactions) { + layer[type].active = false; + this.setLayerActionTool({ layer }); } - else { - if (downloadsactions === undefined) action.state.toggled[index] = false; - else downloadsactions.state.toggled[index] = false; - this.setCurrentActionLayerFeatureTool({ - index, - action, - layer - }); + + /** @FIXME add description */ + if(features.length > 1 && undefined !== downloadsactions) { + layer[DownloadFormats.name].active = false; + } + + /** @FIXME add description */ + if (features.length <= 1 && undefined === downloadsactions) { + action.state.toggled[index] = false; + } + + /** @FIXME add description */ + if (features.length <= 1 && undefined !== downloadsactions) { + downloadsactions.state.toggled[index] = false; + } + + /** @FIXME add description */ + if (features.length <= 1) { + this.setCurrentActionLayerFeatureTool({ index, action, layer }); } }; - if (query.type === 'polygon'){ - //check if multidownload if present + + if ('polygon' !== query.type) { + runDownload(); + } else { // check if multi-download if present const downloadsactions = this.state.layersactions[layer.id].find(action => action.id === 'downloads'); - let {fid, layer:polygonLayer} = query; + let { + fid, + layer:polygonLayer + } = query; const config = { choices: [ { @@ -1447,133 +1819,151 @@ proto.downloadFeatures = async function(type, layer, features=[], action, index) label: 'sdk.mapcontrols.querybypolygon.download.choiches.feature_polygon.label', }, ], - download: type =>{ - // choose between only feature attribute or also polygon attibute - if (type === 'polygon'){ - // id type polygon add paramateres to api download + // choose between only feature attribute or also polygon attibute + download: (type) => { + if ('polygon' === type) { // id type polygon add paramateres to api download data.sbp_qgs_layer_id = polygonLayer.getId(); data.sbp_fid = fid; - } else { - // force to remove + } else { // force to remove delete data.sbp_fid; delete data.sbp_qgs_layer_id; } runDownload(true) } }; - if (features.length === 1) { - if (downloadsactions === undefined) { - action.state.toggled[index] = true; - } - this.state.actiontools[QueryPolygonCsvAttributesComponent.name] = this.state.actiontools[layerId] || {}; - this.state.actiontools[QueryPolygonCsvAttributesComponent.name][layerId] = config; - this.setCurrentActionLayerFeatureTool({ - layer, - index, - action, - component: QueryPolygonCsvAttributesComponent - }); - } else { - if (downloadsactions === undefined) { - layer[type].active = !layer[type].active; - if (layer[type].active) { - this.setLayerActionTool({ - layer, - component: QueryPolygonCsvAttributesComponent, - config - }); - } else this.setLayerActionTool({ - layer - }) - } else { - this.setLayerActionTool({ - layer, - component: QueryPolygonCsvAttributesComponent, - config - }); - } + + /** @FIXME add description */ + if (1 === features.length && undefined === downloadsactions) { + action.state.toggled[index] = true; + } + + /** @FIXME add description */ + if(1 === features.length) { + this.state.actiontools[QueryPolygonCsvAttributesComponent.name] = this.state.actiontools[layer.id] || {}; + this.state.actiontools[QueryPolygonCsvAttributesComponent.name][layer.id] = config; + this.setCurrentActionLayerFeatureTool({ layer, index, action, component: QueryPolygonCsvAttributesComponent }); + } + + /** @FIXME add description */ + if (features.length < 1 && undefined === downloadsactions) { + layer[type].active = !layer[type].active; + } + + /** @FIXME add description */ + if (features.length < 1 && undefined === downloadsactions && layer[type].active) { + this.setLayerActionTool({ layer, component: QueryPolygonCsvAttributesComponent, config }); + } + + /** @FIXME add description */ + if (features.length < 1 && undefined === downloadsactions && !layer[type].active){ + this.setLayerActionTool({ layer }); + } + + /** @FIXME add description */ + if (features.length < 1 && undefined !== downloadsactions) { + this.setLayerActionTool({ layer, component: QueryPolygonCsvAttributesComponent, config }); } - } else runDownload(); + + } }; -proto.downloadGpx = function({id:layerId}={}, feature){ - const fid = feature ? feature.attributes[G3W_FID] : null; - const layer = CatalogLayersStoresRegistry.getLayerById(layerId); - layer.getGpx({fid}).catch((err) => { - GUI.notify.error(t("info.server_error")); - }).finally(() => { - this.layerMenu.loading.shp = false; - this._hideMenu(); - }) +/** + * @FIXME add description + */ +proto.downloadGpx = function({id:layerId}={}, feature) { + CatalogLayersStoresRegistry + .getLayerById(layerId) + .getGpx({ fid: feature ? feature.attributes[G3W_FID] : null }) + .catch((err) => { GUI.notify.error(t("info.server_error")); }) + .finally(() => { this.layerMenu.loading.shp = false; this._hideMenu(); }) }; -proto.downloadXls = function({id:layerId}={}, feature){ - const fid = feature ? feature.attributes[G3W_FID] : null; - const layer = CatalogLayersStoresRegistry.getLayerById(layerId); - layer.getXls({fid}).catch(err => { - GUI.notify.error(t("info.server_error")); - }).finally(() => { - this.layerMenu.loading.shp = false; - this._hideMenu(); - }) +/** + * @FIXME add description + */ +proto.downloadXls = function({id:layerId}={}, feature) { + CatalogLayersStoresRegistry + .getLayerById(layerId) + .getXls({ fid: feature ? feature.attributes[G3W_FID] : null }) + .catch(err => { GUI.notify.error(t("info.server_error")); }) + .finally(() => { this.layerMenu.loading.shp = false; this._hideMenu(); }) }; -proto.listenClearSelection = function(layer, actionId){ - if (!layer.external) { +/** + * + * @FIXME add description + * + * @param layer + * @param actionId + */ +proto.listenClearSelection = function(layer, actionId) { + if (layer.external) { + layer.features + .forEach(feature => { + const selectionFeature = layer.selection.features.find(selectionFeature => feature.id === selectionFeature.getId()); + feature.selection = (selectionFeature) ? selectionFeature.selection : { selected: false }; + }); + } else { const _layer = CatalogLayersStoresRegistry.getLayerById(layer.id); - const handler = ()=>{ - const action = this.state.layersactions[layer.id].find(action => action.id === actionId); - layer.features.forEach((feature, index) => action.state.toggled[index] = false); + const handler = () => { + layer.features.forEach((feature, index) => + this.state.layersactions[layer.id].find(action => action.id === actionId).state.toggled[index] = false + ); }; _layer.on('unselectionall', handler); - this.unlistenerlayeractionevents.push({ - layer:_layer, - event:'unselectionall', - handler - }) - } else { - layer.features.forEach(feature => { - const selectionFeature = layer.selection.features.find(selectionFeature => feature.id === selectionFeature.getId()); - feature.selection = selectionFeature ? selectionFeature.selection : { - selected: false - } - }); + this.unlistenerlayeractionevents.push({ layer:_layer, event:'unselectionall', handler }); } }; +/** + * @FIXME add description + * + * @param layer + */ proto.clearSelectionExtenalLayer = function(layer) { layer.selection.active = false; - const action = this.state.layersactions[layer.id] && this.state.layersactions[layer.id].find(action => action.id === 'selection'); - layer.selection.features.forEach((feature, index) => { - if (feature.selection.selected) { - feature.selection.selected = false; - if (action) action.state.toggled[index] = false; - this.mapService.setSelectionFeatures('remove', { - feature - }); - } - }); + const action = ( + this.state.layersactions[layer.id] && + this.state.layersactions[layer.id].find(action => action.id === 'selection') + ); + layer.selection.features + .forEach((feature, index) => { + if (feature.selection.selected) { + feature.selection.selected = false; + if (action) { + action.state.toggled[index] = false; + } + this.mapService.setSelectionFeatures('remove', { feature }); + } + }); }; -proto.unlistenerEventsActions = function(){ +/** + * @FIXME add description + */ +proto.unlistenerEventsActions = function() { this.unlistenerlayeractionevents.forEach(obj => obj.layer.off(obj.event, obj.handler)); this.unlistenerlayeractionevents = []; }; -proto.addRemoveFilter = function(layer){ - const _layer = CatalogLayersStoresRegistry.getLayerById(layer.id); - _layer.toggleFilterToken(); +/** + * @FIXME add description + * + * @param layer + */ +proto.addRemoveFilter = function(layer) { + CatalogLayersStoresRegistry.getLayerById(layer.id).toggleFilterToken(); }; /** - * + * @FIXME add description + * * @param layer */ proto.selectionFeaturesLayer = function(layer) { - const layerId = layer.id; - const action = this.state.layersactions[layerId].find(action => action.id === 'selection'); - const bool = Object.values(action.state.toggled).reduce((acculmulator, value) => acculmulator && value, true); - const _layer = !layer.external ? CatalogLayersStoresRegistry.getLayerById(layerId) : layer; + const action = this.state.layersactions[layer.id].find(action => action.id === 'selection'); + const bool = Object.values(action.state.toggled).reduce((acculmulator, value) => acculmulator && value, true); + const _layer = layer.external ? layer : CatalogLayersStoresRegistry.getLayerById(layer.id); layer.features.forEach((feature, index) => { action.state.toggled[index] = !bool; this._addRemoveSelectionFeature(_layer, feature, index, bool ? 'remove' : 'add'); @@ -1581,208 +1971,276 @@ proto.selectionFeaturesLayer = function(layer) { }; /** - * + * @FIXME add description + * * @param layer * @param feature * @param index * @param force - * @returns {Promise} - * @private */ -proto._addRemoveSelectionFeature = async function(layer, feature, index, force){ +proto._addRemoveSelectionFeature = async function(layer, feature, index, force) { + /** - * In case of external layer (vector) added by add external layer tool + * An external layer (vector) added by add external layer tool */ - if (typeof layer.external !== "undefined" && layer.external){ - if (typeof layer.selection.features === "undefined") layer.selection.features = {}; + if (layer.external && "undefined" !== typeof layer.external) { + + /** @FIXME add description */ + if ("undefined" === typeof layer.selection.features) { + layer.selection.features = {}; + } + + // Feature used in selection tool action if (!layer.selection.features.find(selectionFeature => selectionFeature.getId() === feature.id)) { - /*** - * Feature used in selection tool action - */ - const selectionFeature = createFeatureFromFeatureObject({ - feature, - id: feature.id - }); + const selectionFeature = createFeatureFromFeatureObject({ feature, id: feature.id }); selectionFeature.__layerId = layer.id; selectionFeature.selection = feature.selection; layer.selection.features.push(selectionFeature); } - if ((force === 'add' && feature.selection.selected) || (force === 'remove') && !feature.selection.selected) return; - else feature.selection.selected = !feature.selection.selected; - this.mapService.setSelectionFeatures(feature.selection.selected ? 'add' : 'remove', { - feature: layer.selection.features.find(selectionFeature => feature.id === selectionFeature.getId()) - }); - /* - * Set selection layer active based on features selection selected properties - */ + + /** @FIXME add description */ + if ( + ('add' === force && feature.selection.selected) || (force === 'remove') && + !feature.selection.selected + ) { + return; + } + + /** @FIXME add description */ + else { + feature.selection.selected = !feature.selection.selected; + } + + /** @FIXME add description */ + this.mapService.setSelectionFeatures( + feature.selection.selected ? 'add' : 'remove', + { feature: layer.selection.features.find(selectionFeature => feature.id === selectionFeature.getId()) } + ); + + // Set selection layer active based on features selection selected properties layer.selection.active = layer.selection.features.reduce((accumulator, feature) => accumulator || feature.selection.selected, false) - } else { // case of project layer on TOC - const fid = feature ? feature.attributes[G3W_FID]: null; + + } + + /** + * A project layer on TOC + */ + if (false === (layer.external && typeof "undefined" !== layer.external)) { + + const fid = feature ? feature.attributes[G3W_FID] : null; const hasAlreadySelectioned = layer.getFilterActive() || layer.hasSelectionFid(fid); - if (!hasAlreadySelectioned) { - if (feature && feature.geometry && !layer.getOlSelectionFeature(fid)) { - layer.addOlSelectionFeature({ - id: fid, - feature - }) - } + + /** @FIXME add description */ + if (!hasAlreadySelectioned && feature && feature.geometry && !layer.getOlSelectionFeature(fid)) { + layer.addOlSelectionFeature({ id: fid, feature }); } - if (force === undefined) layer[hasAlreadySelectioned ? 'excludeSelectionFid': 'includeSelectionFid'](fid); - else if (!hasAlreadySelectioned && force === 'add') await layer.includeSelectionFid(fid); - else if (hasAlreadySelectioned && force === 'remove') await layer.excludeSelectionFid(fid); + + /** @FIXME add description */ + if (undefined === force) { + layer[hasAlreadySelectioned ? 'excludeSelectionFid': 'includeSelectionFid'](fid); + } + + /** @FIXME add description */ + if (undefined !== force && !hasAlreadySelectioned && 'add' === force) { + await layer.includeSelectionFid(fid); + } + + /** @FIXME add description */ + if (undefined !== force && hasAlreadySelectioned && 'remove' === force) { + await layer.excludeSelectionFid(fid); + } + + /** @FIXME add description */ if (layer.getFilterActive()) { + const currentLayer = this.state.layers.find(_layer => _layer.id === layer.getId()); - layer.getSelectionFids().size > 0 && currentLayer && currentLayer.features.splice(index, 1); + + /** @FIXME add description */ + if (layer.getSelectionFids().size > 0 && currentLayer) { + currentLayer.features.splice(index, 1); + } + this.mapService.clearHighlightGeometry(); - this.state.layers.length === 1 && !this.state.layers[0].features.length && this.state.layers.splice(0); + + /** @FIXME add description */ + if (1 === this.state.layers.length && !this.state.layers[0].features.length) { + this.state.layers.splice(0); + } + } + } + }; /** * Initial check of selection active on layer + * * @param layer * @param feature * @param index * @param action */ -proto.checkFeatureSelection = function({layer, feature, index, action}={}){ - if (!layer.external) { - const projectLayer = CatalogLayersStoresRegistry.getLayerById(layer.id); - if (feature) { - const fid = feature ? feature.attributes[G3W_FID]: null; - action.state.toggled[index] = projectLayer.getFilterActive() || projectLayer.hasSelectionFid(fid); - } - } else { +proto.checkFeatureSelection = function({layer, feature, index, action}={}) { + if (layer.external) { action.state.toggled[index] = feature.selection.selected; + } else if (feature) { + action.state.toggled[index] = ( + CatalogLayersStoresRegistry.getLayerById(layer.id).getFilterActive() || + CatalogLayersStoresRegistry.getLayerById(layer.id).hasSelectionFid(feature ? feature.attributes[G3W_FID]: null) + ); } }; /** - * + * @FIXME add description + * * @param layer * @param feature * @param action * @param index */ -proto.addToSelection = function(layer, feature, action, index){ +proto.addToSelection = function(layer, feature, action, index) { const {external=false} = layer; action.state.toggled[index] = !action.state.toggled[index]; - const _layer = !external ? CatalogLayersStoresRegistry.getLayerById(layer.id) : layer; - this._addRemoveSelectionFeature(_layer, feature, index); + this._addRemoveSelectionFeature( + (external ? layer : CatalogLayersStoresRegistry.getLayerById(layer.id)), + feature, + index + ); }; -proto.removeQueryResultLayerFromMap = function(){ +/** + * @FIXME add description + */ +proto.removeQueryResultLayerFromMap = function() { this.resultsQueryLayer.getSource().clear(); this.mapService.getMap().removeLayer(this.resultsQueryLayer) }; -// show layerQuery result on map -proto.addQueryResultsLayerToMap = function({feature, timeout=1500}){ +/** + * Show layerQuery result on map + */ +proto.addQueryResultsLayerToMap = function({feature, timeout=1500}) { this.removeQueryResultLayerFromMap(); this.resultsQueryLayer.getSource().addFeature(feature); this.mapService.getMap().addLayer(this.resultsQueryLayer); - try { - const center = ol.extent.getCenter(feature.getGeometry().getExtent()); - this.mapService.getMap().getView().setCenter(center); - } catch(err){ - + try { this.mapService.getMap().getView().setCenter(ol.extent.getCenter(feature.getGeometry().getExtent())); } + catch(err) {} + if (timeout) { + setTimeout(() => { this.removeQueryResultLayerFromMap(); }, timeout) } - timeout && setTimeout(()=>{ - this.removeQueryResultLayerFromMap(); - }, timeout) }; /** - * - Show featureFormCoordinates + * Show feature from coordinates + * + * @param coordinates */ -proto.showCoordinates = function(coordinates){ - const feature = createFeatureFromCoordinates(coordinates); - this.addQueryResultsLayerToMap({feature}); +proto.showCoordinates = function(coordinates) { + this.addQueryResultsLayerToMap({ feature: createFeatureFromCoordinates(coordinates) }); }; /** * Show BBox + * * @param bbox */ -proto.showBBOX = function(bbox){ - const feature = createFeatureFromBBOX(bbox); - this.addQueryResultsLayerToMap({feature}); +proto.showBBOX = function(bbox) { + this.addQueryResultsLayerToMap({ feature: createFeatureFromBBOX(bbox) }); }; /** * Show Geometry + * * @param geometry */ -proto.showGeometry = function(geometry){ - const feature = createFeatureFromGeometry({ - geometry - }); - this.addQueryResultsLayerToMap({feature}); +proto.showGeometry = function(geometry) { + this.addQueryResultsLayerToMap({ feature: createFeatureFromGeometry({ geometry }) }); }; /** - * + * @FIXME add description + * * @param layer * @param feature */ proto.goToGeometry = function(layer, feature) { - if (feature.geometry) { - const handlerOptions = { - mapServiceMethod: this.isOneLayerResult() ? 'zoomToFeatures' : 'highlightGeometry', - firstParam: this.isOneLayerResult() ? [feature] : feature.geometry, - options: this.isOneLayerResult() ? {} : { - layerId: layer.id, - duration: 1500 - } - }; - if (this._asyncFnc.goToGeometry.async) this._asyncFnc.todo = this.mapService[handlerOptions.mapServiceMethod].bind(this.mapService, handlerOptions.firstParam, handlerOptions.options); - else setTimeout(() => this.mapService[handlerOptions.mapServiceMethod](handlerOptions.firstParam, handlerOptions.options)) + if (!feature.geometry) { + return; + } + const handlerOptions = { + mapServiceMethod: this.isOneLayerResult() ? 'zoomToFeatures' : 'highlightGeometry', + firstParam: this.isOneLayerResult() ? [feature] : feature.geometry, + options: this.isOneLayerResult() ? {} : { layerId: layer.id, duration: 1500 } + }; + if (this._asyncFnc.goToGeometry.async) { + this._asyncFnc.todo = this.mapService[handlerOptions.mapServiceMethod].bind( + this.mapService, + handlerOptions.firstParam, + handlerOptions.options + ); + } else { + setTimeout(() => this.mapService[handlerOptions.mapServiceMethod]( + handlerOptions.firstParam, + handlerOptions.options + )); } }; -//save layer result +/** + * Save layer result + */ proto.saveLayerResult = function({layer, type='csv'}={}) { this.downloadFeatures(type, layer, layer.features); }; +/** + * @FIXME add description + * + * @param layer + * @param feature + */ proto.highlightGeometry = function(layer, feature) { - feature.geometry && this.mapService.highlightGeometry(feature.geometry, { - layerId: layer.id, - zoom: false, - duration: Infinity - }); + if(feature.geometry) { + this.mapService.highlightGeometry( + feature.geometry, + { layerId: layer.id, zoom: false, duration: Infinity } + ); + } }; +/** + * @FIXME add description + * + * @param layer + */ proto.clearHighlightGeometry = function(layer) { this.mapService.clearHighlightGeometry(); - this.isOneLayerResult() && this.highlightFeaturesPermanently(layer); + if (this.isOneLayerResult()) { + this.highlightFeaturesPermanently(layer); + } }; /** - * method to handle show Relation on result - * @param relationId, - * layerId : current layer fathre id - * feature: current feature father id + * + * Handle show Relation on result + * + * - layerId = current layer father id + * - feature = current feature father id + * + * @param relationId */ -proto.showRelation = function({relation, layerId, feature}={}){ - const {name: relationId, nmRelationId} = relation; - const chartRelationIds = []; - const projectRelation = this._project.getRelationById(relationId); - const nmRelation = this._project.getRelationById(nmRelationId); - this.findPlotId(projectRelation.referencingLayer) && chartRelationIds.push(projectRelation.referencingLayer); - +proto.showRelation = function({relation, layerId, feature}={}) { + const projectRelation = this._project.getRelationById(relation.name); GUI.pushContent({ content: new RelationsPage({ currentview: 'relation', relations: [projectRelation], - chartRelationIds, - nmRelation, + chartRelationIds: this.findPlotId(projectRelation.referencingLayer) ? [projectRelation.referencingLayer] : [], + nmRelation: this._project.getRelationById(relation.nmRelationId), feature, - layer: { - id: layerId - } + layer: { id: layerId } }), crumb: { title: projectRelation.name @@ -1792,14 +2250,15 @@ proto.showRelation = function({relation, layerId, feature}={}){ }) }; +/** + * @FIXME add description + * + * @param layer + * @param feature + * @param action + */ proto.showQueryRelations = function(layer, feature, action) { - - GUI.changeCurrentContentOptions({ - crumb: { - title: layer.title - } - }); - + GUI.changeCurrentContentOptions({ crumb: { title: layer.title } }); GUI.pushContent({ content: new RelationsPage({ relations: action.relations, @@ -1818,6 +2277,85 @@ proto.showQueryRelations = function(layer, feature, action) { }); }; +/** + * Get layer from current state.layers showed on result + * + * @since v3.8.0 + */ +proto._getLayer = function(layerId) { + return this.state.layers.find(l => l.id === layerId); +}; + +/** + * Get external layer from current state.layers showed on result + * + * @since v3.8.0 + */ +proto._getExternalLayer = function(layerId) { + return (this._getLayer(layerId) || {}).external; +}; + +/** + * Get ids of the selected features + * + * @since v3.8.0 + */ +proto._getFeaturesIds = function(features, external) { + return features.map(f => external ? f.id : f.attributes[G3W_FID]); +} + +/** + * Extract features from layer object + * + * @since v3.8.0 + */ +proto._getLayerFeatures = function(layer) { + return layer.features || []; +}; + +/** + * Loop and filter the features that we need to remove + * + * @since v3.8.0 + */ +proto._featuresToRemove = function(features, external) { + const features_ids = this._getFeaturesIds(features, external); // get id of the features + return features.filter(feature => (-1 === features_ids.indexOf(external ? feature.id : feature.attributes[G3W_FID]))); +}; + +/** + * Filter features to add + * + * @since v3.8.0 + */ +proto._featuresToAdd = function(features, external) { + const features_ids = this._getFeaturesIds(features, external); + return features.filter(feature => (-1 !== features_ids.indexOf(external ? feature.id : feature.attributes[G3W_FID]))); +}; + +/** + * @since v3.8.0 + */ +proto._toggleLayerFeatureBox = function(layer, feature, collapsed) { + const boxId = this.getBoxId(layer, feature); + if (boxId && this.state.layersFeaturesBoxes[boxId]) { + setTimeout(() => this.state.layersFeaturesBoxes[boxId].collapsed = collapsed); // due to vue reactivity, wait a little bit before update layers + } +}; + +/** + * @since v3.8.0 + */ +proto._removeLayerFeatureBox = function(layer, feature_to_delete) { + setTimeout(()=> delete this.state.layersFeaturesBoxes[this.getBoxId(layer, feature_to_delete)]); +}; + +/** + * @deprecated since v3.8. Will be deleted in 4.x. Use QueryResultsService::updateLayerResultFeatures(layer) instead + */ +proto.addRemoveFeaturesToLayerResult = deprecate(proto.updateLayerResultFeatures, '[G3W-CLIENT] QueryResultsService::addRemoveFeaturesToLayerResult(layer) is deprecated'); + + module.exports = QueryResultsService; From 7d3fd1e88e1eb2ef1dc8bb4478a65a4c099751a1 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Thu, 2 Feb 2023 17:40:35 +0100 Subject: [PATCH 02/48] Fix misspelling and properties position declaration --- .../gui/queryresults/queryresultsservice.js | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 9b30aaffa..0bc234ae5 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -571,11 +571,6 @@ proto.setActionsForLayers = function(layers, options={add: false}) { layers.forEach(layer => { - // set eventually layer action tool and need to be reactive - this.state.layeractiontool[layer.id] = Vue.observable({ component: null, config: null }); - this.state.currentactiontools[layer.id] = Vue.observable(currentactiontoolslayer); - this.state.currentactionfeaturelayer[layer.id] = Vue.observable(currentationfeaturelayer); - const currentactiontoolslayer = {}; const currentationfeaturelayer = {}; layer.features.forEach((_, idx) => { @@ -583,6 +578,11 @@ proto.setActionsForLayers = function(layers, options={add: false}) { currentationfeaturelayer[idx] = null; }); + // set eventually layer action tool and need to be reactive + this.state.layeractiontool[layer.id] = Vue.observable({ component: null, config: null }); + this.state.currentactiontools[layer.id] = Vue.observable(currentactiontoolslayer); + this.state.currentactionfeaturelayer[layer.id] = Vue.observable(currentationfeaturelayer); + const is_external_layer_or_wms = (layer.external) || (layer.source ? layer.source.type === 'wms' : false); if (!this.state.layersactions[layer.id]) { @@ -702,7 +702,7 @@ proto.setActionsForLayers = function(layers, options={add: false}) { downloads.push({ id: `download_${format}_feature`, download: true, - format: format, + format, class: GUI.getFontClass(format), hint: `sdk.tooltips.download_${format}`, cbk: (layer, feature, action, index)=> { @@ -719,7 +719,6 @@ proto.setActionsForLayers = function(layers, options={add: false}) { // set actionstools configs this.state.actiontools[DownloadFormats.name] = this.state.actiontools[DownloadFormats.name] || {}; this.state.actiontools[DownloadFormats.name][layer.id] = { downloads }; - // check if has download actions this.state.layersactions[layer.id] .push({ @@ -1207,8 +1206,7 @@ proto.reset = function() { * * @returns {[]} */ -proto._digestFeaturesForLayers = function(featuresForLayers) { - featuresForLayers = featuresForLayers || []; +proto._digestFeaturesForLayers = function(featuresForLayers=[]) { let id = 0; const layers = []; let layerAttributes, @@ -1216,7 +1214,7 @@ proto._digestFeaturesForLayers = function(featuresForLayers) { layerTitle, layerId; - // converter + // convert response from server const _handleFeatureFoLayer = featuresForLayer => { const layerObj = { editable: false, @@ -1245,7 +1243,6 @@ proto._digestFeaturesForLayers = function(featuresForLayers) { rawdata: null, // rawdata response loading: false, }; - const layer = featuresForLayer.layer; let sourceType; let extractRelations = false; @@ -1346,10 +1343,10 @@ proto._digestFeaturesForLayers = function(featuresForLayers) { layerObj.attributes = this._parseAttributes(layerAttributes, featuresForLayer.features[0], sourceType); layerObj.attributes .forEach(attribute => { - if (formStructure) { + if (layerObj.formStructure) { const relationField = layer.getFields().find(field => field.name === attribute.name); // need to check all field also show false if (!relationField) { - formStructure.fields.push(attribute); + layerObj.formStructure.fields.push(attribute); } } if (attribute.type === 'image') { From 5319305b53a4f8cf34b255b46dcf6db8c69f76f2 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Fri, 3 Feb 2023 09:25:00 +0100 Subject: [PATCH 03/48] Add comments --- src/app/gui/queryresults/queryresultsservice.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 0bc234ae5..57770a39b 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -29,7 +29,7 @@ const VM = new Vue(); function QueryResultsService() { /** - * @FIXME add description + * Service used to work with atlas (print functionality) action tool */ this.printService = new PrintService(); @@ -39,7 +39,8 @@ function QueryResultsService() { this._currentLayerIds = []; /** - * @FIXME add description + * @deprecated since 3.8 + * It used to register change project from Change map button */ ProjectsRegistry.onafter('setCurrentProject', project => { this._project = project; @@ -64,7 +65,7 @@ function QueryResultsService() { }; /** - * @FIXME add description + * to store relations */ this._relations = []; @@ -79,7 +80,7 @@ function QueryResultsService() { this.plotLayerIds = []; /** - * @FIXME add description + * Current project */ this._project = ProjectsRegistry.getCurrentProject(); @@ -197,7 +198,8 @@ function QueryResultsService() { }); /** - * @FIXME add description + * + * where are store vector layer add on runtime */ this._vectorLayers = []; @@ -229,7 +231,7 @@ function QueryResultsService() { }; /** - * @FIXME add description + * Core methods that used from other object to react before or after its call */ this.setters = { @@ -272,7 +274,7 @@ function QueryResultsService() { }, /** - * @FIXME add description + * Method used to add custom component * * @param component */ From 478393f5f6f65efb131392d42e8402f998d47d67 Mon Sep 17 00:00:00 2001 From: Raruto Date: Tue, 28 Mar 2023 16:16:27 +0200 Subject: [PATCH 04/48] Closes https://github.com/g3w-suite/g3w-client/issues/59 --- .../gui/queryresults/queryresultsservice.js | 469 ++++++++++-------- 1 file changed, 258 insertions(+), 211 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 73d91a705..cf6a9a421 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -515,7 +515,8 @@ proto.downloadApplicationWrapper = async function(downloadFnc, options={}) { * be added or removed to current `state.layers` results * * @param {Array} layer - * @since v3.8.0 + * + * @since 3.8.0 */ proto.updateLayerResultFeatures = function(responseLayer) { const layer = this._getLayer(responseLayer.id), // get layer from current `state.layers` showed on result @@ -600,7 +601,7 @@ proto.getBoxId = function(layer, feature, relation_index) { * @param layers * @param options */ -proto.setActionsForLayers = function(layers, options={add: false}) { +proto.setActionsForLayers = function(layers, options = { add: false }) { if (options.add) { return; } @@ -621,240 +622,61 @@ proto.setActionsForLayers = function(layers, options={add: false}) { this.state.currentactiontools[layer.id] = Vue.observable(currentactiontoolslayer); this.state.currentactionfeaturelayer[layer.id] = Vue.observable(currentationfeaturelayer); - const is_external_layer_or_wms = (layer.external) || (layer.source ? layer.source.type === 'wms' : false); + const is_external_layer_or_wms = (layer.external) || (layer.source ? 'wms' === layer.source.type : false); if (!this.state.layersactions[layer.id]) { this.state.layersactions[layer.id] = []; } - /** - * Lookup for layer geometry. - */ + // Lookup for layer geometry. if (layer.hasgeometry) { - this.state.layersactions[layer.id] - .push({ - id: 'gotogeometry', - download: false, - mouseover: true, - class: GUI.getFontClass('marker'), - hint: 'sdk.mapcontrols.query.actions.zoom_to_feature.hint', - cbk: throttle(this.goToGeometry.bind(this)) - }); + this._setActionGoToGeometry(layer.id); } - /** - * Lookup for layer relations. - */ + // Lookup for layer relations. if (this._relations && this._relations[layer.id]) { - - const relations = this._relations[layer.id].filter(relation => 'MANY' === relation.type); - const chartRelationIds = []; - - relations.forEach(relation => { - const id = this.plotLayerIds.find(id => id === relation.referencingLayer); - if (id) { - chartRelationIds.push(id); - } - }); - - /** @FIXME add description */ - if (relations.length) { - this.state.layersactions[layer.id] - .push({ - id: 'show-query-relations', - download: false, - class: GUI.getFontClass('relation'), - hint: 'sdk.mapcontrols.query.actions.relations.hint', - cbk: this.showQueryRelations, - relations, - chartRelationIds - }); - } - - /** @FIXME add description */ - if (chartRelationIds.length) { - this.state.layersactions[layer.id] - .push({ - id: 'show-plots-relations', - download: false, - opened: true, - class: GUI.getFontClass('chart'), - state: this.createActionState({ layer }), - hint: 'sdk.mapcontrols.query.actions.relations_charts.hint', - cbk: throttle(this.showRelationsChart.bind(this, chartRelationIds)) - }); - } - + this._setActionShowQueryAndPlotsRelations(layer); } - /** - * Lookup for layer print atlas. - */ + // Lookup for layer print atlas. if (this.getAtlasByLayerId(layer.id).length) { - this.state.layersactions[layer.id] - .push({ - id: `printatlas`, - download: true, - class: GUI.getFontClass('print'), - hint: `sdk.tooltips.atlas`, - cbk: this.printAtlas.bind(this) - }); + this._setActionPrintAtlas(layer); } - const state = this.createActionState({ layer }); - - /** - * Lookup for layer downloadable features (single). - */ + // Lookup for layer downloadable features (single). if (layer.downloads.length === 1) { - const [format] = layer.downloads; // NB: format == layer.downloads[0] - const cbk = this.downloadFeatures.bind(this, format); - layer[format] = Vue.observable({ active: false }); - this.state.layersactions[layer.id] - .push({ - id: `download_${format}_feature`, - download: true, - state, - class: GUI.getFontClass('download'), - hint: `sdk.tooltips.download_${format}`, - cbk: (layer, feature, action, index) => { - action.state.toggled[index] = !action.state.toggled[index]; - if (action.state.toggled[index]) { - cbk(layer, feature, action, index); - } else { - this.setCurrentActionLayerFeatureTool({ index, action, layer }) - } - } - }); + this._setActionDownloadFeature(layer); } - /** - * Lookup for layer downloadable features (multi). - */ + // Lookup for layer downloadable features (multi). if (layer.downloads.length > 1) { - - const downloads = []; - - layer.downloads - .forEach(format => { - downloads.push({ - id: `download_${format}_feature`, - download: true, - format, - class: GUI.getFontClass(format), - hint: `sdk.tooltips.download_${format}`, - cbk: (layer, feature, action, index)=> { - // un-toggle downloads action - this.downloadFeatures(format, layer, feature, action, index); - if ('polygon' !== this.state.query.type) { - const downloadsaction = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); - downloadsaction.cbk(layer, feature, downloadsaction, index); - } - } - }); - }); - - // set actionstools configs - this.state.actiontools[DownloadFormats.name] = this.state.actiontools[DownloadFormats.name] || {}; - this.state.actiontools[DownloadFormats.name][layer.id] = { downloads }; - // check if has download actions - this.state.layersactions[layer.id] - .push({ - id: `downloads`, - download: true, - class: GUI.getFontClass('download'), - state, - toggleable: true, - hint: `Downloads`, - change({features}) { - features - .forEach((feature, index) => { - if (undefined === this.state.toggled[index]) { - VM.$set(this.state.toggled, index, false); - } else { - this.state.toggled[index] = false; - } - }); - }, - cbk: (layer, feature, action, index) => { - action.state.toggled[index] = !action.state.toggled[index]; - this.setCurrentActionLayerFeatureTool({ layer, index, action, component: (action.state.toggled[index] ? DownloadFormats : null) }); - } - }); + this._setActionMultiDownloadFeature(layer); } - /** - * Lookup for not external layer or WMS. - */ + // Lookup for not external layer or WMS. if (false == is_external_layer_or_wms) { - this.state.layersactions[layer.id].push({ - id: 'removefeaturefromresult', - download: false, - mouseover: true, - class: GUI.getFontClass('minus-square'), - style: { - color: 'red' - }, - hint: 'sdk.mapcontrols.query.actions.remove_feature_from_results.hint', - cbk: this.removeFeatureLayerFromResult.bind(this) - }); + this._setActionRemoveFeatureFromResult(layer); } - /** - * Lookup for layer selection status (active). - */ + // Lookup for layer selection status (active). if (undefined !== layer.selection.active) { - this.state.layersactions[layer.id] - .push({ - id: 'selection', - download: false, - class: GUI.getFontClass('success'), - hint: 'sdk.mapcontrols.query.actions.add_selection.hint', - state: this.createActionState({ layer }), - init: ({feature, index, action}={}) => { - if("undefined" !== typeof layer.selection.active) { - this.checkFeatureSelection({ layer, index, feature, action }) - } - }, - cbk: throttle(this.addToSelection.bind(this)) - }); - // In case of external layer don't listen to `selection` event - this.listenClearSelection(layer, 'selection'); + this._setActionSelection(layer); } - /** - * Lookup for not external layer or WMS (copy link to feature). - */ + // Lookup for not external layer or WMS (copy link to feature). if (!is_external_layer_or_wms && layer.hasgeometry) { - this.state.layersactions[layer.id] - .push({ - id: 'link_zoom_to_fid', - download: false, - class: GUI.getFontClass('link'), - hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint', - hint_change: { - hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint_change', - duration: 1000 - }, - cbk: this.copyZoomToFidUrl.bind(this) - }); + this._setActionLinkZoomToFid(layer); } - /** - * Lookup for editable layer. - */ + // Lookup for editable layer. if (layer.editable && !layer.inediting) { - this.state.layersactions[layer.id] - .push({ - id: 'editing', - class: GUI.getFontClass('pencil'), - hint: 'Editing', - cbk: (layer, feature) => { this.editFeature({ layer, feature }) } - }); + this._setActionEditing(layer); } }); + this.addActionsForLayers(this.state.layersactions, this.state.layers); + }; /** @@ -2341,7 +2163,7 @@ proto.showQueryRelations = function(layer, feature, action) { /** * Get layer from current state.layers showed on result * - * @since v3.8.0 + * @since 3.8.0 */ proto._getLayer = function(layerId) { return this.state.layers.find(l => l.id === layerId); @@ -2350,7 +2172,7 @@ proto._getLayer = function(layerId) { /** * Get external layer from current state.layers showed on result * - * @since v3.8.0 + * @since 3.8.0 */ proto._getExternalLayer = function(layerId) { return (this._getLayer(layerId) || {}).external; @@ -2359,7 +2181,7 @@ proto._getExternalLayer = function(layerId) { /** * Get ids of the selected features * - * @since v3.8.0 + * @since 3.8.0 */ proto._getFeaturesIds = function(features, external) { return features.map(f => external ? f.id : f.attributes[G3W_FID]); @@ -2368,7 +2190,7 @@ proto._getFeaturesIds = function(features, external) { /** * Extract features from layer object * - * @since v3.8.0 + * @since 3.8.0 */ proto._getLayerFeatures = function(layer) { return layer.features || []; @@ -2377,7 +2199,7 @@ proto._getLayerFeatures = function(layer) { /** * Loop and filter the features that we need to remove * - * @since v3.8.0 + * @since 3.8.0 */ proto._featuresToRemove = function(features, external) { const features_ids = this._getFeaturesIds(features, external); // get id of the features @@ -2387,7 +2209,7 @@ proto._featuresToRemove = function(features, external) { /** * Filter features to add * - * @since v3.8.0 + * @since 3.8.0 */ proto._featuresToAdd = function(features, external) { const features_ids = this._getFeaturesIds(features, external); @@ -2395,7 +2217,7 @@ proto._featuresToAdd = function(features, external) { }; /** - * @since v3.8.0 + * @since 3.8.0 */ proto._toggleLayerFeatureBox = function(layer, feature, collapsed) { const boxId = this.getBoxId(layer, feature); @@ -2405,14 +2227,239 @@ proto._toggleLayerFeatureBox = function(layer, feature, collapsed) { }; /** - * @since v3.8.0 + * @since 3.8.0 */ proto._removeLayerFeatureBox = function(layer, feature_to_delete) { setTimeout(()=> delete this.state.layersFeaturesBoxes[this.getBoxId(layer, feature_to_delete)]); }; /** - * @deprecated since v3.8. Will be deleted in 4.x. Use QueryResultsService::updateLayerResultFeatures(layer) instead + * @since 3.8.0 + */ +proto._setActionGoToGeometry = function(layer) { + this.state.layersactions[layer.id] + .push({ + id: 'gotogeometry', + download: false, + mouseover: true, + class: GUI.getFontClass('marker'), + hint: 'sdk.mapcontrols.query.actions.zoom_to_feature.hint', + cbk: throttle(this.goToGeometry.bind(this)) + }); +}; + +/** + * @since 3.8.0 + */ +proto._setActionShowQueryAndPlotsRelations = function(layer) { + const relations = this._relations[layer.id].filter(relation => 'MANY' === relation.type); + const chartRelationIds = []; + + relations.forEach(relation => { + const id = this.plotLayerIds.find(id => id === relation.referencingLayer); + if (id) { + chartRelationIds.push(id); + } + }); + + /** @FIXME add description */ + if (relations.length) { + this.state.layersactions[layer.id] + .push({ + id: 'show-query-relations', + download: false, + class: GUI.getFontClass('relation'), + hint: 'sdk.mapcontrols.query.actions.relations.hint', + cbk: this.showQueryRelations, + relations, + chartRelationIds + }); + } + + /** @FIXME add description */ + if (chartRelationIds.length) { + this.state.layersactions[layer.id] + .push({ + id: 'show-plots-relations', + download: false, + opened: true, + class: GUI.getFontClass('chart'), + state: this.createActionState({ layer }), + hint: 'sdk.mapcontrols.query.actions.relations_charts.hint', + cbk: throttle(this.showRelationsChart.bind(this, chartRelationIds)) + }); + } +}; + +/** + * @since 3.8.0 + */ +proto._setActionPrintAtlas = function(layer) { + this.state.layersactions[layer.id] + .push({ + id: `printatlas`, + download: true, + class: GUI.getFontClass('print'), + hint: `sdk.tooltips.atlas`, + cbk: this.printAtlas.bind(this) + }); +}; + +/** + * @since 3.8.0 + */ +proto._setActionDownloadFeature = function(layer) { + const [format] = layer.downloads; // NB: format == layer.downloads[0] + const cbk = this.downloadFeatures.bind(this, format); + layer[format] = Vue.observable({ active: false }); + this.state.layersactions[layer.id] + .push({ + id: `download_${format}_feature`, + download: true, + state, + class: GUI.getFontClass('download'), + hint: `sdk.tooltips.download_${format}`, + cbk: (layer, feature, action, index) => { + action.state.toggled[index] = !action.state.toggled[index]; + if (action.state.toggled[index]) { + cbk(layer, feature, action, index); + } else { + this.setCurrentActionLayerFeatureTool({ index, action, layer }) + } + } + }); +}; + +/** + * @since 3.8.0 + */ +proto._setActionMultiDownloadFeature = function(layer) { + const state = this.createActionState({ layer }); + + const downloads = []; + + layer.downloads + .forEach(format => { + downloads.push({ + id: `download_${format}_feature`, + download: true, + format, + class: GUI.getFontClass(format), + hint: `sdk.tooltips.download_${format}`, + cbk: (layer, feature, action, index)=> { + // un-toggle downloads action + this.downloadFeatures(format, layer, feature, action, index); + if ('polygon' !== this.state.query.type) { + const downloadsaction = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); + downloadsaction.cbk(layer, feature, downloadsaction, index); + } + } + }); + }); + + // set actionstools configs + this.state.actiontools[DownloadFormats.name] = this.state.actiontools[DownloadFormats.name] || {}; + this.state.actiontools[DownloadFormats.name][layer.id] = { downloads }; + // check if has download actions + this.state.layersactions[layer.id] + .push({ + id: `downloads`, + download: true, + class: GUI.getFontClass('download'), + state, + toggleable: true, + hint: `Downloads`, + change({features}) { + features + .forEach((feature, index) => { + if (undefined === this.state.toggled[index]) { + VM.$set(this.state.toggled, index, false); + } else { + this.state.toggled[index] = false; + } + }); + }, + cbk: (layer, feature, action, index) => { + action.state.toggled[index] = !action.state.toggled[index]; + this.setCurrentActionLayerFeatureTool({ layer, index, action, component: (action.state.toggled[index] ? DownloadFormats : null) }); + } + }); +}; + +/** + * @since 3.8.0 + */ +proto._setActionRemoveFeatureFromResult = function(layer) { + this.state.layersactions[layer.id] + .push({ + id: 'removefeaturefromresult', + download: false, + mouseover: true, + class: GUI.getFontClass('minus-square'), + style: { + color: 'red' + }, + hint: 'sdk.mapcontrols.query.actions.remove_feature_from_results.hint', + cbk: this.removeFeatureLayerFromResult.bind(this) + }); +}; + +/** + * @since 3.8.0 + */ +proto._setActionSelection = function(layer) { + this.state.layersactions[layer.id] + .push({ + id: 'selection', + download: false, + class: GUI.getFontClass('success'), + hint: 'sdk.mapcontrols.query.actions.add_selection.hint', + state: this.createActionState({ layer }), + init: ({feature, index, action}={}) => { + if("undefined" !== typeof layer.selection.active) { + this.checkFeatureSelection({ layer, index, feature, action }) + } + }, + cbk: throttle(this.addToSelection.bind(this)) + }); + + // In case of external layer don't listen to `selection` event + this.listenClearSelection(layer, 'selection'); +}; + +/** + * @since 3.8.0 + */ +proto._setActionLinkZoomToFid = function(layer) { + this.state.layersactions[layer.id] + .push({ + id: 'link_zoom_to_fid', + download: false, + class: GUI.getFontClass('link'), + hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint', + hint_change: { + hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint_change', + duration: 1000 + }, + cbk: this.copyZoomToFidUrl.bind(this) + }); +}; + +/** + * @since 3.8.0 + */ +proto._setActionEditing = function(layer) { + this.state.layersactions[layer.id] + .push({ + id: 'editing', + class: GUI.getFontClass('pencil'), + hint: 'Editing', + cbk: (layer, feature) => { this.editFeature({ layer, feature }) } + }); +}; + +/** + * @deprecated since 3.8.0 Will be deleted in 4.x. Use QueryResultsService::updateLayerResultFeatures(layer) instead */ proto.addRemoveFeaturesToLayerResult = deprecate(proto.updateLayerResultFeatures, '[G3W-CLIENT] QueryResultsService::addRemoveFeaturesToLayerResult(layer) is deprecated'); From 4ac397b49a36b721af4e7b99bdea3da567152cf8 Mon Sep 17 00:00:00 2001 From: Raruto Date: Tue, 28 Mar 2023 16:24:33 +0200 Subject: [PATCH 05/48] wrong parameter --- src/app/gui/queryresults/queryresultsservice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index cf6a9a421..0e0222efc 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -630,7 +630,7 @@ proto.setActionsForLayers = function(layers, options = { add: false }) { // Lookup for layer geometry. if (layer.hasgeometry) { - this._setActionGoToGeometry(layer.id); + this._setActionGoToGeometry(layer); } // Lookup for layer relations. From 3c29fb93cc044e5368a5d20fe7d03728fd0fade0 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 29 Mar 2023 08:18:37 +0200 Subject: [PATCH 06/48] add geometry check within `QueryResultsService::showGeometry(geometry)` --- src/app/gui/queryresults/queryresultsservice.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 0e0222efc..4d2603c09 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -225,7 +225,6 @@ function QueryResultsService() { }); /** - * * where are store vector layer add on runtime */ this._vectorLayers = []; @@ -234,6 +233,7 @@ function QueryResultsService() { * @FIXME add description */ this._addFeaturesLayerResultInteraction = { + /** * Reference to current layer */ @@ -270,8 +270,8 @@ function QueryResultsService() { setQueryResponse(queryResponse, options = { add: false }) { // set mandatory queryResponse fields - if (!queryResponse.data) queryResponse.data = []; - if (!queryResponse.query) queryResponse.query = { external: { add: false, filter: {SELECTED: false }}}; + if (!queryResponse.data) queryResponse.data = []; + if (!queryResponse.query) queryResponse.query = { external: { add: false, filter: { SELECTED: false } } }; if (!queryResponse.query.external) queryResponse.query.external = { add: false, filter: {SELECTED: false }}; // whether add response to current results using addLayerFeaturesToResultsAction @@ -289,7 +289,7 @@ function QueryResultsService() { switch (this.state.query.type) { case 'coordinates': this.showCoordinates(this.state.query.coordinates); break; case 'bbox': this.showBBOX(this.state.query.bbox); break; - case 'polygon': this.state.query.geometry && this.showGeometry(this.state.query.geometry); break; + case 'polygon': this.showGeometry(this.state.query.geometry); break; } } @@ -2040,7 +2040,9 @@ proto.showBBOX = function(bbox) { * @param geometry */ proto.showGeometry = function(geometry) { - this.addQueryResultsLayerToMap({ feature: createFeatureFromGeometry({ geometry }) }); + if (geometry) { + this.addQueryResultsLayerToMap({ feature: createFeatureFromGeometry({ geometry }) }); + } }; /** From 29e258d9ec583986b6b751a1f835e162f63e4ec0 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 29 Mar 2023 09:11:56 +0200 Subject: [PATCH 07/48] extract function `QueryResultsService::_handleFeatureForLayer(featuresForLayer)` --- .../gui/queryresults/queryresultsservice.js | 361 ++++++++++-------- 1 file changed, 196 insertions(+), 165 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 4d2603c09..d55269534 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -77,8 +77,8 @@ function QueryResultsService() { * @FIXME add description */ this._actions = { - 'zoomto': QueryResultsService.zoomToElement, - 'highlightgeometry': this.highlightGeometry.bind(this), + 'zoomto': QueryResultsService.zoomToElement, + 'highlightgeometry': this.highlightGeometry.bind(this), 'clearHighlightGeometry': this.clearHighlightGeometry.bind(this) }; @@ -276,10 +276,11 @@ function QueryResultsService() { // whether add response to current results using addLayerFeaturesToResultsAction if (!options.add) { + // in case of new request results reset the query otherwise maintain the previous request this.clearState(); this.state.query = queryResponse.query; - this.state.type = queryResponse.type; + this.state.type = queryResponse.type; // if true add external layers to response if (true === queryResponse.query.external.add) { @@ -291,6 +292,7 @@ function QueryResultsService() { case 'bbox': this.showBBOX(this.state.query.bbox); break; case 'polygon': this.showGeometry(this.state.query.geometry); break; } + } this.setLayersData(this._digestFeaturesForLayers(queryResponse.data), options); @@ -1067,181 +1069,210 @@ proto.reset = function() { * @returns {[]} */ proto._digestFeaturesForLayers = function(featuresForLayers=[]) { - let id = 0; const layers = []; + /** + * @TODO find out why we need such a level of depth (ie. a nested foreach + triple variables named `featuresForLayer` ?) + */ + featuresForLayers.forEach(featuresForLayer => { + ( + Array.isArray(featuresForLayer) + ? featuresForLayer + : [featuresForLayer] + ).forEach(featuresForLayer => { + const layer = this._handleFeatureForLayer(featuresForLayer); + if (layer) { + layers.push(layer) + } + }); + }); + return layers; +}; + +/** + * Convert response from server + * + * @param featuresForLayer + * + * @since 3.8.0 + */ +proto._handleFeatureForLayer = function(featuresForLayer) { + const layerObj = { + editable: false, + inediting: false, + downloads: [], + infoformats: [], + filter: {}, + selection: {}, + external: false, + source: undefined, + infoformat: undefined, + formStructure: undefined, + attributes: [], + features: [], + hasgeometry: false, + show: true, + addfeaturesresults: { + active:false + }, + [DownloadFormats.name]: { + active: false + }, + expandable: true, + hasImageField: false, + error: '', + rawdata: null, // rawdata response + loading: false, + }; + + const layer = featuresForLayer.layer; + let layerAttributes, layerRelationsAttributes, layerTitle, - layerId; - - // convert response from server - const _handleFeatureFoLayer = featuresForLayer => { - const layerObj = { - editable: false, - inediting: false, - downloads: [], - infoformats: [], - filter: {}, - selection: {}, - external: false, - source: undefined, - infoformat: undefined, - formStructure: undefined, - attributes: [], - features: [], - hasgeometry: false, - show: true, - addfeaturesresults: { - active:false - }, - [DownloadFormats.name]: { - active: false - }, - expandable: true, - hasImageField: false, - error: '', - rawdata: null, // rawdata response - loading: false, - }; - const layer = featuresForLayer.layer; - let sourceType; - let extractRelations = false; - - if (layer instanceof Layer) { - layerObj.editable = layer.isEditable(); - layerObj.inediting = layer.isInEditing(); - layerObj.source = layer.getSource(); - layerObj.infoformats = layer.getInfoFormats(); - layerObj.infoformat = layer.getInfoFormat(); - // set selection filter and relation if not wms - if (-1 === [ - Layer.SourceTypes.WMS, - Layer.SourceTypes.WCS, - Layer.SourceTypes.WMST - ].indexOf(layer.getSourceType()) - ) { - layerObj.filter = layer.state.filter; - layerObj.selection = layer.state.selection; - extractRelations = true; - } - layerObj.downloads = layer.getDownloadableFormats(); - try { sourceType = layer.getSourceType() } catch(err) {} - layerRelationsAttributes = []; - layerTitle = layer.getTitle(); - layerId = layer.getId(); - layerAttributes = ('ows' === this.state.type) /* sanitize attributes layer only if is ows */ - ? layer.getAttributes().map(attribute => { - const sanitizeAttribute = {...attribute}; - sanitizeAttribute.name = sanitizeAttribute.name.replace(/ /g, '_'); - return sanitizeAttribute - }) - : layer.getAttributes(); - if (layer.hasFormStructure()) { - const structure = layer.getLayerEditingFormStructure(); - if (this._relations && this._relations.length) { - const getRelationFieldsFromFormStructure = (node) => { - if (!node.nodes) { - node.name ? node.relation = true : null; - } else { - for (const _node of node.nodes) { - getRelationFieldsFromFormStructure(_node); - } + layerId, + sourceType; + + let extractRelations = false; + + if (layer instanceof Layer) { + layerObj.editable = layer.isEditable(); + layerObj.inediting = layer.isInEditing(); + layerObj.source = layer.getSource(); + layerObj.infoformats = layer.getInfoFormats(); + layerObj.infoformat = layer.getInfoFormat(); + + // set selection filter and relation if not wms + if (-1 === [ + Layer.SourceTypes.WMS, + Layer.SourceTypes.WCS, + Layer.SourceTypes.WMST + ].indexOf(layer.getSourceType()) + ) { + layerObj.filter = layer.state.filter; + layerObj.selection = layer.state.selection; + extractRelations = true; + } + + layerObj.downloads = layer.getDownloadableFormats(); + + try { sourceType = layer.getSourceType() } catch(err) {} + + layerRelationsAttributes = []; + layerTitle = layer.getTitle(); + layerId = layer.getId(); + layerAttributes = ('ows' === this.state.type) /* sanitize attributes layer only if is ows */ + ? layer.getAttributes().map(attribute => { + const sanitizeAttribute = {...attribute}; + sanitizeAttribute.name = sanitizeAttribute.name.replace(/ /g, '_'); + return sanitizeAttribute + }) + : layer.getAttributes(); + + if (layer.hasFormStructure()) { + const structure = layer.getLayerEditingFormStructure(); + if (this._relations && this._relations.length) { + const getRelationFieldsFromFormStructure = (node) => { + if (!node.nodes) { + node.name ? node.relation = true : null; + } else { + for (const _node of node.nodes) { + getRelationFieldsFromFormStructure(_node); } - }; - for (const node of structure) { - getRelationFieldsFromFormStructure(node); } - } - layerObj.formStructure = { - structure, - fields: layer.getFields().filter(field => field.show), // get features show }; + for (const node of structure) { + getRelationFieldsFromFormStructure(node); + } } - } else if (layer instanceof ol.layer.Vector) { - layerObj.selection = layer.selection; - layerAttributes = layer.getProperties(); - layerRelationsAttributes = []; - layerTitle = layer.get('name'); - layerId = layer.get('id'); - layerObj.external = true; - } else if ('string' === typeof layer || layer instanceof String) { - const feature = featuresForLayer.features[0]; - const split_layer_name = layer.split('_'); - sourceType = Layer.LayerTypes.VECTOR; - layerAttributes = (feature ? feature.getProperties() : []); - layerRelationsAttributes = []; - layerId = layer; - layerObj.external = true; - layerTitle = (split_layer_name.length > 4) - ? split_layer_name.slice(0, split_layer_name.length -4).join(' ') - : layer; + layerObj.formStructure = { + structure, + fields: layer.getFields().filter(field => field.show), // get features show + }; } + } else if (layer instanceof ol.layer.Vector) { + layerObj.selection = layer.selection; + layerAttributes = layer.getProperties(); + layerRelationsAttributes = []; + layerTitle = layer.get('name'); + layerId = layer.get('id'); + layerObj.external = true; + } else if ('string' === typeof layer || layer instanceof String) { + const feature = featuresForLayer.features[0]; + const split_layer_name = layer.split('_'); + sourceType = Layer.LayerTypes.VECTOR; + layerAttributes = (feature ? feature.getProperties() : []); + layerRelationsAttributes = []; + layerId = layer; + layerObj.external = true; + layerTitle = (split_layer_name.length > 4) + ? split_layer_name.slice(0, split_layer_name.length -4).join(' ') + : layer; + } - layerObj.title = layerTitle; - layerObj.id = layerId; - layerObj.atlas = this.getAtlasByLayerId(layerId); - layerObj.relationsattributes = layerRelationsAttributes; - - if (featuresForLayer.rawdata) { - layerObj.rawdata = featuresForLayer.rawdata; - layers.push(layerObj) - } else if (featuresForLayer.features && featuresForLayer.features.length) { - const layerSpecialAttributesName = - (layer instanceof Layer) - ? layerAttributes.filter(attribute => { - try { - return ('_' === attribute.name[0] || Number.isInteger(1*attribute.name[0])) - } catch(e) { - return false; - } - }).map(attribute => ({ alias: attribute.name.replace(/_/, ''), name: attribute.name })) - : []; - if (layerSpecialAttributesName.length) { - featuresForLayer.features - .forEach(feature => this._setSpecialAttributesFeatureProperty(layerSpecialAttributesName, feature)); - } - layerObj.attributes = this._parseAttributes(layerAttributes, featuresForLayer.features[0], sourceType); - layerObj.attributes - .forEach(attribute => { - if (layerObj.formStructure) { - const relationField = layer.getFields().find(field => field.name === attribute.name); // need to check all field also show false - if (!relationField) { - layerObj.formStructure.fields.push(attribute); + layerObj.title = layerTitle; + layerObj.id = layerId; + layerObj.atlas = this.getAtlasByLayerId(layerId); + layerObj.relationsattributes = layerRelationsAttributes; + + /** @FIXME add description */ + if (featuresForLayer.rawdata) { + layerObj.rawdata = featuresForLayer.rawdata; + return layerObj; + } + + /** @FIXME add description */ + if (featuresForLayer.features && featuresForLayer.features.length) { + const layerSpecialAttributesName = + (layer instanceof Layer) + ? layerAttributes.filter(attribute => { + try { + return ('_' === attribute.name[0] || Number.isInteger(1*attribute.name[0])) + } catch(e) { + return false; } - } - if (attribute.type === 'image') { - layerObj.hasImageField = true; - } - }); + }).map(attribute => ({ alias: attribute.name.replace(/_/, ''), name: attribute.name })) + : []; + if (layerSpecialAttributesName.length) { featuresForLayer.features - .forEach(feature => { - const props = this.getFeaturePropertiesAndGeometry(feature); - if (props.geometry) { - layerObj.hasgeometry = true; - } - layerObj.features - .push({ - id: layerObj.external ? feature.getId() : props.id, - attributes: props.properties, - geometry: props.geometry, - selection: props.selection, - show: true - }); - id += 1; - }); - layers.push(layerObj); - } else if (featuresForLayer.error) { - layerObj.error = featuresForLayer.error; - } - }; - featuresForLayers.forEach(featuresForLayer => { - if (!Array.isArray(featuresForLayer)) { - _handleFeatureFoLayer(featuresForLayer); - } else { - featuresForLayer.forEach(featuresForLayer => _handleFeatureFoLayer(featuresForLayer)); + .forEach(feature => this._setSpecialAttributesFeatureProperty(layerSpecialAttributesName, feature)); } - }); - return layers; + layerObj.attributes = this._parseAttributes(layerAttributes, featuresForLayer.features[0], sourceType); + layerObj.attributes + .forEach(attribute => { + if (layerObj.formStructure) { + const relationField = layer.getFields().find(field => field.name === attribute.name); // need to check all field also show false + if (!relationField) { + layerObj.formStructure.fields.push(attribute); + } + } + if (attribute.type === 'image') { + layerObj.hasImageField = true; + } + }); + featuresForLayer.features + .forEach(feature => { + const props = this.getFeaturePropertiesAndGeometry(feature); + if (props.geometry) { + layerObj.hasgeometry = true; + } + layerObj.features + .push({ + id: layerObj.external ? feature.getId() : props.id, + attributes: props.properties, + geometry: props.geometry, + selection: props.selection, + show: true + }); + }); + return layerObj; + } + + /** @FIXME missing return type ? */ + /** @FIXME add description */ + if (featuresForLayer.error) { + layerObj.error = featuresForLayer.error; + } + }; /** From 40d0254fb7a5d2bf20e59990c20cde13f92694df Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 29 Mar 2023 09:13:52 +0200 Subject: [PATCH 08/48] remove undefined action `QueryResultsService.zoomToElement` --- src/app/gui/queryresults/queryresultsservice.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index d55269534..428a51acf 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -77,7 +77,6 @@ function QueryResultsService() { * @FIXME add description */ this._actions = { - 'zoomto': QueryResultsService.zoomToElement, 'highlightgeometry': this.highlightGeometry.bind(this), 'clearHighlightGeometry': this.clearHighlightGeometry.bind(this) }; From 9a6071d047c0ce470c4c8a7a5e796315496abb61 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 29 Mar 2023 09:49:21 +0200 Subject: [PATCH 09/48] clean up some code --- .../gui/queryresults/queryresultsservice.js | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 428a51acf..686088eb6 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -256,7 +256,7 @@ function QueryResultsService() { }; /** - * Core methods that used from other object to react before or after its call + * Core methods used from other classes to react before or after its call */ this.setters = { @@ -304,7 +304,7 @@ function QueryResultsService() { * @param layers * @param options */ - setLayersData(layers, options={add:false}) { + setLayersData(layers, options = { add: false }) { if (!options.add) { // set the right order of result layers based on TOC this._currentLayerIds = layers.map(layer => layer.id); @@ -312,7 +312,7 @@ function QueryResultsService() { } // get features from add pick layer in case of a new request query layers.forEach(layer => { options.add ? this.updateLayerResultFeatures(layer) : this.state.layers.push(layer); }); - this.setActionsForLayers(layers, {add: options.add}); + this.setActionsForLayers(layers, { add: options.add }); this.state.changed = true; }, @@ -419,7 +419,7 @@ function QueryResultsService() { /** * @FIXME add description */ - GUI.onbefore('setContent', (options)=> { + GUI.onbefore('setContent', (options) => { this.mapService = this.mapService || ApplicationService.getApplicationService('map'); if (100 === options.perc && GUI.isMobile()) { this._asyncFnc.zoomToLayerFeaturesExtent.async = true; @@ -444,7 +444,7 @@ const proto = QueryResultsService.prototype; * @param type feature or layer * @param position */ -proto.registerCustomComponent = function({id=getUniqueDomId(), layerId, component, type='feature', position='after'}={}) { +proto.registerCustomComponent = function({ id = getUniqueDomId(), layerId, component, type = 'feature', position = 'after' } = {}) { if (undefined === this.state.layerscustomcomponents[layerId]) { this.state.layerscustomcomponents[layerId] = { layer: { before: [], after: [] }, @@ -593,7 +593,9 @@ proto.checkIfLayerHasNoFeatures = function(layer) { * @returns {string} */ proto.getBoxId = function(layer, feature, relation_index) { - return (null !== relation_index && undefined !== relation_index) ? `${layer.id}_${feature.id}_${relation_index}` : `${layer.id}_${feature.id}`; + return (null !== relation_index && undefined !== relation_index) + ? `${layer.id}_${feature.id}_${relation_index}` + : `${layer.id}_${feature.id}`; }; /** @@ -685,9 +687,9 @@ proto.setActionsForLayers = function(layers, options = { add: false }) { */ proto.createActionState = function({layer, dynamicProperties=['toggled']}) { // check number of download formats - const propertiesObject = dynamicProperties.reduce((accumulator, property) => { accumulator[property] = {}; return accumulator; }, {}); - layer.features.map((_, idx) => { Object.keys(propertiesObject).forEach(property => { propertiesObject[property][idx] = null; }); }); - return Vue.observable(propertiesObject); + const properties = dynamicProperties.reduce((obj, prop) => { obj[prop] = {}; return obj; }, {}); + layer.features.map((_, idx) => { Object.keys(properties).forEach(prop => { properties[prop][idx] = null; }); }); + return Vue.observable(properties); }; /** @@ -708,14 +710,15 @@ proto.getActionLayerById = function({layer, id}={}) { * @param value component value or null */ proto.setCurrentActionLayerFeatureTool = function({layer, action, index, component=null}={}) { - if (component) { - if (this.state.currentactiontools[layer.id][index] && action.id !== this.state.currentactionfeaturelayer[layer.id][index].id && this.state.currentactionfeaturelayer[layer.id][index].toggleable) { - this.state.currentactionfeaturelayer[layer.id][index].state.toggled[index] = false; - } - this.state.currentactionfeaturelayer[layer.id][index] = action; - } else { - this.state.currentactionfeaturelayer[layer.id][index] = null; + if ( + component && + this.state.currentactiontools[layer.id][index] && + action.id !== this.state.currentactionfeaturelayer[layer.id][index].id && + this.state.currentactionfeaturelayer[layer.id][index].toggleable + ) { + this.state.currentactionfeaturelayer[layer.id][index].state.toggled[index] = false; } + this.state.currentactionfeaturelayer[layer.id][index] = component ? action : null; this.state.currentactiontools[layer.id][index] = component; }; @@ -724,8 +727,7 @@ proto.setCurrentActionLayerFeatureTool = function({layer, action, index, compone * @FIXME add description */ proto.addCurrentActionToolsLayer = function({id, layer, config={}}) { - this.state.actiontools[id] = {}; - this.state.actiontools[id][layer.id] = config; + this.state.actiontools[id] = { [layer.id]: config }; }; /** @@ -734,15 +736,16 @@ proto.addCurrentActionToolsLayer = function({id, layer, config={}}) { * @param layer */ proto.resetCurrentActionToolsLayer = function(layer) { - layer.features.forEach((_, idx)=> { - if (this.state.currentactiontools[layer.id]) { - if (undefined === this.state.currentactiontools[layer.id][idx]) { - Vue.set(this.state.currentactiontools[layer.id], idx, null); - } else { - this.state.currentactiontools[layer.id][idx] = null; - } - this.state.currentactionfeaturelayer[layer.id][idx] = null; + layer.features.forEach((_, idx) => { + if (!this.state.currentactiontools[layer.id]) { + return; + } + if (undefined === this.state.currentactiontools[layer.id][idx]) { + Vue.set(this.state.currentactiontools[layer.id], idx, null); + } else { + this.state.currentactiontools[layer.id][idx] = null; } + this.state.currentactionfeaturelayer[layer.id][idx] = null; }) }; @@ -2262,7 +2265,7 @@ proto._toggleLayerFeatureBox = function(layer, feature, collapsed) { * @since 3.8.0 */ proto._removeLayerFeatureBox = function(layer, feature_to_delete) { - setTimeout(()=> delete this.state.layersFeaturesBoxes[this.getBoxId(layer, feature_to_delete)]); + setTimeout(() => delete this.state.layersFeaturesBoxes[this.getBoxId(layer, feature_to_delete)]); }; /** @@ -2378,7 +2381,7 @@ proto._setActionMultiDownloadFeature = function(layer) { format, class: GUI.getFontClass(format), hint: `sdk.tooltips.download_${format}`, - cbk: (layer, feature, action, index)=> { + cbk: (layer, feature, action, index) => { // un-toggle downloads action this.downloadFeatures(format, layer, feature, action, index); if ('polygon' !== this.state.query.type) { From 9a84f028530f53129c4e2fb3e6808e61faa775ae Mon Sep 17 00:00:00 2001 From: Raruto Date: Thu, 20 Jul 2023 15:39:11 +0200 Subject: [PATCH 10/48] convert `QueryResultsService` to ES6 class --- .../gui/queryresults/queryresultsservice.js | 4281 +++++++++-------- 1 file changed, 2164 insertions(+), 2117 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 208448ca3..a462f78a9 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -1,21 +1,23 @@ -import GUI from 'services/gui'; -import { G3W_FID, LIST_OF_RELATIONS_TITLE, LIST_OF_RELATIONS_ID } from 'constant'; -import ProjectsRegistry from 'store/projects'; -import DataRouterService from 'services/data'; -import CatalogLayersStoresRegistry from 'store/catalog-layers'; -import DownloadFormats from 'components/QueryResultsActionDownloadFormats.vue'; +import GUI from 'services/gui'; +import { + G3W_FID, + LIST_OF_RELATIONS_TITLE, + LIST_OF_RELATIONS_ID, +} from 'constant'; +import ProjectsRegistry from 'store/projects'; +import DataRouterService from 'services/data'; +import CatalogLayersStoresRegistry from 'store/catalog-layers'; +import DownloadFormats from 'components/QueryResultsActionDownloadFormats.vue'; import QueryPolygonCsvAttributesComponent from 'components/QueryResultsActionQueryPolygonCSVAttributes.vue'; -import ApplicationService from 'services/application'; +import ApplicationService from 'services/application'; const { - base, - inherit, noop, downloadFile, throttle, getUniqueDomId, copyUrl, -} = require('core/utils/utils'); +} = require('core/utils/utils'); const { getAlphanumericPropertiesFromFeature, @@ -25,7 +27,7 @@ const { createFeatureFromCoordinates, intersects, within, -} = require('core/utils/geo'); +} = require('core/utils/geo'); const { t } = require('core/i18n/i18n.service'); const Layer = require('core/layers/layer'); @@ -35,7 +37,7 @@ const PrintService = require('core/print/printservice'); const RelationsPage = require('gui/relations/vue/relationspage'); const PickCoordinatesInteraction = require('g3w-ol/interactions/pickcoordinatesinteraction'); -const deprecate = require('util-deprecate'); +const deprecate = require('util-deprecate'); /** * Get and set vue reactivity to QueryResultsService @@ -44,2462 +46,2507 @@ const deprecate = require('util-deprecate'); */ const VM = new Vue(); -function QueryResultsService() { +class QueryResultsService extends G3WObject { - /** - * Service used to work with atlas (print functionality) action tool - */ - this.printService = new PrintService(); - - /** - * @FIXME add description - */ - this._currentLayerIds = []; - - /** - * @deprecated since 3.8 - * It used to register change project from Change map button - */ - ProjectsRegistry.onafter('setCurrentProject', project => { - this._project = project; - this._setRelations(project); - this._setAtlasActions(project); - this.state.download_data = false; - this.plotLayerIds = []; - }); - - /** - * @FIXME add description - */ - this.unlistenerlayeractionevents = []; - - /** - * @FIXME add description - */ - this._actions = { - 'highlightgeometry': this.highlightGeometry.bind(this), - 'clearHighlightGeometry': this.clearHighlightGeometry.bind(this) - }; - - /** - * to store relations - */ - this._relations = []; - - /** - * @FIXME add description - */ - this._atlas = []; - - /** - * @FIXME add description - */ - this.plotLayerIds = []; - - /** - * Current project - */ - this._project = ProjectsRegistry.getCurrentProject(); - - /** - * Keep the right order for query result based on TOC order layers - */ - this._projectLayerIds = this._project.getConfigLayers().map(layer => layer.id); + constructor() { - /** - * Set reactive state - */ - this.state = { + super(); /** * @FIXME add description */ - zoomToResult: true, + this._currentLayerIds = []; /** * @FIXME add description */ - components: [], + this.unlistenerlayeractionevents = []; /** * @FIXME add description */ - layers: [], + this._actions = { + 'highlightgeometry': this.highlightGeometry.bind(this), + 'clearHighlightGeometry': this.clearHighlightGeometry.bind(this), + }; /** - * @FIXME add description + * to store relations */ - changed: false, + this._relations = []; /** * @FIXME add description */ - query: null, + this._atlas = []; /** - * 'ows' = default - * 'api' = search - */ - type: 'ows', - - /** - * An action is an object that contains: - * - * ``` - * { - * "id": (required) Unique action Id - * "download": wether action is download or not - * "class": (required) fontawsome classname to show icon - * "state": need to be reactive. Used for example to toggled state of action icon - * "hint": Tooltip text - * "init": Method called when action is loaded - * "clear": Method called before clear the service. Used for example to clear unwatch - * "change": Method called when feature of layer is changed - * "cbk": (required) Method called when action is cliccked - * } - * ``` - **/ - layersactions: {}, - - /** - * Add action tools (for features) + * @FIXME add description */ - actiontools:{}, + this.plotLayerIds = []; /** - * Current action tools contain component - * of a specific action (eg. download) + * Set reactive state */ - currentactiontools:{}, + this.state = { + + /** + * @FIXME add description + */ + zoomToResult: true, + + /** + * @FIXME add description + */ + components: [], + + /** + * @FIXME add description + */ + layers: [], + + /** + * @FIXME add description + */ + changed: false, + + /** + * @FIXME add description + */ + query: null, + + /** + * 'ows' = default + * 'api' = search + */ + type: 'ows', + + /** + * An action is an object that contains: + * + * ``` + * { + * "id": (required) Unique action Id + * "download": wether action is download or not + * "class": (required) fontawsome classname to show icon + * "state": need to be reactive. Used for example to toggled state of action icon + * "hint": Tooltip text + * "init": Method called when action is loaded + * "clear": Method called before clear the service. Used for example to clear unwatch + * "change": Method called when feature of layer is changed + * "cbk": (required) Method called when action is cliccked + * } + * ``` + **/ + layersactions: {}, + + /** + * Add action tools (for features) + */ + actiontools:{}, + + /** + * Current action tools contain component + * of a specific action (eg. download) + */ + currentactiontools:{}, + + /** + * Contains current action that expose vue component + * (useful for comparing the id other action is + * triggered and exposing the component) + */ + currentactionfeaturelayer:{}, + + /** + * @FIXME add description + */ + layeractiontool: {}, + + /** + * @FIXME add description + */ + layersFeaturesBoxes:{}, + + /** + * Used to show a custom component for a layer + */ + layerscustomcomponents:{} // - /** - * Contains current action that expose vue component - * (useful for comparing the id other action is - * triggered and exposing the component) - */ - currentactionfeaturelayer:{}, + }; /** - * @FIXME add description + * where are store vector layer add on runtime */ - layeractiontool: {}, + this._vectorLayers = []; /** * @FIXME add description */ - layersFeaturesBoxes:{}, - - /** - * Used to show a custom component for a layer - */ - layerscustomcomponents:{} // - - }; - - /** - * @FIXME add description - */ - this.init = function() { - this.clearState(); - }; - - /** - * Vector layer used by query result to show query - * request as coordinates, bbox, polygon, etc .. - * - * @type {ol.layer.Vector} - */ - this.resultsQueryLayer = new ol.layer.Vector({ - source: new ol.source.Vector(), - style(feature) { - const fill = new ol.style.Fill({ color: 'rgba(0, 0, 255, 0.7)' }); - const stroke = new ol.style.Stroke({ color: 'blue', width: 3 }); - if ('Point' === feature.getGeometry().getType()) { - return new ol.style.Style({ - text: new ol.style.Text({ fill, stroke, text: '\uf3c5', font: '900 3em "Font Awesome 5 Free"', offsetY : -15 }) - }); - } - return new ol.style.Style({ stroke }); - } - }); + this._addFeaturesLayerResultInteraction = { - /** - * where are store vector layer add on runtime - */ - this._vectorLayers = []; + /** + * Reference to current layer + */ + id: null, - /** - * @FIXME add description - */ - this._addFeaturesLayerResultInteraction = { + /** + * Interaction bind to layer, + */ + interaction: null, - /** - * Reference to current layer - */ - id: null, + /** + * Add current toggled map control if toggled + */ + mapcontrol: null, - /** - * Interaction bind to layer, - */ - interaction: null, + /** + * @FIXME add description + */ + toggleeventhandler: null - /** - * Add current toggled map control if toggled - */ - mapcontrol: null, + }; /** * @FIXME add description */ - toggleeventhandler: null - - }; - - /** - * Core methods used from other classes to react before or after its call - */ - this.setters = { + this._asyncFnc = { + todo: noop, + zoomToLayerFeaturesExtent: { async: false }, + goToGeometry: { async: false }, + }; /** - * Hook method called when response is handled by Data Provider + * Vector layer used by query result to show query + * request as coordinates, bbox, polygon, etc .. * - * @param queryResponse - * @param {{ add: boolean }} options `add` is used to know if is a new query request or add/remove query request + * @type {ol.layer.Vector} */ - setQueryResponse(queryResponse, options = { add: false }) { - - // set mandatory queryResponse fields - if (!queryResponse.data) queryResponse.data = []; - if (!queryResponse.query) queryResponse.query = { external: { add: false, filter: { SELECTED: false } } }; - if (!queryResponse.query.external) queryResponse.query.external = { add: false, filter: {SELECTED: false }}; - - // whether add response to current results using addLayerFeaturesToResultsAction - if (!options.add) { - - // in case of new request results reset the query otherwise maintain the previous request - this.clearState(); - this.state.query = queryResponse.query; - this.state.type = queryResponse.type; - - // if true add external layers to response - if (true === queryResponse.query.external.add) { - this._addVectorLayersDataToQueryResponse(queryResponse); - } - - switch (this.state.query.type) { - case 'coordinates': this.showCoordinates(this.state.query.coordinates); break; - case 'bbox': this.showBBOX(this.state.query.bbox); break; - case 'polygon': this.showGeometry(this.state.query.geometry); break; + this.resultsQueryLayer = new ol.layer.Vector({ + source: new ol.source.Vector(), + style(feature) { + const fill = new ol.style.Fill({ color: 'rgba(0, 0, 255, 0.7)' }); + const stroke = new ol.style.Stroke({ color: 'blue', width: 3 }); + if ('Point' === feature.getGeometry().getType()) { + return new ol.style.Style({ + text: new ol.style.Text({ fill, stroke, text: '\uf3c5', font: '900 3em "Font Awesome 5 Free"', offsetY : -15 }) + }); } - + return new ol.style.Style({ stroke }); } - - this.setLayersData(this._digestFeaturesForLayers(queryResponse.data), options); - - }, + }); /** - * Setter method called when adding layer and feature for response - * - * @param layers - * @param options + * Service used to work with atlas (print functionality) action tool */ - setLayersData(layers, options = { add: false }) { - if (!options.add) { - // set the right order of result layers based on TOC - this._currentLayerIds = layers.map(layer => layer.id); - this._orderResponseByProjectLayers(layers); - } - // get features from add pick layer in case of a new request query - layers.forEach(layer => { options.add ? this.updateLayerResultFeatures(layer) : this.state.layers.push(layer); }); - this.setActionsForLayers(layers, { add: options.add }); - this.state.changed = true; - }, + this.printService = new PrintService(); /** - * Method used to add custom component - * - * @param component + * @deprecated since 3.8 + * It used to register change project from Change map button */ - addComponent(component) { - this._addComponent(component) - }, + ProjectsRegistry.onafter('setCurrentProject', project => { + this._project = project; + this._setRelations(project); + this._setAtlasActions(project); + this.state.download_data = false; + this.plotLayerIds = []; + }); /** - * @FIXME add description - * - * @param actions - * @param layers + * Current project */ - addActionsForLayers(actions, layers) {}, + this._project = ProjectsRegistry.getCurrentProject(); /** - * @FIXME add description - * - * @param element + * Keep the right order for query result based on TOC order layers */ - postRender(element) {}, + this._projectLayerIds = this._project.getConfigLayers().map(layer => layer.id); /** * @FIXME add description */ - closeComponent() {}, + this._setRelations(this._project); /** * @FIXME add description - * - * @param layer */ - changeLayerResult(layer) { - this._changeLayerResult(layer); - }, + this._setAtlasActions(this._project); /** * @FIXME add description */ - activeMapInteraction() {}, - - /** - * Setter method related to relation table - */ - editFeature({layer, feature}={}) {}, - - /** - * Setter method called when opening/closing feature info data content. - * - * @param open - * @param layer - * @param feature - * @param container - */ - openCloseFeatureResult({open, layer, feature, container}={}) {} - - }; + GUI.onbefore('setContent', (options) => { + this.mapService = this.mapService || ApplicationService.getApplicationService('map'); + if (100 === options.perc && GUI.isMobile()) { + this._asyncFnc.zoomToLayerFeaturesExtent.async = true; + this._asyncFnc.goToGeometry.async = true; + } + }); - base(this); + } /** * @FIXME add description */ - this.addLayersPlotIds = function(layerIds=[]) { this.plotLayerIds = layerIds; }; + init() { + this.clearState(); + } /** * @FIXME add description */ - this.getPlotIds = function() { return this.plotLayerIds; }; + addLayersPlotIds(layerIds = []) { + this.plotLayerIds = layerIds; + } /** * @FIXME add description */ - this.findPlotId = function(id) { return this.plotLayerIds.find(plotId => plotId == id); }; + getPlotIds() { + return this.plotLayerIds; + } /** * @FIXME add description */ - this._setRelations(this._project); + findPlotId(id) { + return this.plotLayerIds.find(plotId => plotId == id); + } /** - * @FIXME add description + * Register for plugin or other component of application to add + * custom component on result for each layer feature or layer + * + * @param id unique id identification + * @param layerId Layer id of layer + * @param component custom component + * @param type feature or layer + * @param position */ - this._setAtlasActions(this._project); + registerCustomComponent({ + id = getUniqueDomId(), + layerId, + component, + type = 'feature', + position = 'after', + } = {}) { + if (undefined === this.state.layerscustomcomponents[layerId]) { + this.state.layerscustomcomponents[layerId] = { + layer: { before: [], after: [] }, + feature: { before: [], after: [] } + }; + } + this.state.layerscustomcomponents[layerId][type][position].push({ id, component }); + return id; + } /** - * @FIXME add description + * Check position + * + * @param id + * @param layerId + * @param type */ - this._asyncFnc = { - todo: noop, - zoomToLayerFeaturesExtent: { - async: false - }, - goToGeometry: { - async: false - } + unRegisterCustomComponent({ + id, + layerId, + type, + position + }) { + if (position) { + this.state.layerscustomcomponents[layerId][type][position] = this.state.layerscustomcomponents[layerId][type][position].filter(({id:componentId}) => componentId !== id); + return; + } + + Object + .keys(this.state.layerscustomcomponents[layerId][type]) + .forEach(position => { + this.state.layerscustomcomponents[layerId][type][position] = this.state.layerscustomcomponents[layerId][type][position].filter(({id:componentId}) => componentId !== id); + }); }; /** - * @FIXME add description + * Add a feature to current layer result + * + * @param layer + * @param feature */ - GUI.onbefore('setContent', (options) => { - this.mapService = this.mapService || ApplicationService.getApplicationService('map'); - if (100 === options.perc && GUI.isMobile()) { - this._asyncFnc.zoomToLayerFeaturesExtent.async = true; - this._asyncFnc.goToGeometry.async = true; - } - }); - -} - -// Make the public service en Event Emitter -inherit(QueryResultsService, G3WObject); - -const proto = QueryResultsService.prototype; + addFeatureLayerToResult(layer, feature) { + this.state.layersFeaturesBoxes[this.getBoxId(layer, feature)].collapsed = true; + } -/** - * Register for plugin or other component of application to add - * custom component on result for each layer feature or layer - * - * @param id unique id identification - * @param layerId Layer id of layer - * @param component custom component - * @param type feature or layer - * @param position - */ -proto.registerCustomComponent = function({ id = getUniqueDomId(), layerId, component, type = 'feature', position = 'after' } = {}) { - if (undefined === this.state.layerscustomcomponents[layerId]) { - this.state.layerscustomcomponents[layerId] = { - layer: { before: [], after: [] }, - feature: { before: [], after: [] } - }; + /** + * Remove a feature from current layer result + * + * @param layer + * @param feature + */ + removeFeatureLayerFromResult(layer, feature) { + this.updateLayerResultFeatures({ id: layer.id, external: layer.external, features: [feature] }); } - this.state.layerscustomcomponents[layerId][type][position].push({ id, component }); - return id; -}; -/** - * Check position - * - * @param id - * @param layerId - * @param type - */ -proto.unRegisterCustomComponent = function({id, layerId, type, position}) { - if (position) { - this.state.layerscustomcomponents[layerId][type][position] = this.state.layerscustomcomponents[layerId][type][position].filter(({id:componentId}) => componentId !== id); - } else { - Object.keys(this.state.layerscustomcomponents[layerId][type]) - .forEach(position => { - this.state.layerscustomcomponents[layerId][type][position] = this.state.layerscustomcomponents[layerId][type][position].filter(({id:componentId}) => componentId !== id); - }) + /** + * Wrapper for download + * + * @param downloadFnc + * @param options + */ + async downloadApplicationWrapper(downloadFnc, options = {}) { + const download_caller_id = ApplicationService.setDownload(true); + GUI.setLoadingContent(true); + try { + await downloadFnc(options); + } catch(err) { + GUI.showUserMessage({ type: 'alert', message: err || 'server_error', textMessage: !!err }) + } + ApplicationService.setDownload(false, download_caller_id); + GUI.setLoadingContent(false); } -}; -/** - * Add a feature to current layer result - * - * @param layer - * @param feature - */ -proto.addFeatureLayerToResult = function(layer, feature) { - this.state.layersFeaturesBoxes[this.getBoxId(layer, feature)].collapsed = true; -}; + /** + * Based on layer response check if features layer need to + * be added or removed to current `state.layers` results + * + * @param {Array} layer + * + * @since 3.8.0 + */ + updateLayerResultFeatures(responseLayer) { + const layer = this._getLayer(responseLayer.id), // get layer from current `state.layers` showed on result + features = this._getLayerFeatures(responseLayer), // extract features from layer object + external = this._getExternalLayer(responseLayer.id); // get id of external layer or not (`external` is a layer added by mapcontrol addexternlayer) + + if (layer && features.length) { + const _featuresToAdd = this._featuresToAdd(features, external); // filter the features that we had to add + const _featuresToRemove = this._featuresToRemove(features, external); // filter the features that we had to remove (because they are already loaded in `state.layers`) + + /** + * @TODO check if the first loop `features.forEach` is redundant, + * it can be replaced by `_featuresToAdd.forEach` ? + */ + features.forEach(feature => this._toggleLayerFeatureBox(layer, feature, true)); + // _featuresToAdd.forEach(feature => this._toggleLayerFeatureBox(layer, feature, true)); + _featuresToRemove.forEach(feature => this._removeLayerFeatureBox(layer, feature)); + + // new layer features + layer.features = [ ..._featuresToRemove, ..._featuresToAdd ]; + + // in case of removed features + if (1 === _featuresToRemove.length) { + this._toggleLayerFeatureBox(layer, _featuresToRemove[0], false); + } -/** - * Remove a feature from current layer result - * - * @param layer - * @param feature - */ -proto.removeFeatureLayerFromResult = function(layer, feature) { - this.updateLayerResultFeatures({ id: layer.id, external: layer.external, features: [feature] }); -}; + // in case no more features on layer remove interaction pickcoordinate to get result from map + this.checkIfLayerHasNoFeatures(layer); -/** - * Wrapper for download - * - * @param downloadFnc - * @param options - */ -proto.downloadApplicationWrapper = async function(downloadFnc, options={}) { - const download_caller_id = ApplicationService.setDownload(true); - GUI.setLoadingContent(true); - try { - await downloadFnc(options); - } catch(err) { - GUI.showUserMessage({ type: 'alert', message: err || 'server_error', textMessage: !!err }) - } - ApplicationService.setDownload(false, download_caller_id); - GUI.setLoadingContent(false); -}; + } -/** - * Based on layer response check if features layer need to - * be added or removed to current `state.layers` results - * - * @param {Array} layer - * - * @since 3.8.0 - */ -proto.updateLayerResultFeatures = function(responseLayer) { - const layer = this._getLayer(responseLayer.id), // get layer from current `state.layers` showed on result - features = this._getLayerFeatures(responseLayer), // extract features from layer object - external = this._getExternalLayer(responseLayer.id); // get id of external layer or not (`external` is a layer added by mapcontrol addexternlayer) + // hightlight new feature + if (1 === this.state.layers.length) { + this.highlightFeaturesPermanently(this.state.layers[0]); + } - if (layer && features.length) { - const _featuresToAdd = this._featuresToAdd(features, external); // filter the features that we had to add - const _featuresToRemove = this._featuresToRemove(features, external); // filter the features that we had to remove (because they are already loaded in `state.layers`) + this.changeLayerResult(layer); + } - /** - * @TODO check if the first loop `features.forEach` is redundant, - * it can be replaced by `_featuresToAdd.forEach` ? - */ - features.forEach(feature => this._toggleLayerFeatureBox(layer, feature, true)); - // _featuresToAdd.forEach(feature => this._toggleLayerFeatureBox(layer, feature, true)); - _featuresToRemove.forEach(feature => this._removeLayerFeatureBox(layer, feature)); + /** + * Called when layer result features is changed + * + * @param layer + */ + _changeLayerResult(layer) { + this.state.layersactions[layer.id].forEach(action => action.change && action.change(layer)); // call if present change method to action + this.resetCurrentActionToolsLayer(layer); // reset layer current actions tools + } - // new layer features - layer.features = [ ..._featuresToRemove, ..._featuresToAdd ]; - - // in case of removed features - if (1 === _featuresToRemove.length) { - this._toggleLayerFeatureBox(layer, _featuresToRemove[0], false); + /** + * Check and do action if layer has no features after delete feature(s) + * + * @param layer + */ + checkIfLayerHasNoFeatures(layer) { + if (layer && 0 === layer.features.length) { + // due to vue reactivity, wait a little bit before update layers + setTimeout(() => { + this.state.layers = this.state.layers.filter(l => l.id !== layer.id); + this.clearHighlightGeometry(layer); + this.removeAddFeaturesLayerResultInteraction({ toggle: true }); + }) } - - // in case no more features on layer remove interaction pickcoordinate to get result from map - this.checkIfLayerHasNoFeatures(layer); - } - // hightlight new feature - if (1 === this.state.layers.length) { - this.highlightFeaturesPermanently(this.state.layers[0]); + /** + * Create boxid identify to query result hmtl + * + * @param layer + * @param feature + * @param relation_index + * + * @returns {string} + */ + getBoxId(layer, feature, relation_index) { + return (null !== relation_index && undefined !== relation_index) + ? `${layer.id}_${feature.id}_${relation_index}` + : `${layer.id}_${feature.id}`; } - this.changeLayerResult(layer); -}; + /** + * @FIXME add description + * + * @param layers + * @param options + */ + setActionsForLayers(layers, options = { add: false }) { + if (options.add) { + return; + } + + this.unlistenerlayeractionevents = []; + + layers.forEach(layer => { -/** - * Called when layer result features is changed - * - * @param layer - */ -proto._changeLayerResult = function(layer) { - this.state.layersactions[layer.id].forEach(action => action.change && action.change(layer)); // call if present change method to action - this.resetCurrentActionToolsLayer(layer); // reset layer current actions tools -}; + const currentactiontoolslayer = {}; + const currentationfeaturelayer = {}; + layer.features.forEach((_, idx) => { + currentactiontoolslayer[idx] = null; + currentationfeaturelayer[idx] = null; + }); -/** - * Check and do action if layer has no features after delete feature(s) - * - * @param layer - */ -proto.checkIfLayerHasNoFeatures = function(layer) { - if (layer && 0 === layer.features.length) { - // due to vue reactivity, wait a little bit before update layers - setTimeout(() => { - this.state.layers = this.state.layers.filter(l => l.id !== layer.id); - this.clearHighlightGeometry(layer); - this.removeAddFeaturesLayerResultInteraction({ toggle: true }); - }) - } -}; + // set eventually layer action tool and need to be reactive + this.state.layeractiontool[layer.id] = Vue.observable({ component: null, config: null }); + this.state.currentactiontools[layer.id] = Vue.observable(currentactiontoolslayer); + this.state.currentactionfeaturelayer[layer.id] = Vue.observable(currentationfeaturelayer); -/** - * Create boxid identify to query result hmtl - * - * @param layer - * @param feature - * @param relation_index - * - * @returns {string} - */ -proto.getBoxId = function(layer, feature, relation_index) { - return (null !== relation_index && undefined !== relation_index) - ? `${layer.id}_${feature.id}_${relation_index}` - : `${layer.id}_${feature.id}`; -}; + const is_external_layer_or_wms = (layer.external) || (layer.source ? 'wms' === layer.source.type : false); + + if (!this.state.layersactions[layer.id]) { + this.state.layersactions[layer.id] = []; + } -/** - * @FIXME add description - * - * @param layers - * @param options - */ -proto.setActionsForLayers = function(layers, options = { add: false }) { - if (options.add) { - return; - } - - this.unlistenerlayeractionevents = []; - - layers.forEach(layer => { + // Lookup for layer geometry. + if (layer.hasgeometry) { + this._setActionGoToGeometry(layer); + } - const currentactiontoolslayer = {}; - const currentationfeaturelayer = {}; - layer.features.forEach((_, idx) => { - currentactiontoolslayer[idx] = null; - currentationfeaturelayer[idx] = null; - }); + // Lookup for layer relations. + if (this._relations && this._relations[layer.id]) { + this._setActionShowQueryAndPlotsRelations(layer); + } - // set eventually layer action tool and need to be reactive - this.state.layeractiontool[layer.id] = Vue.observable({ component: null, config: null }); - this.state.currentactiontools[layer.id] = Vue.observable(currentactiontoolslayer); - this.state.currentactionfeaturelayer[layer.id] = Vue.observable(currentationfeaturelayer); + // Lookup for layer print atlas. + if (this.getAtlasByLayerId(layer.id).length) { + this._setActionPrintAtlas(layer); + } + + // Lookup for layer downloadable features (single). + if (layer.downloads.length === 1) { + this._setActionDownloadFeature(layer); + } - const is_external_layer_or_wms = (layer.external) || (layer.source ? 'wms' === layer.source.type : false); - - if (!this.state.layersactions[layer.id]) { - this.state.layersactions[layer.id] = []; - } + // Lookup for layer downloadable features (multi). + if (layer.downloads.length > 1) { + this._setActionMultiDownloadFeature(layer); + } - // Lookup for layer geometry. - if (layer.hasgeometry) { - this._setActionGoToGeometry(layer); - } + // Lookup for not external layer or WMS. + if (false == is_external_layer_or_wms) { + this._setActionRemoveFeatureFromResult(layer); + } - // Lookup for layer relations. - if (this._relations && this._relations[layer.id]) { - this._setActionShowQueryAndPlotsRelations(layer); - } + // Lookup for layer selection status (active). + if (undefined !== layer.selection.active) { + this._setActionSelection(layer); + } - // Lookup for layer print atlas. - if (this.getAtlasByLayerId(layer.id).length) { - this._setActionPrintAtlas(layer); - } - - // Lookup for layer downloadable features (single). - if (layer.downloads.length === 1) { - this._setActionDownloadFeature(layer); - } - - // Lookup for layer downloadable features (multi). - if (layer.downloads.length > 1) { - this._setActionMultiDownloadFeature(layer); - } - - // Lookup for not external layer or WMS. - if (false == is_external_layer_or_wms) { - this._setActionRemoveFeatureFromResult(layer); - } - - // Lookup for layer selection status (active). - if (undefined !== layer.selection.active) { - this._setActionSelection(layer); - } - - // Lookup for not external layer or WMS (copy link to feature). - if (!is_external_layer_or_wms && layer.hasgeometry) { - this._setActionLinkZoomToFid(layer); - } - - // Lookup for editable layer. - if (layer.editable && !layer.inediting) { - this._setActionEditing(layer); - } - - }); - - this.addActionsForLayers(this.state.layersactions, this.state.layers); + // Lookup for not external layer or WMS (copy link to feature). + if (!is_external_layer_or_wms && layer.hasgeometry) { + this._setActionLinkZoomToFid(layer); + } -}; + // Lookup for editable layer. + if (layer.editable && !layer.inediting) { + this._setActionEditing(layer); + } -/** - * @FIXME add description - */ -proto.createActionState = function({layer, dynamicProperties=['toggled']}) { - // check number of download formats - const properties = dynamicProperties.reduce((obj, prop) => { obj[prop] = {}; return obj; }, {}); - layer.features.map((_, idx) => { Object.keys(properties).forEach(prop => { properties[prop][idx] = null; }); }); - return Vue.observable(properties); -}; + }); -/** - * Get action referred to layer getting the action id - * - * @param layer layer linked to action - * @param id action id - */ -proto.getActionLayerById = function({layer, id}={}) { - return this.state.layersactions[layer.id].find(action => action.id === id); -}; + this.addActionsForLayers(this.state.layersactions, this.state.layers); -/** - * Set current layer action tool in feature - * - * @param layer current layer - * @param index feature index - * @param value component value or null - */ -proto.setCurrentActionLayerFeatureTool = function({layer, action, index, component=null}={}) { - if ( - component && - this.state.currentactiontools[layer.id][index] && - action.id !== this.state.currentactionfeaturelayer[layer.id][index].id && - this.state.currentactionfeaturelayer[layer.id][index].toggleable - ) { - this.state.currentactionfeaturelayer[layer.id][index].state.toggled[index] = false; - } - this.state.currentactionfeaturelayer[layer.id][index] = component ? action : null; - this.state.currentactiontools[layer.id][index] = component; -}; + } + /** + * @FIXME add description + */ + createActionState({ + layer, + dynamicProperties = ['toggled'], + }) { + // number of download formats + const properties = dynamicProperties.reduce((obj, prop) => { obj[prop] = {}; return obj; }, {}); + layer.features.map((_, idx) => { Object.keys(properties).forEach(prop => { properties[prop][idx] = null; }); }); + return Vue.observable(properties); + }; -/** - * @FIXME add description - */ -proto.addCurrentActionToolsLayer = function({id, layer, config={}}) { - this.state.actiontools[id] = { [layer.id]: config }; -}; + /** + * Get action referred to layer getting the action id + * + * @param layer layer linked to action + * @param id action id + */ + getActionLayerById({ + layer, + id, + } = {}) { + return this.state.layersactions[layer.id].find(action => action.id === id); + }; -/** - * Reset current action tools on layer when feature layer change - * - * @param layer - */ -proto.resetCurrentActionToolsLayer = function(layer) { - layer.features.forEach((_, idx) => { - if (!this.state.currentactiontools[layer.id]) { - return; - } - if (undefined === this.state.currentactiontools[layer.id][idx]) { - Vue.set(this.state.currentactiontools[layer.id], idx, null); - } else { - this.state.currentactiontools[layer.id][idx] = null; + /** + * Set current layer action tool in feature + * + * @param layer current layer + * @param index feature index + * @param value component value or null + */ + setCurrentActionLayerFeatureTool({ + layer, + action, + index, + component = null + } = {}) { + if ( + component && + this.state.currentactiontools[layer.id][index] && + action.id !== this.state.currentactionfeaturelayer[layer.id][index].id && + this.state.currentactionfeaturelayer[layer.id][index].toggleable + ) { + this.state.currentactionfeaturelayer[layer.id][index].state.toggled[index] = false; } - this.state.currentactionfeaturelayer[layer.id][idx] = null; - }) -}; - -/** - * @FIXME add description - */ -proto.setLayerActionTool = function({layer, component=null, config=null}={}) { - this.state.layeractiontool[layer.id].component = component; - this.state.layeractiontool[layer.id].config = config; -}; - -/** - * Copy `zoomtofid` url - * - * @param layer - * @param feature - */ -proto.copyZoomToFidUrl = function(layer, feature, action) { - const url = new URL(location.href); - url.searchParams.set('zoom_to_fid', `${layer.id}|${feature.attributes[G3W_FID]}`); - copyUrl(url.toString()); - action.hint_changed = true; -}; + this.state.currentactionfeaturelayer[layer.id][index] = component ? action : null; + this.state.currentactiontools[layer.id][index] = component; + } -/** - * Clear all - */ -proto.clear = function() { - this.runAsyncTodo(); - this.unlistenerEventsActions(); - this.mapService.clearHighlightGeometry(); - this.resultsQueryLayer.getSource().clear(); - this.removeAddFeaturesLayerResultInteraction({ toggle: true }); - this._asyncFnc = null; - this._asyncFnc = { - todo: noop, - zoomToLayerFeaturesExtent: { - async: false - }, - goToGeometry: { - async: false - } - }; - this.clearState(); - this.closeComponent(); - this.removeQueryResultLayerFromMap(); -}; -/** - * @FIXME add description - */ -proto.getCurrentLayersIds = function() { - return this._currentLayerIds; -}; + /** + * @FIXME add description + */ + addCurrentActionToolsLayer({ + id, + layer, + config = {} + }) { + this.state.actiontools[id] = { [layer.id]: config }; + } -/** - * @FIXME add description - */ -proto.runAsyncTodo = function() { - this._asyncFnc.todo(); -}; + /** + * Reset current action tools on layer when feature layer change + * + * @param layer + */ + resetCurrentActionToolsLayer(layer) { + layer.features.forEach((_, idx) => { + if (!this.state.currentactiontools[layer.id]) { + return; + } + if (undefined === this.state.currentactiontools[layer.id][idx]) { + Vue.set(this.state.currentactiontools[layer.id], idx, null); + } else { + this.state.currentactiontools[layer.id][idx] = null; + } + this.state.currentactionfeaturelayer[layer.id][idx] = null; + }) + } -/** - * @FIXME add description - */ -proto._orderResponseByProjectLayers = function(layers) { - layers.sort((layerA, layerB) => (this._projectLayerIds.indexOf(layerA.id) > this._projectLayerIds.indexOf(layerB.id) ? 1 : -1)); -}; + /** + * @FIXME add description + */ + setLayerActionTool({ + layer, + component = null, + config = null, + } = {}) { + this.state.layeractiontool[layer.id].component = component; + this.state.layeractiontool[layer.id].config = config; + }; -/** - * @FIXME add description - */ -proto.setZoomToResults = function(bool=true) { - this.state.zoomToResult = bool; -}; + /** + * Copy `zoomtofid` url + * + * @param layer + * @param feature + */ + copyZoomToFidUrl(layer, feature, action) { + const url = new URL(location.href); + url.searchParams.set('zoom_to_fid', `${layer.id}|${feature.attributes[G3W_FID]}`); + copyUrl(url.toString()); + action.hint_changed = true; + } -/** - * @FIXME add description - */ -proto.highlightFeaturesPermanently = function(layer) { - this.mapService.highlightFeatures(layer.features, { duration: Infinity }); -}; + /** + * Clear all + */ + clear() { + this.runAsyncTodo(); + this.unlistenerEventsActions(); + this.mapService.clearHighlightGeometry(); + this.resultsQueryLayer.getSource().clear(); + this.removeAddFeaturesLayerResultInteraction({ toggle: true }); + this._asyncFnc = null; + this._asyncFnc = { + todo: noop, + zoomToLayerFeaturesExtent: { async: false }, + goToGeometry: { async: false }, + }; + this.clearState(); + this.closeComponent(); + this.removeQueryResultLayerFromMap(); + } -/** - * Check if one layer result - * - * @returns {boolean} - */ -proto.isOneLayerResult = function() { - return (1 === this.state.layers.length); -}; + /** + * @FIXME add description + */ + getCurrentLayersIds() { + return this._currentLayerIds; + } -/** - * @FIXME add description - * - * @param toggle boolean If true toggle true the mapcontrol - */ -proto.removeAddFeaturesLayerResultInteraction = function({toggle=false}={}) { - if (this._addFeaturesLayerResultInteraction.interaction) { - this.mapService.removeInteraction(this._addFeaturesLayerResultInteraction.interaction); + /** + * @FIXME add description + */ + runAsyncTodo() { + this._asyncFnc.todo(); } - this._addFeaturesLayerResultInteraction.interaction = null; - this._addFeaturesLayerResultInteraction.id = null; + /** + * @FIXME add description + */ + _orderResponseByProjectLayers(layers) { + layers.sort((a, b) => (this._projectLayerIds.indexOf(a.id) > this._projectLayerIds.indexOf(b.id) ? 1 : -1)); + } - // check if query map control is toggled and registered - if (toggle && this._addFeaturesLayerResultInteraction.mapcontrol) { - this._addFeaturesLayerResultInteraction.mapcontrol.toggle(true); + /** + * @FIXME add description + */ + setZoomToResults(bool = true) { + this.state.zoomToResult = bool; } - this._addFeaturesLayerResultInteraction.mapcontrol = null; + /** + * @FIXME add description + */ + highlightFeaturesPermanently(layer) { + this.mapService.highlightFeatures(layer.features, { duration: Infinity }); + } - if (this._addFeaturesLayerResultInteraction.toggleeventhandler) { - this.mapService.off('mapcontrol:toggled', this._addFeaturesLayerResultInteraction.toggleeventhandler); + /** + * Check if one layer result + * + * @returns {boolean} + */ + isOneLayerResult() { + return (1 === this.state.layers.length); } - this._addFeaturesLayerResultInteraction.toggleeventhandler = null; -}; + /** + * @FIXME add description + * + * @param toggle boolean If true toggle true the mapcontrol + */ + removeAddFeaturesLayerResultInteraction({ + toggle = false + } = {}) { + if (this._addFeaturesLayerResultInteraction.interaction) { + this.mapService.removeInteraction(this._addFeaturesLayerResultInteraction.interaction); + } -/** - * Adds feature to Features results - * - * @param layer - */ -proto.addLayerFeaturesToResultsAction = function(layer) { - - // Check if layer is current layer to add or clear previous - if ( - null !== this._addFeaturesLayerResultInteraction.id && - layer.id !== this._addFeaturesLayerResultInteraction.id - ) { + this._addFeaturesLayerResultInteraction.interaction = null; + this._addFeaturesLayerResultInteraction.id = null; - const layer = this.state.layers.find(layer => layer.id === this._addFeaturesLayerResultInteraction.id); - if (layer) { - layer.addfeaturesresults.active = false; + // check if query map control is toggled and registered + if (toggle && this._addFeaturesLayerResultInteraction.mapcontrol) { + this._addFeaturesLayerResultInteraction.mapcontrol.toggle(true); } - // remove previous add result interaction - if (this._addFeaturesLayerResultInteraction.interaction) { - this.mapService.removeInteraction(this._addFeaturesLayerResultInteraction.interaction); + this._addFeaturesLayerResultInteraction.mapcontrol = null; + + if (this._addFeaturesLayerResultInteraction.toggleeventhandler) { + this.mapService.off('mapcontrol:toggled', this._addFeaturesLayerResultInteraction.toggleeventhandler); } + this._addFeaturesLayerResultInteraction.toggleeventhandler = null; } - this._addFeaturesLayerResultInteraction.id = layer.id; + /** + * Adds feature to Features results + * + * @param layer + */ + addLayerFeaturesToResultsAction(layer) { + + // Check if layer is current layer to add or clear previous + if ( + null !== this._addFeaturesLayerResultInteraction.id && + layer.id !== this._addFeaturesLayerResultInteraction.id + ) { + + const layer = this.state.layers.find(layer => layer.id === this._addFeaturesLayerResultInteraction.id); + if (layer) { + layer.addfeaturesresults.active = false; + } - layer.addfeaturesresults.active = !layer.addfeaturesresults.active; + // remove previous add result interaction + if (this._addFeaturesLayerResultInteraction.interaction) { + this.mapService.removeInteraction(this._addFeaturesLayerResultInteraction.interaction); + } - if (!layer.addfeaturesresults.active) { + } - this.removeAddFeaturesLayerResultInteraction({ toggle: true }); + this._addFeaturesLayerResultInteraction.id = layer.id; - } else { + layer.addfeaturesresults.active = !layer.addfeaturesresults.active; - this.activeMapInteraction(); // useful to send an event - const external_layer = layer.external; + if (!layer.addfeaturesresults.active) { - if (!this._addFeaturesLayerResultInteraction.mapcontrol) { - this._addFeaturesLayerResultInteraction.mapcontrol = this.mapService.getCurrentToggledMapControl(); - } + this.removeAddFeaturesLayerResultInteraction({ toggle: true }); - const interaction = this._addFeaturesLayerResultInteraction.interaction = new PickCoordinatesInteraction(); + } else { - this.mapService.addInteraction(interaction, { close: false }); + this.activeMapInteraction(); // useful to send an event + const external_layer = layer.external; - interaction.on('picked', async (e) => { - if (external_layer) { - this.setQueryResponse( - { - data: [ - this.getVectorLayerFeaturesFromQueryRequest( - this._vectorLayers.find(vectorLayer => layer.id === vectorLayer.get('id')), - { coordinates } - ) - ], - query: { - coordinates: e.coordinate - } - }, - { add: true } - ); - } else { - await DataRouterService.getData( - 'query:coordinates', - { - inputs: { - coordinates: e.coordinate, - query_point_tolerance: this._project.getQueryPointTolerance(), - layerIds: [layer.id], - multilayers: false, + if (!this._addFeaturesLayerResultInteraction.mapcontrol) { + this._addFeaturesLayerResultInteraction.mapcontrol = this.mapService.getCurrentToggledMapControl(); + } + + const interaction = this._addFeaturesLayerResultInteraction.interaction = new PickCoordinatesInteraction(); + + this.mapService.addInteraction(interaction, { close: false }); + + interaction.on('picked', async (e) => { + if (external_layer) { + this.setQueryResponse( + { + data: [ + this.getVectorLayerFeaturesFromQueryRequest( + this._vectorLayers.find(vectorLayer => layer.id === vectorLayer.get('id')), + { coordinates } + ) + ], + query: { + coordinates: e.coordinate + } }, - outputs: { - show: { - add: true + { add: true } + ); + } else { + await DataRouterService.getData( + 'query:coordinates', + { + inputs: { + coordinates: e.coordinate, + query_point_tolerance: this._project.getQueryPointTolerance(), + layerIds: [layer.id], + multilayers: false, + }, + outputs: { + show: { + add: true + } } } - } - ); - } - }); + ); + } + }); - this._addFeaturesLayerResultInteraction.toggleeventhandler = (evt) => { - if (evt.target.isToggled() && evt.target.isClickMap()) { - layer.addfeaturesresults.active = false; - } - }; + this._addFeaturesLayerResultInteraction.toggleeventhandler = (evt) => { + if (evt.target.isToggled() && evt.target.isClickMap()) { + layer.addfeaturesresults.active = false; + } + }; - this.mapService.once('mapcontrol:toggled', this._addFeaturesLayerResultInteraction.toggleeventhandler); + this.mapService.once('mapcontrol:toggled', this._addFeaturesLayerResultInteraction.toggleeventhandler); + } } -}; -/** - * @FIXME add description - */ -proto.deactiveQueryInteractions = function() { - this.state.layers.forEach(layer => { - if (layer.addfeaturesresults) layer.addfeaturesresults.active = false; - }); - this.removeAddFeaturesLayerResultInteraction(); -}; + /** + * @FIXME add description + */ + deactiveQueryInteractions() { + this.state.layers.forEach(layer => { if (layer.addfeaturesresults) layer.addfeaturesresults.active = false; }); + this.removeAddFeaturesLayerResultInteraction(); + } -/** - * @FIXME add description - * - * @param layer - * @param options - */ -proto.zoomToLayerFeaturesExtent = function(layer, options={}) { - options.highlight = !this.isOneLayerResult(); - if (this._asyncFnc.zoomToLayerFeaturesExtent.async) { - this._asyncFnc.todo = this.mapService.zoomToFeatures.bind(this.mapService, layer.features, options); - } else { - this.mapService.zoomToFeatures(layer.features, options); + /** + * @FIXME add description + * + * @param layer + * @param options + */ + zoomToLayerFeaturesExtent(layer, options = {}) { + options.highlight = !this.isOneLayerResult(); + if (this._asyncFnc.zoomToLayerFeaturesExtent.async) { + this._asyncFnc.todo = this.mapService.zoomToFeatures.bind(this.mapService, layer.features, options); + } else { + this.mapService.zoomToFeatures(layer.features, options); + } } -}; -/** - * @FIXME add description - */ -proto.clearState = function(options={}) { - this.state.layers.splice(0); - this.state.query = null; - this.state.querytitle = ""; - this.state.changed = false; - // clear action if present - Object.values(this.state.layersactions) - .forEach(layeractions => - layeractions.forEach(action => action.clear && action.clear()) - ); - this.state.layersactions = {}; - this.state.actiontools = {}; - this.state.layeractiontool = {}; - // current action tools - this.state.currentactiontools = {}; - this.state.layersFeaturesBoxes = {}; - this.removeAddFeaturesLayerResultInteraction(); -}; + /** + * @FIXME add description + */ + clearState(options = {}) { + this.state.layers.splice(0); + this.state.query = null; + this.state.querytitle = ""; + this.state.changed = false; + // clear action if present + Object.values(this.state.layersactions) + .forEach(layeractions => + layeractions.forEach(action => action.clear && action.clear()) + ); + this.state.layersactions = {}; + this.state.actiontools = {}; + this.state.layeractiontool = {}; + // current action tools + this.state.currentactiontools = {}; + this.state.layersFeaturesBoxes = {}; + this.removeAddFeaturesLayerResultInteraction(); + } -/** - * @FIXME add description - */ -proto.getState = function() { - return this.state; -}; + /** + * @FIXME add description + */ + getState() { + return this.state; + } -/** - * @FIXME add description - * - * @param state - */ -proto.setState = function(state) { - this.state = state; -}; + /** + * @FIXME add description + * + * @param state + */ + setState(state) { + this.state = state; + } -/** - * @FIXME add description - * - * @param project - */ -proto._setRelations = function(project) { - const projectRelations = project.getRelations(); - this._relations = projectRelations ? _.groupBy(projectRelations,'referencedLayer'): []; -}; + /** + * @FIXME add description + * + * @param project + */ + _setRelations(project) { + const projectRelations = project.getRelations(); + this._relations = projectRelations ? _.groupBy(projectRelations,'referencedLayer'): []; + } -/** - * @param layerId - */ -proto.getAtlasByLayerId = function(layerId) { - return this._atlas.filter(atlas => atlas.atlas.qgs_layer_id === layerId); -}; + /** + * @param layerId + */ + getAtlasByLayerId(layerId) { + return this._atlas.filter(atlas => atlas.atlas.qgs_layer_id === layerId); + } -/** - * @FIXME add description - * - * @param project - */ -proto._setAtlasActions = function(project) { - this._atlas = project.getPrint().filter(printconfig => printconfig.atlas) || []; -}; - -/** - * @FIXME add description - * - * @param querytitle - */ -proto.setTitle = function(querytitle) { - this.state.querytitle = querytitle || ""; -}; + /** + * @FIXME add description + * + * @param project + */ + _setAtlasActions(project) { + this._atlas = project.getPrint().filter(printconfig => printconfig.atlas) || []; + } -/** - * @FIXME add description - */ -proto.reset = function() { - this.clearState(); -}; + /** + * @FIXME add description + * + * @param querytitle + */ + setTitle(querytitle) { + this.state.querytitle = querytitle || ""; + } -/** - * Converts response from DataProvider into a QueryResult component data structure - * - * @param featuresForLayers: Array contains for each layer features - * - * @returns {[]} - */ -proto._digestFeaturesForLayers = function(featuresForLayers=[]) { - const layers = []; - /** - * @TODO find out why we need such a level of depth (ie. a nested foreach + triple variables named `featuresForLayer` ?) - */ - featuresForLayers.forEach(featuresForLayer => { - ( - Array.isArray(featuresForLayer) - ? featuresForLayer - : [featuresForLayer] - ).forEach(featuresForLayer => { - const layer = this._handleFeatureForLayer(featuresForLayer); - if (layer) { - layers.push(layer) - } - }); - }); - return layers; -}; + /** + * @FIXME add description + */ + reset() { + this.clearState(); + } -/** - * Convert response from server - * - * @param featuresForLayer - * - * @since 3.8.0 - */ -proto._handleFeatureForLayer = function(featuresForLayer) { - const layerObj = { - editable: false, - inediting: false, - downloads: [], - infoformats: [], - filter: {}, - selection: {}, - external: false, - source: undefined, - infoformat: undefined, - formStructure: undefined, - attributes: [], - features: [], - hasgeometry: false, - show: true, - addfeaturesresults: { - active:false - }, - [DownloadFormats.name]: { - active: false - }, - expandable: true, - hasImageField: false, - error: '', - rawdata: null, // rawdata response - loading: false, - }; + /** + * Converts response from DataProvider into a QueryResult component data structure + * + * @param featuresForLayers: Array contains for each layer features + * + * @returns {[]} + */ + _digestFeaturesForLayers(featuresForLayers = []) { + const layers = []; + /** + * @TODO find out why we need such a level of depth (ie. a nested foreach + triple variables named `featuresForLayer` ?) + */ + featuresForLayers.forEach(featuresForLayer => { + ( + Array.isArray(featuresForLayer) + ? featuresForLayer + : [featuresForLayer] + ).forEach(featuresForLayer => { + const layer = this._handleFeatureForLayer(featuresForLayer); + if (layer) { + layers.push(layer) + } + }); + }); + return layers; + } - const layer = featuresForLayer.layer; + /** + * Convert response from server + * + * @param featuresForLayer + * + * @since 3.8.0 + */ + _handleFeatureForLayer(featuresForLayer) { + const layerObj = { + editable: false, + inediting: false, + downloads: [], + infoformats: [], + filter: {}, + selection: {}, + external: false, + source: undefined, + infoformat: undefined, + formStructure: undefined, + attributes: [], + features: [], + hasgeometry: false, + show: true, + addfeaturesresults: { + active:false + }, + [DownloadFormats.name]: { + active: false + }, + expandable: true, + hasImageField: false, + error: '', + rawdata: null, // rawdata response + loading: false, + }; - let layerAttributes, - layerRelationsAttributes, - layerTitle, - layerId, - sourceType; - - let extractRelations = false; - - if (layer instanceof Layer) { - layerObj.editable = layer.isEditable(); - layerObj.inediting = layer.isInEditing(); - layerObj.source = layer.getSource(); - layerObj.infoformats = layer.getInfoFormats(); - layerObj.infoformat = layer.getInfoFormat(); - - // set selection filter and relation if not wms - if (-1 === [ - Layer.SourceTypes.WMS, - Layer.SourceTypes.WCS, - Layer.SourceTypes.WMST - ].indexOf(layer.getSourceType()) - ) { - layerObj.filter = layer.state.filter; - layerObj.selection = layer.state.selection; - extractRelations = true; - } + const layer = featuresForLayer.layer; + + let layerAttributes, + layerRelationsAttributes, + layerTitle, + layerId, + sourceType; + + let extractRelations = false; + + if (layer instanceof Layer) { + layerObj.editable = layer.isEditable(); + layerObj.inediting = layer.isInEditing(); + layerObj.source = layer.getSource(); + layerObj.infoformats = layer.getInfoFormats(); + layerObj.infoformat = layer.getInfoFormat(); + + // set selection filter and relation if not wms + if (-1 === [ + Layer.SourceTypes.WMS, + Layer.SourceTypes.WCS, + Layer.SourceTypes.WMST + ].indexOf(layer.getSourceType()) + ) { + layerObj.filter = layer.state.filter; + layerObj.selection = layer.state.selection; + extractRelations = true; + } - layerObj.downloads = layer.getDownloadableFormats(); - - try { sourceType = layer.getSourceType() } catch(err) {} - - layerRelationsAttributes = []; - layerTitle = layer.getTitle(); - layerId = layer.getId(); - layerAttributes = ('ows' === this.state.type) /* sanitize attributes layer only if is ows */ - ? layer.getAttributes().map(attribute => { - const sanitizeAttribute = {...attribute}; - sanitizeAttribute.name = sanitizeAttribute.name.replace(/ /g, '_'); - return sanitizeAttribute - }) - : layer.getAttributes(); - - if (layer.hasFormStructure()) { - const structure = layer.getLayerEditingFormStructure(); - if (this._relations && this._relations.length) { - const getRelationFieldsFromFormStructure = (node) => { - if (!node.nodes) { - node.name ? node.relation = true : null; - } else { - for (const _node of node.nodes) { - getRelationFieldsFromFormStructure(_node); + layerObj.downloads = layer.getDownloadableFormats(); + + try { sourceType = layer.getSourceType() } catch(err) {} + + layerRelationsAttributes = []; + layerTitle = layer.getTitle(); + layerId = layer.getId(); + layerAttributes = ('ows' === this.state.type) /* sanitize attributes layer only if is ows */ + ? layer.getAttributes().map(attribute => { + const sanitizeAttribute = {...attribute}; + sanitizeAttribute.name = sanitizeAttribute.name.replace(/ /g, '_'); + return sanitizeAttribute + }) + : layer.getAttributes(); + + if (layer.hasFormStructure()) { + const structure = layer.getLayerEditingFormStructure(); + if (this._relations && this._relations.length) { + const getRelationFieldsFromFormStructure = (node) => { + if (!node.nodes) { + node.name ? node.relation = true : null; + } else { + for (const _node of node.nodes) { + getRelationFieldsFromFormStructure(_node); + } } + }; + for (const node of structure) { + getRelationFieldsFromFormStructure(node); } - }; - for (const node of structure) { - getRelationFieldsFromFormStructure(node); } + layerObj.formStructure = { + structure, + fields: layer.getFields().filter(field => field.show), // get features show + }; } - layerObj.formStructure = { - structure, - fields: layer.getFields().filter(field => field.show), // get features show - }; - } - } else if (layer instanceof ol.layer.Vector) { - layerObj.selection = layer.selection; - layerAttributes = layer.getProperties(); - layerRelationsAttributes = []; - layerTitle = layer.get('name'); - layerId = layer.get('id'); - layerObj.external = true; - } else if ('string' === typeof layer || layer instanceof String) { - const feature = featuresForLayer.features[0]; - const split_layer_name = layer.split('_'); - sourceType = Layer.LayerTypes.VECTOR; - layerAttributes = (feature ? feature.getProperties() : []); - layerRelationsAttributes = []; - layerId = layer; - layerObj.external = true; - layerTitle = (split_layer_name.length > 4) - ? split_layer_name.slice(0, split_layer_name.length -4).join(' ') - : layer; - } - - layerObj.title = layerTitle; - layerObj.id = layerId; - layerObj.atlas = this.getAtlasByLayerId(layerId); - layerObj.relationsattributes = layerRelationsAttributes; - - /** @FIXME add description */ - if (featuresForLayer.rawdata) { - layerObj.rawdata = featuresForLayer.rawdata; - return layerObj; - } - - /** @FIXME add description */ - if (featuresForLayer.features && featuresForLayer.features.length) { - const layerSpecialAttributesName = - (layer instanceof Layer) - ? layerAttributes.filter(attribute => { - try { - return ('_' === attribute.name[0] || Number.isInteger(1*attribute.name[0])) - } catch(e) { - return false; - } - }).map(attribute => ({ alias: attribute.name.replace(/_/, ''), name: attribute.name })) - : []; - if (layerSpecialAttributesName.length) { - featuresForLayer.features - .forEach(feature => this._setSpecialAttributesFeatureProperty(layerSpecialAttributesName, feature)); + } else if (layer instanceof ol.layer.Vector) { + layerObj.selection = layer.selection; + layerAttributes = layer.getProperties(); + layerRelationsAttributes = []; + layerTitle = layer.get('name'); + layerId = layer.get('id'); + layerObj.external = true; + } else if ('string' === typeof layer || layer instanceof String) { + const feature = featuresForLayer.features[0]; + const split_layer_name = layer.split('_'); + sourceType = Layer.LayerTypes.VECTOR; + layerAttributes = (feature ? feature.getProperties() : []); + layerRelationsAttributes = []; + layerId = layer; + layerObj.external = true; + layerTitle = (split_layer_name.length > 4) + ? split_layer_name.slice(0, split_layer_name.length -4).join(' ') + : layer; } - layerObj.attributes = this._parseAttributes(layerAttributes, featuresForLayer.features[0], sourceType); - layerObj.attributes - .forEach(attribute => { - if (layerObj.formStructure) { - const relationField = layer.getFields().find(field => field.name === attribute.name); // need to check all field also show false - if (!relationField) { - layerObj.formStructure.fields.push(attribute); - } - } - if (attribute.type === 'image') { - layerObj.hasImageField = true; - } - }); - featuresForLayer.features - .forEach(feature => { - const props = this.getFeaturePropertiesAndGeometry(feature); - if (props.geometry) { - layerObj.hasgeometry = true; - } - layerObj.features - .push({ - id: layerObj.external ? feature.getId() : props.id, - attributes: props.properties, - geometry: props.geometry, - selection: props.selection, - show: true - }); - }); - return layerObj; - } - - /** @FIXME missing return type ? */ - /** @FIXME add description */ - if (featuresForLayer.error) { - layerObj.error = featuresForLayer.error; - } -}; + layerObj.title = layerTitle; + layerObj.id = layerId; + layerObj.atlas = this.getAtlasByLayerId(layerId); + layerObj.relationsattributes = layerRelationsAttributes; -/** - * Set special attributes - * - * @param layerSpecialAttributesName - * @param feature - */ -proto._setSpecialAttributesFeatureProperty = function(layerSpecialAttributesName, feature) { - if (!layerSpecialAttributesName.length) { - return; - } - // get attributes special keys from feature properties received by server request - const featureAttributesNames = Object.keys(feature.getProperties()); - layerSpecialAttributesName - .forEach(layerAttr => { - featureAttributesNames - .find(featureAttr => { - if (featureAttr === layerAttr.alias) { - feature.set(layerAttr.name, feature.get(featureAttr)); - return true + /** @FIXME add description */ + if (featuresForLayer.rawdata) { + layerObj.rawdata = featuresForLayer.rawdata; + return layerObj; + } + + /** @FIXME add description */ + if (featuresForLayer.features && featuresForLayer.features.length) { + const layerSpecialAttributesName = + (layer instanceof Layer) + ? layerAttributes.filter(attribute => { + try { + return ('_' === attribute.name[0] || Number.isInteger(1*attribute.name[0])) + } catch(e) { + return false; + } + }).map(attribute => ({ alias: attribute.name.replace(/_/, ''), name: attribute.name })) + : []; + if (layerSpecialAttributesName.length) { + featuresForLayer.features + .forEach(feature => this._setSpecialAttributesFeatureProperty(layerSpecialAttributesName, feature)); + } + layerObj.attributes = this._parseAttributes(layerAttributes, featuresForLayer.features[0], sourceType); + layerObj.attributes + .forEach(attribute => { + if (layerObj.formStructure) { + const relationField = layer.getFields().find(field => field.name === attribute.name); // need to check all field also show false + if (!relationField) { + layerObj.formStructure.fields.push(attribute); + } } - }) - }); -}; - -/** - * Get `properties`, `geometry` and `id` from different types of feature - * - * @param feature - * - * @returns {{geometry: (undefined|*|null|ol.Feature), id: *, properties: string[]}|{geometry: *, id: *, properties: *}} - */ -proto.getFeaturePropertiesAndGeometry = function(feature) { - const isOlFeature = feature instanceof ol.Feature; - return { - selection: feature.selection, - properties: isOlFeature ? feature.getProperties() : feature.properties, - geometry: isOlFeature ? feature.getGeometry() : feature.geometry, - id: isOlFeature ? feature.getId() : feature.id - }; -}; - -/** - * Parse attributes to show on result based on field - * - * @param layerAttributes - * @param feature - * @param sourceType - * - * @returns {{name: T, show: boolean, label: T}[]|*} - */ -proto._parseAttributes = function(layerAttributes, feature, sourceType) { - let featureAttributesNames = getAlphanumericPropertiesFromFeature( - Object.keys(this.getFeaturePropertiesAndGeometry(feature).properties) - ); - if (layerAttributes && layerAttributes.length) { - return layerAttributes.filter(attr => featureAttributesNames.indexOf(attr.name) > -1); - } - const {GDAL, WMS, WCS, WMST} = Layer.SourceTypes; - const sourcesTypes = [GDAL, WMS, WCS, WMST]; - return featureAttributesNames.map(featureAttr => ({ - name: featureAttr, - label: featureAttr, - show: (G3W_FID !== featureAttr) && (undefined === sourceType || -1 !== sourcesTypes.indexOf(sourceType)), - type: 'varchar' - })) -}; + if (attribute.type === 'image') { + layerObj.hasImageField = true; + } + }); + featuresForLayer.features + .forEach(feature => { + const props = this.getFeaturePropertiesAndGeometry(feature); + if (props.geometry) { + layerObj.hasgeometry = true; + } + layerObj.features + .push({ + id: layerObj.external ? feature.getId() : props.id, + attributes: props.properties, + geometry: props.geometry, + selection: props.selection, + show: true + }); + }); + return layerObj; + } -/** - * @FIXME add description - * - * @param actionId - * @param layer - * @param feature - * @param index - * @param container - */ -proto.trigger = async function(actionId, layer, feature, index, container) { - if (this._actions[actionId]) { - this._actions[actionId](layer, feature, index); - } - if (layer && this.state.layersactions[layer.id]) { - const action = this.state.layersactions[layer.id].find(layerAction => layerAction.id === actionId); - if (action) { - await this.triggerLayerAction(action, layer, feature, index, container); + /** @FIXME missing return type ? */ + /** @FIXME add description */ + if (featuresForLayer.error) { + layerObj.error = featuresForLayer.error; } + } -}; -/** - * @FIXME add description - * - * @param action - * @param layer - * @param feature - * @param index - * @param container - */ -proto.triggerLayerAction = async function(action, layer, feature, index, container) { - if (action.cbk) { - await action.cbk(layer,feature, action, index, container); - } - if (action.route) { - let url = action.route.replace(/{(\w*)}/g, (m, key) => feature.attributes.hasOwnProperty(key) ? feature.attributes[key] : ""); - if (url && '' !== url) { - GUI.goto(url); + /** + * Set special attributes + * + * @param layerSpecialAttributesName + * @param feature + */ + _setSpecialAttributesFeatureProperty(layerSpecialAttributesName, feature) { + if (!layerSpecialAttributesName.length) { + return; } + // get attributes special keys from feature properties received by server request + const featureAttributesNames = Object.keys(feature.getProperties()); + layerSpecialAttributesName + .forEach(layerAttr => { + featureAttributesNames + .find(featureAttr => { + if (featureAttr === layerAttr.alias) { + feature.set(layerAttr.name, feature.get(featureAttr)); + return true + } + }) + }); } -}; -/** - * @FIXME add description - * - * @param vectorLayer - */ -proto.registerVectorLayer = function(vectorLayer) { - if (-1 === this._vectorLayers.indexOf(vectorLayer)) { - this._vectorLayers.push(vectorLayer); + /** + * Get `properties`, `geometry` and `id` from different types of feature + * + * @param feature + * + * @returns {{geometry: (undefined|*|null|ol.Feature), id: *, properties: string[]}|{geometry: *, id: *, properties: *}} + */ + getFeaturePropertiesAndGeometry(feature) { + const isOlFeature = feature instanceof ol.Feature; + return { + selection: feature.selection, + properties: isOlFeature ? feature.getProperties() : feature.properties, + geometry: isOlFeature ? feature.getGeometry() : feature.geometry, + id: isOlFeature ? feature.getId() : feature.id + }; } -}; - -/** - * @FIXME add description - * - * @param vectorLayer - */ -proto.unregisterVectorLayer = function(vectorLayer) { - this._vectorLayers = this._vectorLayers.filter(layer => { - this.state.layers = this.state.layers && this.state.layers.filter(layer => layer.id !== vectorLayer.get('id')); - return layer !== vectorLayer; - }); -}; -/** - * @FIXME add description - * - * @param vectorLayer - * @param query - * - * @returns {Object|Boolean} - */ -proto.getVectorLayerFeaturesFromQueryRequest = function(vectorLayer, query={}) { - let { - coordinates, - bbox, - geometry, - filterConfig = {} - } = query; // extract information about query type - - let features = []; - - // case query coordinates - if (coordinates && Array.isArray(coordinates)) { - const pixel = this.mapService.viewer.map.getPixelFromCoordinate(coordinates); - this.mapService.viewer.map.forEachFeatureAtPixel( - pixel, - (feature, layer) => { features.push(feature); }, - { layerFilter(layer) { return layer === vectorLayer; } } + /** + * Parse attributes to show on result based on field + * + * @param layerAttributes + * @param feature + * @param sourceType + * + * @returns {{name: T, show: boolean, label: T}[]|*} + */ + _parseAttributes(layerAttributes, feature, sourceType) { + let featureAttributesNames = getAlphanumericPropertiesFromFeature( + Object.keys(this.getFeaturePropertiesAndGeometry(feature).properties) ); + if (layerAttributes && layerAttributes.length) { + return layerAttributes.filter(attr => featureAttributesNames.indexOf(attr.name) > -1); + } + const sourcesTypes = [ + Layer.SourceTypes.GDAL, + Layer.SourceTypes.WMS, + Layer.SourceTypes.WCS, + Layer.SourceTypes.WMST, + ]; + return featureAttributesNames.map(featureAttr => ({ + name: featureAttr, + label: featureAttr, + show: (G3W_FID !== featureAttr) && (undefined === sourceType || -1 !== sourcesTypes.indexOf(sourceType)), + type: 'varchar' + })) } - // TODO: rewrite this in order to avoid nested else-if conditions - else { - - // case query bbox - if (bbox && Array.isArray(bbox)) { - //set geometry has Polygon - geometry = ol.geom.Polygon.fromExtent(bbox); + /** + * @FIXME add description + * + * @param actionId + * @param layer + * @param feature + * @param index + * @param container + */ + async trigger(actionId, layer, feature, index, container) { + if (this._actions[actionId]) { + this._actions[actionId](layer, feature, index); + } + if (layer && this.state.layersactions[layer.id]) { + const action = this.state.layersactions[layer.id].find(layerAction => layerAction.id === actionId); + if (action) { + await this.triggerLayerAction(action, layer, feature, index, container); + } } + } - // check query geometry (Polygon or MultiPolygon) - if (geometry instanceof ol.geom.Polygon || geometry instanceof ol.geom.MultiPolygon) { - switch (vectorLayer.constructor) { - case VectorLayer: - features = vectorLayer.getIntersectedFeatures(geometry); - break; - case ol.layer.Vector: - vectorLayer.getSource().getFeatures().forEach(feature => { - let add; - switch (filterConfig.spatialMethod) { - case 'intersects': add = intersects(geometry, feature.getGeometry()); break; - case 'within': add = within(geometry, feature.getGeometry()); break; - default: add = geometry.intersectsExtent(feature.getGeometry().getExtent()); break; - } - if (true === add) { - features.push(feature); - } - }); - break; + /** + * @FIXME add description + * + * @param action + * @param layer + * @param feature + * @param index + * @param container + */ + async triggerLayerAction(action, layer, feature, index, container) { + if (action.cbk) { + await action.cbk(layer,feature, action, index, container); + } + if (action.route) { + let url = action.route.replace(/{(\w*)}/g, (m, key) => feature.attributes.hasOwnProperty(key) ? feature.attributes[key] : ""); + if (url && '' !== url) { + GUI.goto(url); } } } - return { - features, - layer: vectorLayer - }; + /** + * @FIXME add description + * + * @param vectorLayer + */ + registerVectorLayer(vectorLayer) { + if (-1 === this._vectorLayers.indexOf(vectorLayer)) { + this._vectorLayers.push(vectorLayer); + } + } -}; + /** + * @FIXME add description + * + * @param vectorLayer + */ + unregisterVectorLayer(vectorLayer) { + this._vectorLayers = this._vectorLayers.filter(layer => { + this.state.layers = this.state.layers && this.state.layers.filter(layer => layer.id !== vectorLayer.get('id')); + return layer !== vectorLayer; + }); + } -/** - * @TODO add description (eg. what is a vector layer ?) - */ -proto._addVectorLayersDataToQueryResponse = function(queryResponse) { - const catalogService = GUI.getService('catalog'); + /** + * @FIXME add description + * + * @param vectorLayer + * @param query + * + * @returns {Object|Boolean} + */ + getVectorLayerFeaturesFromQueryRequest(vectorLayer, query = {}) { + let { + coordinates, + bbox, + geometry, + filterConfig = {} + } = query; // extract information about query type + + let features = []; + + // case query coordinates + if (coordinates && Array.isArray(coordinates)) { + const pixel = this.mapService.viewer.map.getPixelFromCoordinate(coordinates); + this.mapService.viewer.map.forEachFeatureAtPixel( + pixel, + (feature, layer) => { features.push(feature); }, + { layerFilter(layer) { return layer === vectorLayer; } } + ); + } - /** @type { boolean | undefined } */ - const isExternalFilterSelected = queryResponse.query.external.filter.SELECTED; + // TODO: rewrite this in order to avoid nested else-if conditions + else { - // add visible layers to query response (vector layers) - this._vectorLayers - .forEach(layer => { - const isLayerSelected = catalogService.isExternalLayerSelected({ id: layer.get('id'), type: 'vector' }); - if ( - layer.getVisible() && ( // TODO: extract this into `layer.isSomething()` ? - (true === isLayerSelected && true === isExternalFilterSelected) || - (false === isLayerSelected && false === isExternalFilterSelected) || - ("undefined" === typeof isExternalFilterSelected) - ) - ) { - queryResponse.data.push(this.getVectorLayerFeaturesFromQueryRequest(layer, queryResponse.query)); + // case query bbox + if (bbox && Array.isArray(bbox)) { + //set geometry has Polygon + geometry = ol.geom.Polygon.fromExtent(bbox); } - }); -}; -/** - * Add custom component in query result - * - * @param component - */ -proto._addComponent = function(component) { - this.state.components.push(component) -}; + // check query geometry (Polygon or MultiPolygon) + if (geometry instanceof ol.geom.Polygon || geometry instanceof ol.geom.MultiPolygon) { + switch (vectorLayer.constructor) { + case VectorLayer: + features = vectorLayer.getIntersectedFeatures(geometry); + break; + case ol.layer.Vector: + vectorLayer.getSource().getFeatures().forEach(feature => { + let add; + switch (filterConfig.spatialMethod) { + case 'intersects': add = intersects(geometry, feature.getGeometry()); break; + case 'within': add = within(geometry, feature.getGeometry()); break; + default: add = geometry.intersectsExtent(feature.getGeometry().getExtent()); break; + } + if (true === add) { + features.push(feature); + } + }); + break; + } + } + } -/** - * @FIXME add description - */ -proto._printSingleAtlas = function({ atlas = {}, features = [] } = {}) { + return { + features, + layer: vectorLayer + }; - // TODO: make it easier to understand.. (what variables are declared? which ones are aliased?) - let { - name: template, - atlas: { field_name = '' } - } = atlas; + } - field_name = field_name || '$id'; + /** + * @TODO add description (eg. what is a vector layer ?) + */ + _addVectorLayersDataToQueryResponse(queryResponse) { + const catalogService = GUI.getService('catalog'); + + /** @type { boolean | undefined } */ + const isExternalFilterSelected = queryResponse.query.external.filter.SELECTED; + + // add visible layers to query response (vector layers) + this._vectorLayers + .forEach(layer => { + const isLayerSelected = catalogService.isExternalLayerSelected({ id: layer.get('id'), type: 'vector' }); + if ( + layer.getVisible() && ( // TODO: extract this into `layer.isSomething()` ? + (true === isLayerSelected && true === isExternalFilterSelected) || + (false === isLayerSelected && false === isExternalFilterSelected) || + ("undefined" === typeof isExternalFilterSelected) + ) + ) { + queryResponse.data.push(this.getVectorLayerFeaturesFromQueryRequest(layer, queryResponse.query)); + } + }); + } - const values = features.map(feat => feat.attributes['$id' === field_name ? G3W_FID : field_name]); - const download_caller_id = ApplicationService.setDownload(true); + /** + * Add custom component in query result + * + * @param component + */ + _addComponent(component) { + this.state.components.push(component) + } - return this.printService - .printAtlas({ - field: field_name, - values, - template, - download: true - }) - .then(({url}) => { - downloadFile({ url, filename: template, mime_type: 'application/pdf' }) - .catch(error => { GUI.showUserMessage({ type: 'alert', error }) }) - .finally(() => { ApplicationService.setDownload(false, download_caller_id); GUI.setLoadingContent(false); }); - }); -}; + /** + * @FIXME add description + */ + _printSingleAtlas({ + atlas = {}, + features = [], + } = {}) { -/** - * @FIXME add description - * - * @param ids - * @param container - * @param relationData - */ -proto.showChart = function(ids, container, relationData) { - this.emit('show-chart', ids, container, relationData); -}; + // TODO: make it easier to understand.. (what variables are declared? which ones are aliased?) + let { + name: template, + atlas: { field_name = '' } + } = atlas; -/** - * @FIXME add description - */ -proto.hideChart = function(container) { - this.emit('hide-chart', container); -}; + field_name = field_name || '$id'; -/** - * @FIXME add description - * - * @param ids - * @param layer - * @param feature - * @param action - * @param index - * @param container - */ -proto.showRelationsChart = function(ids=[], layer, feature, action, index, container) { - action.state.toggled[index] = !action.state.toggled[index]; - if (action.state.toggled[index]) { - this.emit('show-chart', ids, container, { - relations: this._relations[layer.id], - fid: feature.attributes[G3W_FID], - height: 400 - }); - } else { - this.hideChart(container); - } -}; + const values = features.map(feat => feat.attributes['$id' === field_name ? G3W_FID : field_name]); + const download_caller_id = ApplicationService.setDownload(true); + return this.printService + .printAtlas({ + field: field_name, + values, + template, + download: true + }) + .then(({url}) => { + downloadFile({ url, filename: template, mime_type: 'application/pdf' }) + .catch(error => { GUI.showUserMessage({ type: 'alert', error }) }) + .finally(() => { ApplicationService.setDownload(false, download_caller_id); GUI.setLoadingContent(false); }); + }); + } -/** - * @FIXME add description - * - * @param layer - * @param feature - */ -proto.printAtlas = function(layer, feature) { - const features = feature ? [feature] : layer.features; - const atlasLayer = this.getAtlasByLayerId(layer.id); - - /** @FIXME add description */ - if (atlasLayer.length <= 1) { - this._printSingleAtlas({ features, atlas: atlasLayer[0] }); - return; - } - - let inputs = ''; - - atlasLayer.forEach((atlas, index) => { - const id = getUniqueDomId(); - inputs += ``; - inputs += ``; - inputs += `
`; - }); - - GUI.showModalDialog({ - title: t('sdk.atlas.template_dialog.title'), - message: inputs, - buttons: { - success: { - label: "OK", - className: "skin-button", - callback: () => { - const index = $('input[name="template"]:checked').attr('g3w_atlas_index'); - if (undefined === index) { - return false; // prevent default - } - this._printSingleAtlas({ features, atlas: atlasLayer[index] }); - } - } - } - }); + /** + * @FIXME add description + * + * @param ids + * @param container + * @param relationData + */ + showChart(ids, container, relationData) { + this.emit('show-chart', ids, container, relationData); + } -}; + /** + * @FIXME add description + */ + hideChart(container) { + this.emit('hide-chart', container); + } -/** - * @FIXME add description - * - * @param layer - */ -proto.showLayerDownloadFormats = function(layer) { - const name = DownloadFormats.name; - layer[name].active = !layer[name].active; - this.setLayerActionTool({ - layer, - component: layer[name].active ? DownloadFormats : null, - config: layer[name].active ? this.state.actiontools[name][layer.id] : null - }) -}; - -/** - * @FIXME add description - * - * @param type - * @param layer - * @param features - * @param action - * @param index - */ -proto.downloadFeatures = async function(type, layer, features=[], action, index) { - features = features - ? Array.isArray(features) - ? features - : [features] - : features; - const { - query = {} - } = this.state; - const fids = features.map(feature => feature.attributes[G3W_FID]).join(','); - const data = { fids }; - - /** - * A function that che be called in case of querybypolygon - * - * @param active - */ - const runDownload = async (active=false) => { - if (features.length > 1) { - layer[DownloadFormats.name].active = active; - this.setLayerActionTool({ layer }); - } - const download_caller_id = ApplicationService.setDownload(true); - GUI.setLoadingContent(true); - try { - await CatalogLayersStoresRegistry.getLayerById(layer.id).getDownloadFilefromDownloadDataType(type, { data }) || Promise.resolve(); - } catch(err) { - GUI.notify.error(err || t("info.server_error")); - } - ApplicationService.setDownload(false, download_caller_id); - GUI.setLoadingContent(false); - const downloadsactions = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); - - /** @FIXME add description */ - if (features.length > 1 && undefined === downloadsactions) { - layer[type].active = false; - this.setLayerActionTool({ layer }); + /** + * @FIXME add description + * + * @param ids + * @param layer + * @param feature + * @param action + * @param index + * @param container + */ + showRelationsChart(ids = [], layer, feature, action, index, container) { + action.state.toggled[index] = !action.state.toggled[index]; + if (action.state.toggled[index]) { + this.emit('show-chart', ids, container, { + relations: this._relations[layer.id], + fid: feature.attributes[G3W_FID], + height: 400 + }); + } else { + this.hideChart(container); } + } - /** @FIXME add description */ - if(features.length > 1 && undefined !== downloadsactions) { - layer[DownloadFormats.name].active = false; - } + /** + * @FIXME add description + * + * @param layer + * @param feature + */ + printAtlas(layer, feature) { + const features = feature ? [feature] : layer.features; + const atlasLayer = this.getAtlasByLayerId(layer.id); /** @FIXME add description */ - if (features.length <= 1 && undefined === downloadsactions) { - action.state.toggled[index] = false; + if (atlasLayer.length <= 1) { + this._printSingleAtlas({ features, atlas: atlasLayer[0] }); + return; } - /** @FIXME add description */ - if (features.length <= 1 && undefined !== downloadsactions) { - downloadsactions.state.toggled[index] = false; - } + let inputs = ''; - /** @FIXME add description */ - if (features.length <= 1) { - this.setCurrentActionLayerFeatureTool({ index, action, layer }); - } - }; + atlasLayer.forEach((atlas, index) => { + const id = getUniqueDomId(); + inputs += ``; + inputs += ``; + inputs += `
`; + }); - if ('polygon' !== query.type) { - runDownload(); - } else { // check if multi-download if present - const downloadsactions = this.state.layersactions[layer.id].find(action => action.id === 'downloads'); - let { - fid, - layer:polygonLayer - } = query; - const config = { - choices: [ - { - id: getUniqueDomId(), - type: 'feature', - label: 'sdk.mapcontrols.querybypolygon.download.choiches.feature.label', - }, - { - id: getUniqueDomId(), - type: 'polygon', - label: 'sdk.mapcontrols.querybypolygon.download.choiches.feature_polygon.label', - }, - ], - // choose between only feature attribute or also polygon attibute - download: (type) => { - if ('polygon' === type) { // id type polygon add paramateres to api download - data.sbp_qgs_layer_id = polygonLayer.getId(); - data.sbp_fid = fid; - } else { // force to remove - delete data.sbp_fid; - delete data.sbp_qgs_layer_id; + GUI.showModalDialog({ + title: t('sdk.atlas.template_dialog.title'), + message: inputs, + buttons: { + success: { + label: "OK", + className: "skin-button", + callback: () => { + const index = $('input[name="template"]:checked').attr('g3w_atlas_index'); + if (undefined === index) { + return false; // prevent default + } + this._printSingleAtlas({ features, atlas: atlasLayer[index] }); + } } - runDownload(true) } - }; - - /** @FIXME add description */ - if (1 === features.length && undefined === downloadsactions) { - action.state.toggled[index] = true; - } + }); - /** @FIXME add description */ - if(1 === features.length) { - this.state.actiontools[QueryPolygonCsvAttributesComponent.name] = this.state.actiontools[layer.id] || {}; - this.state.actiontools[QueryPolygonCsvAttributesComponent.name][layer.id] = config; - this.setCurrentActionLayerFeatureTool({ layer, index, action, component: QueryPolygonCsvAttributesComponent }); - } + } - /** @FIXME add description */ - if (features.length < 1 && undefined === downloadsactions) { - layer[type].active = !layer[type].active; - } + /** + * @FIXME add description + * + * @param layer + */ + showLayerDownloadFormats(layer) { + const name = DownloadFormats.name; + layer[name].active = !layer[name].active; + this.setLayerActionTool({ + layer, + component: layer[name].active ? DownloadFormats : null, + config: layer[name].active ? this.state.actiontools[name][layer.id] : null + }) + } - /** @FIXME add description */ - if (features.length < 1 && undefined === downloadsactions && layer[type].active) { - this.setLayerActionTool({ layer, component: QueryPolygonCsvAttributesComponent, config }); - } + /** + * @FIXME add description + * + * @param type + * @param layer + * @param features + * @param action + * @param index + */ + async downloadFeatures(type, layer, features = [], action, index) { + features = features + ? Array.isArray(features) + ? features + : [features] + : features; + const { + query = {} + } = this.state; + const fids = features.map(feature => feature.attributes[G3W_FID]).join(','); + const data = { fids }; - /** @FIXME add description */ - if (features.length < 1 && undefined === downloadsactions && !layer[type].active){ - this.setLayerActionTool({ layer }); - } + /** + * A function that che be called in case of querybypolygon + * + * @param active + */ + const runDownload = async (active=false) => { + if (features.length > 1) { + layer[DownloadFormats.name].active = active; + this.setLayerActionTool({ layer }); + } + const download_caller_id = ApplicationService.setDownload(true); + GUI.setLoadingContent(true); + try { + await CatalogLayersStoresRegistry.getLayerById(layer.id).getDownloadFilefromDownloadDataType(type, { data }) || Promise.resolve(); + } catch(err) { + GUI.notify.error(err || t("info.server_error")); + } + ApplicationService.setDownload(false, download_caller_id); + GUI.setLoadingContent(false); + const downloadsactions = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); - /** @FIXME add description */ - if (features.length < 1 && undefined !== downloadsactions) { - this.setLayerActionTool({ layer, component: QueryPolygonCsvAttributesComponent, config }); - } + /** @FIXME add description */ + if (features.length > 1 && undefined === downloadsactions) { + layer[type].active = false; + this.setLayerActionTool({ layer }); + } - } -}; + /** @FIXME add description */ + if(features.length > 1 && undefined !== downloadsactions) { + layer[DownloadFormats.name].active = false; + } -/** - * @FIXME add description - */ -proto.downloadGpx = function({id:layerId}={}, feature) { - CatalogLayersStoresRegistry - .getLayerById(layerId) - .getGpx({ fid: feature ? feature.attributes[G3W_FID] : null }) - .catch((err) => { GUI.notify.error(t("info.server_error")); }) - .finally(() => { this.layerMenu.loading.shp = false; this._hideMenu(); }) -}; + /** @FIXME add description */ + if (features.length <= 1 && undefined === downloadsactions) { + action.state.toggled[index] = false; + } -/** - * @FIXME add description - */ -proto.downloadXls = function({id:layerId}={}, feature) { - CatalogLayersStoresRegistry - .getLayerById(layerId) - .getXls({ fid: feature ? feature.attributes[G3W_FID] : null }) - .catch(err => { GUI.notify.error(t("info.server_error")); }) - .finally(() => { this.layerMenu.loading.shp = false; this._hideMenu(); }) -}; + /** @FIXME add description */ + if (features.length <= 1 && undefined !== downloadsactions) { + downloadsactions.state.toggled[index] = false; + } -/** - * - * @FIXME add description - * - * @param layer - * @param actionId - */ -proto.listenClearSelection = function(layer, actionId) { - if (layer.external) { - layer.features - .forEach(feature => { - const selectionFeature = layer.selection.features.find(selectionFeature => feature.id === selectionFeature.getId()); - feature.selection = (selectionFeature) ? selectionFeature.selection : { selected: false }; - }); - } else { - const _layer = CatalogLayersStoresRegistry.getLayerById(layer.id); - const handler = () => { - layer.features.forEach((feature, index) => - this.state.layersactions[layer.id].find(action => action.id === actionId).state.toggled[index] = false - ); + /** @FIXME add description */ + if (features.length <= 1) { + this.setCurrentActionLayerFeatureTool({ index, action, layer }); + } }; - _layer.on('unselectionall', handler); - this.unlistenerlayeractionevents.push({ layer:_layer, event:'unselectionall', handler }); - } -}; -/** - * @FIXME add description - * - * @param layer - */ -proto.clearSelectionExtenalLayer = function(layer) { - layer.selection.active = false; - const action = ( - this.state.layersactions[layer.id] && - this.state.layersactions[layer.id].find(action => action.id === 'selection') - ); - layer.selection.features - .forEach((feature, index) => { - if (feature.selection.selected) { - feature.selection.selected = false; - if (action) { - action.state.toggled[index] = false; + if ('polygon' !== query.type) { + runDownload(); + } else { // check if multi-download if present + const downloadsactions = this.state.layersactions[layer.id].find(action => action.id === 'downloads'); + let { + fid, + layer:polygonLayer + } = query; + const config = { + choices: [ + { + id: getUniqueDomId(), + type: 'feature', + label: 'sdk.mapcontrols.querybypolygon.download.choiches.feature.label', + }, + { + id: getUniqueDomId(), + type: 'polygon', + label: 'sdk.mapcontrols.querybypolygon.download.choiches.feature_polygon.label', + }, + ], + // choose between only feature attribute or also polygon attibute + download: (type) => { + if ('polygon' === type) { // id type polygon add paramateres to api download + data.sbp_qgs_layer_id = polygonLayer.getId(); + data.sbp_fid = fid; + } else { // force to remove + delete data.sbp_fid; + delete data.sbp_qgs_layer_id; + } + runDownload(true) } - this.mapService.setSelectionFeatures('remove', { feature }); + }; + + /** @FIXME add description */ + if (1 === features.length && undefined === downloadsactions) { + action.state.toggled[index] = true; } - }); -}; -/** - * @FIXME add description - */ -proto.unlistenerEventsActions = function() { - this.unlistenerlayeractionevents.forEach(obj => obj.layer.off(obj.event, obj.handler)); - this.unlistenerlayeractionevents = []; -}; + /** @FIXME add description */ + if(1 === features.length) { + this.state.actiontools[QueryPolygonCsvAttributesComponent.name] = this.state.actiontools[layer.id] || {}; + this.state.actiontools[QueryPolygonCsvAttributesComponent.name][layer.id] = config; + this.setCurrentActionLayerFeatureTool({ layer, index, action, component: QueryPolygonCsvAttributesComponent }); + } -/** - * @FIXME add description - * - * @param layer - */ -proto.addRemoveFilter = function(layer) { - CatalogLayersStoresRegistry.getLayerById(layer.id).toggleFilterToken(); -}; + /** @FIXME add description */ + if (features.length < 1 && undefined === downloadsactions) { + layer[type].active = !layer[type].active; + } -/** - * @FIXME add description - * - * @param layer - */ -proto.selectionFeaturesLayer = function(layer) { - const action = this.state.layersactions[layer.id].find(action => action.id === 'selection'); - const bool = Object.values(action.state.toggled).reduce((acculmulator, value) => acculmulator && value, true); - const _layer = layer.external ? layer : CatalogLayersStoresRegistry.getLayerById(layer.id); - layer.features.forEach((feature, index) => { - action.state.toggled[index] = !bool; - this._addRemoveSelectionFeature(_layer, feature, index, bool ? 'remove' : 'add'); - }) -}; + /** @FIXME add description */ + if (features.length < 1 && undefined === downloadsactions && layer[type].active) { + this.setLayerActionTool({ layer, component: QueryPolygonCsvAttributesComponent, config }); + } -/** - * @FIXME add description - * - * @param layer - * @param feature - * @param index - * @param force - */ -proto._addRemoveSelectionFeature = async function(layer, feature, index, force) { + /** @FIXME add description */ + if (features.length < 1 && undefined === downloadsactions && !layer[type].active){ + this.setLayerActionTool({ layer }); + } - /** - * An external layer (vector) added by add external layer tool - */ - if (layer.external && "undefined" !== typeof layer.external) { + /** @FIXME add description */ + if (features.length < 1 && undefined !== downloadsactions) { + this.setLayerActionTool({ layer, component: QueryPolygonCsvAttributesComponent, config }); + } - /** @FIXME add description */ - if ("undefined" === typeof layer.selection.features) { - layer.selection.features = {}; } + } - // Feature used in selection tool action - if (!layer.selection.features.find(selectionFeature => selectionFeature.getId() === feature.id)) { - const selectionFeature = createFeatureFromFeatureObject({ feature, id: feature.id }); - selectionFeature.__layerId = layer.id; - selectionFeature.selection = feature.selection; - layer.selection.features.push(selectionFeature); - } + /** + * @FIXME add description + */ + downloadGpx({ id: layerId } = {}, feature) { + CatalogLayersStoresRegistry + .getLayerById(layerId) + .getGpx({ fid: feature ? feature.attributes[G3W_FID] : null }) + .catch((err) => { GUI.notify.error(t("info.server_error")); }) + .finally(() => { this.layerMenu.loading.shp = false; this._hideMenu(); }) + } - /** @FIXME add description */ - if ( - ('add' === force && feature.selection.selected) || (force === 'remove') && - !feature.selection.selected - ) { - return; - } + /** + * @FIXME add description + */ + downloadXls({ id: layerId } = {}, feature) { + CatalogLayersStoresRegistry + .getLayerById(layerId) + .getXls({ fid: feature ? feature.attributes[G3W_FID] : null }) + .catch(err => { GUI.notify.error(t("info.server_error")); }) + .finally(() => { this.layerMenu.loading.shp = false; this._hideMenu(); }) + } - /** @FIXME add description */ - else { - feature.selection.selected = !feature.selection.selected; + /** + * + * @FIXME add description + * + * @param layer + * @param actionId + */ + listenClearSelection(layer, actionId) { + if (layer.external) { + layer.features + .forEach(feature => { + const selectionFeature = layer.selection.features.find(selectionFeature => feature.id === selectionFeature.getId()); + feature.selection = (selectionFeature) ? selectionFeature.selection : { selected: false }; + }); + } else { + const _layer = CatalogLayersStoresRegistry.getLayerById(layer.id); + const handler = () => { + layer.features.forEach((feature, index) => + this.state.layersactions[layer.id].find(action => action.id === actionId).state.toggled[index] = false + ); + }; + _layer.on('unselectionall', handler); + this.unlistenerlayeractionevents.push({ layer:_layer, event:'unselectionall', handler }); } + } - /** @FIXME add description */ - this.mapService.setSelectionFeatures( - feature.selection.selected ? 'add' : 'remove', - { feature: layer.selection.features.find(selectionFeature => feature.id === selectionFeature.getId()) } + /** + * @FIXME add description + * + * @param layer + */ + clearSelectionExtenalLayer(layer) { + layer.selection.active = false; + const action = ( + this.state.layersactions[layer.id] && + this.state.layersactions[layer.id].find(action => action.id === 'selection') ); + layer.selection.features + .forEach((feature, index) => { + if (feature.selection.selected) { + feature.selection.selected = false; + if (action) { + action.state.toggled[index] = false; + } + this.mapService.setSelectionFeatures('remove', { feature }); + } + }); + } - // Set selection layer active based on features selection selected properties - layer.selection.active = layer.selection.features.reduce((accumulator, feature) => accumulator || feature.selection.selected, false) - + /** + * @FIXME add description + */ + unlistenerEventsActions() { + this.unlistenerlayeractionevents.forEach(obj => obj.layer.off(obj.event, obj.handler)); + this.unlistenerlayeractionevents = []; } - + /** - * A project layer on TOC + * @FIXME add description + * + * @param layer */ - if (false === (layer.external && typeof "undefined" !== layer.external)) { + addRemoveFilter(layer) { + CatalogLayersStoresRegistry.getLayerById(layer.id).toggleFilterToken(); + } - const fid = feature ? feature.attributes[G3W_FID] : null; - const hasAlreadySelectioned = layer.getFilterActive() || layer.hasSelectionFid(fid); - - /** @FIXME add description */ - if (!hasAlreadySelectioned && feature && feature.geometry && !layer.getOlSelectionFeature(fid)) { - layer.addOlSelectionFeature({ id: fid, feature }); - } - - /** @FIXME add description */ - if (undefined === force) { - layer[hasAlreadySelectioned ? 'excludeSelectionFid': 'includeSelectionFid'](fid); - } + /** + * @FIXME add description + * + * @param layer + */ + selectionFeaturesLayer(layer) { + const action = this.state.layersactions[layer.id].find(action => action.id === 'selection'); + const bool = Object.values(action.state.toggled).reduce((acculmulator, value) => acculmulator && value, true); + const _layer = layer.external ? layer : CatalogLayersStoresRegistry.getLayerById(layer.id); + layer.features.forEach((feature, index) => { + action.state.toggled[index] = !bool; + this._addRemoveSelectionFeature(_layer, feature, index, bool ? 'remove' : 'add'); + }) + } - /** @FIXME add description */ - if (undefined !== force && !hasAlreadySelectioned && 'add' === force) { - await layer.includeSelectionFid(fid); - } + /** + * @FIXME add description + * + * @param layer + * @param feature + * @param index + * @param force + */ + async _addRemoveSelectionFeature(layer, feature, index, force) { - /** @FIXME add description */ - if (undefined !== force && hasAlreadySelectioned && 'remove' === force) { - await layer.excludeSelectionFid(fid); - } + /** + * An external layer (vector) added by add external layer tool + */ + if (layer.external && "undefined" !== typeof layer.external) { - /** @FIXME add description */ - if (layer.getFilterActive()) { + /** @FIXME add description */ + if ("undefined" === typeof layer.selection.features) { + layer.selection.features = {}; + } - const currentLayer = this.state.layers.find(_layer => _layer.id === layer.getId()); + // Feature used in selection tool action + if (!layer.selection.features.find(selectionFeature => selectionFeature.getId() === feature.id)) { + const selectionFeature = createFeatureFromFeatureObject({ feature, id: feature.id }); + selectionFeature.__layerId = layer.id; + selectionFeature.selection = feature.selection; + layer.selection.features.push(selectionFeature); + } /** @FIXME add description */ - if (layer.getSelectionFids().size > 0 && currentLayer) { - currentLayer.features.splice(index, 1); + if ( + ('add' === force && feature.selection.selected) || (force === 'remove') && + !feature.selection.selected + ) { + return; } - this.mapService.clearHighlightGeometry(); - /** @FIXME add description */ - if (1 === this.state.layers.length && !this.state.layers[0].features.length) { - this.state.layers.splice(0); + else { + feature.selection.selected = !feature.selection.selected; } + /** @FIXME add description */ + this.mapService.setSelectionFeatures( + feature.selection.selected ? 'add' : 'remove', + { feature: layer.selection.features.find(selectionFeature => feature.id === selectionFeature.getId()) } + ); + + // Set selection layer active based on features selection selected properties + layer.selection.active = layer.selection.features.reduce((accumulator, feature) => accumulator || feature.selection.selected, false) + } + + /** + * A project layer on TOC + */ + if (false === (layer.external && typeof "undefined" !== layer.external)) { - } + const fid = feature ? feature.attributes[G3W_FID] : null; + const hasAlreadySelectioned = layer.getFilterActive() || layer.hasSelectionFid(fid); + + /** @FIXME add description */ + if (!hasAlreadySelectioned && feature && feature.geometry && !layer.getOlSelectionFeature(fid)) { + layer.addOlSelectionFeature({ id: fid, feature }); + } + + /** @FIXME add description */ + if (undefined === force) { + layer[hasAlreadySelectioned ? 'excludeSelectionFid': 'includeSelectionFid'](fid); + } -}; + /** @FIXME add description */ + if (undefined !== force && !hasAlreadySelectioned && 'add' === force) { + await layer.includeSelectionFid(fid); + } -/** - * Initial check of selection active on layer - * - * @param layer - * @param feature - * @param index - * @param action - */ -proto.checkFeatureSelection = function({layer, feature, index, action}={}) { - if (layer.external) { - action.state.toggled[index] = feature.selection.selected; - } else if (feature) { - action.state.toggled[index] = ( - CatalogLayersStoresRegistry.getLayerById(layer.id).getFilterActive() || - CatalogLayersStoresRegistry.getLayerById(layer.id).hasSelectionFid(feature ? feature.attributes[G3W_FID]: null) - ); - } -}; + /** @FIXME add description */ + if (undefined !== force && hasAlreadySelectioned && 'remove' === force) { + await layer.excludeSelectionFid(fid); + } -/** - * @FIXME add description - * - * @param layer - * @param feature - * @param action - * @param index - */ -proto.addToSelection = function(layer, feature, action, index) { - const {external=false} = layer; - action.state.toggled[index] = !action.state.toggled[index]; - this._addRemoveSelectionFeature( - (external ? layer : CatalogLayersStoresRegistry.getLayerById(layer.id)), - feature, - index - ); -}; + /** @FIXME add description */ + if (layer.getFilterActive()) { -/** - * @FIXME add description - */ -proto.removeQueryResultLayerFromMap = function() { - this.resultsQueryLayer.getSource().clear(); - this.mapService.getMap().removeLayer(this.resultsQueryLayer) -}; + const currentLayer = this.state.layers.find(_layer => _layer.id === layer.getId()); -/** - * @FIXME add description - * - * @since 3.8.0 - */ -proto.addQueryResultLayerToMap = function(feature) { - this.resultsQueryLayer.getSource().addFeature(feature); - this.mapService.getMap().addLayer(this.resultsQueryLayer); -}; + /** @FIXME add description */ + if (layer.getSelectionFids().size > 0 && currentLayer) { + currentLayer.features.splice(index, 1); + } -/** - * Show layerQuery result on map - */ -proto.addQueryResultsLayerToMap = function({ feature }) { - this.removeQueryResultLayerFromMap(); - this.addQueryResultLayerToMap(feature); - this.mapService.setZIndexLayer({ layer: this.resultsQueryLayer }); // make sure that layer is on top of other map. -}; + this.mapService.clearHighlightGeometry(); -/** - * Show feature from coordinates - * - * @param coordinates - */ -proto.showCoordinates = function(coordinates) { - this.addQueryResultsLayerToMap({ feature: createFeatureFromCoordinates(coordinates) }); -}; + /** @FIXME add description */ + if (1 === this.state.layers.length && !this.state.layers[0].features.length) { + this.state.layers.splice(0); + } -/** - * Show BBox - * - * @param bbox - */ -proto.showBBOX = function(bbox) { - this.addQueryResultsLayerToMap({ feature: createFeatureFromBBOX(bbox) }); -}; + } + + } -/** - * Show Geometry - * - * @param geometry - */ -proto.showGeometry = function(geometry) { - if (geometry) { - this.addQueryResultsLayerToMap({ feature: createFeatureFromGeometry({ geometry }) }); } -}; -/** - * @FIXME add description - * - * @param layer - * @param feature - */ -proto.goToGeometry = function(layer, feature) { - if (!feature.geometry) { - return; - } - const handlerOptions = { - mapServiceMethod: this.isOneLayerResult() ? 'zoomToFeatures' : 'highlightGeometry', - firstParam: this.isOneLayerResult() ? [feature] : feature.geometry, - options: this.isOneLayerResult() ? {} : { layerId: layer.id, duration: 1500 } - }; - if (this._asyncFnc.goToGeometry.async) { - this._asyncFnc.todo = this.mapService[handlerOptions.mapServiceMethod].bind( - this.mapService, - handlerOptions.firstParam, - handlerOptions.options + /** + * Initial check of selection active on layer + * + * @param layer + * @param feature + * @param index + * @param action + */ + checkFeatureSelection({ + layer, + feature, + index, + action + } = {}) { + if (layer.external) { + action.state.toggled[index] = feature.selection.selected; + } else if (feature) { + action.state.toggled[index] = ( + CatalogLayersStoresRegistry.getLayerById(layer.id).getFilterActive() || + CatalogLayersStoresRegistry.getLayerById(layer.id).hasSelectionFid(feature ? feature.attributes[G3W_FID]: null) + ); + } + } + + /** + * @FIXME add description + * + * @param layer + * @param feature + * @param action + * @param index + */ + addToSelection(layer, feature, action, index) { + const { external = false } = layer; + action.state.toggled[index] = !action.state.toggled[index]; + this._addRemoveSelectionFeature( + (external ? layer : CatalogLayersStoresRegistry.getLayerById(layer.id)), + feature, + index ); - } else { - setTimeout(() => this.mapService[handlerOptions.mapServiceMethod]( - handlerOptions.firstParam, - handlerOptions.options - )); } -}; -/** - * Save layer result - */ -proto.saveLayerResult = function({layer, type='csv'}={}) { - this.downloadFeatures(type, layer, layer.features); -}; + /** + * @FIXME add description + */ + removeQueryResultLayerFromMap() { + this.resultsQueryLayer.getSource().clear(); + this.mapService.getMap().removeLayer(this.resultsQueryLayer) + } -/** - * @FIXME add description - * - * @param layer - * @param feature - */ -proto.highlightGeometry = function(layer, feature) { - if (feature.geometry) { - this.mapService.highlightGeometry( - feature.geometry, - { layerId: layer.id, zoom: false, duration: Infinity } - ); + /** + * @FIXME add description + * + * @since 3.8.0 + */ + addQueryResultLayerToMap(feature) { + this.resultsQueryLayer.getSource().addFeature(feature); + this.mapService.getMap().addLayer(this.resultsQueryLayer); } -}; -/** - * @FIXME add description - * - * @param layer - */ -proto.clearHighlightGeometry = function(layer) { - this.mapService.clearHighlightGeometry(); - if (this.isOneLayerResult()) { - this.highlightFeaturesPermanently(layer); + /** + * Show layerQuery result on map + */ + addQueryResultsLayerToMap({ feature }) { + this.removeQueryResultLayerFromMap(); + this.addQueryResultLayerToMap(feature); + this.mapService.setZIndexLayer({ layer: this.resultsQueryLayer }); // make sure that layer is on top of other map. } -}; -/** - * Handle show Relation on result - * - * - layerId = current layer father id - * - feature = current feature father id - * - * @param relationId - */ -proto.showRelation = function({relation, layerId, feature}={}) { - const projectRelation = this._project.getRelationById(relation.name); - GUI.pushContent({ - content: new RelationsPage({ - currentview: 'relation', - relations: [projectRelation], - chartRelationIds: this.findPlotId(projectRelation.referencingLayer) ? [projectRelation.referencingLayer] : [], - nmRelation: this._project.getRelationById(relation.nmRelationId), - feature, - layer: { id: layerId } - }), - crumb: { - title: projectRelation.name - }, - title: projectRelation.name, - closable: false - }) -}; + /** + * Show feature from coordinates + * + * @param coordinates + */ + showCoordinates(coordinates) { + this.addQueryResultsLayerToMap({ feature: createFeatureFromCoordinates(coordinates) }); + } -/** - * @FIXME add description - * - * @param layer - * @param feature - * @param action - */ -proto.showQueryRelations = function(layer, feature, action) { - GUI.changeCurrentContentOptions({ crumb: { title: layer.title } }); - GUI.pushContent({ - content: new RelationsPage({ - relations: action.relations, - chartRelationIds: action.chartRelationIds, - feature, - layer - }), - backonclose: true, - title: LIST_OF_RELATIONS_TITLE, - id: LIST_OF_RELATIONS_ID, - crumb: { + /** + * Show BBox + * + * @param bbox + */ + showBBOX(bbox) { + this.addQueryResultsLayerToMap({ feature: createFeatureFromBBOX(bbox) }); + } + + /** + * Show Geometry + * + * @param geometry + */ + showGeometry(geometry) { + if (geometry) { + this.addQueryResultsLayerToMap({ feature: createFeatureFromGeometry({ geometry }) }); + } + } + + /** + * @FIXME add description + * + * @param layer + * @param feature + */ + goToGeometry(layer, feature) { + if (!feature.geometry) { + return; + } + const handlerOptions = { + mapServiceMethod: this.isOneLayerResult() ? 'zoomToFeatures' : 'highlightGeometry', + firstParam: this.isOneLayerResult() ? [feature] : feature.geometry, + options: this.isOneLayerResult() ? {} : { layerId: layer.id, duration: 1500 } + }; + if (this._asyncFnc.goToGeometry.async) { + this._asyncFnc.todo = this.mapService[handlerOptions.mapServiceMethod].bind( + this.mapService, + handlerOptions.firstParam, + handlerOptions.options + ); + } else { + setTimeout(() => this.mapService[handlerOptions.mapServiceMethod]( + handlerOptions.firstParam, + handlerOptions.options + )); + } + } + + /** + * Save layer result + */ + saveLayerResult({ + layer, + type = 'csv' + } = {}) { + this.downloadFeatures(type, layer, layer.features); + } + + /** + * @FIXME add description + * + * @param layer + * @param feature + */ + highlightGeometry(layer, feature) { + if (feature.geometry) { + this.mapService.highlightGeometry( + feature.geometry, + { layerId: layer.id, zoom: false, duration: Infinity } + ); + } + } + + /** + * @FIXME add description + * + * @param layer + */ + clearHighlightGeometry(layer) { + this.mapService.clearHighlightGeometry(); + if (this.isOneLayerResult()) { + this.highlightFeaturesPermanently(layer); + } + } + + /** + * Handle show Relation on result + * + * - layerId = current layer father id + * - feature = current feature father id + * + * @param relationId + */ + showRelation({ + relation, + layerId, + feature + } = {}) { + const projectRelation = this._project.getRelationById(relation.name); + GUI.pushContent({ + content: new RelationsPage({ + currentview: 'relation', + relations: [projectRelation], + chartRelationIds: this.findPlotId(projectRelation.referencingLayer) ? [projectRelation.referencingLayer] : [], + nmRelation: this._project.getRelationById(relation.nmRelationId), + layer: { id: layerId }, + feature, + }), + crumb: { + title: projectRelation.name + }, + title: projectRelation.name, + closable: false + }) + }; + + /** + * @FIXME add description + * + * @param layer + * @param feature + * @param action + */ + showQueryRelations(layer, feature, action) { + GUI.changeCurrentContentOptions({ crumb: { title: layer.title } }); + GUI.pushContent({ + content: new RelationsPage({ + relations: action.relations, + chartRelationIds: action.chartRelationIds, + feature, + layer + }), + backonclose: true, title: LIST_OF_RELATIONS_TITLE, - trigger: null - }, - closable: false - }); -}; + id: LIST_OF_RELATIONS_ID, + crumb: { + title: LIST_OF_RELATIONS_TITLE, + trigger: null + }, + closable: false + }); + } -/** - * Get layer from current state.layers showed on result - * - * @since 3.8.0 - */ -proto._getLayer = function(layerId) { - return this.state.layers.find(l => l.id === layerId); -}; + /** + * Get layer from current state.layers showed on result + * + * @since 3.8.0 + */ + _getLayer(layerId) { + return this.state.layers.find(l => l.id === layerId); + } -/** - * Get external layer from current state.layers showed on result - * - * @since 3.8.0 - */ -proto._getExternalLayer = function(layerId) { - return (this._getLayer(layerId) || {}).external; -}; + /** + * Get external layer from current state.layers showed on result + * + * @since 3.8.0 + */ + _getExternalLayer(layerId) { + return (this._getLayer(layerId) || {}).external; + } -/** - * Get ids of the selected features - * - * @since 3.8.0 - */ -proto._getFeaturesIds = function(features, external) { - return features.map(f => external ? f.id : f.attributes[G3W_FID]); -} + /** + * Get ids of the selected features + * + * @since 3.8.0 + */ + _getFeaturesIds(features, external) { + return features.map(f => external ? f.id : f.attributes[G3W_FID]); + } -/** - * Extract features from layer object - * - * @since 3.8.0 - */ -proto._getLayerFeatures = function(layer) { - return layer.features || []; -}; + /** + * Extract features from layer object + * + * @since 3.8.0 + */ + _getLayerFeatures(layer) { + return layer.features || []; + } -/** - * Loop and filter the features that we need to remove - * - * @since 3.8.0 - */ -proto._featuresToRemove = function(features, external) { - const features_ids = this._getFeaturesIds(features, external); // get id of the features - return features.filter(feature => (-1 === features_ids.indexOf(external ? feature.id : feature.attributes[G3W_FID]))); -}; + /** + * Loop and filter the features that we need to remove + * + * @since 3.8.0 + */ + _featuresToRemove(features, external) { + const features_ids = this._getFeaturesIds(features, external); // get id of the features + return features.filter(feature => (-1 === features_ids.indexOf(external ? feature.id : feature.attributes[G3W_FID]))); + } -/** - * Filter features to add - * - * @since 3.8.0 - */ -proto._featuresToAdd = function(features, external) { - const features_ids = this._getFeaturesIds(features, external); - return features.filter(feature => (-1 !== features_ids.indexOf(external ? feature.id : feature.attributes[G3W_FID]))); -}; + /** + * Filter features to add + * + * @since 3.8.0 + */ + _featuresToAdd(features, external) { + const features_ids = this._getFeaturesIds(features, external); + return features.filter(feature => (-1 !== features_ids.indexOf(external ? feature.id : feature.attributes[G3W_FID]))); + } -/** - * @since 3.8.0 - */ -proto._toggleLayerFeatureBox = function(layer, feature, collapsed) { - const boxId = this.getBoxId(layer, feature); - if (boxId && this.state.layersFeaturesBoxes[boxId]) { - setTimeout(() => this.state.layersFeaturesBoxes[boxId].collapsed = collapsed); // due to vue reactivity, wait a little bit before update layers + /** + * @since 3.8.0 + */ + _toggleLayerFeatureBox(layer, feature, collapsed) { + const boxId = this.getBoxId(layer, feature); + if (boxId && this.state.layersFeaturesBoxes[boxId]) { + setTimeout(() => this.state.layersFeaturesBoxes[boxId].collapsed = collapsed); // due to vue reactivity, wait a little bit before update layers + } } -}; -/** - * @since 3.8.0 - */ -proto._removeLayerFeatureBox = function(layer, feature_to_delete) { - setTimeout(() => delete this.state.layersFeaturesBoxes[this.getBoxId(layer, feature_to_delete)]); -}; + /** + * @since 3.8.0 + */ + _removeLayerFeatureBox(layer, feature_to_delete) { + setTimeout(() => delete this.state.layersFeaturesBoxes[this.getBoxId(layer, feature_to_delete)]); + } -/** - * @since 3.8.0 - */ -proto._setActionGoToGeometry = function(layer) { - this.state.layersactions[layer.id] - .push({ - id: 'gotogeometry', - download: false, - mouseover: true, - class: GUI.getFontClass('marker'), - hint: 'sdk.mapcontrols.query.actions.zoom_to_feature.hint', - cbk: throttle(this.goToGeometry.bind(this)) - }); -}; + /** + * @since 3.8.0 + */ + _setActionGoToGeometry(layer) { + this.state.layersactions[layer.id] + .push({ + id: 'gotogeometry', + download: false, + mouseover: true, + class: GUI.getFontClass('marker'), + hint: 'sdk.mapcontrols.query.actions.zoom_to_feature.hint', + cbk: throttle(this.goToGeometry.bind(this)) + }); + } -/** - * @since 3.8.0 - */ -proto._setActionShowQueryAndPlotsRelations = function(layer) { - const relations = this._relations[layer.id].filter(relation => 'MANY' === relation.type); - const chartRelationIds = []; - - relations.forEach(relation => { - const id = this.plotLayerIds.find(id => id === relation.referencingLayer); - if (id) { - chartRelationIds.push(id); + /** + * @since 3.8.0 + */ + _setActionShowQueryAndPlotsRelations(layer) { + const relations = this._relations[layer.id].filter(relation => 'MANY' === relation.type); + const chartRelationIds = []; + + relations.forEach(relation => { + const id = this.plotLayerIds.find(id => id === relation.referencingLayer); + if (id) { + chartRelationIds.push(id); + } + }); + + /** @FIXME add description */ + if (relations.length) { + this.state.layersactions[layer.id] + .push({ + id: 'show-query-relations', + download: false, + class: GUI.getFontClass('relation'), + hint: 'sdk.mapcontrols.query.actions.relations.hint', + cbk: this.showQueryRelations, + relations, + chartRelationIds + }); + } + + /** @FIXME add description */ + if (chartRelationIds.length) { + this.state.layersactions[layer.id] + .push({ + id: 'show-plots-relations', + download: false, + opened: true, + class: GUI.getFontClass('chart'), + state: this.createActionState({ layer }), + hint: 'sdk.mapcontrols.query.actions.relations_charts.hint', + cbk: throttle(this.showRelationsChart.bind(this, chartRelationIds)) + }); } - }); + } + + /** + * @since 3.8.0 + */ + _setActionPrintAtlas(layer) { + this.state.layersactions[layer.id] + .push({ + id: `printatlas`, + download: true, + class: GUI.getFontClass('print'), + hint: `sdk.tooltips.atlas`, + cbk: this.printAtlas.bind(this) + }); + } - /** @FIXME add description */ - if (relations.length) { + /** + * @since 3.8.0 + */ + _setActionDownloadFeature(layer) { + const [format] = layer.downloads; // NB: format == layer.downloads[0] + const cbk = this.downloadFeatures.bind(this, format); + layer[format] = Vue.observable({ active: false }); this.state.layersactions[layer.id] .push({ - id: 'show-query-relations', + id: `download_${format}_feature`, + download: true, + state, + class: GUI.getFontClass('download'), + hint: `sdk.tooltips.download_${format}`, + cbk: (layer, feature, action, index) => { + action.state.toggled[index] = !action.state.toggled[index]; + if (action.state.toggled[index]) { + cbk(layer, feature, action, index); + } else { + this.setCurrentActionLayerFeatureTool({ index, action, layer }) + } + } + }); + } + + /** + * @since 3.8.0 + */ + _setActionMultiDownloadFeature(layer) { + const state = this.createActionState({ layer }); + + const downloads = []; + + layer.downloads + .forEach(format => { + downloads.push({ + id: `download_${format}_feature`, + download: true, + format, + class: GUI.getFontClass(format), + hint: `sdk.tooltips.download_${format}`, + cbk: (layer, feature, action, index) => { + // un-toggle downloads action + this.downloadFeatures(format, layer, feature, action, index); + if ('polygon' !== this.state.query.type) { + const downloadsaction = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); + downloadsaction.cbk(layer, feature, downloadsaction, index); + } + } + }); + }); + + // set actionstools configs + this.state.actiontools[DownloadFormats.name] = this.state.actiontools[DownloadFormats.name] || {}; + this.state.actiontools[DownloadFormats.name][layer.id] = { downloads }; + // check if has download actions + this.state.layersactions[layer.id] + .push({ + id: `downloads`, + download: true, + class: GUI.getFontClass('download'), + state, + toggleable: true, + hint: `Downloads`, + change({features}) { + features + .forEach((feature, index) => { + if (undefined === this.state.toggled[index]) { + VM.$set(this.state.toggled, index, false); + } else { + this.state.toggled[index] = false; + } + }); + }, + cbk: (layer, feature, action, index) => { + action.state.toggled[index] = !action.state.toggled[index]; + this.setCurrentActionLayerFeatureTool({ layer, index, action, component: (action.state.toggled[index] ? DownloadFormats : null) }); + } + }); + } + + /** + * @since 3.8.0 + */ + _setActionRemoveFeatureFromResult(layer) { + this.state.layersactions[layer.id] + .push({ + id: 'removefeaturefromresult', download: false, - class: GUI.getFontClass('relation'), - hint: 'sdk.mapcontrols.query.actions.relations.hint', - cbk: this.showQueryRelations, - relations, - chartRelationIds + mouseover: true, + class: GUI.getFontClass('minus-square'), + style: { + color: 'red' + }, + hint: 'sdk.mapcontrols.query.actions.remove_feature_from_results.hint', + cbk: this.removeFeatureLayerFromResult.bind(this) }); } - /** @FIXME add description */ - if (chartRelationIds.length) { + /** + * @since 3.8.0 + */ + _setActionSelection(layer) { this.state.layersactions[layer.id] .push({ - id: 'show-plots-relations', + id: 'selection', download: false, - opened: true, - class: GUI.getFontClass('chart'), + class: GUI.getFontClass('success'), + hint: 'sdk.mapcontrols.query.actions.add_selection.hint', state: this.createActionState({ layer }), - hint: 'sdk.mapcontrols.query.actions.relations_charts.hint', - cbk: throttle(this.showRelationsChart.bind(this, chartRelationIds)) + init: ({feature, index, action}={}) => { + if("undefined" !== typeof layer.selection.active) { + this.checkFeatureSelection({ layer, index, feature, action }) + } + }, + cbk: throttle(this.addToSelection.bind(this)) }); + + // In case of external layer don't listen to `selection` event + this.listenClearSelection(layer, 'selection'); } -}; -/** - * @since 3.8.0 - */ -proto._setActionPrintAtlas = function(layer) { - this.state.layersactions[layer.id] - .push({ - id: `printatlas`, - download: true, - class: GUI.getFontClass('print'), - hint: `sdk.tooltips.atlas`, - cbk: this.printAtlas.bind(this) - }); -}; + /** + * @since 3.8.0 + */ + _setActionLinkZoomToFid(layer) { + this.state.layersactions[layer.id] + .push({ + id: 'link_zoom_to_fid', + download: false, + class: GUI.getFontClass('link'), + hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint', + hint_change: { + hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint_change', + duration: 1000 + }, + cbk: this.copyZoomToFidUrl.bind(this) + }); + } + + /** + * @since 3.8.0 + */ + _setActionEditing(layer) { + this.state.layersactions[layer.id] + .push({ + id: 'editing', + class: GUI.getFontClass('pencil'), + hint: 'Editing', + cbk: (layer, feature) => { this.editFeature({ layer, feature }) } + }); + } + +} /** - * @since 3.8.0 + * @deprecated since 3.8.0 Will be deleted in 4.x. Use QueryResultsService::updateLayerResultFeatures(layer) instead */ -proto._setActionDownloadFeature = function(layer) { - const [format] = layer.downloads; // NB: format == layer.downloads[0] - const cbk = this.downloadFeatures.bind(this, format); - layer[format] = Vue.observable({ active: false }); - this.state.layersactions[layer.id] - .push({ - id: `download_${format}_feature`, - download: true, - state, - class: GUI.getFontClass('download'), - hint: `sdk.tooltips.download_${format}`, - cbk: (layer, feature, action, index) => { - action.state.toggled[index] = !action.state.toggled[index]; - if (action.state.toggled[index]) { - cbk(layer, feature, action, index); - } else { - this.setCurrentActionLayerFeatureTool({ index, action, layer }) - } - } - }); -}; +QueryResultsService.prototype.addRemoveFeaturesToLayerResult = deprecate(QueryResultsService.prototype.updateLayerResultFeatures, '[G3W-CLIENT] QueryResultsService::addRemoveFeaturesToLayerResult(layer) is deprecated'); /** - * @since 3.8.0 + * Core methods used from other classes to react before or after its call */ -proto._setActionMultiDownloadFeature = function(layer) { - const state = this.createActionState({ layer }); +QueryResultsService.prototype.setters = { - const downloads = []; + /** + * Hook method called when response is handled by Data Provider + * + * @param queryResponse + * @param {{ add: boolean }} options `add` is used to know if is a new query request or add/remove query request + */ + setQueryResponse(queryResponse, options = { add: false }) { - layer.downloads - .forEach(format => { - downloads.push({ - id: `download_${format}_feature`, - download: true, - format, - class: GUI.getFontClass(format), - hint: `sdk.tooltips.download_${format}`, - cbk: (layer, feature, action, index) => { - // un-toggle downloads action - this.downloadFeatures(format, layer, feature, action, index); - if ('polygon' !== this.state.query.type) { - const downloadsaction = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); - downloadsaction.cbk(layer, feature, downloadsaction, index); - } - } - }); - }); + // set mandatory queryResponse fields + if (!queryResponse.data) queryResponse.data = []; + if (!queryResponse.query) queryResponse.query = { external: { add: false, filter: { SELECTED: false } } }; + if (!queryResponse.query.external) queryResponse.query.external = { add: false, filter: {SELECTED: false }}; - // set actionstools configs - this.state.actiontools[DownloadFormats.name] = this.state.actiontools[DownloadFormats.name] || {}; - this.state.actiontools[DownloadFormats.name][layer.id] = { downloads }; - // check if has download actions - this.state.layersactions[layer.id] - .push({ - id: `downloads`, - download: true, - class: GUI.getFontClass('download'), - state, - toggleable: true, - hint: `Downloads`, - change({features}) { - features - .forEach((feature, index) => { - if (undefined === this.state.toggled[index]) { - VM.$set(this.state.toggled, index, false); - } else { - this.state.toggled[index] = false; - } - }); - }, - cbk: (layer, feature, action, index) => { - action.state.toggled[index] = !action.state.toggled[index]; - this.setCurrentActionLayerFeatureTool({ layer, index, action, component: (action.state.toggled[index] ? DownloadFormats : null) }); + // whether add response to current results using addLayerFeaturesToResultsAction + if (!options.add) { + + // in case of new request results reset the query otherwise maintain the previous request + this.clearState(); + this.state.query = queryResponse.query; + this.state.type = queryResponse.type; + + // if true add external layers to response + if (true === queryResponse.query.external.add) { + this._addVectorLayersDataToQueryResponse(queryResponse); } - }); -}; -/** - * @since 3.8.0 - */ -proto._setActionRemoveFeatureFromResult = function(layer) { - this.state.layersactions[layer.id] - .push({ - id: 'removefeaturefromresult', - download: false, - mouseover: true, - class: GUI.getFontClass('minus-square'), - style: { - color: 'red' - }, - hint: 'sdk.mapcontrols.query.actions.remove_feature_from_results.hint', - cbk: this.removeFeatureLayerFromResult.bind(this) - }); -}; + switch (this.state.query.type) { + case 'coordinates': this.showCoordinates(this.state.query.coordinates); break; + case 'bbox': this.showBBOX(this.state.query.bbox); break; + case 'polygon': this.showGeometry(this.state.query.geometry); break; + } + + } -/** - * @since 3.8.0 - */ -proto._setActionSelection = function(layer) { - this.state.layersactions[layer.id] - .push({ - id: 'selection', - download: false, - class: GUI.getFontClass('success'), - hint: 'sdk.mapcontrols.query.actions.add_selection.hint', - state: this.createActionState({ layer }), - init: ({feature, index, action}={}) => { - if("undefined" !== typeof layer.selection.active) { - this.checkFeatureSelection({ layer, index, feature, action }) - } - }, - cbk: throttle(this.addToSelection.bind(this)) - }); - - // In case of external layer don't listen to `selection` event - this.listenClearSelection(layer, 'selection'); -}; + this.setLayersData(this._digestFeaturesForLayers(queryResponse.data), options); -/** - * @since 3.8.0 - */ -proto._setActionLinkZoomToFid = function(layer) { - this.state.layersactions[layer.id] - .push({ - id: 'link_zoom_to_fid', - download: false, - class: GUI.getFontClass('link'), - hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint', - hint_change: { - hint: 'sdk.mapcontrols.query.actions.copy_zoom_to_fid_url.hint_change', - duration: 1000 - }, - cbk: this.copyZoomToFidUrl.bind(this) - }); -}; + }, -/** - * @since 3.8.0 - */ -proto._setActionEditing = function(layer) { - this.state.layersactions[layer.id] - .push({ - id: 'editing', - class: GUI.getFontClass('pencil'), - hint: 'Editing', - cbk: (layer, feature) => { this.editFeature({ layer, feature }) } - }); -}; + /** + * Setter method called when adding layer and feature for response + * + * @param layers + * @param options + */ + setLayersData(layers, options = { add: false }) { + if (!options.add) { + // set the right order of result layers based on TOC + this._currentLayerIds = layers.map(layer => layer.id); + this._orderResponseByProjectLayers(layers); + } + // get features from add pick layer in case of a new request query + layers.forEach(layer => { options.add ? this.updateLayerResultFeatures(layer) : this.state.layers.push(layer); }); + this.setActionsForLayers(layers, { add: options.add }); + this.state.changed = true; + }, -/** - * @deprecated since 3.8.0 Will be deleted in 4.x. Use QueryResultsService::updateLayerResultFeatures(layer) instead - */ -proto.addRemoveFeaturesToLayerResult = deprecate(proto.updateLayerResultFeatures, '[G3W-CLIENT] QueryResultsService::addRemoveFeaturesToLayerResult(layer) is deprecated'); + /** + * Method used to add custom component + * + * @param component + */ + addComponent(component) { + this._addComponent(component) + }, + /** + * @FIXME add description + * + * @param actions + * @param layers + */ + addActionsForLayers(actions, layers) {}, -module.exports = QueryResultsService; + /** + * @FIXME add description + * + * @param element + */ + postRender(element) {}, + + /** + * @FIXME add description + */ + closeComponent() {}, + + /** + * @FIXME add description + * + * @param layer + */ + changeLayerResult(layer) { + this._changeLayerResult(layer); + }, + + /** + * @FIXME add description + */ + activeMapInteraction() {}, + + /** + * Setter method related to relation table + */ + editFeature({layer, feature}={}) {}, + + /** + * Setter method called when opening/closing feature info data content. + * + * @param open + * @param layer + * @param feature + * @param container + */ + openCloseFeatureResult({open, layer, feature, container}={}) {} + +}; +module.exports = QueryResultsService; \ No newline at end of file From 8a33ba47337a54d314328f8e64e8d5f23c27414c Mon Sep 17 00:00:00 2001 From: Raruto Date: Thu, 17 Aug 2023 14:53:54 +0200 Subject: [PATCH 11/48] update @since tags --- .../gui/queryresults/queryresultsservice.js | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index a462f78a9..51278730e 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -1003,7 +1003,7 @@ class QueryResultsService extends G3WObject { * * @param featuresForLayer * - * @since 3.8.0 + * @since 3.9.0 */ _handleFeatureForLayer(featuresForLayer) { const layerObj = { @@ -1953,7 +1953,7 @@ class QueryResultsService extends G3WObject { /** * @FIXME add description * - * @since 3.8.0 + * @since 3.9.0 */ addQueryResultLayerToMap(feature) { this.resultsQueryLayer.getSource().addFeature(feature); @@ -2125,7 +2125,7 @@ class QueryResultsService extends G3WObject { /** * Get layer from current state.layers showed on result * - * @since 3.8.0 + * @since 3.9.0 */ _getLayer(layerId) { return this.state.layers.find(l => l.id === layerId); @@ -2134,7 +2134,7 @@ class QueryResultsService extends G3WObject { /** * Get external layer from current state.layers showed on result * - * @since 3.8.0 + * @since 3.9.0 */ _getExternalLayer(layerId) { return (this._getLayer(layerId) || {}).external; @@ -2143,7 +2143,7 @@ class QueryResultsService extends G3WObject { /** * Get ids of the selected features * - * @since 3.8.0 + * @since 3.9.0 */ _getFeaturesIds(features, external) { return features.map(f => external ? f.id : f.attributes[G3W_FID]); @@ -2152,7 +2152,7 @@ class QueryResultsService extends G3WObject { /** * Extract features from layer object * - * @since 3.8.0 + * @since 3.9.0 */ _getLayerFeatures(layer) { return layer.features || []; @@ -2161,7 +2161,7 @@ class QueryResultsService extends G3WObject { /** * Loop and filter the features that we need to remove * - * @since 3.8.0 + * @since 3.9.0 */ _featuresToRemove(features, external) { const features_ids = this._getFeaturesIds(features, external); // get id of the features @@ -2171,7 +2171,7 @@ class QueryResultsService extends G3WObject { /** * Filter features to add * - * @since 3.8.0 + * @since 3.9.0 */ _featuresToAdd(features, external) { const features_ids = this._getFeaturesIds(features, external); @@ -2179,7 +2179,7 @@ class QueryResultsService extends G3WObject { } /** - * @since 3.8.0 + * @since 3.9.0 */ _toggleLayerFeatureBox(layer, feature, collapsed) { const boxId = this.getBoxId(layer, feature); @@ -2189,14 +2189,14 @@ class QueryResultsService extends G3WObject { } /** - * @since 3.8.0 + * @since 3.9.0 */ _removeLayerFeatureBox(layer, feature_to_delete) { setTimeout(() => delete this.state.layersFeaturesBoxes[this.getBoxId(layer, feature_to_delete)]); } /** - * @since 3.8.0 + * @since 3.9.0 */ _setActionGoToGeometry(layer) { this.state.layersactions[layer.id] @@ -2211,7 +2211,7 @@ class QueryResultsService extends G3WObject { } /** - * @since 3.8.0 + * @since 3.9.0 */ _setActionShowQueryAndPlotsRelations(layer) { const relations = this._relations[layer.id].filter(relation => 'MANY' === relation.type); @@ -2254,7 +2254,7 @@ class QueryResultsService extends G3WObject { } /** - * @since 3.8.0 + * @since 3.9.0 */ _setActionPrintAtlas(layer) { this.state.layersactions[layer.id] @@ -2268,7 +2268,7 @@ class QueryResultsService extends G3WObject { } /** - * @since 3.8.0 + * @since 3.9.0 */ _setActionDownloadFeature(layer) { const [format] = layer.downloads; // NB: format == layer.downloads[0] @@ -2293,7 +2293,7 @@ class QueryResultsService extends G3WObject { } /** - * @since 3.8.0 + * @since 3.9.0 */ _setActionMultiDownloadFeature(layer) { const state = this.createActionState({ layer }); @@ -2349,7 +2349,7 @@ class QueryResultsService extends G3WObject { } /** - * @since 3.8.0 + * @since 3.9.0 */ _setActionRemoveFeatureFromResult(layer) { this.state.layersactions[layer.id] @@ -2367,7 +2367,7 @@ class QueryResultsService extends G3WObject { } /** - * @since 3.8.0 + * @since 3.9.0 */ _setActionSelection(layer) { this.state.layersactions[layer.id] @@ -2390,7 +2390,7 @@ class QueryResultsService extends G3WObject { } /** - * @since 3.8.0 + * @since 3.9.0 */ _setActionLinkZoomToFid(layer) { this.state.layersactions[layer.id] @@ -2408,7 +2408,7 @@ class QueryResultsService extends G3WObject { } /** - * @since 3.8.0 + * @since 3.9.0 */ _setActionEditing(layer) { this.state.layersactions[layer.id] From 2ea3e0f87bcca978dd2a7c5fac5c29023173d21a Mon Sep 17 00:00:00 2001 From: Raruto Date: Thu, 17 Aug 2023 15:02:27 +0200 Subject: [PATCH 12/48] update @param tag --- .../gui/queryresults/queryresultsservice.js | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 51278730e..00c7301d1 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -318,11 +318,11 @@ class QueryResultsService extends G3WObject { * Register for plugin or other component of application to add * custom component on result for each layer feature or layer * - * @param id unique id identification - * @param layerId Layer id of layer - * @param component custom component - * @param type feature or layer - * @param position + * @param opts.id unique id identification + * @param opts.layerId Layer id of layer + * @param opts.component custom component + * @param opts.type feature or layer + * @param opts.position */ registerCustomComponent({ id = getUniqueDomId(), @@ -344,9 +344,9 @@ class QueryResultsService extends G3WObject { /** * Check position * - * @param id - * @param layerId - * @param type + * @param opts.id + * @param opts.layerId + * @param opts.type */ unRegisterCustomComponent({ id, @@ -591,8 +591,8 @@ class QueryResultsService extends G3WObject { /** * Get action referred to layer getting the action id * - * @param layer layer linked to action - * @param id action id + * @param opts.layer layer linked to action + * @param opts.id action id */ getActionLayerById({ layer, @@ -604,9 +604,9 @@ class QueryResultsService extends G3WObject { /** * Set current layer action tool in feature * - * @param layer current layer - * @param index feature index - * @param value component value or null + * @param opts.layer current layer + * @param opts.index feature index + * @param opts.value component value or null */ setCurrentActionLayerFeatureTool({ layer, @@ -749,7 +749,7 @@ class QueryResultsService extends G3WObject { /** * @FIXME add description * - * @param toggle boolean If true toggle true the mapcontrol + * @param opts.toggle boolean If true toggle true the mapcontrol */ removeAddFeaturesLayerResultInteraction({ toggle = false @@ -1903,10 +1903,10 @@ class QueryResultsService extends G3WObject { /** * Initial check of selection active on layer * - * @param layer - * @param feature - * @param index - * @param action + * @param opts.layer + * @param opts.feature + * @param opts.index + * @param opts.action */ checkFeatureSelection({ layer, @@ -2067,10 +2067,9 @@ class QueryResultsService extends G3WObject { /** * Handle show Relation on result * - * - layerId = current layer father id - * - feature = current feature father id - * - * @param relationId + * @param opts.relation + * @param opts.layerId current layer father id + * @param opts.feature current feature father id */ showRelation({ relation, @@ -2539,10 +2538,10 @@ QueryResultsService.prototype.setters = { /** * Setter method called when opening/closing feature info data content. * - * @param open - * @param layer - * @param feature - * @param container + * @param opts.open + * @param opts.layer + * @param opts.feature + * @param opts.container */ openCloseFeatureResult({open, layer, feature, container}={}) {} From c984762b0e852f1e1284316bc575da546dba12a1 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Mon, 21 Aug 2023 08:55:42 +0200 Subject: [PATCH 13/48] :bug: Fix bugs due refactoring --- .../gui/queryresults/queryresultsservice.js | 384 +++++++++--------- 1 file changed, 188 insertions(+), 196 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index ed1062483..7459955c6 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -52,6 +52,11 @@ class QueryResultsService extends G3WObject { super(); + /** + * Service used to work with atlas (print functionality) action tool + */ + this.printService = new PrintService(); + /** * @FIXME add description */ @@ -236,11 +241,6 @@ class QueryResultsService extends G3WObject { } }); - /** - * Service used to work with atlas (print functionality) action tool - */ - this.printService = new PrintService(); - /** * @deprecated since 3.8 * It used to register change project from Change map button @@ -283,34 +283,6 @@ class QueryResultsService extends G3WObject { this._asyncFnc.goToGeometry.async = true; } }); - /** - * @FIXME add description - */ - this._addVectorLayersDataToQueryResponse(); - - /** - * @FIXME add description - */ - this._asyncFnc = { - todo: noop, - zoomToLayerFeaturesExtent: { - async: false - }, - goToGeometry: { - async: false - } - }; - - /** - * @FIXME add description - */ - GUI.onbefore('setContent', (options)=> { - this.mapService = this.mapService || ApplicationService.getApplicationService('map'); - if (100 === options.perc && GUI.isMobile()) { - this._asyncFnc.zoomToLayerFeaturesExtent.async = true; - this._asyncFnc.goToGeometry.async = true; - } - }); } @@ -436,41 +408,45 @@ class QueryResultsService extends G3WObject { * Based on layer response check if features layer need to * be added or removed to current `state.layers` results * - * @param {Array} layer + * @param responseLayer layer structure coming from request * * @since 3.8.0 */ updateLayerResultFeatures(responseLayer) { - const layer = this._getLayer(responseLayer.id), // get layer from current `state.layers` showed on result - features = this._getLayerFeatures(responseLayer), // extract features from layer object - external = this._getExternalLayer(responseLayer.id); // get id of external layer or not (`external` is a layer added by mapcontrol addexternlayer) + const layer = this._getLayer(responseLayer.id), // get layer from current `state.layers` showed on result + features = this._getLayerFeatures(layer), // extract features from layer object + responseFeatures = this._getLayerFeatures(responseLayer), // extract features from responseLayer object + external = this._getExternalLayer(responseLayer.id); // get id of external layer or not (`external` is a layer added by mapcontrol addexternlayer) if (layer && features.length) { - const _featuresToAdd = this._featuresToAdd(features, external); // filter the features that we had to add - const _featuresToRemove = this._featuresToRemove(features, external); // filter the features that we had to remove (because they are already loaded in `state.layers`) - - /** - * @TODO check if the first loop `features.forEach` is redundant, - * it can be replaced by `_featuresToAdd.forEach` ? - */ - features.forEach(feature => this._toggleLayerFeatureBox(layer, feature, true)); - // _featuresToAdd.forEach(feature => this._toggleLayerFeatureBox(layer, feature, true)); - _featuresToRemove.forEach(feature => this._removeLayerFeatureBox(layer, feature)); - - // new layer features - layer.features = [ ..._featuresToRemove, ..._featuresToAdd ]; + //get features id from current layer on result + const features_ids = this._getFeaturesIds(features, external); + // filter the features that we had to remove + responseFeatures.forEach(feature => { + //check if feature on result is already add + if (features_ids.find(id => id === this._getFeatureId(feature, external))) { + this._removeLayerFeatureBox(layer, feature); + // remove feature from layer result + this._removeFeatureFromLayer(layer, feature, external); + } else { + // add feature from layer result + this._addFeatureFromLayer(layer, feature); + } + }); - // in case of removed features - if (1 === _featuresToRemove.length) { - this._toggleLayerFeatureBox(layer, _featuresToRemove[0], false); + //in case of only on feature in layer + if (layer.features.length === 1) { + this._toggleLayerFeatureBox(layer, features[0], false); + } else if (layer.features.length > 1) { + //collapse all feature layer + layer.features.forEach(feature => this._toggleLayerFeatureBox(layer, feature, true)); } - // in case no more features on layer remove interaction pickcoordinate to get result from map this.checkIfLayerHasNoFeatures(layer); } - // hightlight new feature + // highlight new feature if (1 === this.state.layers.length) { this.highlightFeaturesPermanently(this.state.layers[0]); } @@ -530,12 +506,15 @@ class QueryResultsService extends G3WObject { return; } + //reset (Empty array) unlistenerlayeractionevents this.unlistenerlayeractionevents = []; + //Loop through layers results layers.forEach(layer => { const currentactiontoolslayer = {}; const currentationfeaturelayer = {}; + layer.features.forEach((_, idx) => { currentactiontoolslayer[idx] = null; currentationfeaturelayer[idx] = null; @@ -578,7 +557,7 @@ class QueryResultsService extends G3WObject { } // Lookup for not external layer or WMS. - if (false == is_external_layer_or_wms) { + if (false === is_external_layer_or_wms) { this._setActionRemoveFeatureFromResult(layer); } @@ -588,12 +567,12 @@ class QueryResultsService extends G3WObject { } // Lookup for not external layer or WMS (copy link to feature). - if (!is_external_layer_or_wms && layer.hasgeometry) { + if (false === is_external_layer_or_wms && layer.hasgeometry) { this._setActionLinkZoomToFid(layer); } // Lookup for editable layer. - if (layer.editable && !layer.inediting) { + if (layer.editable && false === layer.inediting) { this._setActionEditing(layer); } @@ -605,6 +584,8 @@ class QueryResultsService extends G3WObject { /** * @FIXME add description + * @param opts.layer + * @param @opts.dynamicProperties */ createActionState({ layer, @@ -657,6 +638,11 @@ class QueryResultsService extends G3WObject { /** * @FIXME add description + * + * @param opts.id action layer id + * @param opts.layer layer + * @param opts.config configuration Object + * */ addCurrentActionToolsLayer({ id, @@ -673,7 +659,7 @@ class QueryResultsService extends G3WObject { */ resetCurrentActionToolsLayer(layer) { layer.features.forEach((_, idx) => { - if (!this.state.currentactiontools[layer.id]) { + if (undefined === this.state.currentactiontools[layer.id]) { return; } if (undefined === this.state.currentactiontools[layer.id][idx]) { @@ -687,6 +673,9 @@ class QueryResultsService extends G3WObject { /** * @FIXME add description + * @param opts.layer current layer + * @param opts.component vue component + * @param opts.config configuration Object */ setLayerActionTool({ layer, @@ -700,8 +689,9 @@ class QueryResultsService extends G3WObject { /** * Copy `zoomtofid` url * - * @param layer - * @param feature + * @param layer current layer + * @param feature current feature + * @param action action */ copyZoomToFidUrl(layer, feature, action) { const url = new URL(location.href); @@ -745,14 +735,16 @@ class QueryResultsService extends G3WObject { } /** - * @FIXME add description + * Order response layer as catalog layer project order + * @param layers Array of layers */ - _orderResponseByProjectLayers(layers) { + _orderResponseByProjectLayers(layers=[]) { layers.sort((a, b) => (this._projectLayerIds.indexOf(a.id) > this._projectLayerIds.indexOf(b.id) ? 1 : -1)); } /** * @FIXME add description + * @param bool Boolean */ setZoomToResults(bool = true) { this.state.zoomToResult = bool; @@ -804,7 +796,7 @@ class QueryResultsService extends G3WObject { } /** - * Adds feature to Features results + * Adds feature to Features layer results * * @param layer */ @@ -832,35 +824,31 @@ class QueryResultsService extends G3WObject { layer.addfeaturesresults.active = !layer.addfeaturesresults.active; - if (!layer.addfeaturesresults.active) { - - this.removeAddFeaturesLayerResultInteraction({ toggle: true }); - - } else { + if (layer.addfeaturesresults.active) { this.activeMapInteraction(); // useful to send an event - const external_layer = layer.external; + const external_layer = this._getExternalLayer(layer.id); - if (!this._addFeaturesLayerResultInteraction.mapcontrol) { + if (false === this._addFeaturesLayerResultInteraction.mapcontrol) { this._addFeaturesLayerResultInteraction.mapcontrol = this.mapService.getCurrentToggledMapControl(); } - const interaction = this._addFeaturesLayerResultInteraction.interaction = new PickCoordinatesInteraction(); + this._addFeaturesLayerResultInteraction.interaction = new PickCoordinatesInteraction(); - this.mapService.addInteraction(interaction, { close: false }); + this.mapService.addInteraction(this._addFeaturesLayerResultInteraction.interaction, { close: false }); - interaction.on('picked', async (e) => { + this._addFeaturesLayerResultInteraction.interaction.on('picked', async (evt) => { if (external_layer) { this.setQueryResponse( { data: [ this.getVectorLayerFeaturesFromQueryRequest( this._vectorLayers.find(vectorLayer => layer.id === vectorLayer.get('id')), - { coordinates } + { coordinates: evt.coordinate } ) ], query: { - coordinates: e.coordinate + coordinates: evt.coordinate } }, { add: true } @@ -870,7 +858,7 @@ class QueryResultsService extends G3WObject { 'query:coordinates', { inputs: { - coordinates: e.coordinate, + coordinates: evt.coordinate, query_point_tolerance: this._project.getQueryPointTolerance(), layerIds: [layer.id], multilayers: false, @@ -893,6 +881,8 @@ class QueryResultsService extends G3WObject { this.mapService.once('mapcontrol:toggled', this._addFeaturesLayerResultInteraction.toggleeventhandler); + } else { + this.removeAddFeaturesLayerResultInteraction({ toggle: true }); } } @@ -913,14 +903,15 @@ class QueryResultsService extends G3WObject { zoomToLayerFeaturesExtent(layer, options = {}) { options.highlight = !this.isOneLayerResult(); if (this._asyncFnc.zoomToLayerFeaturesExtent.async) { - this._asyncFnc.todo = this.mapService.zoomToFeatures.bind(this.mapService, layer.features, options); + this._asyncFnc.todo = this.mapService.zoomToFeatures.bind(this.mapService, this._getLayerFeatures(layer), options); } else { - this.mapService.zoomToFeatures(layer.features, options); + this.mapService.zoomToFeatures(this._getLayerFeatures(layer), options); } } /** * @FIXME add description + * @param options Object */ clearState(options = {}) { this.state.layers.splice(0); @@ -1147,6 +1138,8 @@ class QueryResultsService extends G3WObject { : layer; } + //set other properties for layerObj + layerObj.title = layerTitle; layerObj.id = layerId; layerObj.atlas = this.getAtlasByLayerId(layerId); @@ -1219,8 +1212,8 @@ class QueryResultsService extends G3WObject { * @param layerSpecialAttributesName * @param feature */ - _setSpecialAttributesFeatureProperty(layerSpecialAttributesName, feature) { - if (!layerSpecialAttributesName.length) { + _setSpecialAttributesFeatureProperty(layerSpecialAttributesName=[], feature) { + if (layerSpecialAttributesName.length === 0) { return; } // get attributes special keys from feature properties received by server request @@ -1267,7 +1260,7 @@ class QueryResultsService extends G3WObject { let featureAttributesNames = getAlphanumericPropertiesFromFeature( Object.keys(this.getFeaturePropertiesAndGeometry(feature).properties) ); - if (layerAttributes && layerAttributes.length) { + if (layerAttributes && layerAttributes.length > 0) { return layerAttributes.filter(attr => featureAttributesNames.indexOf(attr.name) > -1); } const sourcesTypes = [ @@ -1496,6 +1489,7 @@ class QueryResultsService extends G3WObject { /** * @FIXME add description + * @param container DOM element */ hideChart(container) { this.emit('hide-chart', container); @@ -1697,28 +1691,23 @@ class QueryResultsService extends G3WObject { this.state.actiontools[QueryPolygonCsvAttributesComponent.name] = this.state.actiontools[layer.id] || {}; this.state.actiontools[QueryPolygonCsvAttributesComponent.name][layer.id] = config; this.setCurrentActionLayerFeatureTool({ layer, index, action, component: QueryPolygonCsvAttributesComponent }); - } - - /** @FIXME add description */ - if (features.length < 1 && undefined === downloadsactions) { - layer[type].active = !layer[type].active; - } + } else { + /** @FIXME add description */ + if (undefined === downloadsactions) { + layer[type].active = !layer[type].active; - /** @FIXME add description */ - if (features.length < 1 && undefined === downloadsactions && layer[type].active) { + /** @FIXME add description */ + if (layer[type].active) { + this.setLayerActionTool({ layer, component: QueryPolygonCsvAttributesComponent, config }); + } else { + /** @FIXME add description */ + this.setLayerActionTool({ layer }); + } + } else { + /** @FIXME add description */ this.setLayerActionTool({ layer, component: QueryPolygonCsvAttributesComponent, config }); + } } - - /** @FIXME add description */ - if (features.length < 1 && undefined === downloadsactions && !layer[type].active){ - this.setLayerActionTool({ layer }); - } - - /** @FIXME add description */ - if (features.length < 1 && undefined !== downloadsactions) { - this.setLayerActionTool({ layer, component: QueryPolygonCsvAttributesComponent, config }); - } - } } @@ -1834,20 +1823,27 @@ class QueryResultsService extends G3WObject { * @param force */ async _addRemoveSelectionFeature(layer, feature, index, force) { - + //get feature if from feature + const fid = feature ? this._getFeatureId(feature, layer.external): null; /** - * An external layer (vector) added by add external layer tool + * In case of external layer (vector) added by add external layer tool */ - if (layer.external && "undefined" !== typeof layer.external) { + if (undefined !== "undefined" && layer.external) { /** @FIXME add description */ - if ("undefined" === typeof layer.selection.features) { + if (undefined === layer.selection.features) { layer.selection.features = {}; } - // Feature used in selection tool action - if (!layer.selection.features.find(selectionFeature => selectionFeature.getId() === feature.id)) { - const selectionFeature = createFeatureFromFeatureObject({ feature, id: feature.id }); + /** @FIXME add description */ + if (undefined !== layer.selection.features.find(selectionFeature => selectionFeature.getId() === fid)) { + /*** + * Feature used in selection tool action + */ + const selectionFeature = createFeatureFromFeatureObject({ + feature, + id: fid + }); selectionFeature.__layerId = layer.id; selectionFeature.selection = feature.selection; layer.selection.features.push(selectionFeature); @@ -1855,75 +1851,62 @@ class QueryResultsService extends G3WObject { /** @FIXME add description */ if ( - ('add' === force && feature.selection.selected) || (force === 'remove') && - !feature.selection.selected + (force === 'add' && feature.selection.selected) || + ( + (force === 'remove') && + (false === feature.selection.selected) + ) ) { - return; + return; } - /** @FIXME add description */ else { feature.selection.selected = !feature.selection.selected; } - /** @FIXME add description */ - this.mapService.setSelectionFeatures( - feature.selection.selected ? 'add' : 'remove', - { feature: layer.selection.features.find(selectionFeature => feature.id === selectionFeature.getId()) } - ); - - // Set selection layer active based on features selection selected properties + this.mapService.setSelectionFeatures(feature.selection.selected ? 'add' : 'remove', { + feature: layer.selection.features.find(selectionFeature => fid === selectionFeature.getId()) + }); + /* + * Set selection layer active based on features selection selected properties + */ layer.selection.active = layer.selection.features.reduce((accumulator, feature) => accumulator || feature.selection.selected, false) + } else { // case of project layer on TOC - } - - /** - * A project layer on TOC - */ - if (false === (layer.external && typeof "undefined" !== layer.external)) { - - const fid = feature ? feature.attributes[G3W_FID] : null; const hasAlreadySelectioned = layer.getFilterActive() || layer.hasSelectionFid(fid); - /** @FIXME add description */ - if (!hasAlreadySelectioned && feature && feature.geometry && !layer.getOlSelectionFeature(fid)) { - layer.addOlSelectionFeature({ id: fid, feature }); + if (false === hasAlreadySelectioned) { + if (feature && feature.geometry && !layer.getOlSelectionFeature(fid)) { + layer.addOlSelectionFeature({ + id: fid, + feature + }) + } } - /** @FIXME add description */ if (undefined === force) { layer[hasAlreadySelectioned ? 'excludeSelectionFid': 'includeSelectionFid'](fid); - } - - /** @FIXME add description */ - if (undefined !== force && !hasAlreadySelectioned && 'add' === force) { - await layer.includeSelectionFid(fid); - } - - /** @FIXME add description */ - if (undefined !== force && hasAlreadySelectioned && 'remove' === force) { - await layer.excludeSelectionFid(fid); + } else { + /** @FIXME add description */ + if (false === hasAlreadySelectioned && force === 'add') { + await layer.includeSelectionFid(fid); + } + /** @FIXME add description */ + if (hasAlreadySelectioned && force === 'remove') { + await layer.excludeSelectionFid(fid); + } } /** @FIXME add description */ if (layer.getFilterActive()) { - const currentLayer = this.state.layers.find(_layer => _layer.id === layer.getId()); - /** @FIXME add description */ - if (layer.getSelectionFids().size > 0 && currentLayer) { - currentLayer.features.splice(index, 1); + if (layer.getSelectionFids().size > 0) { + currentLayer && currentLayer.features.splice(index, 1); } - this.mapService.clearHighlightGeometry(); - - /** @FIXME add description */ - if (1 === this.state.layers.length && !this.state.layers[0].features.length) { - this.state.layers.splice(0); - } - + this.state.layers.length === 1 && !this.state.layers[0].features.length && this.state.layers.splice(0); } - } } @@ -1946,8 +1929,12 @@ class QueryResultsService extends G3WObject { action.state.toggled[index] = feature.selection.selected; } else if (feature) { action.state.toggled[index] = ( - CatalogLayersStoresRegistry.getLayerById(layer.id).getFilterActive() || - CatalogLayersStoresRegistry.getLayerById(layer.id).hasSelectionFid(feature ? feature.attributes[G3W_FID]: null) + CatalogLayersStoresRegistry + .getLayerById(layer.id) + .getFilterActive() || + CatalogLayersStoresRegistry + .getLayerById(layer.id) + .hasSelectionFid(feature ? this._getFeatureId(feature, layer.external): null) ); } } @@ -1961,7 +1948,7 @@ class QueryResultsService extends G3WObject { * @param index */ addToSelection(layer, feature, action, index) { - const { external = false } = layer; + const external = this._getExternalLayer(layer.id) || false; action.state.toggled[index] = !action.state.toggled[index]; this._addRemoveSelectionFeature( (external ? layer : CatalogLayersStoresRegistry.getLayerById(layer.id)), @@ -2057,6 +2044,8 @@ class QueryResultsService extends G3WObject { /** * Save layer result + * @param opts.layer current layer + * @parm opts.type type of format */ saveLayerResult({ layer, @@ -2131,6 +2120,7 @@ class QueryResultsService extends G3WObject { */ showQueryRelations(layer, feature, action) { GUI.changeCurrentContentOptions({ crumb: { title: layer.title } }); + GUI.pushContent({ content: new RelationsPage({ relations: action.relations, @@ -2167,13 +2157,22 @@ class QueryResultsService extends G3WObject { return (this._getLayer(layerId) || {}).external; } + /** + * Get id of the feature + * + * @since 3.9.0 + */ + _getFeatureId(feature, external){ + return external ? feature.id : feature.attributes[G3W_FID]; + } + /** * Get ids of the selected features * * @since 3.9.0 */ _getFeaturesIds(features, external) { - return features.map(f => external ? f.id : f.attributes[G3W_FID]); + return features.map(feature => this._getFeatureId(feature)); } /** @@ -2181,28 +2180,26 @@ class QueryResultsService extends G3WObject { * * @since 3.9.0 */ - _getLayerFeatures(layer) { + _getLayerFeatures(layer={}) { return layer.features || []; } /** - * Loop and filter the features that we need to remove + * Add feature from layer * * @since 3.9.0 */ - _featuresToRemove(features, external) { - const features_ids = this._getFeaturesIds(features, external); // get id of the features - return features.filter(feature => (-1 === features_ids.indexOf(external ? feature.id : feature.attributes[G3W_FID]))); + _addFeatureFromLayer(layer, feature) { + layer.features.push(feature); } - /** - * Filter features to add + * Remove feature from layer * * @since 3.9.0 */ - _featuresToAdd(features, external) { - const features_ids = this._getFeaturesIds(features, external); - return features.filter(feature => (-1 !== features_ids.indexOf(external ? feature.id : feature.attributes[G3W_FID]))); + _removeFeatureFromLayer(layer, feature, external) { + layer.features = layer.features.filter(f => this._getFeatureId(f, external) !== this._getFeatureId(feature, external)); + return layer.features; } /** @@ -2227,14 +2224,14 @@ class QueryResultsService extends G3WObject { */ _setActionGoToGeometry(layer) { this.state.layersactions[layer.id] - .push({ - id: 'gotogeometry', - download: false, - mouseover: true, - class: GUI.getFontClass('marker'), - hint: 'sdk.mapcontrols.query.actions.zoom_to_feature.hint', - cbk: throttle(this.goToGeometry.bind(this)) - }); + .push({ + id: 'gotogeometry', + download: false, + mouseover: true, + class: GUI.getFontClass('marker'), + hint: 'sdk.mapcontrols.query.actions.zoom_to_feature.hint', + cbk: throttle(this.goToGeometry.bind(this)) + }); } /** @@ -2252,7 +2249,7 @@ class QueryResultsService extends G3WObject { }); /** @FIXME add description */ - if (relations.length) { + if (relations.length > 0) { this.state.layersactions[layer.id] .push({ id: 'show-query-relations', @@ -2266,7 +2263,7 @@ class QueryResultsService extends G3WObject { } /** @FIXME add description */ - if (chartRelationIds.length) { + if (chartRelationIds.length > 0) { this.state.layersactions[layer.id] .push({ id: 'show-plots-relations', @@ -2285,13 +2282,13 @@ class QueryResultsService extends G3WObject { */ _setActionPrintAtlas(layer) { this.state.layersactions[layer.id] - .push({ - id: `printatlas`, - download: true, - class: GUI.getFontClass('print'), - hint: `sdk.tooltips.atlas`, - cbk: this.printAtlas.bind(this) - }); + .push({ + id: `printatlas`, + download: true, + class: GUI.getFontClass('print'), + hint: `sdk.tooltips.atlas`, + cbk: this.printAtlas.bind(this) + }); } /** @@ -2323,7 +2320,6 @@ class QueryResultsService extends G3WObject { * @since 3.9.0 */ _setActionMultiDownloadFeature(layer) { - const state = this.createActionState({ layer }); const downloads = []; @@ -2338,10 +2334,8 @@ class QueryResultsService extends G3WObject { cbk: (layer, feature, action, index) => { // un-toggle downloads action this.downloadFeatures(format, layer, feature, action, index); - if ('polygon' !== this.state.query.type) { - const downloadsaction = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); - downloadsaction.cbk(layer, feature, downloadsaction, index); - } + const downloadsaction = this.state.layersactions[layer.id].find(action => action.id === 'downloads'); + if (this.state.query.type !== 'polygon') downloadsaction.cbk(layer, feature, downloadsaction, index); } }); }); @@ -2349,13 +2343,13 @@ class QueryResultsService extends G3WObject { // set actionstools configs this.state.actiontools[DownloadFormats.name] = this.state.actiontools[DownloadFormats.name] || {}; this.state.actiontools[DownloadFormats.name][layer.id] = { downloads }; - // check if has download actions + // check if it has download actions this.state.layersactions[layer.id] .push({ id: `downloads`, download: true, class: GUI.getFontClass('download'), - state, + state: this.createActionState({ layer }), toggleable: true, hint: `Downloads`, change({features}) { @@ -2385,9 +2379,7 @@ class QueryResultsService extends G3WObject { download: false, mouseover: true, class: GUI.getFontClass('minus-square'), - style: { - color: 'red' - }, + style: {color: 'red'}, hint: 'sdk.mapcontrols.query.actions.remove_feature_from_results.hint', cbk: this.removeFeatureLayerFromResult.bind(this) }); @@ -2473,7 +2465,7 @@ QueryResultsService.prototype.setters = { if (!queryResponse.query.external) queryResponse.query.external = { add: false, filter: {SELECTED: false }}; // whether add response to current results using addLayerFeaturesToResultsAction - if (!options.add) { + if (false === options.add) { // in case of new request results reset the query otherwise maintain the previous request this.clearState(); @@ -2504,7 +2496,7 @@ QueryResultsService.prototype.setters = { * @param options */ setLayersData(layers, options = { add: false }) { - if (!options.add) { + if (false === options.add) { // set the right order of result layers based on TOC this._currentLayerIds = layers.map(layer => layer.id); this._orderResponseByProjectLayers(layers); From 4f6bd9db60ffc820cfbb63649621e1dc47295512 Mon Sep 17 00:00:00 2001 From: Raruto Date: Mon, 21 Aug 2023 09:15:11 +0200 Subject: [PATCH 14/48] wrong merge --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 6e3f8c046..6f3c4b3fa 100755 --- a/package.json +++ b/package.json @@ -61,8 +61,6 @@ "core-js-latest": "npm:core-js@^3.16.2", "current-git-branch": "^1.1.0", "del": "^2.2.2", - "generator-browserify": "^0.4.1", - "generator-karma": "^2.0.0", "gulp": "^3.9.1", "gulp-clean-css": "^2.0.2", "gulp-concat": "^2.6.1", @@ -88,7 +86,6 @@ "gulp-uglify": "^3.0.2", "gulp-useref": "^3.0.7", "gulp-watch": "^4.3.5", - "http-proxy": "^1.11.0", "imgurify": "^2.0.1", "inquirer": "^7.0.0", "jshint": "^2.9.1", From 30a6f60ae2dbb5b7c58850c61196d440acf4071e Mon Sep 17 00:00:00 2001 From: volterra79 Date: Mon, 21 Aug 2023 10:13:06 +0200 Subject: [PATCH 15/48] :bug: Fix undefined check and change layer.selection.features to empty array instead empty object --- src/app/gui/queryresults/queryresultsservice.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 7459955c6..3a8dc2674 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -1828,15 +1828,15 @@ class QueryResultsService extends G3WObject { /** * In case of external layer (vector) added by add external layer tool */ - if (undefined !== "undefined" && layer.external) { + if (undefined !== layer.external && layer.external) { /** @FIXME add description */ if (undefined === layer.selection.features) { - layer.selection.features = {}; + layer.selection.features = []; } /** @FIXME add description */ - if (undefined !== layer.selection.features.find(selectionFeature => selectionFeature.getId() === fid)) { + if (undefined === layer.selection.features.find(selectionFeature => selectionFeature.getId() === fid)) { /*** * Feature used in selection tool action */ @@ -1858,9 +1858,8 @@ class QueryResultsService extends G3WObject { ) ) { return; - } - /** @FIXME add description */ - else { + } else { + /** @FIXME add description */ feature.selection.selected = !feature.selection.selected; } /** @FIXME add description */ From 2b1c7e136113b89c14872be03d9940ec67eee05a Mon Sep 17 00:00:00 2001 From: Raruto Date: Mon, 21 Aug 2023 12:53:34 +0200 Subject: [PATCH 16/48] code readability - slim down function `updateLayerResultFeatures()` - alias long variable name `this._addFeaturesLayerResultInteraction` - slim down function `downloadFeatures()` - extract function `_handleExternalVectorLayerSelection()` - extract function `_handleProjectLayerSelection()` --- .../gui/queryresults/queryresultsservice.js | 572 +++++++++--------- 1 file changed, 273 insertions(+), 299 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 3a8dc2674..6d3841acf 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -413,39 +413,31 @@ class QueryResultsService extends G3WObject { * @since 3.8.0 */ updateLayerResultFeatures(responseLayer) { - const layer = this._getLayer(responseLayer.id), // get layer from current `state.layers` showed on result - features = this._getLayerFeatures(layer), // extract features from layer object - responseFeatures = this._getLayerFeatures(responseLayer), // extract features from responseLayer object - external = this._getExternalLayer(responseLayer.id); // get id of external layer or not (`external` is a layer added by mapcontrol addexternlayer) - - if (layer && features.length) { - //get features id from current layer on result - const features_ids = this._getFeaturesIds(features, external); - // filter the features that we had to remove - responseFeatures.forEach(feature => { - //check if feature on result is already add - if (features_ids.find(id => id === this._getFeatureId(feature, external))) { - this._removeLayerFeatureBox(layer, feature); - // remove feature from layer result - this._removeFeatureFromLayer(layer, feature, external); - } else { - // add feature from layer result - this._addFeatureFromLayer(layer, feature); - } - }); - - //in case of only on feature in layer - if (layer.features.length === 1) { - this._toggleLayerFeatureBox(layer, features[0], false); - } else if (layer.features.length > 1) { - //collapse all feature layer - layer.features.forEach(feature => this._toggleLayerFeatureBox(layer, feature, true)); - } - // in case no more features on layer remove interaction pickcoordinate to get result from map - this.checkIfLayerHasNoFeatures(layer); - + const layer = this._getLayer(responseLayer.id), // get layer from current `state.layers` showed on result + responseFeatures = this._getLayerFeatures(responseLayer), // extract features from responseLayer object + external = this._getExternalLayer(responseLayer.id) // get id of external layer or not (`external` is a layer added by mapcontrol addexternlayer) + has_features = layer && layer.features && layer.features.length; + + if (has_features) { + const features_ids = this._getFeaturesIds(layer.features, external); // get features id from current layer on result + responseFeatures + .forEach(feature => { + const feature_id = this._getFeatureId(feature, external); + if (features_ids.find(id => id === feature_id)) { // remove feature (because is already loaded) + this._removeLayerFeatureBox(layer, feature); + layer.features = layer.features.filter(f => this._getFeatureId(f, external) !== feature_id); + } else { // add feature + layer.features.push(feature); + } + }); + layer + .features + .forEach(feature => this._toggleLayerFeatureBox(layer, feature, layer.features.length > 1)); } + // in case no more features on layer remove interaction pickcoordinate to get result from map + this.checkIfLayerHasNoFeatures(layer); + // highlight new feature if (1 === this.state.layers.length) { this.highlightFeaturesPermanently(this.state.layers[0]); @@ -506,10 +498,10 @@ class QueryResultsService extends G3WObject { return; } - //reset (Empty array) unlistenerlayeractionevents + // reset array this.unlistenerlayeractionevents = []; - //Loop through layers results + // loop results layers.forEach(layer => { const currentactiontoolslayer = {}; @@ -584,8 +576,9 @@ class QueryResultsService extends G3WObject { /** * @FIXME add description + * * @param opts.layer - * @param @opts.dynamicProperties + * @param opts.dynamicProperties */ createActionState({ layer, @@ -637,12 +630,9 @@ class QueryResultsService extends G3WObject { /** - * @FIXME add description - * - * @param opts.id action layer id - * @param opts.layer layer - * @param opts.config configuration Object - * + * @param opts.id action layer id + * @param opts.layer layer + * @param opts.config configuration object */ addCurrentActionToolsLayer({ id, @@ -735,7 +725,8 @@ class QueryResultsService extends G3WObject { } /** - * Order response layer as catalog layer project order + * Sort Response layer as Catalog layer project order. + * * @param layers Array of layers */ _orderResponseByProjectLayers(layers=[]) { @@ -743,8 +734,7 @@ class QueryResultsService extends G3WObject { } /** - * @FIXME add description - * @param bool Boolean + * @param bool whether to zoom to results */ setZoomToResults(bool = true) { this.state.zoomToResult = bool; @@ -769,30 +759,30 @@ class QueryResultsService extends G3WObject { /** * @FIXME add description * - * @param opts.toggle boolean If true toggle true the mapcontrol + * @param {boolean} opts.toggle If true toggle true the mapcontrol */ removeAddFeaturesLayerResultInteraction({ toggle = false } = {}) { - if (this._addFeaturesLayerResultInteraction.interaction) { - this.mapService.removeInteraction(this._addFeaturesLayerResultInteraction.interaction); + const interaction = this._addFeaturesLayerResultInteraction; + + if (interaction.interaction) { + this.mapService.removeInteraction(interaction.interaction); } - this._addFeaturesLayerResultInteraction.interaction = null; - this._addFeaturesLayerResultInteraction.id = null; - // check if query map control is toggled and registered - if (toggle && this._addFeaturesLayerResultInteraction.mapcontrol) { - this._addFeaturesLayerResultInteraction.mapcontrol.toggle(true); + if (toggle && interaction.mapcontrol) { + interaction.mapcontrol.toggle(true); } - this._addFeaturesLayerResultInteraction.mapcontrol = null; - - if (this._addFeaturesLayerResultInteraction.toggleeventhandler) { - this.mapService.off('mapcontrol:toggled', this._addFeaturesLayerResultInteraction.toggleeventhandler); + if (interaction.toggleeventhandler) { + this.mapService.off('mapcontrol:toggled', interaction.toggleeventhandler); } - this._addFeaturesLayerResultInteraction.toggleeventhandler = null; + interaction.interaction = null; + interaction.id = null; + interaction.mapcontrol = null; + interaction.toggleeventhandler = null; } /** @@ -801,88 +791,75 @@ class QueryResultsService extends G3WObject { * @param layer */ addLayerFeaturesToResultsAction(layer) { + const interaction = this._addFeaturesLayerResultInteraction; - // Check if layer is current layer to add or clear previous - if ( - null !== this._addFeaturesLayerResultInteraction.id && - layer.id !== this._addFeaturesLayerResultInteraction.id - ) { - - const layer = this.state.layers.find(layer => layer.id === this._addFeaturesLayerResultInteraction.id); - if (layer) { - layer.addfeaturesresults.active = false; - } + const not_current = ![null, layer.id].includes(interaction.id); + const new_layer = not_current && this.state.layers.find(layer => layer.id === interaction.id); - // remove previous add result interaction - if (this._addFeaturesLayerResultInteraction.interaction) { - this.mapService.removeInteraction(this._addFeaturesLayerResultInteraction.interaction); - } + // disable previous layer + if (not_current && new_layer) { + new_layer.addfeaturesresults.active = false; + } + // remove previous interaction + if (not_current && interaction.interaction) { + this.mapService.removeInteraction(interaction.interaction); } - this._addFeaturesLayerResultInteraction.id = layer.id; + // set new layer + interaction.id = layer.id; layer.addfeaturesresults.active = !layer.addfeaturesresults.active; - if (layer.addfeaturesresults.active) { + if (false === layer.addfeaturesresults.active) { + this.removeAddFeaturesLayerResultInteraction({ toggle: true }); + } else { this.activeMapInteraction(); // useful to send an event + const external_layer = this._getExternalLayer(layer.id); - if (false === this._addFeaturesLayerResultInteraction.mapcontrol) { - this._addFeaturesLayerResultInteraction.mapcontrol = this.mapService.getCurrentToggledMapControl(); - } + interaction.mapcontrol = interaction.mapcontrol || this.mapService.getCurrentToggledMapControl(); + interaction.interaction = new PickCoordinatesInteraction(); - this._addFeaturesLayerResultInteraction.interaction = new PickCoordinatesInteraction(); - - this.mapService.addInteraction(this._addFeaturesLayerResultInteraction.interaction, { close: false }); - - this._addFeaturesLayerResultInteraction.interaction.on('picked', async (evt) => { - if (external_layer) { - this.setQueryResponse( - { - data: [ - this.getVectorLayerFeaturesFromQueryRequest( - this._vectorLayers.find(vectorLayer => layer.id === vectorLayer.get('id')), - { coordinates: evt.coordinate } - ) - ], - query: { - coordinates: evt.coordinate - } - }, - { add: true } - ); - } else { - await DataRouterService.getData( - 'query:coordinates', - { - inputs: { - coordinates: evt.coordinate, - query_point_tolerance: this._project.getQueryPointTolerance(), - layerIds: [layer.id], - multilayers: false, + this.mapService.addInteraction(interaction.interaction, { close: false }); + + interaction.interaction + .on('picked', async ({ coordinate: coordinates }) => { + if (external_layer) { + this.setQueryResponse( + { + data: [ this.getVectorLayerFeaturesFromQueryRequest(this._vectorLayers.find(v => layer.id === v.get('id')), { coordinates }) ], + query: { coordinates } }, - outputs: { - show: { - add: true + { add: true } + ); + } else { + await DataRouterService.getData( + 'query:coordinates', + { + inputs: { + coordinates, + query_point_tolerance: this._project.getQueryPointTolerance(), + layerIds: [layer.id], + multilayers: false, + }, + outputs: { + show: { add: true } } } - } - ); - } - }); + ); + } + }); - this._addFeaturesLayerResultInteraction.toggleeventhandler = (evt) => { + interaction.toggleeventhandler = (evt) => { if (evt.target.isToggled() && evt.target.isClickMap()) { layer.addfeaturesresults.active = false; } }; - this.mapService.once('mapcontrol:toggled', this._addFeaturesLayerResultInteraction.toggleeventhandler); + this.mapService.once('mapcontrol:toggled', interaction.toggleeventhandler); - } else { - this.removeAddFeaturesLayerResultInteraction({ toggle: true }); } } @@ -910,8 +887,7 @@ class QueryResultsService extends G3WObject { } /** - * @FIXME add description - * @param options Object + * @param options object */ clearState(options = {}) { this.state.layers.splice(0); @@ -1020,37 +996,36 @@ class QueryResultsService extends G3WObject { /** * Convert response from server * - * @param featuresForLayer + * @param featuresForLayer.layer + * @param featuresForLayer.features + * @param featuresForLayer.rawdata + * @param featuresForLayer.error * * @since 3.9.0 */ _handleFeatureForLayer(featuresForLayer) { const layerObj = { - editable: false, - inediting: false, - downloads: [], - infoformats: [], - filter: {}, - selection: {}, - external: false, - source: undefined, - infoformat: undefined, - formStructure: undefined, - attributes: [], - features: [], - hasgeometry: false, - show: true, - addfeaturesresults: { - active:false - }, - [DownloadFormats.name]: { - active: false - }, - expandable: true, - hasImageField: false, - error: '', - rawdata: null, // rawdata response - loading: false, + editable: false, + inediting: false, + downloads: [], + infoformats: [], + filter: {}, + selection: {}, + external: false, + source: undefined, + infoformat: undefined, + formStructure: undefined, + attributes: [], + features: [], + hasgeometry: false, + show: true, + addfeaturesresults: { active: false }, + [DownloadFormats.name]: { active: false }, + expandable: true, + hasImageField: false, + error: '', + rawdata: null, // rawdata response + loading: false, }; const layer = featuresForLayer.layer; @@ -1138,7 +1113,7 @@ class QueryResultsService extends G3WObject { : layer; } - //set other properties for layerObj + // set other properties for layerObj layerObj.title = layerTitle; layerObj.id = layerId; @@ -1212,8 +1187,8 @@ class QueryResultsService extends G3WObject { * @param layerSpecialAttributesName * @param feature */ - _setSpecialAttributesFeatureProperty(layerSpecialAttributesName=[], feature) { - if (layerSpecialAttributesName.length === 0) { + _setSpecialAttributesFeatureProperty(layerSpecialAttributesName = [], feature) { + if (0 === layerSpecialAttributesName.length) { return; } // get attributes special keys from feature properties received by server request @@ -1489,6 +1464,7 @@ class QueryResultsService extends G3WObject { /** * @FIXME add description + * * @param container DOM element */ hideChart(container) { @@ -1647,68 +1623,72 @@ class QueryResultsService extends G3WObject { } }; + /** @FIXME add description */ if ('polygon' !== query.type) { runDownload(); - } else { // check if multi-download if present - const downloadsactions = this.state.layersactions[layer.id].find(action => action.id === 'downloads'); - let { - fid, - layer:polygonLayer - } = query; - const config = { - choices: [ - { - id: getUniqueDomId(), - type: 'feature', - label: 'sdk.mapcontrols.querybypolygon.download.choiches.feature.label', - }, - { - id: getUniqueDomId(), - type: 'polygon', - label: 'sdk.mapcontrols.querybypolygon.download.choiches.feature_polygon.label', - }, - ], - // choose between only feature attribute or also polygon attibute - download: (type) => { - if ('polygon' === type) { // id type polygon add paramateres to api download - data.sbp_qgs_layer_id = polygonLayer.getId(); - data.sbp_fid = fid; - } else { // force to remove - delete data.sbp_fid; - delete data.sbp_qgs_layer_id; - } - runDownload(true) - } - }; - - /** @FIXME add description */ - if (1 === features.length && undefined === downloadsactions) { - action.state.toggled[index] = true; - } + return; + } - /** @FIXME add description */ - if(1 === features.length) { - this.state.actiontools[QueryPolygonCsvAttributesComponent.name] = this.state.actiontools[layer.id] || {}; - this.state.actiontools[QueryPolygonCsvAttributesComponent.name][layer.id] = config; - this.setCurrentActionLayerFeatureTool({ layer, index, action, component: QueryPolygonCsvAttributesComponent }); - } else { - /** @FIXME add description */ - if (undefined === downloadsactions) { - layer[type].active = !layer[type].active; + // check if multi-download if present + const downloadsactions = this.state.layersactions[layer.id].find(action => action.id === 'downloads'); - /** @FIXME add description */ - if (layer[type].active) { - this.setLayerActionTool({ layer, component: QueryPolygonCsvAttributesComponent, config }); - } else { - /** @FIXME add description */ - this.setLayerActionTool({ layer }); - } - } else { - /** @FIXME add description */ - this.setLayerActionTool({ layer, component: QueryPolygonCsvAttributesComponent, config }); + const config = { + choices: [ + { + id: getUniqueDomId(), + type: 'feature', + label: 'sdk.mapcontrols.querybypolygon.download.choiches.feature.label', + }, + { + id: getUniqueDomId(), + type: 'polygon', + label: 'sdk.mapcontrols.querybypolygon.download.choiches.feature_polygon.label', + }, + ], + // choose between only feature attribute or also polygon attibute + download: (type) => { + if ('polygon' === type) { // id type polygon add paramateres to api download + data.sbp_qgs_layer_id = query.layer.getId(); + data.sbp_fid = query.fid; + } else { // force to remove + delete data.sbp_fid; + delete data.sbp_qgs_layer_id; } + runDownload(true) } + }; + + /** @FIXME add description */ + if (1 === features.length && undefined === downloadsactions) { + action.state.toggled[index] = true; } + + /** @FIXME add description */ + if (1 === features.length) { + this.state.actiontools[QueryPolygonCsvAttributesComponent.name] = this.state.actiontools[layer.id] || {}; + this.state.actiontools[QueryPolygonCsvAttributesComponent.name][layer.id] = config; + this.setCurrentActionLayerFeatureTool({ + layer, + index, + action, + component: QueryPolygonCsvAttributesComponent, + }); + } + + /** @FIXME add description */ + if (1 !== features.length && undefined === downloadsactions) { + layer[type].active = !layer[type].active; + } + + /** @FIXME add description */ + if (1 !== features.length) { + this.setLayerActionTool({ + layer, + component: (layer[type].active || undefined !== downloadsactions) ? QueryPolygonCsvAttributesComponent : null, + config: (layer[type].active || undefined !== downloadsactions) ? config : null, + }); + } + } /** @@ -1823,91 +1803,99 @@ class QueryResultsService extends G3WObject { * @param force */ async _addRemoveSelectionFeature(layer, feature, index, force) { - //get feature if from feature - const fid = feature ? this._getFeatureId(feature, layer.external): null; - /** - * In case of external layer (vector) added by add external layer tool - */ - if (undefined !== layer.external && layer.external) { + const fid = feature ? this._getFeatureId(feature, layer.external) : null; + if (layer.external) { + this._handleExternalVectorLayerSelection(fid, layer, feature, index, force); + } else { + this._handleProjectLayerSelection(fid, layer, feature, index, force); + } + } - /** @FIXME add description */ - if (undefined === layer.selection.features) { - layer.selection.features = []; - } + /** + * External layer (vector) added by add external layer tool + * + * @since 3.9.0 + */ + _handleExternalVectorLayerSelection(fid, layer, feature, index, force) { + /** @FIXME add description */ + if (undefined === layer.selection.features) { + layer.selection.features = []; + } - /** @FIXME add description */ - if (undefined === layer.selection.features.find(selectionFeature => selectionFeature.getId() === fid)) { - /*** - * Feature used in selection tool action - */ - const selectionFeature = createFeatureFromFeatureObject({ - feature, - id: fid - }); - selectionFeature.__layerId = layer.id; - selectionFeature.selection = feature.selection; - layer.selection.features.push(selectionFeature); - } + // Set feature used in selection tool action + if (undefined === layer.selection.features.find(f => f.getId() === fid)) { + const feat = createFeatureFromFeatureObject({ feature, id: fid }); + feat.__layerId = layer.id; + feat.selection = feature.selection; + layer.selection.features.push(feat); + } - /** @FIXME add description */ - if ( - (force === 'add' && feature.selection.selected) || - ( - (force === 'remove') && - (false === feature.selection.selected) - ) - ) { - return; - } else { - /** @FIXME add description */ - feature.selection.selected = !feature.selection.selected; - } - /** @FIXME add description */ - this.mapService.setSelectionFeatures(feature.selection.selected ? 'add' : 'remove', { - feature: layer.selection.features.find(selectionFeature => fid === selectionFeature.getId()) - }); - /* - * Set selection layer active based on features selection selected properties - */ - layer.selection.active = layer.selection.features.reduce((accumulator, feature) => accumulator || feature.selection.selected, false) - } else { // case of project layer on TOC + const GIVE_ME_A_NAME = ('add' === force && feature.selection.selected) || ('remove' === force && !feature.selection.selected); + + /** @FIXME add description */ + if (GIVE_ME_A_NAME) { + return; + } - const hasAlreadySelectioned = layer.getFilterActive() || layer.hasSelectionFid(fid); + /** @FIXME add description */ + feature.selection.selected = !feature.selection.selected; - if (false === hasAlreadySelectioned) { - if (feature && feature.geometry && !layer.getOlSelectionFeature(fid)) { - layer.addOlSelectionFeature({ - id: fid, - feature - }) - } - } - /** @FIXME add description */ - if (undefined === force) { - layer[hasAlreadySelectioned ? 'excludeSelectionFid': 'includeSelectionFid'](fid); - } else { - /** @FIXME add description */ - if (false === hasAlreadySelectioned && force === 'add') { - await layer.includeSelectionFid(fid); - } - /** @FIXME add description */ - if (hasAlreadySelectioned && force === 'remove') { - await layer.excludeSelectionFid(fid); - } - } + /** @FIXME add description */ + this + .mapService + .setSelectionFeatures( + (feature.selection.selected ? 'add' : 'remove'), + { feature: layer.selection.features.find(selectionFeature => fid === selectionFeature.getId()) } + ); - /** @FIXME add description */ - if (layer.getFilterActive()) { - const currentLayer = this.state.layers.find(_layer => _layer.id === layer.getId()); - /** @FIXME add description */ - if (layer.getSelectionFids().size > 0) { - currentLayer && currentLayer.features.splice(index, 1); - } - this.mapService.clearHighlightGeometry(); - this.state.layers.length === 1 && !this.state.layers[0].features.length && this.state.layers.splice(0); - } + // Set selection layer active based on features selection selected properties. + layer.selection.active = layer.selection.features.reduce((acc, feature) => acc || feature.selection.selected, false) + } + + /** + * Project layer (on TOC) + * + * @since 3.9.0 + */ + async _handleProjectLayerSelection(fid, layer, feature, index, force) { + const is_selected = layer.getFilterActive() || layer.hasSelectionFid(fid); + + if (!is_selected && feature && feature.geometry && !layer.getOlSelectionFeature(fid)) { + layer.addOlSelectionFeature({ id: fid, feature }) + } + + /** @FIXME add description */ + if (undefined === force) { + layer[is_selected ? 'excludeSelectionFid': 'includeSelectionFid'](fid); + } + + /** @FIXME add description */ + if ('add' === force && !is_selected) { + await layer.includeSelectionFid(fid); + } + + /** @FIXME add description */ + if ('remove' === force) { + await layer.excludeSelectionFid(fid); + } + + /** @FIXME add description */ + if (!layer.getFilterActive()) { + return; + } + + const currentLayer = this.state.layers.find(l => l.id === layer.getId()); + + /** @FIXME add description */ + if (currentLayer && layer.getSelectionFids().size > 0) { + currentLayer.features.splice(index, 1); } + this.mapService.clearHighlightGeometry(); + + if (1 === this.state.layers.length && !this.state.layers[0].features.length) { + this.state.layers.splice(0); + } } /** @@ -2183,24 +2171,6 @@ class QueryResultsService extends G3WObject { return layer.features || []; } - /** - * Add feature from layer - * - * @since 3.9.0 - */ - _addFeatureFromLayer(layer, feature) { - layer.features.push(feature); - } - /** - * Remove feature from layer - * - * @since 3.9.0 - */ - _removeFeatureFromLayer(layer, feature, external) { - layer.features = layer.features.filter(f => this._getFeatureId(f, external) !== this._getFeatureId(feature, external)); - return layer.features; - } - /** * @since 3.9.0 */ @@ -2333,8 +2303,10 @@ class QueryResultsService extends G3WObject { cbk: (layer, feature, action, index) => { // un-toggle downloads action this.downloadFeatures(format, layer, feature, action, index); - const downloadsaction = this.state.layersactions[layer.id].find(action => action.id === 'downloads'); - if (this.state.query.type !== 'polygon') downloadsaction.cbk(layer, feature, downloadsaction, index); + if ('polygon' !== this.state.query.type) { + const downloadsaction = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); + downloadsaction.cbk(layer, feature, downloadsaction, index); + } } }); }); @@ -2378,7 +2350,9 @@ class QueryResultsService extends G3WObject { download: false, mouseover: true, class: GUI.getFontClass('minus-square'), - style: {color: 'red'}, + style: { + color: 'red' + }, hint: 'sdk.mapcontrols.query.actions.remove_feature_from_results.hint', cbk: this.removeFeatureLayerFromResult.bind(this) }); From e843165d9e546fa7c6e3084f87ffb50238b80af0 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Mon, 21 Aug 2023 14:41:51 +0200 Subject: [PATCH 17/48] :bug: Ensure that find array method return undefined (not found). Add extenal parameter into _getFeaturesIds --- src/app/gui/queryresults/queryresultsservice.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 6d3841acf..47414c746 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -415,19 +415,19 @@ class QueryResultsService extends G3WObject { updateLayerResultFeatures(responseLayer) { const layer = this._getLayer(responseLayer.id), // get layer from current `state.layers` showed on result responseFeatures = this._getLayerFeatures(responseLayer), // extract features from responseLayer object - external = this._getExternalLayer(responseLayer.id) // get id of external layer or not (`external` is a layer added by mapcontrol addexternlayer) - has_features = layer && layer.features && layer.features.length; + external = this._getExternalLayer(responseLayer.id), // get id of external layer or not (`external` is a layer added by mapcontrol addexternlayer) + has_features = layer && this._getLayerFeatures(layer).length > 0; //check if current layer has features on response if (has_features) { const features_ids = this._getFeaturesIds(layer.features, external); // get features id from current layer on result responseFeatures .forEach(feature => { const feature_id = this._getFeatureId(feature, external); - if (features_ids.find(id => id === feature_id)) { // remove feature (because is already loaded) + if (undefined === features_ids.find(id => id === feature_id)) { // add feature + layer.features.push(feature); + } else { // remove feature (because is already loaded) this._removeLayerFeatureBox(layer, feature); layer.features = layer.features.filter(f => this._getFeatureId(f, external) !== feature_id); - } else { // add feature - layer.features.push(feature); } }); layer @@ -2159,7 +2159,7 @@ class QueryResultsService extends G3WObject { * @since 3.9.0 */ _getFeaturesIds(features, external) { - return features.map(feature => this._getFeatureId(feature)); + return features.map(feature => this._getFeatureId(feature, external)); } /** From 23461964b1a0095ccc23808a7b1e923386089a46 Mon Sep 17 00:00:00 2001 From: Raruto Date: Mon, 21 Aug 2023 15:43:59 +0200 Subject: [PATCH 18/48] slim down `unRegisterCustomComponent` --- .../gui/queryresults/queryresultsservice.js | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 47414c746..fe8875426 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -347,6 +347,7 @@ class QueryResultsService extends G3WObject { * @param opts.id * @param opts.layerId * @param opts.type + * @param opts.position */ unRegisterCustomComponent({ id, @@ -354,17 +355,18 @@ class QueryResultsService extends G3WObject { type, position }) { + const component = this.state.layerscustomcomponents[layerId][type]; + const by_id = ({ id: componentId }) => componentId !== id; + if (position) { - this.state.layerscustomcomponents[layerId][type][position] = this.state.layerscustomcomponents[layerId][type][position].filter(({id:componentId}) => componentId !== id); + component[position] = component[position].filter(by_id); return; } Object - .keys(this.state.layerscustomcomponents[layerId][type]) - .forEach(position => { - this.state.layerscustomcomponents[layerId][type][position] = this.state.layerscustomcomponents[layerId][type][position].filter(({id:componentId}) => componentId !== id); - }); - }; + .keys(component[position]) + .forEach(position => { component[position] = component[position].filter(by_id); }); + } /** * Add a feature to current layer result @@ -413,19 +415,19 @@ class QueryResultsService extends G3WObject { * @since 3.8.0 */ updateLayerResultFeatures(responseLayer) { - const layer = this._getLayer(responseLayer.id), // get layer from current `state.layers` showed on result - responseFeatures = this._getLayerFeatures(responseLayer), // extract features from responseLayer object - external = this._getExternalLayer(responseLayer.id), // get id of external layer or not (`external` is a layer added by mapcontrol addexternlayer) - has_features = layer && this._getLayerFeatures(layer).length > 0; //check if current layer has features on response + const layer = this._getLayer(responseLayer.id), // get layer from current `state.layers` showed on result + responseFeatures = this._getLayerFeatures(responseLayer), // extract features from responseLayer object + external = this._getExternalLayer(responseLayer.id), // get id of external layer or not (`external` is a layer added by mapcontrol addexternlayer) + has_features = layer && this._getLayerFeatures(layer).length > 0; // check if current layer has features on response if (has_features) { - const features_ids = this._getFeaturesIds(layer.features, external); // get features id from current layer on result + const features_ids = this._getFeaturesIds(layer.features, external); // get features id from current layer on result responseFeatures .forEach(feature => { const feature_id = this._getFeatureId(feature, external); if (undefined === features_ids.find(id => id === feature_id)) { // add feature layer.features.push(feature); - } else { // remove feature (because is already loaded) + } else { // remove feature (because is already loaded) this._removeLayerFeatureBox(layer, feature); layer.features = layer.features.filter(f => this._getFeatureId(f, external) !== feature_id); } From f50a74bf6414ff0de38e81191f198d10cb49b075 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Mon, 21 Aug 2023 17:59:09 +0200 Subject: [PATCH 19/48] :bug: Fix add feature to a external layer. fix add settimeout to querycontrol --- src/app/g3w-ol/controls/querycontrol.js | 11 ++-- .../gui/queryresults/queryresultsservice.js | 58 +++++++++++-------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/app/g3w-ol/controls/querycontrol.js b/src/app/g3w-ol/controls/querycontrol.js index e2cc10171..5cd5149ae 100644 --- a/src/app/g3w-ol/controls/querycontrol.js +++ b/src/app/g3w-ol/controls/querycontrol.js @@ -35,14 +35,17 @@ proto.setMap = function(map) { let eventSingleClickKey = null; this.on('toggled', ({toggled}) => { - if (true !== toggled) { ol.Observable.unByKey(eventSingleClickKey); eventSingleClickKey = null; } else if (null === eventSingleClickKey && map) { - // register click on map event. It use to dispatch picked event by control - eventSingleClickKey = map - .on('singleclick', throttle(evt => this.dispatchEvent({ type: 'picked', coordinates:evt.coordinate }))); + //need to be set timeout otherwise can be get picked from other interaction + setTimeout(() => { + // register click on map event. It uses to dispatch picked event by control + eventSingleClickKey = map + .on('singleclick', throttle(evt => this.dispatchEvent({ type: 'picked', coordinates:evt.coordinate }))); + }) + } }); diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index fe8875426..65bc8addf 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -206,7 +206,7 @@ class QueryResultsService extends G3WObject { mapcontrol: null, /** - * @FIXME add description + * Method to handle interaction when a mapcontrol is toggled */ toggleeventhandler: null @@ -421,20 +421,26 @@ class QueryResultsService extends G3WObject { has_features = layer && this._getLayerFeatures(layer).length > 0; // check if current layer has features on response if (has_features) { - const features_ids = this._getFeaturesIds(layer.features, external); // get features id from current layer on result + const features_ids = this._getFeaturesIds(layer.features, external); // get features id from current layer on result + //cycle on response features responseFeatures - .forEach(feature => { + .forEach(feature => { + //get feature id const feature_id = this._getFeatureId(feature, external); - if (undefined === features_ids.find(id => id === feature_id)) { // add feature + //check if already present on current layer features result + if (undefined === features_ids.find(id => id === feature_id)) { + // add feature layer.features.push(feature); - } else { // remove feature (because is already loaded) + } else { + // remove feature (because is already loaded) this._removeLayerFeatureBox(layer, feature); - layer.features = layer.features.filter(f => this._getFeatureId(f, external) !== feature_id); + // set new layer features removing already add feature + layer.features = this._getLayerFeatures(layer).filter(f => this._getFeatureId(f, external) !== feature_id); } }); - layer - .features - .forEach(feature => this._toggleLayerFeatureBox(layer, feature, layer.features.length > 1)); + //toggle current layer features based on features length of layer + this._getLayerFeatures(layer) + .forEach(feature => this._toggleLayerFeatureBox(layer, feature, this._getLayerFeatures(layer).length > 1)); } // in case no more features on layer remove interaction pickcoordinate to get result from map @@ -464,7 +470,7 @@ class QueryResultsService extends G3WObject { * @param layer */ checkIfLayerHasNoFeatures(layer) { - if (layer && 0 === layer.features.length) { + if (layer && 0 === this._getLayerFeatures(layer).length) { // due to vue reactivity, wait a little bit before update layers setTimeout(() => { this.state.layers = this.state.layers.filter(l => l.id !== layer.id); @@ -650,7 +656,7 @@ class QueryResultsService extends G3WObject { * @param layer */ resetCurrentActionToolsLayer(layer) { - layer.features.forEach((_, idx) => { + this._getLayerFeatures(layer).forEach((_, idx) => { if (undefined === this.state.currentactiontools[layer.id]) { return; } @@ -767,24 +773,26 @@ class QueryResultsService extends G3WObject { toggle = false } = {}) { const interaction = this._addFeaturesLayerResultInteraction; - - if (interaction.interaction) { - this.mapService.removeInteraction(interaction.interaction); + + if (null !== interaction.toggleeventhandler) { + this.mapService.off('mapcontrol:toggled', interaction.toggleeventhandler); } - // check if query map control is toggled and registered - if (toggle && interaction.mapcontrol) { - interaction.mapcontrol.toggle(true); + //remove current interaction to get features from layer + if (null !== interaction.interaction) { + this.mapService.removeInteraction(interaction.interaction); } - if (interaction.toggleeventhandler) { - this.mapService.off('mapcontrol:toggled', interaction.toggleeventhandler); + // check if query map control is toggled and registered + if (null !== interaction.mapcontrol) { + interaction.mapcontrol.toggle(toggle); } + //reset values interaction.interaction = null; interaction.id = null; - interaction.mapcontrol = null; interaction.toggleeventhandler = null; + interaction.mapcontrol = null; } /** @@ -829,10 +837,11 @@ class QueryResultsService extends G3WObject { interaction.interaction .on('picked', async ({ coordinate: coordinates }) => { if (external_layer) { + //in case of external layer call setters method setQueryResponse directly this.setQueryResponse( { data: [ this.getVectorLayerFeaturesFromQueryRequest(this._vectorLayers.find(v => layer.id === v.get('id')), { coordinates }) ], - query: { coordinates } + query: {coordinates,} }, { add: true } ); @@ -897,10 +906,9 @@ class QueryResultsService extends G3WObject { this.state.querytitle = ""; this.state.changed = false; // clear action if present - Object.values(this.state.layersactions) - .forEach(layeractions => - layeractions.forEach(action => action.clear && action.clear()) - ); + Object + .values(this.state.layersactions) + .forEach(layeractions => layeractions.forEach(action => action.clear && action.clear())); this.state.layersactions = {}; this.state.actiontools = {}; this.state.layeractiontool = {}; From 1ee3c1c594cea9de03661527e0bd3344bfc71426 Mon Sep 17 00:00:00 2001 From: Raruto Date: Tue, 22 Aug 2023 09:06:36 +0200 Subject: [PATCH 20/48] code readability - slim down comments - alias long variable name `this.state.currentactiontools` - alias long variable name `this.state.currentactionfeaturelayer` - alias long variable name ` this.state.layersFeaturesBoxes` - extract function `_clearActions` - alias prototype function `QueryResultsService.init` - alias prototype function `QueryResultsService.reset` --- .../gui/queryresults/queryresultsservice.js | 161 +++++++++--------- 1 file changed, 81 insertions(+), 80 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 65bc8addf..c2998b9cc 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -206,7 +206,7 @@ class QueryResultsService extends G3WObject { mapcontrol: null, /** - * Method to handle interaction when a mapcontrol is toggled + * Method that handles interaction when a mapcontrol is toggled */ toggleeventhandler: null @@ -286,12 +286,6 @@ class QueryResultsService extends G3WObject { } - /** - * @FIXME add description - */ - init() { - this.clearState(); - } /** * @FIXME add description @@ -407,8 +401,9 @@ class QueryResultsService extends G3WObject { } /** - * Based on layer response check if features layer need to - * be added or removed to current `state.layers` results + * Loop over response features based on layer response and + * check if features layer need to be added or removed to + * current `state.layers` results. * * @param responseLayer layer structure coming from request * @@ -421,25 +416,19 @@ class QueryResultsService extends G3WObject { has_features = layer && this._getLayerFeatures(layer).length > 0; // check if current layer has features on response if (has_features) { - const features_ids = this._getFeaturesIds(layer.features, external); // get features id from current layer on result - //cycle on response features + const features_ids = this._getFeaturesIds(layer.features, external); // get features id from current layer on result responseFeatures .forEach(feature => { - //get feature id const feature_id = this._getFeatureId(feature, external); - //check if already present on current layer features result - if (undefined === features_ids.find(id => id === feature_id)) { - // add feature - layer.features.push(feature); - } else { - // remove feature (because is already loaded) + if (features_ids.some(id => id === feature_id)) { // remove feature (because is already loaded) this._removeLayerFeatureBox(layer, feature); - // set new layer features removing already add feature layer.features = this._getLayerFeatures(layer).filter(f => this._getFeatureId(f, external) !== feature_id); + } else { // add feature + layer.features.push(feature); } }); - //toggle current layer features based on features length of layer - this._getLayerFeatures(layer) + this + ._getLayerFeatures(layer) .forEach(feature => this._toggleLayerFeatureBox(layer, feature, this._getLayerFeatures(layer).length > 1)); } @@ -624,16 +613,13 @@ class QueryResultsService extends G3WObject { index, component = null } = {}) { - if ( - component && - this.state.currentactiontools[layer.id][index] && - action.id !== this.state.currentactionfeaturelayer[layer.id][index].id && - this.state.currentactionfeaturelayer[layer.id][index].toggleable - ) { - this.state.currentactionfeaturelayer[layer.id][index].state.toggled[index] = false; - } - this.state.currentactionfeaturelayer[layer.id][index] = component ? action : null; - this.state.currentactiontools[layer.id][index] = component; + const tools = this.state.currentactiontools[layer.id]; + const feats = this.state.currentactionfeaturelayer[layer.id]; + const toggled = feats[index].state.toggled[index]; + + feats[index].state.toggled[index] = (component && tools[index] && action.id !== feats[index].id && feats[index].toggleable) ? toggled : false; + feats[index] = component ? action : null; + tools[index] = component; } @@ -656,17 +642,20 @@ class QueryResultsService extends G3WObject { * @param layer */ resetCurrentActionToolsLayer(layer) { - this._getLayerFeatures(layer).forEach((_, idx) => { - if (undefined === this.state.currentactiontools[layer.id]) { - return; - } - if (undefined === this.state.currentactiontools[layer.id][idx]) { - Vue.set(this.state.currentactiontools[layer.id], idx, null); - } else { - this.state.currentactiontools[layer.id][idx] = null; - } - this.state.currentactionfeaturelayer[layer.id][idx] = null; - }) + this + ._getLayerFeatures(layer) + .forEach((_, idx) => { + const tool = this.state.currentactiontools[layer.id]; + if (undefined === tool) { + return; + } + if (undefined === tool[idx]) { + Vue.set(tool, idx, null); + } else { + tool[idx] = null; + } + tool[idx] = null; + }); } /** @@ -778,7 +767,7 @@ class QueryResultsService extends G3WObject { this.mapService.off('mapcontrol:toggled', interaction.toggleeventhandler); } - //remove current interaction to get features from layer + // remove current interaction to get features from layer if (null !== interaction.interaction) { this.mapService.removeInteraction(interaction.interaction); } @@ -788,11 +777,11 @@ class QueryResultsService extends G3WObject { interaction.mapcontrol.toggle(toggle); } - //reset values + // reset values interaction.interaction = null; interaction.id = null; interaction.toggleeventhandler = null; - interaction.mapcontrol = null; + interaction.mapcontrol = null; } /** @@ -837,11 +826,11 @@ class QueryResultsService extends G3WObject { interaction.interaction .on('picked', async ({ coordinate: coordinates }) => { if (external_layer) { - //in case of external layer call setters method setQueryResponse directly + // call setQueryResponse setters method directly in case of external layer this.setQueryResponse( { data: [ this.getVectorLayerFeaturesFromQueryRequest(this._vectorLayers.find(v => layer.id === v.get('id')), { coordinates }) ], - query: {coordinates,} + query: { coordinates } }, { add: true } ); @@ -898,26 +887,31 @@ class QueryResultsService extends G3WObject { } /** - * @param options object + * Reset internal state */ - clearState(options = {}) { + clearState() { this.state.layers.splice(0); - this.state.query = null; - this.state.querytitle = ""; - this.state.changed = false; - // clear action if present - Object - .values(this.state.layersactions) - .forEach(layeractions => layeractions.forEach(action => action.clear && action.clear())); - this.state.layersactions = {}; - this.state.actiontools = {}; - this.state.layeractiontool = {}; - // current action tools - this.state.currentactiontools = {}; + this.state.query = null; + this.state.querytitle = ""; + this.state.changed = false; + this._clearActions(); this.state.layersFeaturesBoxes = {}; this.removeAddFeaturesLayerResultInteraction(); } + /** + * Clear layer actions (if present) + * + * @since 3.9.0 + */ + _clearActions() { + Object.values(this.state.layersactions).forEach(l => l.forEach(action => action.clear && action.clear())); + this.state.layersactions = {}; + this.state.actiontools = {}; + this.state.layeractiontool = {}; + this.state.currentactiontools = {}; + } + /** * @FIXME add description */ @@ -969,13 +963,6 @@ class QueryResultsService extends G3WObject { this.state.querytitle = querytitle || ""; } - /** - * @FIXME add description - */ - reset() { - this.clearState(); - } - /** * Converts response from DataProvider into a QueryResult component data structure * @@ -1574,16 +1561,15 @@ class QueryResultsService extends G3WObject { * @param index */ async downloadFeatures(type, layer, features = [], action, index) { - features = features - ? Array.isArray(features) - ? features - : [features] - : features; - const { - query = {} - } = this.state; - const fids = features.map(feature => feature.attributes[G3W_FID]).join(','); - const data = { fids }; + + if (features && !Array.isArray(features)) { + features = [features]; + } + + const { query = {} } = this.state; + const data = { + fids: features.map(f => f.attributes[G3W_FID]).join(',') + }; /** * A function that che be called in case of querybypolygon @@ -1591,18 +1577,24 @@ class QueryResultsService extends G3WObject { * @param active */ const runDownload = async (active=false) => { + if (features.length > 1) { layer[DownloadFormats.name].active = active; this.setLayerActionTool({ layer }); } + const download_caller_id = ApplicationService.setDownload(true); + GUI.setLoadingContent(true); + try { await CatalogLayersStoresRegistry.getLayerById(layer.id).getDownloadFilefromDownloadDataType(type, { data }) || Promise.resolve(); } catch(err) { GUI.notify.error(err || t("info.server_error")); } + ApplicationService.setDownload(false, download_caller_id); + GUI.setLoadingContent(false); const downloadsactions = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); @@ -2186,8 +2178,9 @@ class QueryResultsService extends G3WObject { */ _toggleLayerFeatureBox(layer, feature, collapsed) { const boxId = this.getBoxId(layer, feature); - if (boxId && this.state.layersFeaturesBoxes[boxId]) { - setTimeout(() => this.state.layersFeaturesBoxes[boxId].collapsed = collapsed); // due to vue reactivity, wait a little bit before update layers + const box = boxId && this.state.layersFeaturesBoxes[boxId]; + if (box) { + setTimeout(() => box.collapsed = collapsed); // due to vue reactivity, wait a little bit before update layers } } @@ -2429,6 +2422,14 @@ class QueryResultsService extends G3WObject { */ QueryResultsService.prototype.addRemoveFeaturesToLayerResult = deprecate(QueryResultsService.prototype.updateLayerResultFeatures, '[G3W-CLIENT] QueryResultsService::addRemoveFeaturesToLayerResult(layer) is deprecated'); +/** + * Alias functions + * + * @TODO choose which ones deprecate + */ +QueryResultsService.prototype.init = QueryResultsService.prototype.clearState; +QueryResultsService.prototype.reset = QueryResultsService.prototype.clearState; + /** * Core methods used from other classes to react before or after its call */ From 8c738865f3065927d385ed22fc8b463e620f2550 Mon Sep 17 00:00:00 2001 From: Raruto Date: Tue, 22 Aug 2023 09:36:28 +0200 Subject: [PATCH 21/48] refactor function `QueryControl.setMap()` --- src/app/g3w-ol/controls/querycontrol.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/app/g3w-ol/controls/querycontrol.js b/src/app/g3w-ol/controls/querycontrol.js index 5cd5149ae..cca1110d9 100644 --- a/src/app/g3w-ol/controls/querycontrol.js +++ b/src/app/g3w-ol/controls/querycontrol.js @@ -29,23 +29,20 @@ const proto = QueryControl.prototype; /** * @param {ol.Map} map * + * @fires picked fired after map `singleclick` * @listens InteractionControl~toggled + * @listens ol~singleclick */ proto.setMap = function(map) { - let eventSingleClickKey = null; + let key = null; - this.on('toggled', ({toggled}) => { + this.on('toggled', ({ toggled }) => { if (true !== toggled) { - ol.Observable.unByKey(eventSingleClickKey); - eventSingleClickKey = null; - } else if (null === eventSingleClickKey && map) { - //need to be set timeout otherwise can be get picked from other interaction - setTimeout(() => { - // register click on map event. It uses to dispatch picked event by control - eventSingleClickKey = map - .on('singleclick', throttle(evt => this.dispatchEvent({ type: 'picked', coordinates:evt.coordinate }))); - }) - + ol.Observable.unByKey(key); + key = null; + } else if (null === key && map) { + // set timeout otherwise it can be get picked by another interaction + setTimeout(() => { key = map.on('singleclick', throttle(evt => this.dispatchEvent({ type: 'picked', coordinates: evt.coordinate }))); }) } }); @@ -59,6 +56,7 @@ proto.setMap = function(map) { /** * @since 3.8.0 + * * @param event */ proto.runQuery = async function({coordinates}) { From 49c93da82d2bf6f560516e20d385948931918f8e Mon Sep 17 00:00:00 2001 From: Raruto Date: Tue, 22 Aug 2023 12:14:47 +0200 Subject: [PATCH 22/48] refactor `_handleFeatureForLayer` --- .../gui/queryresults/queryresultsservice.js | 276 +++++++++--------- 1 file changed, 133 insertions(+), 143 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index c2998b9cc..fe7fcdb39 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -470,7 +470,7 @@ class QueryResultsService extends G3WObject { } /** - * Create boxid identify to query result hmtl + * Generate a boxid identifier to query result html * * @param layer * @param feature @@ -501,18 +501,18 @@ class QueryResultsService extends G3WObject { // loop results layers.forEach(layer => { - const currentactiontoolslayer = {}; - const currentationfeaturelayer = {}; + const action_tools = {}; + const action_layer = {}; layer.features.forEach((_, idx) => { - currentactiontoolslayer[idx] = null; - currentationfeaturelayer[idx] = null; + action_tools[idx] = null; + action_layer[idx] = null; }); // set eventually layer action tool and need to be reactive this.state.layeractiontool[layer.id] = Vue.observable({ component: null, config: null }); - this.state.currentactiontools[layer.id] = Vue.observable(currentactiontoolslayer); - this.state.currentactionfeaturelayer[layer.id] = Vue.observable(currentationfeaturelayer); + this.state.currentactiontools[layer.id] = Vue.observable(action_tools); + this.state.currentactionfeaturelayer[layer.id] = Vue.observable(action_layer); const is_external_layer_or_wms = (layer.external) || (layer.source ? 'wms' === layer.source.type : false); @@ -651,8 +651,6 @@ class QueryResultsService extends G3WObject { } if (undefined === tool[idx]) { Vue.set(tool, idx, null); - } else { - tool[idx] = null; } tool[idx] = null; }); @@ -1001,145 +999,67 @@ class QueryResultsService extends G3WObject { * @since 3.9.0 */ _handleFeatureForLayer(featuresForLayer) { - const layerObj = { - editable: false, - inediting: false, - downloads: [], - infoformats: [], - filter: {}, - selection: {}, - external: false, - source: undefined, - infoformat: undefined, - formStructure: undefined, - attributes: [], - features: [], - hasgeometry: false, - show: true, - addfeaturesresults: { active: false }, - [DownloadFormats.name]: { active: false }, - expandable: true, - hasImageField: false, - error: '', - rawdata: null, // rawdata response - loading: false, - }; - const layer = featuresForLayer.layer; - - let layerAttributes, - layerRelationsAttributes, - layerTitle, - layerId, - sourceType; - - let extractRelations = false; - - if (layer instanceof Layer) { - layerObj.editable = layer.isEditable(); - layerObj.inediting = layer.isInEditing(); - layerObj.source = layer.getSource(); - layerObj.infoformats = layer.getInfoFormats(); - layerObj.infoformat = layer.getInfoFormat(); - - // set selection filter and relation if not wms - if (-1 === [ - Layer.SourceTypes.WMS, - Layer.SourceTypes.WCS, - Layer.SourceTypes.WMST - ].indexOf(layer.getSourceType()) - ) { - layerObj.filter = layer.state.filter; - layerObj.selection = layer.state.selection; - extractRelations = true; - } + const layer = featuresForLayer.layer; + const has_features = featuresForLayer.features && featuresForLayer.features.length; - layerObj.downloads = layer.getDownloadableFormats(); - - try { sourceType = layer.getSourceType() } catch(err) {} - - layerRelationsAttributes = []; - layerTitle = layer.getTitle(); - layerId = layer.getId(); - layerAttributes = ('ows' === this.state.type) /* sanitize attributes layer only if is ows */ - ? layer.getAttributes().map(attribute => { - const sanitizeAttribute = {...attribute}; - sanitizeAttribute.name = sanitizeAttribute.name.replace(/ /g, '_'); - return sanitizeAttribute - }) - : layer.getAttributes(); - - if (layer.hasFormStructure()) { - const structure = layer.getLayerEditingFormStructure(); - if (this._relations && this._relations.length) { - const getRelationFieldsFromFormStructure = (node) => { - if (!node.nodes) { - node.name ? node.relation = true : null; - } else { - for (const _node of node.nodes) { - getRelationFieldsFromFormStructure(_node); - } - } - }; - for (const node of structure) { - getRelationFieldsFromFormStructure(node); - } - } - layerObj.formStructure = { - structure, - fields: layer.getFields().filter(field => field.show), // get features show - }; + const is_layer = layer instanceof Layer; + const is_vector = layer instanceof ol.layer.Vector; + const is_string = 'string' === typeof layer || layer instanceof String; + + let sourceType; + + if (is_string) { + sourceType = Layer.LayerTypes.VECTOR; + } else { + try { + sourceType = layer.getSourceType(); + } catch (error) { + console.warn('uknown source type for layer:', layer) } - } else if (layer instanceof ol.layer.Vector) { - layerObj.selection = layer.selection; - layerAttributes = layer.getProperties(); - layerRelationsAttributes = []; - layerTitle = layer.get('name'); - layerId = layer.get('id'); - layerObj.external = true; - } else if ('string' === typeof layer || layer instanceof String) { - const feature = featuresForLayer.features[0]; - const split_layer_name = layer.split('_'); - sourceType = Layer.LayerTypes.VECTOR; - layerAttributes = (feature ? feature.getProperties() : []); - layerRelationsAttributes = []; - layerId = layer; - layerObj.external = true; - layerTitle = (split_layer_name.length > 4) - ? split_layer_name.slice(0, split_layer_name.length -4).join(' ') - : layer; } - // set other properties for layerObj + // set selection filter and relation if not wms + const not_wms = -1 === [ + Layer.SourceTypes.WMS, + Layer.SourceTypes.WCS, + Layer.SourceTypes.WMST + ].indexOf(sourceType); + + const name = is_string && layer.split('_'); - layerObj.title = layerTitle; - layerObj.id = layerId; - layerObj.atlas = this.getAtlasByLayerId(layerId); - layerObj.relationsattributes = layerRelationsAttributes; + const layerId = (is_layer ? layer.getId() : undefined) || (is_vector ? layer.get('id') : undefined) || (is_string ? layer : undefined); - /** @FIXME add description */ - if (featuresForLayer.rawdata) { - layerObj.rawdata = featuresForLayer.rawdata; - return layerObj; - } + const layerObj = { + id: layerId, + features: [], + hasgeometry: false, + hasImageField: false, + loading: false, + show: true, + expandable: true, + addfeaturesresults: { active: false }, + [DownloadFormats.name]: { active: false }, + external: (is_vector || is_string), + editable: is_layer ? layer.isEditable() : false, + inediting: is_layer ? layer.isInEditing() : false, + source: is_layer ? layer.getSource() : undefined, + infoformats: is_layer ? layer.getInfoFormats() : [], + infoformat: is_layer ? layer.getInfoFormat() : undefined, + downloads: is_layer ? layer.getDownloadableFormats() : [], + formStructure: is_layer ? this._parseLayerObjFormStructure(layer) : undefined, + relationsattributes: (is_layer || is_vector || is_string) ? [] : undefined, + filter: (is_layer && not_wms) ? layer.state.filter : {}, + selection: (is_layer && not_wms ? layer.state.selection : {}) || (is_vector ? layer.selection : {}), + title: (is_layer ? layer.getTitle() : undefined) || (is_vector ? layer.get('name') : undefined) || (is_string && name ? (name.length > 4 ? name.slice(0, name.length -4).join(' ') : layer) : undefined), + attributes: has_features ? this._parseLayerObjAttributes(layer, featuresForLayer.features, sourceType) : [], + atlas: this.getAtlasByLayerId(layerId), + rawdata: featuresForLayer.rawdata ? featuresForLayer.rawdata : null, // rawdata response + error: featuresForLayer.error ? featuresForLayer.error : '', + }; /** @FIXME add description */ - if (featuresForLayer.features && featuresForLayer.features.length) { - const layerSpecialAttributesName = - (layer instanceof Layer) - ? layerAttributes.filter(attribute => { - try { - return ('_' === attribute.name[0] || Number.isInteger(1*attribute.name[0])) - } catch(e) { - return false; - } - }).map(attribute => ({ alias: attribute.name.replace(/_/, ''), name: attribute.name })) - : []; - if (layerSpecialAttributesName.length) { - featuresForLayer.features - .forEach(feature => this._setSpecialAttributesFeatureProperty(layerSpecialAttributesName, feature)); - } - layerObj.attributes = this._parseAttributes(layerAttributes, featuresForLayer.features[0], sourceType); + if (has_features && !featuresForLayer.rawdata) { layerObj.attributes .forEach(attribute => { if (layerObj.formStructure) { @@ -1167,17 +1087,87 @@ class QueryResultsService extends G3WObject { show: true }); }); - return layerObj; } /** @FIXME missing return type ? */ - /** @FIXME add description */ - if (featuresForLayer.error) { - layerObj.error = featuresForLayer.error; + if (has_features || featuresForLayer.rawdata) { + return layerObj; } } + /** + * @since 3.9.0 + */ + _parseLayerObjFormStructure(layer) { + const structure = layer.hasFormStructure() && layer.getLayerEditingFormStructure(); + if (false === (structure && this._relations && this._relations.length)) { + return; + } + const setRelationField = (node) => { + if (node.nodes) { + for (const _node of node.nodes) { + setRelationField(_node); + } + } else if (node.name) { + node.relation = true; + } + }; + for (const node of structure) { + setRelationField(node); + } + return { + structure, + fields: layer.getFields().filter(field => field.show), // get features show + }; + } + + /** + * @since 3.9.0 + */ + _parseLayerObjAttributes(layer, features, sourceType) { + + let layerAttrs; + + if (layer instanceof Layer && 'ows' !== this.state.type) { + layerAttrs = layer.getAttributes(); + } + + /* Sanitize OWS Layer attributes */ + if (layer instanceof Layer && 'ows' === this.state.type) { + layerAttrs = layer + .getAttributes() + .map(attribute => { + const sanitizeAttribute = {...attribute}; + sanitizeAttribute.name = sanitizeAttribute.name.replace(/ /g, '_'); + return sanitizeAttribute + }); + } + + if (layer instanceof ol.layer.Vector) { + layerAttrs = layer.getProperties(); + } + + if ('string' === typeof layer || layer instanceof String) { + layerAttrs = (features[0] ? features[0].getProperties() : []) + } + + const specialAttrs = + (layer instanceof Layer) + ? layerAttrs.filter(attr => { + try { + return ('_' === attr.name[0] || Number.isInteger(1 * attr.name[0])) + } catch(e) { + return false; + } + }).map(attr => ({ alias: attr.name.replace(/_/, ''), name: attr.name })) + : []; + if (specialAttrs.length) { + features.forEach(f => this._setSpecialAttributesFeatureProperty(specialAttrs, f)); + } + return this._parseAttributes(layerAttrs, features[0], sourceType); + } + /** * Set special attributes * From 0472a492a1f9e4dd574d73ca79f8443e983126a1 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Tue, 22 Aug 2023 12:33:20 +0200 Subject: [PATCH 23/48] :recycle: Set right event 'picked' on queryControl --- src/app/g3w-ol/controls/querycontrol.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/g3w-ol/controls/querycontrol.js b/src/app/g3w-ol/controls/querycontrol.js index cca1110d9..b3ef548e3 100644 --- a/src/app/g3w-ol/controls/querycontrol.js +++ b/src/app/g3w-ol/controls/querycontrol.js @@ -41,8 +41,7 @@ proto.setMap = function(map) { ol.Observable.unByKey(key); key = null; } else if (null === key && map) { - // set timeout otherwise it can be get picked by another interaction - setTimeout(() => { key = map.on('singleclick', throttle(evt => this.dispatchEvent({ type: 'picked', coordinates: evt.coordinate }))); }) + key = this.getInteraction().on('picked', throttle(evt => this.runQuery({coordinates: evt.coordinate }))); } }); From 3e4f491acb65f44033d2c7294a77af295f0a89fa Mon Sep 17 00:00:00 2001 From: Raruto Date: Tue, 22 Aug 2023 13:38:33 +0200 Subject: [PATCH 24/48] comments --- src/app/g3w-ol/controls/querycontrol.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/g3w-ol/controls/querycontrol.js b/src/app/g3w-ol/controls/querycontrol.js index b3ef548e3..492e05a8b 100644 --- a/src/app/g3w-ol/controls/querycontrol.js +++ b/src/app/g3w-ol/controls/querycontrol.js @@ -29,9 +29,8 @@ const proto = QueryControl.prototype; /** * @param {ol.Map} map * - * @fires picked fired after map `singleclick` + * @fires picked fired after map `singleclick` ? * @listens InteractionControl~toggled - * @listens ol~singleclick */ proto.setMap = function(map) { let key = null; From 7c937c398f15e65b86bbab7035403e0e79edd08d Mon Sep 17 00:00:00 2001 From: volterra79 Date: Tue, 22 Aug 2023 14:38:26 +0200 Subject: [PATCH 25/48] :bug: Fix setCurrentActionLayerFeatureTool --- .../gui/queryresults/queryresultsservice.js | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index fe7fcdb39..b22b7ee8b 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -382,23 +382,6 @@ class QueryResultsService extends G3WObject { this.updateLayerResultFeatures({ id: layer.id, external: layer.external, features: [feature] }); } - /** - * Wrapper for download - * - * @param downloadFnc - * @param options - */ - async downloadApplicationWrapper(downloadFnc, options = {}) { - const download_caller_id = ApplicationService.setDownload(true); - GUI.setLoadingContent(true); - try { - await downloadFnc(options); - } catch(err) { - GUI.showUserMessage({ type: 'alert', message: err || 'server_error', textMessage: !!err }) - } - ApplicationService.setDownload(false, download_caller_id); - GUI.setLoadingContent(false); - } /** * Loop over response features based on layer response and @@ -606,6 +589,7 @@ class QueryResultsService extends G3WObject { * @param opts.layer current layer * @param opts.index feature index * @param opts.value component value or null + * @param opts.component vue component */ setCurrentActionLayerFeatureTool({ layer, @@ -613,16 +597,22 @@ class QueryResultsService extends G3WObject { index, component = null } = {}) { - const tools = this.state.currentactiontools[layer.id]; + const tools = this.state.currentactiontools[layer.id]; //get current action tools const feats = this.state.currentactionfeaturelayer[layer.id]; - const toggled = feats[index].state.toggled[index]; + feats[index] = component ? action : null; + tools[index] = component; //set component + + //need to check if pass component and + if ( + tools[index] && // if component is set + action.id !== feats[index].id && // same action + feats[index].toggleable // check if toggleable + ) { + feats[index].state.toggled[index] = false; + } - feats[index].state.toggled[index] = (component && tools[index] && action.id !== feats[index].id && feats[index].toggleable) ? toggled : false; - feats[index] = component ? action : null; - tools[index] = component; } - /** * @param opts.id action layer id * @param opts.layer layer @@ -1541,6 +1531,24 @@ class QueryResultsService extends G3WObject { }) } + /** + * Wrapper for download + * + * @param downloadFnc + * @param options + */ + async downloadApplicationWrapper(downloadFnc, options = {}) { + const download_caller_id = ApplicationService.setDownload(true); + GUI.setLoadingContent(true); + try { + await downloadFnc(options); + } catch(err) { + GUI.showUserMessage({ type: 'alert', message: err || 'server_error', textMessage: !!err }) + } + ApplicationService.setDownload(false, download_caller_id); + GUI.setLoadingContent(false); + } + /** * @FIXME add description * @@ -1586,6 +1594,7 @@ class QueryResultsService extends G3WObject { ApplicationService.setDownload(false, download_caller_id); GUI.setLoadingContent(false); + const downloadsactions = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); /** @FIXME add description */ @@ -2284,7 +2293,6 @@ class QueryResultsService extends G3WObject { _setActionMultiDownloadFeature(layer) { const downloads = []; - layer.downloads .forEach(format => { downloads.push({ From 05e84d527efa936ba05a41a706a260d511bbd16b Mon Sep 17 00:00:00 2001 From: volterra79 Date: Tue, 22 Aug 2023 15:23:41 +0200 Subject: [PATCH 26/48] Check and correct some refactoring code --- .../gui/queryresults/queryresultsservice.js | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index b22b7ee8b..347b53739 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -991,17 +991,17 @@ class QueryResultsService extends G3WObject { _handleFeatureForLayer(featuresForLayer) { const layer = featuresForLayer.layer; - const has_features = featuresForLayer.features && featuresForLayer.features.length; + const has_features = featuresForLayer.features && featuresForLayer.features.length > 0; const is_layer = layer instanceof Layer; - const is_vector = layer instanceof ol.layer.Vector; - const is_string = 'string' === typeof layer || layer instanceof String; + const is_vector = layer instanceof ol.layer.Vector; //instance of openlayers layer Vector Class + const is_string = 'string' === typeof layer || layer instanceof String; // can be created by string let sourceType; if (is_string) { sourceType = Layer.LayerTypes.VECTOR; - } else { + } else if (is_layer) { try { sourceType = layer.getSourceType(); } catch (error) { @@ -1010,7 +1010,7 @@ class QueryResultsService extends G3WObject { } // set selection filter and relation if not wms - const not_wms = -1 === [ + const not_wms_wcs_wmst = -1 === [ Layer.SourceTypes.WMS, Layer.SourceTypes.WCS, Layer.SourceTypes.WMST @@ -1018,7 +1018,9 @@ class QueryResultsService extends G3WObject { const name = is_string && layer.split('_'); - const layerId = (is_layer ? layer.getId() : undefined) || (is_vector ? layer.get('id') : undefined) || (is_string ? layer : undefined); + const layerId = (is_layer ? layer.getId() : undefined) || + (is_vector ? layer.get('id') : undefined) || + (is_string ? layer : undefined); const layerObj = { id: layerId, @@ -1031,17 +1033,21 @@ class QueryResultsService extends G3WObject { addfeaturesresults: { active: false }, [DownloadFormats.name]: { active: false }, external: (is_vector || is_string), - editable: is_layer ? layer.isEditable() : false, - inediting: is_layer ? layer.isInEditing() : false, - source: is_layer ? layer.getSource() : undefined, - infoformats: is_layer ? layer.getInfoFormats() : [], - infoformat: is_layer ? layer.getInfoFormat() : undefined, - downloads: is_layer ? layer.getDownloadableFormats() : [], - formStructure: is_layer ? this._parseLayerObjFormStructure(layer) : undefined, - relationsattributes: (is_layer || is_vector || is_string) ? [] : undefined, - filter: (is_layer && not_wms) ? layer.state.filter : {}, - selection: (is_layer && not_wms ? layer.state.selection : {}) || (is_vector ? layer.selection : {}), - title: (is_layer ? layer.getTitle() : undefined) || (is_vector ? layer.get('name') : undefined) || (is_string && name ? (name.length > 4 ? name.slice(0, name.length -4).join(' ') : layer) : undefined), + editable: is_layer ? layer.isEditable() : false, + inediting: is_layer ? layer.isInEditing() : false, + source: is_layer ? layer.getSource() : undefined, + infoformats: is_layer ? layer.getInfoFormats() : [], + infoformat: is_layer ? layer.getInfoFormat() : undefined, + downloads: is_layer ? layer.getDownloadableFormats() : [], + formStructure: is_layer ? this._parseLayerObjFormStructure(layer) : undefined, + relationsattributes: (is_layer || is_vector || is_string) ? [] : undefined, + filter: (is_layer && not_wms_wcs_wmst) ? layer.state.filter : {}, + selection: (is_layer && not_wms_wcs_wmst ? layer.state.selection : undefined) || + (is_vector ? layer.selection : undefined) || + {}, + title: (is_layer ? layer.getTitle() : undefined) || + (is_vector ? layer.get('name') : undefined) || + (is_string && name ? (name.length > 4 ? name.slice(0, name.length - 4).join(' ') : layer) : undefined), attributes: has_features ? this._parseLayerObjAttributes(layer, featuresForLayer.features, sourceType) : [], atlas: this.getAtlasByLayerId(layerId), rawdata: featuresForLayer.rawdata ? featuresForLayer.rawdata : null, // rawdata response @@ -1052,11 +1058,12 @@ class QueryResultsService extends G3WObject { if (has_features && !featuresForLayer.rawdata) { layerObj.attributes .forEach(attribute => { - if (layerObj.formStructure) { - const relationField = layer.getFields().find(field => field.name === attribute.name); // need to check all field also show false - if (!relationField) { - layerObj.formStructure.fields.push(attribute); - } + if ( + layerObj.formStructure && + undefined !== layer.getFields().find(field => field.name === attribute.name) + ) + { + layerObj.formStructure.fields.push(attribute); } if (attribute.type === 'image') { layerObj.hasImageField = true; @@ -1079,10 +1086,7 @@ class QueryResultsService extends G3WObject { }); } - /** @FIXME missing return type ? */ - if (has_features || featuresForLayer.rawdata) { - return layerObj; - } + return layerObj; } From 48fa11a65dd1866e3be3667e44570c7b29488bc6 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Tue, 22 Aug 2023 15:47:48 +0200 Subject: [PATCH 27/48] :sparkles: Create common downloadApplicationWrapper to run download --- .../gui/queryresults/queryresultsservice.js | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 347b53739..ee878f602 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -1416,7 +1416,6 @@ class QueryResultsService extends G3WObject { field_name = field_name || '$id'; const values = features.map(feat => feat.attributes['$id' === field_name ? G3W_FID : field_name]); - const download_caller_id = ApplicationService.setDownload(true); return this.printService .printAtlas({ @@ -1426,9 +1425,10 @@ class QueryResultsService extends G3WObject { download: true }) .then(({url}) => { - downloadFile({ url, filename: template, mime_type: 'application/pdf' }) - .catch(error => { GUI.showUserMessage({ type: 'alert', error }) }) - .finally(() => { ApplicationService.setDownload(false, download_caller_id); GUI.setLoadingContent(false); }); + this.downloadApplicationWrapper( + downloadFile, + { url, filename: template, mime_type: 'application/pdf' } + ) }); } @@ -1585,19 +1585,19 @@ class QueryResultsService extends G3WObject { this.setLayerActionTool({ layer }); } - const download_caller_id = ApplicationService.setDownload(true); - - GUI.setLoadingContent(true); - - try { - await CatalogLayersStoresRegistry.getLayerById(layer.id).getDownloadFilefromDownloadDataType(type, { data }) || Promise.resolve(); - } catch(err) { - GUI.notify.error(err || t("info.server_error")); - } - - ApplicationService.setDownload(false, download_caller_id); - - GUI.setLoadingContent(false); + await this.downloadApplicationWrapper( + ({layer, type, data}= {}) => { + return CatalogLayersStoresRegistry + .getLayerById(layer.id) + .getDownloadFilefromDownloadDataType(type, { data }) || + Promise.resolve(); + }, + { + layer, + type, + data + } + ); const downloadsactions = this.state.layersactions[layer.id].find(action => 'downloads' === action.id); From 1b81a60e684c7950c1ac9a424bae31e5b7152508 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Tue, 22 Aug 2023 15:56:58 +0200 Subject: [PATCH 28/48] :recycle: Move downloadApplicationWrapper from queryresultsservice.js to index.prod.js as GUI method --- .../gui/queryresults/queryresultsservice.js | 22 ++----------------- src/index.prod.js | 20 ++++++++++++++++- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index ee878f602..e3a3b5d4d 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -1425,7 +1425,7 @@ class QueryResultsService extends G3WObject { download: true }) .then(({url}) => { - this.downloadApplicationWrapper( + GUI.downloadApplicationWrapper( downloadFile, { url, filename: template, mime_type: 'application/pdf' } ) @@ -1535,24 +1535,6 @@ class QueryResultsService extends G3WObject { }) } - /** - * Wrapper for download - * - * @param downloadFnc - * @param options - */ - async downloadApplicationWrapper(downloadFnc, options = {}) { - const download_caller_id = ApplicationService.setDownload(true); - GUI.setLoadingContent(true); - try { - await downloadFnc(options); - } catch(err) { - GUI.showUserMessage({ type: 'alert', message: err || 'server_error', textMessage: !!err }) - } - ApplicationService.setDownload(false, download_caller_id); - GUI.setLoadingContent(false); - } - /** * @FIXME add description * @@ -1585,7 +1567,7 @@ class QueryResultsService extends G3WObject { this.setLayerActionTool({ layer }); } - await this.downloadApplicationWrapper( + await GUI.downloadApplicationWrapper( ({layer, type, data}= {}) => { return CatalogLayersStoresRegistry .getLayerById(layer.id) diff --git a/src/index.prod.js b/src/index.prod.js index df085c76a..5cf664a0d 100644 --- a/src/index.prod.js +++ b/src/index.prod.js @@ -1067,7 +1067,25 @@ const ApplicationTemplate = function({ApplicationService}) { title: '', perc: 100 }); - } + }; + + /** + * Wrapper for download + * @since 3.9.0 + * @param downloadFnc function to call + * @param options Object parameters + */ + GUI.downloadApplicationWrapper = async function(downloadFnc, options = {}) { + const download_caller_id = ApplicationService.setDownload(true); + GUI.setLoadingContent(true); + try { + await downloadFnc(options); + } catch(err) { + GUI.showUserMessage({ type: 'alert', message: err || 'server_error', textMessage: !!err }) + } + ApplicationService.setDownload(false, download_caller_id); + GUI.setLoadingContent(false); + }; }; base(this); From 06512c8856a523c9dc6dca196687a51a429921f3 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 08:24:34 +0200 Subject: [PATCH 29/48] move application wrapper --- src/index.prod.js | 17 ----------------- src/services/gui.js | 23 ++++++++++++++++++++++- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/index.prod.js b/src/index.prod.js index 5cf664a0d..3ddb55524 100644 --- a/src/index.prod.js +++ b/src/index.prod.js @@ -1069,23 +1069,6 @@ const ApplicationTemplate = function({ApplicationService}) { }); }; - /** - * Wrapper for download - * @since 3.9.0 - * @param downloadFnc function to call - * @param options Object parameters - */ - GUI.downloadApplicationWrapper = async function(downloadFnc, options = {}) { - const download_caller_id = ApplicationService.setDownload(true); - GUI.setLoadingContent(true); - try { - await downloadFnc(options); - } catch(err) { - GUI.showUserMessage({ type: 'alert', message: err || 'server_error', textMessage: !!err }) - } - ApplicationService.setDownload(false, download_caller_id); - GUI.setLoadingContent(false); - }; }; base(this); diff --git a/src/services/gui.js b/src/services/gui.js index b302ea969..3da797bb8 100644 --- a/src/services/gui.js +++ b/src/services/gui.js @@ -1,4 +1,5 @@ -import RouterService from 'services/router'; +import ApplicationService from 'services/application'; +import RouterService from 'services/router'; import ComponentsRegistry from 'store/components'; const { base, inherit, noop } = require('core/utils/utils'); @@ -91,4 +92,24 @@ function GUI() { inherit(GUI, G3WObject); +/** + * Wrapper for download + * + * @param { Function } downloadFnc function to call + * @param { Object } options Object parameters + * + * @since 3.9.0 + */ +GUI.prototype.downloadWrapper = async function(downloadFnc, options = {}) { + const download_caller_id = ApplicationService.setDownload(true); + GUI.setLoadingContent(true); + try { + await downloadFnc(options); + } catch(err) { + GUI.showUserMessage({ type: 'alert', message: err || 'server_error', textMessage: !!err }) + } + ApplicationService.setDownload(false, download_caller_id); + GUI.setLoadingContent(false); +}; + export default new GUI(); From c711a7e4a6b46376e4a3422585f6f136e9da97d4 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 08:25:02 +0200 Subject: [PATCH 30/48] extract `_hasLayerObjGeometry` and `_parseLayerObjFeatures` --- .../gui/queryresults/queryresultsservice.js | 82 ++++++++++++------- 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index e3a3b5d4d..d03908b1e 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -597,16 +597,16 @@ class QueryResultsService extends G3WObject { index, component = null } = {}) { - const tools = this.state.currentactiontools[layer.id]; //get current action tools + const tools = this.state.currentactiontools[layer.id]; // get current action tools const feats = this.state.currentactionfeaturelayer[layer.id]; feats[index] = component ? action : null; - tools[index] = component; //set component + tools[index] = component; // set component - //need to check if pass component and + // need to check if pass component and if ( - tools[index] && // if component is set + tools[index] && // if component is set action.id !== feats[index].id && // same action - feats[index].toggleable // check if toggleable + feats[index].toggleable // check if toggleable ) { feats[index].state.toggled[index] = false; } @@ -983,7 +983,7 @@ class QueryResultsService extends G3WObject { * * @param featuresForLayer.layer * @param featuresForLayer.features - * @param featuresForLayer.rawdata + * @param featuresForLayer.rawdata rawdata response * @param featuresForLayer.error * * @since 3.9.0 @@ -994,7 +994,7 @@ class QueryResultsService extends G3WObject { const has_features = featuresForLayer.features && featuresForLayer.features.length > 0; const is_layer = layer instanceof Layer; - const is_vector = layer instanceof ol.layer.Vector; //instance of openlayers layer Vector Class + const is_vector = layer instanceof ol.layer.Vector; // instance of openlayers layer Vector Class const is_string = 'string' === typeof layer || layer instanceof String; // can be created by string let sourceType; @@ -1022,10 +1022,11 @@ class QueryResultsService extends G3WObject { (is_vector ? layer.get('id') : undefined) || (is_string ? layer : undefined); + const layerObj = { id: layerId, - features: [], - hasgeometry: false, + features: has_features ? this._parseLayerObjFeatures(featuresForLayer.features, featuresForLayer.rawdata) : [], + hasgeometry: has_features ? this._hasLayerObjGeometry(featuresForLayer.features, featuresForLayer.rawdata) : false, hasImageField: false, loading: false, show: true, @@ -1036,8 +1037,8 @@ class QueryResultsService extends G3WObject { editable: is_layer ? layer.isEditable() : false, inediting: is_layer ? layer.isInEditing() : false, source: is_layer ? layer.getSource() : undefined, - infoformats: is_layer ? layer.getInfoFormats() : [], infoformat: is_layer ? layer.getInfoFormat() : undefined, + infoformats: is_layer ? layer.getInfoFormats() : [], downloads: is_layer ? layer.getDownloadableFormats() : [], formStructure: is_layer ? this._parseLayerObjFormStructure(layer) : undefined, relationsattributes: (is_layer || is_vector || is_string) ? [] : undefined, @@ -1045,12 +1046,12 @@ class QueryResultsService extends G3WObject { selection: (is_layer && not_wms_wcs_wmst ? layer.state.selection : undefined) || (is_vector ? layer.selection : undefined) || {}, - title: (is_layer ? layer.getTitle() : undefined) || + title: (is_layer ? layer.getTitle() : undefined) || (is_vector ? layer.get('name') : undefined) || (is_string && name ? (name.length > 4 ? name.slice(0, name.length - 4).join(' ') : layer) : undefined), attributes: has_features ? this._parseLayerObjAttributes(layer, featuresForLayer.features, sourceType) : [], atlas: this.getAtlasByLayerId(layerId), - rawdata: featuresForLayer.rawdata ? featuresForLayer.rawdata : null, // rawdata response + rawdata: featuresForLayer.rawdata ? featuresForLayer.rawdata : null, error: featuresForLayer.error ? featuresForLayer.error : '', }; @@ -1069,27 +1070,45 @@ class QueryResultsService extends G3WObject { layerObj.hasImageField = true; } }); - featuresForLayer.features - .forEach(feature => { - const props = this.getFeaturePropertiesAndGeometry(feature); - if (props.geometry) { - layerObj.hasgeometry = true; - } - layerObj.features - .push({ - id: layerObj.external ? feature.getId() : props.id, - attributes: props.properties, - geometry: props.geometry, - selection: props.selection, - show: true - }); - }); } return layerObj; } + /** + * @since 3.9.0 + */ + _parseLayerObjFeatures(features, rawdata) { + const features = []; + if (!rawdata) { + features.forEach(f => { + const props = this.getFeaturePropertiesAndGeometry(f); + features + .push({ + id: layerObj.external ? f.getId() : props.id, + attributes: props.properties, + geometry: props.geometry, + selection: props.selection, + show: true, + }); + }); + } + return features; + } + + /** + * @since 3.9.0 + */ + _hasLayerObjGeometry(features, rawdata) { + return !rawdata && features.some(f => { + const props = this.getFeaturePropertiesAndGeometry(feature); + if (props.geometry) { + return true; + } + }); + } + /** * @since 3.9.0 */ @@ -1425,7 +1444,7 @@ class QueryResultsService extends G3WObject { download: true }) .then(({url}) => { - GUI.downloadApplicationWrapper( + GUI.downloadWrapper( downloadFile, { url, filename: template, mime_type: 'application/pdf' } ) @@ -1567,7 +1586,7 @@ class QueryResultsService extends G3WObject { this.setLayerActionTool({ layer }); } - await GUI.downloadApplicationWrapper( + await GUI.downloadWrapper( ({layer, type, data}= {}) => { return CatalogLayersStoresRegistry .getLayerById(layer.id) @@ -2406,6 +2425,11 @@ class QueryResultsService extends G3WObject { */ QueryResultsService.prototype.addRemoveFeaturesToLayerResult = deprecate(QueryResultsService.prototype.updateLayerResultFeatures, '[G3W-CLIENT] QueryResultsService::addRemoveFeaturesToLayerResult(layer) is deprecated'); +/** + * @deprecated since 3.9.0 Will be deleted in 4.x. Use GUI::downloadWrapper(downloadFnc, options) instead + */ +QueryResultsService.prototype.downloadApplicationWrapper = deprecate(GUI.prototype.downloadWrapper, '[G3W-CLIENT] QueryResultsService::downloadApplicationWrapper(layer) is deprecated'); + /** * Alias functions * From 41f435f651b278e5488598ffc421f96ee565ca7f Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 08:28:44 +0200 Subject: [PATCH 31/48] params destrcuturing: `featuresForLayer` --- src/app/gui/queryresults/queryresultsservice.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index d03908b1e..18319bccd 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -988,10 +988,9 @@ class QueryResultsService extends G3WObject { * * @since 3.9.0 */ - _handleFeatureForLayer(featuresForLayer) { + _handleFeatureForLayer({ layer, features, rawdata, error }) { - const layer = featuresForLayer.layer; - const has_features = featuresForLayer.features && featuresForLayer.features.length > 0; + const has_features = features && features.length > 0; const is_layer = layer instanceof Layer; const is_vector = layer instanceof ol.layer.Vector; // instance of openlayers layer Vector Class @@ -1025,8 +1024,9 @@ class QueryResultsService extends G3WObject { const layerObj = { id: layerId, - features: has_features ? this._parseLayerObjFeatures(featuresForLayer.features, featuresForLayer.rawdata) : [], - hasgeometry: has_features ? this._hasLayerObjGeometry(featuresForLayer.features, featuresForLayer.rawdata) : false, + attributes: has_features ? this._parseLayerObjAttributes(layer, features, sourceType) : [], + features: has_features ? this._parseLayerObjFeatures(features, rawdata) : [], + hasgeometry: has_features ? this._hasLayerObjGeometry(features, rawdata) : false, hasImageField: false, loading: false, show: true, @@ -1049,14 +1049,13 @@ class QueryResultsService extends G3WObject { title: (is_layer ? layer.getTitle() : undefined) || (is_vector ? layer.get('name') : undefined) || (is_string && name ? (name.length > 4 ? name.slice(0, name.length - 4).join(' ') : layer) : undefined), - attributes: has_features ? this._parseLayerObjAttributes(layer, featuresForLayer.features, sourceType) : [], atlas: this.getAtlasByLayerId(layerId), - rawdata: featuresForLayer.rawdata ? featuresForLayer.rawdata : null, - error: featuresForLayer.error ? featuresForLayer.error : '', + rawdata: rawdata ? rawdata : null, + error: error ? error : '', }; /** @FIXME add description */ - if (has_features && !featuresForLayer.rawdata) { + if (has_features && !rawdata) { layerObj.attributes .forEach(attribute => { if ( From 5fc858ee076d1828cbffb6b62de9432674b8eeba Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 08:32:42 +0200 Subject: [PATCH 32/48] simplify condition --- src/app/gui/queryresults/queryresultsservice.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 18319bccd..b97721896 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -1026,7 +1026,7 @@ class QueryResultsService extends G3WObject { id: layerId, attributes: has_features ? this._parseLayerObjAttributes(layer, features, sourceType) : [], features: has_features ? this._parseLayerObjFeatures(features, rawdata) : [], - hasgeometry: has_features ? this._hasLayerObjGeometry(features, rawdata) : false, + hasgeometry: this._hasLayerObjGeometry(features, rawdata), hasImageField: false, loading: false, show: true, @@ -1100,7 +1100,7 @@ class QueryResultsService extends G3WObject { * @since 3.9.0 */ _hasLayerObjGeometry(features, rawdata) { - return !rawdata && features.some(f => { + return Array.isArray(features) && !rawdata && features.some(f => { const props = this.getFeaturePropertiesAndGeometry(feature); if (props.geometry) { return true; From c2aff7cd6aa2ccc31f569bedecdd2ab2bee0fa33 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 09:46:55 +0200 Subject: [PATCH 33/48] refactor `setQueryResponse` - remove function `_digestFeaturesForLayers()` - rename function `_handleFeatureForLayer()` into `_responseToLayer()` - extract function `_hasLayerObjGeometry()` - extract function `_hasLayerObjImageField()` - refactor function `_parseLayerObjFormStructure()` --- .../gui/queryresults/queryresultsservice.js | 151 +++++++++--------- 1 file changed, 73 insertions(+), 78 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index b97721896..73c0e1b24 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -951,33 +951,6 @@ class QueryResultsService extends G3WObject { this.state.querytitle = querytitle || ""; } - /** - * Converts response from DataProvider into a QueryResult component data structure - * - * @param featuresForLayers: Array contains for each layer features - * - * @returns {[]} - */ - _digestFeaturesForLayers(featuresForLayers = []) { - const layers = []; - /** - * @TODO find out why we need such a level of depth (ie. a nested foreach + triple variables named `featuresForLayer` ?) - */ - featuresForLayers.forEach(featuresForLayer => { - ( - Array.isArray(featuresForLayer) - ? featuresForLayer - : [featuresForLayer] - ).forEach(featuresForLayer => { - const layer = this._handleFeatureForLayer(featuresForLayer); - if (layer) { - layers.push(layer) - } - }); - }); - return layers; - } - /** * Convert response from server * @@ -988,7 +961,7 @@ class QueryResultsService extends G3WObject { * * @since 3.9.0 */ - _handleFeatureForLayer({ layer, features, rawdata, error }) { + _responseToLayer({ layer, features, rawdata, error }) { const has_features = features && features.length > 0; @@ -1017,73 +990,80 @@ class QueryResultsService extends G3WObject { const name = is_string && layer.split('_'); - const layerId = (is_layer ? layer.getId() : undefined) || + const id = (is_layer ? layer.getId() : undefined) || (is_vector ? layer.get('id') : undefined) || (is_string ? layer : undefined); + const attributes = has_features ? this._parseLayerObjAttributes(layer, features, sourceType) : []; const layerObj = { - id: layerId, - attributes: has_features ? this._parseLayerObjAttributes(layer, features, sourceType) : [], - features: has_features ? this._parseLayerObjFeatures(features, rawdata) : [], + id, + attributes, + features: has_features ? this._parseLayerObjFeatures(features, rawdata) : [], hasgeometry: this._hasLayerObjGeometry(features, rawdata), - hasImageField: false, + hasImageField: this._hasLayerObjImageField(features, rawdata, attributes), loading: false, show: true, expandable: true, addfeaturesresults: { active: false }, [DownloadFormats.name]: { active: false }, external: (is_vector || is_string), - editable: is_layer ? layer.isEditable() : false, - inediting: is_layer ? layer.isInEditing() : false, - source: is_layer ? layer.getSource() : undefined, - infoformat: is_layer ? layer.getInfoFormat() : undefined, - infoformats: is_layer ? layer.getInfoFormats() : [], - downloads: is_layer ? layer.getDownloadableFormats() : [], - formStructure: is_layer ? this._parseLayerObjFormStructure(layer) : undefined, - relationsattributes: (is_layer || is_vector || is_string) ? [] : undefined, - filter: (is_layer && not_wms_wcs_wmst) ? layer.state.filter : {}, + editable: is_layer ? layer.isEditable() : false, + inediting: is_layer ? layer.isInEditing() : false, + source: is_layer ? layer.getSource() : undefined, + infoformat: is_layer ? layer.getInfoFormat() : undefined, + infoformats: is_layer ? layer.getInfoFormats() : [], + downloads: is_layer ? layer.getDownloadableFormats() : [], + formStructure: is_layer ? this._parseLayerObjFormStructure(layer, features, rawdata, attributes) : undefined, + relationsattributes: (is_layer || is_vector || is_string) ? [] : undefined, + filter: (is_layer && not_wms_wcs_wmst) ? layer.state.filter : {}, selection: (is_layer && not_wms_wcs_wmst ? layer.state.selection : undefined) || (is_vector ? layer.selection : undefined) || {}, title: (is_layer ? layer.getTitle() : undefined) || (is_vector ? layer.get('name') : undefined) || (is_string && name ? (name.length > 4 ? name.slice(0, name.length - 4).join(' ') : layer) : undefined), - atlas: this.getAtlasByLayerId(layerId), + atlas: this.getAtlasByLayerId(id), rawdata: rawdata ? rawdata : null, error: error ? error : '', }; - /** @FIXME add description */ - if (has_features && !rawdata) { - layerObj.attributes - .forEach(attribute => { - if ( - layerObj.formStructure && - undefined !== layer.getFields().find(field => field.name === attribute.name) - ) - { - layerObj.formStructure.fields.push(attribute); - } - if (attribute.type === 'image') { - layerObj.hasImageField = true; - } - }); - } - return layerObj; + } + /** + * @since 3.9.0 + */ + _hasLayerObjGeometry(features, rawdata) { + return Array.isArray(features) && !rawdata && features.some(f => { + const props = this.getFeaturePropertiesAndGeometry(feature); + if (props.geometry) { + return true; + } + }); + } + + /** + * @since 3.9.0 + */ + _hasLayerObjImageField(features, rawdata, attributes) { + /** @FIXME add description */ + return Array.isArray(features) && features.length && !rawdata && attributes.some(attr => { + if ('image' === attr.type) { + return true; + } + }); } /** * @since 3.9.0 */ _parseLayerObjFeatures(features, rawdata) { - const features = []; + const _features = []; if (!rawdata) { features.forEach(f => { const props = this.getFeaturePropertiesAndGeometry(f); - features + _features .push({ id: layerObj.external ? f.getId() : props.id, attributes: props.properties, @@ -1093,25 +1073,13 @@ class QueryResultsService extends G3WObject { }); }); } - return features; + return _features; } /** * @since 3.9.0 */ - _hasLayerObjGeometry(features, rawdata) { - return Array.isArray(features) && !rawdata && features.some(f => { - const props = this.getFeaturePropertiesAndGeometry(feature); - if (props.geometry) { - return true; - } - }); - } - - /** - * @since 3.9.0 - */ - _parseLayerObjFormStructure(layer) { + _parseLayerObjFormStructure(layer, features, rawdata, attributes) { const structure = layer.hasFormStructure() && layer.getLayerEditingFormStructure(); if (false === (structure && this._relations && this._relations.length)) { return; @@ -1128,10 +1096,22 @@ class QueryResultsService extends G3WObject { for (const node of structure) { setRelationField(node); } - return { + + const formStructure = { structure, fields: layer.getFields().filter(field => field.show), // get features show }; + + /** @FIXME add description */ + if (!rawdata && Array.isArray(features) && features.length) { + attributes + .forEach(attr => { + if (layer.getFields().some(field => field.name === attr.name)) { + layerObj.formStructure.fields.push(attr); + } + }); + } + return formStructure; } /** @@ -2476,7 +2456,22 @@ QueryResultsService.prototype.setters = { } - this.setLayersData(this._digestFeaturesForLayers(queryResponse.data), options); + // Convert response from DataProvider into a QueryResult component data structure + const layers = []; + queryResponse.data.forEach(featuresForLayer => { + ( + Array.isArray(featuresForLayer) + ? featuresForLayer + : [featuresForLayer] + ).forEach(featuresForLayer => { + const layer = this._responseToLayer(featuresForLayer); + if (layer) { + layers.push(layer) + } + }); + }); + + this.setLayersData(layers, options); }, From 1da881ae697d25431285fb4b314ee032e0099f64 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 09:52:05 +0200 Subject: [PATCH 34/48] alias `_changeLayerResult` --- .../gui/queryresults/queryresultsservice.js | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 73c0e1b24..4172cfe79 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -426,16 +426,6 @@ class QueryResultsService extends G3WObject { this.changeLayerResult(layer); } - /** - * Called when layer result features is changed - * - * @param layer - */ - _changeLayerResult(layer) { - this.state.layersactions[layer.id].forEach(action => action.change && action.change(layer)); // call if present change method to action - this.resetCurrentActionToolsLayer(layer); // reset layer current actions tools - } - /** * Check and do action if layer has no features after delete feature(s) * @@ -2414,8 +2404,9 @@ QueryResultsService.prototype.downloadApplicationWrapper = deprecate(GUI.prototy * * @TODO choose which ones deprecate */ -QueryResultsService.prototype.init = QueryResultsService.prototype.clearState; -QueryResultsService.prototype.reset = QueryResultsService.prototype.clearState; +QueryResultsService.prototype.init = QueryResultsService.prototype.clearState; +QueryResultsService.prototype.reset = QueryResultsService.prototype.clearState; +QueryResultsService.prototype._changeLayerResult = QueryResultsService.prototype.changeLayerResult /** * Core methods used from other classes to react before or after its call @@ -2523,12 +2514,13 @@ QueryResultsService.prototype.setters = { closeComponent() {}, /** - * @FIXME add description + * Called when layer result features is changed * * @param layer */ changeLayerResult(layer) { - this._changeLayerResult(layer); + this.state.layersactions[layer.id].forEach(action => action.change && action.change(layer)); // call if present change method to action + this.resetCurrentActionToolsLayer(layer); // reset layer current actions tools }, /** From e4b996c44fb9543a269773e18953ab0eb14dc95e Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 10:01:07 +0200 Subject: [PATCH 35/48] remove `_orderResponseByProjectLayers` --- src/app/gui/queryresults/queryresultsservice.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 4172cfe79..ab07e190c 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -699,15 +699,6 @@ class QueryResultsService extends G3WObject { this._asyncFnc.todo(); } - /** - * Sort Response layer as Catalog layer project order. - * - * @param layers Array of layers - */ - _orderResponseByProjectLayers(layers=[]) { - layers.sort((a, b) => (this._projectLayerIds.indexOf(a.id) > this._projectLayerIds.indexOf(b.id) ? 1 : -1)); - } - /** * @param bool whether to zoom to results */ @@ -2476,7 +2467,8 @@ QueryResultsService.prototype.setters = { if (false === options.add) { // set the right order of result layers based on TOC this._currentLayerIds = layers.map(layer => layer.id); - this._orderResponseByProjectLayers(layers); + // sort layers as Catalog project layers. + layers.sort((a, b) => (this._projectLayerIds.indexOf(a.id) > this._projectLayerIds.indexOf(b.id) ? 1 : -1)); } // get features from add pick layer in case of a new request query layers.forEach(layer => { options.add ? this.updateLayerResultFeatures(layer) : this.state.layers.push(layer); }); From 59d8e672daa638eaf350a5bda97c7a047efb48a8 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 11:01:31 +0200 Subject: [PATCH 36/48] apply some fixme and todos - remove nested if-else in: `getVectorLayerFeaturesFromQueryRequest()` - incorporate function `_addVectorLayersDataToQueryResponse()` into `*/ setQueryResponse()` setter - simplify destructuring assignment within `_printSingleAtlas()` function - remove function `_addComponent()` --- .../gui/queryresults/queryresultsservice.js | 162 ++++++++---------- 1 file changed, 69 insertions(+), 93 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index ab07e190c..d94dbfff3 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -52,6 +52,13 @@ class QueryResultsService extends G3WObject { super(); + /** + * BACKCOMP + */ + this._changeLayerResult = this.setters.changeLayerResult; + this._addComponent = this.setters.addComponent; + + /** * Service used to work with atlas (print functionality) action tool */ @@ -1295,45 +1302,44 @@ class QueryResultsService extends G3WObject { let features = []; + const has_coords = coordinates && Array.isArray(coordinates); + const has_bbox = bbox && Array.isArray(bbox); + const is_poly = geometry instanceof ol.geom.Polygon || geometry instanceof ol.geom.MultiPolygon; + // case query coordinates - if (coordinates && Array.isArray(coordinates)) { - const pixel = this.mapService.viewer.map.getPixelFromCoordinate(coordinates); + if (has_coords) { this.mapService.viewer.map.forEachFeatureAtPixel( - pixel, + this.mapService.viewer.map.getPixelFromCoordinate(coordinates), (feature, layer) => { features.push(feature); }, { layerFilter(layer) { return layer === vectorLayer; } } ); } - // TODO: rewrite this in order to avoid nested else-if conditions - else { - - // case query bbox - if (bbox && Array.isArray(bbox)) { - //set geometry has Polygon - geometry = ol.geom.Polygon.fromExtent(bbox); - } + // case query bbox + if (has_bbox && !has_coords) { + //set geometry has Polygon + geometry = ol.geom.Polygon.fromExtent(bbox); + } - // check query geometry (Polygon or MultiPolygon) - if (geometry instanceof ol.geom.Polygon || geometry instanceof ol.geom.MultiPolygon) { - switch (vectorLayer.constructor) { - case VectorLayer: - features = vectorLayer.getIntersectedFeatures(geometry); - break; - case ol.layer.Vector: - vectorLayer.getSource().getFeatures().forEach(feature => { - let add; - switch (filterConfig.spatialMethod) { - case 'intersects': add = intersects(geometry, feature.getGeometry()); break; - case 'within': add = within(geometry, feature.getGeometry()); break; - default: add = geometry.intersectsExtent(feature.getGeometry().getExtent()); break; - } - if (true === add) { - features.push(feature); - } - }); - break; - } + // check query geometry (Polygon or MultiPolygon) + if (is_poly && !has_coords) { + switch (vectorLayer.constructor) { + case VectorLayer: + features = vectorLayer.getIntersectedFeatures(geometry); + break; + case ol.layer.Vector: + vectorLayer.getSource().getFeatures().forEach(feature => { + let add; + switch (filterConfig.spatialMethod) { + case 'intersects': add = intersects(geometry, feature.getGeometry()); break; + case 'within': add = within(geometry, feature.getGeometry()); break; + default: add = geometry.intersectsExtent(feature.getGeometry().getExtent()); break; + } + if (true === add) { + features.push(feature); + } + }); + break; } } @@ -1344,40 +1350,6 @@ class QueryResultsService extends G3WObject { } - /** - * @TODO add description (eg. what is a vector layer ?) - */ - _addVectorLayersDataToQueryResponse(queryResponse) { - const catalogService = GUI.getService('catalog'); - - /** @type { boolean | undefined } */ - const isExternalFilterSelected = queryResponse.query.external.filter.SELECTED; - - // add visible layers to query response (vector layers) - this._vectorLayers - .forEach(layer => { - const isLayerSelected = catalogService.isExternalLayerSelected({ id: layer.get('id'), type: 'vector' }); - if ( - layer.getVisible() && ( // TODO: extract this into `layer.isSomething()` ? - (true === isLayerSelected && true === isExternalFilterSelected) || - (false === isLayerSelected && false === isExternalFilterSelected) || - ("undefined" === typeof isExternalFilterSelected) - ) - ) { - queryResponse.data.push(this.getVectorLayerFeaturesFromQueryRequest(layer, queryResponse.query)); - } - }); - } - - /** - * Add custom component in query result - * - * @param component - */ - _addComponent(component) { - this.state.components.push(component) - } - /** * @FIXME add description */ @@ -1385,29 +1357,16 @@ class QueryResultsService extends G3WObject { atlas = {}, features = [], } = {}) { - - // TODO: make it easier to understand.. (what variables are declared? which ones are aliased?) - let { - name: template, - atlas: { field_name = '' } - } = atlas; - - field_name = field_name || '$id'; - - const values = features.map(feat => feat.attributes['$id' === field_name ? G3W_FID : field_name]); - + let field = atlas.atlas && atlas.atlas.field_name ? atlas.atlas.field_name : '$id'; return this.printService .printAtlas({ - field: field_name, - values, - template, + field, + values: features.map(feat => feat.attributes['$id' === field ? G3W_FID : field]), + template: atlas.name, download: true }) .then(({url}) => { - GUI.downloadWrapper( - downloadFile, - { url, filename: template, mime_type: 'application/pdf' } - ) + GUI.downloadWrapper(downloadFile, { url, filename: atlas.name, mime_type: 'application/pdf' }) }); } @@ -1714,7 +1673,7 @@ class QueryResultsService extends G3WObject { layer.selection.active = false; const action = ( this.state.layersactions[layer.id] && - this.state.layersactions[layer.id].find(action => action.id === 'selection') + this.state.layersactions[layer.id].find(action => 'selection' === action.id) ); layer.selection.features .forEach((feature, index) => { @@ -2397,7 +2356,6 @@ QueryResultsService.prototype.downloadApplicationWrapper = deprecate(GUI.prototy */ QueryResultsService.prototype.init = QueryResultsService.prototype.clearState; QueryResultsService.prototype.reset = QueryResultsService.prototype.clearState; -QueryResultsService.prototype._changeLayerResult = QueryResultsService.prototype.changeLayerResult /** * Core methods used from other classes to react before or after its call @@ -2419,23 +2377,41 @@ QueryResultsService.prototype.setters = { // whether add response to current results using addLayerFeaturesToResultsAction if (false === options.add) { - // in case of new request results reset the query otherwise maintain the previous request this.clearState(); this.state.query = queryResponse.query; this.state.type = queryResponse.type; + } - // if true add external layers to response - if (true === queryResponse.query.external.add) { - this._addVectorLayersDataToQueryResponse(queryResponse); - } + // whether add external layers to response + if (true === queryResponse.query.external.add && false === options.add) { + const catalogService = GUI.getService('catalog'); + + /** @type { boolean | undefined } */ + const FILTER_SELECTED = queryResponse.query.external.filter.SELECTED; + + // add visible layers to query response (vector layers) + this._vectorLayers + .forEach(layer => { + const is_selected = catalogService.isExternalLayerSelected({ id: layer.get('id'), type: 'vector' }); + if ( + layer.getVisible() && ( // TODO: extract this into `layer.isSomething()` ? + (true === is_selected && true === FILTER_SELECTED) || + (false === is_selected && false === FILTER_SELECTED) || + (undefined === FILTER_SELECTED) + ) + ) { + queryResponse.data.push(this.getVectorLayerFeaturesFromQueryRequest(layer, queryResponse.query)); + } + }); + } + if (false === options.add) { switch (this.state.query.type) { case 'coordinates': this.showCoordinates(this.state.query.coordinates); break; case 'bbox': this.showBBOX(this.state.query.bbox); break; case 'polygon': this.showGeometry(this.state.query.geometry); break; } - } // Convert response from DataProvider into a QueryResult component data structure @@ -2477,12 +2453,12 @@ QueryResultsService.prototype.setters = { }, /** - * Method used to add custom component + * Add custom component in query result * * @param component */ addComponent(component) { - this._addComponent(component) + this.state.components.push(component) }, /** From faf3a93488694e0af260657aa184cb03c9db8e1f Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 11:53:29 +0200 Subject: [PATCH 37/48] wrong variable reference --- src/app/gui/queryresults/queryresultsservice.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index d94dbfff3..90b516980 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -983,11 +983,13 @@ class QueryResultsService extends G3WObject { (is_string ? layer : undefined); const attributes = has_features ? this._parseLayerObjAttributes(layer, features, sourceType) : []; + const external = (is_vector || is_string); const layerObj = { id, attributes, - features: has_features ? this._parseLayerObjFeatures(features, rawdata) : [], + external, + features: has_features ? this._parseLayerObjFeatures(features, rawdata, external) : [], hasgeometry: this._hasLayerObjGeometry(features, rawdata), hasImageField: this._hasLayerObjImageField(features, rawdata, attributes), loading: false, @@ -995,7 +997,6 @@ class QueryResultsService extends G3WObject { expandable: true, addfeaturesresults: { active: false }, [DownloadFormats.name]: { active: false }, - external: (is_vector || is_string), editable: is_layer ? layer.isEditable() : false, inediting: is_layer ? layer.isInEditing() : false, source: is_layer ? layer.getSource() : undefined, @@ -1046,14 +1047,14 @@ class QueryResultsService extends G3WObject { /** * @since 3.9.0 */ - _parseLayerObjFeatures(features, rawdata) { + _parseLayerObjFeatures(features, rawdata, external) { const _features = []; if (!rawdata) { features.forEach(f => { const props = this.getFeaturePropertiesAndGeometry(f); _features .push({ - id: layerObj.external ? f.getId() : props.id, + id: external ? f.getId() : props.id, attributes: props.properties, geometry: props.geometry, selection: props.selection, @@ -1095,7 +1096,7 @@ class QueryResultsService extends G3WObject { attributes .forEach(attr => { if (layer.getFields().some(field => field.name === attr.name)) { - layerObj.formStructure.fields.push(attr); + formStructure.fields.push(attr); } }); } @@ -2347,7 +2348,7 @@ QueryResultsService.prototype.addRemoveFeaturesToLayerResult = deprecate(QueryRe /** * @deprecated since 3.9.0 Will be deleted in 4.x. Use GUI::downloadWrapper(downloadFnc, options) instead */ -QueryResultsService.prototype.downloadApplicationWrapper = deprecate(GUI.prototype.downloadWrapper, '[G3W-CLIENT] QueryResultsService::downloadApplicationWrapper(layer) is deprecated'); +QueryResultsService.prototype.downloadApplicationWrapper = deprecate(GUI.downloadWrapper, '[G3W-CLIENT] QueryResultsService::downloadApplicationWrapper(layer) is deprecated'); /** * Alias functions From d5f517f04b6de185faa02c3c9faabbe4fa726ef9 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 12:35:12 +0200 Subject: [PATCH 38/48] wrong variable name --- src/app/gui/queryresults/queryresultsservice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 90b516980..d3bc7ac76 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -1025,7 +1025,7 @@ class QueryResultsService extends G3WObject { */ _hasLayerObjGeometry(features, rawdata) { return Array.isArray(features) && !rawdata && features.some(f => { - const props = this.getFeaturePropertiesAndGeometry(feature); + const props = this.getFeaturePropertiesAndGeometry(f); if (props.geometry) { return true; } From ddd1ab71e1a391371abc3473369926a46ddb821b Mon Sep 17 00:00:00 2001 From: volterra79 Date: Wed, 23 Aug 2023 14:18:09 +0200 Subject: [PATCH 39/48] :bug: Fix case when click to add feature to layer result and no current mapcontrol is active (undefined). Need to be set null to align default value and evaluation. --- src/app/gui/queryresults/queryresultsservice.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index d3bc7ac76..c8f852d71 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -794,7 +794,10 @@ class QueryResultsService extends G3WObject { const external_layer = this._getExternalLayer(layer.id); - interaction.mapcontrol = interaction.mapcontrol || this.mapService.getCurrentToggledMapControl(); + interaction.mapcontrol = + interaction.mapcontrol || + this.mapService.getCurrentToggledMapControl() || + null; //need to be set null when this.mapService.getCurrentToggledMapControl() is undefined interaction.interaction = new PickCoordinatesInteraction(); this.mapService.addInteraction(interaction.interaction, { close: false }); From 3f51f84a681da9e5b59e1666faa118eef8b94d16 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 15:12:08 +0200 Subject: [PATCH 40/48] `console.warn` --- src/store/plugins.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/store/plugins.js b/src/store/plugins.js index f2e04c5cd..8256f9d33 100644 --- a/src/store/plugins.js +++ b/src/store/plugins.js @@ -169,7 +169,8 @@ function PluginsRegistry() { this._loadedPluginUrls.push(scriptUrl); resolve(); }) - .fail(()=>{ + .fail((jqxhr, settings, exception)=>{ + console.warn('[G3W-PLUGIN]', scriptUrl, exception, settings, jqxhr); this.removeLoadingPlugin(name, false); reject(); }) From 0919c4e0a349e9c3eff8d9af48401d78df617b08 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 23 Aug 2023 15:45:02 +0200 Subject: [PATCH 41/48] wrong comments --- src/app/gui/queryresults/queryresultsservice.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index c8f852d71..c680f8df5 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -2351,7 +2351,7 @@ QueryResultsService.prototype.addRemoveFeaturesToLayerResult = deprecate(QueryRe /** * @deprecated since 3.9.0 Will be deleted in 4.x. Use GUI::downloadWrapper(downloadFnc, options) instead */ -QueryResultsService.prototype.downloadApplicationWrapper = deprecate(GUI.downloadWrapper, '[G3W-CLIENT] QueryResultsService::downloadApplicationWrapper(layer) is deprecated'); +QueryResultsService.prototype.downloadApplicationWrapper = deprecate(GUI.downloadWrapper, '[G3W-CLIENT] QueryResultsService::downloadApplicationWrapper(downloadFnc, options) is deprecated'); /** * Alias functions From ed81891e2508067a46031e1efcd2413e74255ab4 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Wed, 23 Aug 2023 16:23:40 +0200 Subject: [PATCH 42/48] :bug: In case of no features for a specific layer, return undefind, so layer is not add to query results state layers array --- src/app/gui/queryresults/queryresultsservice.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index c680f8df5..54c9f2f08 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -950,11 +950,19 @@ class QueryResultsService extends G3WObject { * @param featuresForLayer.rawdata rawdata response * @param featuresForLayer.error * + * return layerObj or undefined in case of no features + * * @since 3.9.0 */ _responseToLayer({ layer, features, rawdata, error }) { - const has_features = features && features.length > 0; + const has_features = Array.isArray(features) ? features.length > 0 : false; + + // in case no features related to layer return undefined + // so no layer is returned + if (false === has_features){ + return; + } const is_layer = layer instanceof Layer; const is_vector = layer instanceof ol.layer.Vector; // instance of openlayers layer Vector Class @@ -2427,7 +2435,7 @@ QueryResultsService.prototype.setters = { : [featuresForLayer] ).forEach(featuresForLayer => { const layer = this._responseToLayer(featuresForLayer); - if (layer) { + if (undefined !== layer) { layers.push(layer) } }); From d32e27cf4a2ab7a017f36fe24e5a59ca92407673 Mon Sep 17 00:00:00 2001 From: Raruto Date: Thu, 24 Aug 2023 07:41:01 +0200 Subject: [PATCH 43/48] simplify conditions --- src/app/gui/queryresults/queryresultsservice.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 54c9f2f08..5321d587f 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -950,17 +950,16 @@ class QueryResultsService extends G3WObject { * @param featuresForLayer.rawdata rawdata response * @param featuresForLayer.error * - * return layerObj or undefined in case of no features + * @returns { layerObj | undefined } * * @since 3.9.0 */ _responseToLayer({ layer, features, rawdata, error }) { - const has_features = Array.isArray(features) ? features.length > 0 : false; + const has_features = Array.isArray(features) && features.length > 0; - // in case no features related to layer return undefined - // so no layer is returned - if (false === has_features){ + // Skip when layer has no features + if (false === has_features) { return; } @@ -993,14 +992,14 @@ class QueryResultsService extends G3WObject { (is_vector ? layer.get('id') : undefined) || (is_string ? layer : undefined); - const attributes = has_features ? this._parseLayerObjAttributes(layer, features, sourceType) : []; + const attributes = this._parseLayerObjAttributes(layer, features, sourceType); const external = (is_vector || is_string); const layerObj = { id, attributes, external, - features: has_features ? this._parseLayerObjFeatures(features, rawdata, external) : [], + features: this._parseLayerObjFeatures(features, rawdata, external), hasgeometry: this._hasLayerObjGeometry(features, rawdata), hasImageField: this._hasLayerObjImageField(features, rawdata, attributes), loading: false, @@ -2435,7 +2434,7 @@ QueryResultsService.prototype.setters = { : [featuresForLayer] ).forEach(featuresForLayer => { const layer = this._responseToLayer(featuresForLayer); - if (undefined !== layer) { + if (layer) { layers.push(layer) } }); From 55ee0c1275221817de682a0355c3e559a2595167 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Thu, 24 Aug 2023 08:59:19 +0200 Subject: [PATCH 44/48] :bug: Need to refer to instance and not to "class" --- src/services/gui.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/gui.js b/src/services/gui.js index 3da797bb8..4a02b3bf2 100644 --- a/src/services/gui.js +++ b/src/services/gui.js @@ -102,14 +102,14 @@ inherit(GUI, G3WObject); */ GUI.prototype.downloadWrapper = async function(downloadFnc, options = {}) { const download_caller_id = ApplicationService.setDownload(true); - GUI.setLoadingContent(true); + this.setLoadingContent(true); try { await downloadFnc(options); } catch(err) { - GUI.showUserMessage({ type: 'alert', message: err || 'server_error', textMessage: !!err }) + this.showUserMessage({ type: 'alert', message: err || 'server_error', textMessage: !!err }) } ApplicationService.setDownload(false, download_caller_id); - GUI.setLoadingContent(false); + this.setLoadingContent(false); }; export default new GUI(); From bf23b38a733fcae50dd58ecf1be6e7f664ec37b3 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Thu, 24 Aug 2023 09:48:32 +0200 Subject: [PATCH 45/48] :bug: Handle selection feature action when add new feature to layer result --- src/app/gui/queryresults/queryresultsservice.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 5321d587f..c96f4f596 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -484,7 +484,7 @@ class QueryResultsService extends G3WObject { const action_tools = {}; const action_layer = {}; - layer.features.forEach((_, idx) => { + this._getLayerFeatures(layer).forEach((_, idx) => { action_tools[idx] = null; action_layer[idx] = null; }); @@ -2310,6 +2310,20 @@ class QueryResultsService extends G3WObject { this.checkFeatureSelection({ layer, index, feature, action }) } }, + /**@since 3.9.0**/ + //when add new feature need to create reactive toggled + // it is call on query result context so this is referred to service + // and not action + change({features}){ + features + .forEach((feature, index) => { + //exclude existing feature + if (undefined === this.state.toggled[index]) { + //add reactive property of array + VM.$set(this.state.toggled, index, false); + } + }); + }, cbk: throttle(this.addToSelection.bind(this)) }); From 7e628b92c5eb6287ef9924bdb017655c98ebeb20 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Thu, 24 Aug 2023 11:21:51 +0200 Subject: [PATCH 46/48] :bug: Add custom drawpolygon type for query by drw polygon otherwise it conflict to download action to get all attributes of polygon query (query by polygon) --- src/app/g3w-ol/controls/querybydrawpolygoncontrol.js | 5 ++++- src/services/data-query.js | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/g3w-ol/controls/querybydrawpolygoncontrol.js b/src/app/g3w-ol/controls/querybydrawpolygoncontrol.js index fdc643510..7fe865dc2 100644 --- a/src/app/g3w-ol/controls/querybydrawpolygoncontrol.js +++ b/src/app/g3w-ol/controls/querybydrawpolygoncontrol.js @@ -165,7 +165,10 @@ proto.runSpatialQuery = async function() { filterConfig: { spatialMethod: this.getSpatialMethod() }, - multilayers: ProjectsRegistry.getCurrentProject().isQueryMultiLayers(this.name) + multilayers: ProjectsRegistry.getCurrentProject().isQueryMultiLayers(this.name), + /**@since 3.9.0**/ + //add a custom type + type: 'drawpolygon', }, outputs: { show({error = false}) { diff --git a/src/services/data-query.js b/src/services/data-query.js index 52cf149ce..edec449bd 100644 --- a/src/services/data-query.js +++ b/src/services/data-query.js @@ -48,7 +48,9 @@ function QueryService(){ filter: { SELECTED : false } - } + }, + /**@since 3.9.0**/ + type = 'polygon' } = {}) { const hasExternalLayersSelected = this.hasExternalLayerSelected({ type: "vector" }); const fid = (hasExternalLayersSelected) ? feature.getId() : feature.get(G3W_FID); @@ -92,7 +94,7 @@ function QueryService(){ fid, geometry, layerName, - type: 'polygon', + type, filterConfig, external } From aadffaf90a37e6dde5009e6b6eda6c4d62fc1996 Mon Sep 17 00:00:00 2001 From: volterra79 Date: Thu, 24 Aug 2023 11:23:18 +0200 Subject: [PATCH 47/48] :bug: fix download action --- .../gui/queryresults/queryresultsservice.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index c96f4f596..99221398b 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -1561,7 +1561,7 @@ class QueryResultsService extends G3WObject { /** @FIXME add description */ if ('polygon' !== query.type) { - runDownload(); + await runDownload(); return; } @@ -1581,10 +1581,10 @@ class QueryResultsService extends G3WObject { label: 'sdk.mapcontrols.querybypolygon.download.choiches.feature_polygon.label', }, ], - // choose between only feature attribute or also polygon attibute + // choose between only feature attribute or also polygon attribute download: (type) => { - if ('polygon' === type) { // id type polygon add paramateres to api download - data.sbp_qgs_layer_id = query.layer.getId(); + if ('polygon' === type) { // id type polygon add parameters to api download + data.sbp_qgs_layer_id = layer.id; data.sbp_fid = query.fid; } else { // force to remove delete data.sbp_fid; @@ -1609,19 +1609,21 @@ class QueryResultsService extends G3WObject { action, component: QueryPolygonCsvAttributesComponent, }); - } - - /** @FIXME add description */ - if (1 !== features.length && undefined === downloadsactions) { - layer[type].active = !layer[type].active; } /** @FIXME add description */ if (1 !== features.length) { + if (undefined === downloadsactions) { + layer[type].active = !layer[type].active; + } + const has_component_and_config = ( + (undefined === downloadsactions && layer[type].active) || + (undefined !== downloadsactions) + ) this.setLayerActionTool({ layer, - component: (layer[type].active || undefined !== downloadsactions) ? QueryPolygonCsvAttributesComponent : null, - config: (layer[type].active || undefined !== downloadsactions) ? config : null, + component: has_component_and_config ? QueryPolygonCsvAttributesComponent : null, + config: has_component_and_config ? config : null, }); } From bfb46c772414e3c88126cd8b868dac55d968fbed Mon Sep 17 00:00:00 2001 From: Raruto Date: Thu, 24 Aug 2023 11:56:26 +0200 Subject: [PATCH 48/48] boolean logic --- src/app/gui/queryresults/queryresultsservice.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/app/gui/queryresults/queryresultsservice.js b/src/app/gui/queryresults/queryresultsservice.js index 99221398b..6a1822c1b 100644 --- a/src/app/gui/queryresults/queryresultsservice.js +++ b/src/app/gui/queryresults/queryresultsservice.js @@ -1611,19 +1611,18 @@ class QueryResultsService extends G3WObject { }); } + /** @FIXME add description */ + if (undefined === downloadsactions && 1 !== features.length) { + layer[type].active = !layer[type].active; + } + /** @FIXME add description */ if (1 !== features.length) { - if (undefined === downloadsactions) { - layer[type].active = !layer[type].active; - } - const has_component_and_config = ( - (undefined === downloadsactions && layer[type].active) || - (undefined !== downloadsactions) - ) + const has_config = (downloadsactions || (layer[type].active && undefined === downloadsactions)); this.setLayerActionTool({ layer, - component: has_component_and_config ? QueryPolygonCsvAttributesComponent : null, - config: has_component_and_config ? config : null, + component: has_config ? QueryPolygonCsvAttributesComponent : null, + config: has_config ? config : null, }); }