From 22c8c28ccd7be03a85c5097d17a152e3e54a41c4 Mon Sep 17 00:00:00 2001 From: Raruto Date: Mon, 18 Dec 2023 09:22:33 +0100 Subject: [PATCH 1/4] jsdoc --- src/app/core/iframe/services/baseservice.js | 93 +++++++++++++-------- 1 file changed, 58 insertions(+), 35 deletions(-) diff --git a/src/app/core/iframe/services/baseservice.js b/src/app/core/iframe/services/baseservice.js index 9f3f0b0f0..3ff680b02 100644 --- a/src/app/core/iframe/services/baseservice.js +++ b/src/app/core/iframe/services/baseservice.js @@ -8,7 +8,7 @@ const G3WObject = require('core/g3wobject'); function BaseIframeService(options={}){ base(this); this.ready = false; - this.init = function(){ + this.init = function() { //overwrite each service } } @@ -18,48 +18,57 @@ inherit(BaseIframeService, G3WObject); const proto = BaseIframeService.prototype; /** - * Common mapService attribute + * mapService attribute */ proto.mapService = GUI.getComponent('map').getService(); /** - * Common current project attribute + * Current project attribute */ proto.project = ProjectsRegistry.getCurrentProject(); /** - * - * @type {null} + * @type { null } */ proto.layers = undefined; /** * Return a qgs_layer_id array based on passed qgis_layer_id - * @param qgs_layer_id : String , Array of Strings or null/undefined) - * @returns Array oa qgs_layer_id strings + * + * @param { Object } opts + * @param { string | string[] | null | undefined } opts.qgs_layer_id + * @param { Array } noValue + * + * @returns { string[] } qgs_layer_id + * * @private */ -proto.getQgsLayerId = function({qgs_layer_id, noValue=this.layers.map(layer => layer.id)}){ - return qgs_layer_id ? Array.isArray(qgs_layer_id) ? qgs_layer_id: [qgs_layer_id] : noValue; +proto.getQgsLayerId = function({ + qgs_layer_id, + noValue = this.layers.map(layer => layer.id) +}){ + return qgs_layer_id ? + ( + Array.isArray(qgs_layer_id) ? + qgs_layer_id : + [qgs_layer_id] + ) : + noValue; }; /** - * Method to getFeature from DataProvider + * getFeature from DataProvider + * * @private */ proto.searchFeature = async function({layer, feature}){ - const search_endpoint = this.project.getSearchEndPoint(); - const {field, value} = feature; - const { data=[] } = await DataRouterService.getData('search:features', { + const search_endpoint = this.project.getSearchEndPoint(); + const { field, value } = feature; + const { data = [] } = await DataRouterService.getData('search:features', { inputs: { layer, search_endpoint, - filter: createFilterFormField({ - layer, - search_endpoint, - field, - value - }) + filter: createFilterFormField({ layer, search_endpoint, field, value }) }, outputs: false }); @@ -67,14 +76,22 @@ proto.searchFeature = async function({layer, feature}){ }; /** - * Comme method to search feature/s by field and value - * @param qgs_layer_id - * @param feature - * @param zoom - * @param highlight - * @returns {Promise<{qgs_layer_id: null, features: [], found: boolean}>} + * Search feature(s) by field and value + * + * @param { Object } opts + * @param opts.qgs_layer_id + * @param opts.feature + * @param opts.zoom + * @param opts.highlight + * + * @returns { Promise<{ qgs_layer_id: null, features: [], found: boolean }>} */ -proto.findFeaturesWithGeometry = async function({qgs_layer_id=[], feature, zoom=false, highlight=false}={}){ +proto.findFeaturesWithGeometry = async function({ + feature, + qgs_layer_id = [], + zoom = false, + highlight = false, +}={}) { const response = { found: false, features: [], @@ -104,13 +121,15 @@ proto.findFeaturesWithGeometry = async function({qgs_layer_id=[], feature, zoom= } catch(err){i++} } // in case of no response zoom too initial extent - !response.found && this.mapService.zoomToProjectInitExtent(); - + if (!response.found) { + this.mapService.zoomToProjectInitExtent(); + } return response; }; /** * Set layer function + * * @param layers */ proto.setLayers = function(layers=[]){ @@ -122,7 +141,8 @@ proto.getLayers = function(){ }; /** - * Method to set ready the service + * Set ready service + * * @param bool */ proto.setReady = function(bool=false){ @@ -134,16 +154,19 @@ proto.getReady = function(){ }; /** - * Method overwrite single service: Usefult to sto eventually running action - * * @returns {Promise} + * Overwrite single service: Usefult to stop eventually running action + * + * @virtual method need to be implemented by subclasses + * + * @returns { Promise } */ -proto.stop = async function(){}; +proto.stop = async function() {}; /** * Overwrite each single service + * + * @virtual method need to be implemented by subclasses */ -proto.clear = function(){ - //overwrite single service -}; +proto.clear = function() {}; module.exports = BaseIframeService; \ No newline at end of file From 5383212bfcf2f31bb49c09f7de5aff6629f613fd Mon Sep 17 00:00:00 2001 From: Raruto Date: Mon, 18 Dec 2023 09:58:24 +0100 Subject: [PATCH 2/4] delete files: `src/app/core/iframe/services/index.js` and `src/app/core/iframe/routerservice.js` --- src/app/core/iframe/routerservice.js | 11 ---- src/app/core/iframe/services/baseservice.js | 63 +++++++++++-------- src/app/core/iframe/services/index.js | 7 --- .../core/iframe/services/plugins/service.js | 17 +++-- src/services/application.js | 2 +- src/services/iframe-app.js | 4 +- src/services/iframe-editing.js | 3 +- src/services/iframe-plugin.js | 5 +- 8 files changed, 57 insertions(+), 55 deletions(-) delete mode 100755 src/app/core/iframe/routerservice.js delete mode 100644 src/app/core/iframe/services/index.js diff --git a/src/app/core/iframe/routerservice.js b/src/app/core/iframe/routerservice.js deleted file mode 100755 index 312a85e86..000000000 --- a/src/app/core/iframe/routerservice.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * DEPRECATED: this file will be removed after v3.4 (use "services/iframe-router.js" instead) - */ -import IframePluginService from 'services/iframe-plugin'; - -/** - * @FIXME: application is broken using like the following line - */ -// import { IframePluginService } from 'services'; - -module.exports = IframePluginService; \ No newline at end of file diff --git a/src/app/core/iframe/services/baseservice.js b/src/app/core/iframe/services/baseservice.js index 3ff680b02..ffb111ac0 100644 --- a/src/app/core/iframe/services/baseservice.js +++ b/src/app/core/iframe/services/baseservice.js @@ -5,33 +5,40 @@ import GUI from 'services/gui'; const { base, inherit, createFilterFormField } = require('utils'); const G3WObject = require('core/g3wobject'); -function BaseIframeService(options={}){ +function BaseIframeService(options={}) { + base(this); + + /** + * @type { boolean } + */ this.ready = false; + + /** + * Map service + */ + this.mapService = GUI.getService('map'); + + /** + * Current project + */ + this.project = ProjectsRegistry.getCurrentProject(); + + /** + * @type { Array | undefined } + */ + this.layers = undefined; + this.init = function() { - //overwrite each service - } + // overwrite each service + }; + } inherit(BaseIframeService, G3WObject); const proto = BaseIframeService.prototype; -/** - * mapService attribute - */ -proto.mapService = GUI.getComponent('map').getService(); - -/** - * Current project attribute - */ -proto.project = ProjectsRegistry.getCurrentProject(); - -/** - * @type { null } - */ -proto.layers = undefined; - /** * Return a qgs_layer_id array based on passed qgis_layer_id * @@ -46,7 +53,7 @@ proto.layers = undefined; proto.getQgsLayerId = function({ qgs_layer_id, noValue = this.layers.map(layer => layer.id) -}){ +}) { return qgs_layer_id ? ( Array.isArray(qgs_layer_id) ? @@ -61,7 +68,7 @@ proto.getQgsLayerId = function({ * * @private */ -proto.searchFeature = async function({layer, feature}){ +proto.searchFeature = async function({layer, feature}) { const search_endpoint = this.project.getSearchEndPoint(); const { field, value } = feature; const { data = [] } = await DataRouterService.getData('search:features', { @@ -102,10 +109,7 @@ proto.findFeaturesWithGeometry = async function({ while (!response.found && i < layersCount) { const layer = this.project.getLayerById(qgs_layer_id[i]); try { - const data = layer && await this.searchFeature({ - layer, - feature - }); + const data = layer && await this.searchFeature({ layer, feature }); if (data.length) { const features = data[0].features; response.found = features.length > 0 && !!features.find(feature => feature.getGeometry()); @@ -115,10 +119,15 @@ proto.findFeaturesWithGeometry = async function({ zoom && this.mapService.zoomToFeatures(features, { highlight }); + } else { + i++; } - else i++; - } else i++; - } catch(err){i++} + } else { + i++; + } + } catch(err) { + i++; + } } // in case of no response zoom too initial extent if (!response.found) { diff --git a/src/app/core/iframe/services/index.js b/src/app/core/iframe/services/index.js deleted file mode 100644 index 94ac9fb4d..000000000 --- a/src/app/core/iframe/services/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import AppService from 'services/iframe-app'; -import EditingService from 'services/iframe-editing'; - -module.exports = { - app: AppService, - editing: EditingService -}; \ No newline at end of file diff --git a/src/app/core/iframe/services/plugins/service.js b/src/app/core/iframe/services/plugins/service.js index 58293a9cf..e0ece0faf 100644 --- a/src/app/core/iframe/services/plugins/service.js +++ b/src/app/core/iframe/services/plugins/service.js @@ -3,12 +3,19 @@ import PluginsRegistry from 'store/plugins'; const { base, inherit } = require('utils'); const BaseService = require('core/iframe/services/baseservice'); -function BasePluginService(){ +function BasePluginService() { + base(this); + // common attributes between plugin service + this.pluginName; + this.dependencyApi ={}; - this.init = async function({layers={}}={}){ + + this.init = async function({ + layers = {} + } = {}) { this.layers = layers; // check if the plugin in in configuration if (PluginsRegistry.isPluginInConfiguration(this.pluginName)) { @@ -28,7 +35,7 @@ function BasePluginService(){ } }; - this.clear = function(){ + this.clear = function() { //TO OVERWRITE }; } @@ -37,11 +44,11 @@ inherit(BasePluginService, BaseService); const proto = BasePluginService.prototype; -proto.setDependencyApi = function(api={}){ +proto.setDependencyApi = function(api={}) { this.dependencyApi = api; }; -proto.getDependecyApi = function(){ +proto.getDependecyApi = function() { return this.dependencyApi; }; diff --git a/src/services/application.js b/src/services/application.js index f4b961c0c..327afe684 100644 --- a/src/services/application.js +++ b/src/services/application.js @@ -658,7 +658,7 @@ const ApplicationService = function() { * iframeservice */ this.startIFrameService = function({project}={}) { - const iframeService = require('core/iframe/routerservice'); + const iframeService = require('services/iframe-plugin').default; iframeService.init({project}); }; diff --git a/src/services/iframe-app.js b/src/services/iframe-app.js index c5e4c8998..b4f6e65ca 100644 --- a/src/services/iframe-app.js +++ b/src/services/iframe-app.js @@ -8,8 +8,10 @@ import DataRouterService from 'services/data'; const { base, inherit } = require('utils'); const BaseService = require('core/iframe/services/baseservice'); -function AppService(){ +function AppService() { + base(this); + this.mapControls = { screenshot: { control: null diff --git a/src/services/iframe-editing.js b/src/services/iframe-editing.js index f8932aaa9..01fd10ac6 100644 --- a/src/services/iframe-editing.js +++ b/src/services/iframe-editing.js @@ -6,8 +6,7 @@ import GUI from 'services/gui'; const BasePluginService = require('core/iframe/services/plugins/service'); - -const { base, inherit } = g3wsdk.core.utils; +const { base, inherit } = require('utils'); function EditingService() { base(this); diff --git a/src/services/iframe-plugin.js b/src/services/iframe-plugin.js index 13f88af4b..9fa02eb2e 100644 --- a/src/services/iframe-plugin.js +++ b/src/services/iframe-plugin.js @@ -12,7 +12,10 @@ function IframePluginService(options={}) { this.pendingactions = {}; this.init = async function({project}={}) { await GUI.isReady(); - this.services = require('core/iframe/services/index'); + this.services = { + app: require('services/iframe-app').default, + editing: require('services/iframe-editing').default, + }; //set eventResponse handler to alla services this.eventResponseServiceHandler = ({action, response}) => { this.postMessage({ From bcac7f4f0be0eb361ad97c516f0f693bc6d333a6 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 20 Dec 2023 12:33:47 +0100 Subject: [PATCH 3/4] convert to classes + open in iframe map control --- config.template.js | 7 + src/app/api.js | 2 +- .../core/iframe/services/plugins/service.js | 59 -- src/app/core/iframe/test/data/template.js | 5 - .../core/iframe/test/templates/template.html | 26 - src/index.dev.js | 31 +- src/services/application.js | 2 +- src/services/data.js | 2 +- src/services/iframe-editing.js | 347 ------ src/services/iframe-plugin.js | 152 --- src/services/iframe.js | 988 ++++++++++++++++++ src/services/index.js | 4 +- 12 files changed, 1030 insertions(+), 595 deletions(-) delete mode 100644 src/app/core/iframe/services/plugins/service.js delete mode 100644 src/app/core/iframe/test/data/template.js delete mode 100644 src/app/core/iframe/test/templates/template.html delete mode 100644 src/services/iframe-editing.js delete mode 100644 src/services/iframe-plugin.js create mode 100644 src/services/iframe.js diff --git a/config.template.js b/config.template.js index d4336118f..53383711d 100755 --- a/config.template.js +++ b/config.template.js @@ -16,6 +16,12 @@ const G3W_KEYS = { // bing: '' }; +const G3W_IFRAME = { // testing MESSAGE sent to "Open in iframe" map control + id: null, + action: 'app:getcenter', // or 'app:getextent' + data: { epsg: 4326 } +}; + let conf = { assetsFolder: './src/assets', // path to G3W-CLIENT assets folder pluginsFolder: './src/plugins', // path to G3W-CLIENT plugins folder @@ -39,6 +45,7 @@ let conf = { initConfig.group.vendorkeys = Object.assign(initConfig.group.vendorkeys || {}, G3W_KEYS); initConfig.group.plugins = Object.assign(initConfig.group.plugins || {}, G3W_PLUGINS.reduce((a, v) => ({ ...a, [v]: { ...initConfig.group.plugins[v], gid: initConfig.group.initproject, baseUrl: initConfig.staticurl }}), {})); }); + g3wsdk.gui.GUI.once('iframe:message', (w, e) => { w.postMessage(G3W_IFRAME, '*') }); g3wsdk.gui.GUI.once('ready', () => { console.log('ready'); }); } }; diff --git a/src/app/api.js b/src/app/api.js index b6b65ef6b..f361c5736 100644 --- a/src/app/api.js +++ b/src/app/api.js @@ -23,7 +23,7 @@ import CatalogLayersStoresRegistry from 'store/catalog-layers'; import DataRouterService from 'services/data'; import ChangesManager from 'services/editing'; import SessionsRegistry from 'store/sessions'; -import IFrameRouterService from 'services/iframe-plugin'; +import IFrameRouterService from 'services/iframe'; import MapLayersStoresRegistry from 'store/map-layers'; import PluginsRegistry from 'store/plugins'; import ProjectsRegistry from 'store/projects'; diff --git a/src/app/core/iframe/services/plugins/service.js b/src/app/core/iframe/services/plugins/service.js deleted file mode 100644 index e0ece0faf..000000000 --- a/src/app/core/iframe/services/plugins/service.js +++ /dev/null @@ -1,59 +0,0 @@ -import PluginsRegistry from 'store/plugins'; - -const { base, inherit } = require('utils'); -const BaseService = require('core/iframe/services/baseservice'); - -function BasePluginService() { - - base(this); - - // common attributes between plugin service - - this.pluginName; - - this.dependencyApi ={}; - - this.init = async function({ - layers = {} - } = {}) { - this.layers = layers; - // check if the plugin in in configuration - if (PluginsRegistry.isPluginInConfiguration(this.pluginName)) { - const plugin = PluginsRegistry.getPlugin(this.pluginName); - if (plugin) { - this.setDependencyApi(plugin.getApi()); - this.setReady(true); - } else { - PluginsRegistry.onafter('registerPlugin', async plugin =>{ - await plugin.isReady(); - if (plugin.getName() === this.pluginName) { - this.setDependencyApi(plugin.getApi()); - this.setReady(true); - } - }) - } - } - }; - - this.clear = function() { - //TO OVERWRITE - }; -} - -inherit(BasePluginService, BaseService); - -const proto = BasePluginService.prototype; - -proto.setDependencyApi = function(api={}) { - this.dependencyApi = api; -}; - -proto.getDependecyApi = function() { - return this.dependencyApi; -}; - - -module.exports = BasePluginService; - - - diff --git a/src/app/core/iframe/test/data/template.js b/src/app/core/iframe/test/data/template.js deleted file mode 100644 index 7ea6f4e8b..000000000 --- a/src/app/core/iframe/test/data/template.js +++ /dev/null @@ -1,5 +0,0 @@ -const MESSAGE = { - id: null, // id of action, - action: ":", // foe example "app:zoomtofeature" - data: {} // data contain all mandatory attribute information -}; \ No newline at end of file diff --git a/src/app/core/iframe/test/templates/template.html b/src/app/core/iframe/test/templates/template.html deleted file mode 100644 index 8658b0b71..000000000 --- a/src/app/core/iframe/test/templates/template.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - - Test Iframe - - - - - - diff --git a/src/index.dev.js b/src/index.dev.js index 154c0dcf1..d194c27c3 100644 --- a/src/index.dev.js +++ b/src/index.dev.js @@ -16,4 +16,33 @@ import './globals'; window.g3wsdk.info(); // run app (index.prod.js) -require('./index.prod'); \ No newline at end of file +require('./index.prod'); + +// custom map control: "Open in iframe" +g3wsdk.gui.GUI.once('ready', () => { + g3wsdk.gui.GUI.getService('map').once('ready', function() { + this.createMapControl('onclick', + { + id: "OPENIFRAME", + options: { + add: true, + clickmap: false, + name: 'OPENIFRAME', + tipLabel: 'Open in iframe', + customClass: g3wsdk.gui.GUI.getFontClass('plugin'), + onclick() { + const w = window.open('about:blank', '_blank', `fullscreen=yes`); + w.document.write(`Test Iframe`); + // send message to iframe when app is ready + w.addEventListener('message', e => { + if (e.data.action === 'app:ready') { + setTimeout(() => g3wsdk.gui.GUI.emit('iframe:message', w.document.querySelector('iframe').contentWindow, e), 2000) + } + }, false); + // prevent page refresh (eg. CTRL+R) + w.onbeforeunload = () => w.close(); + } + }, + }); + }); +}); \ No newline at end of file diff --git a/src/services/application.js b/src/services/application.js index 327afe684..e57560f36 100644 --- a/src/services/application.js +++ b/src/services/application.js @@ -658,7 +658,7 @@ const ApplicationService = function() { * iframeservice */ this.startIFrameService = function({project}={}) { - const iframeService = require('services/iframe-plugin').default; + const iframeService = require('services/iframe').default; iframeService.init({project}); }; diff --git a/src/services/data.js b/src/services/data.js index d6c0eef18..167a8a314 100644 --- a/src/services/data.js +++ b/src/services/data.js @@ -8,7 +8,7 @@ import OWSService from 'services/data-ows'; import ProxyService from 'services/data-proxy'; import QueryService from 'services/data-query'; import SearchService from 'services/data-search'; -import IFrameRouterService from 'services/iframe-plugin'; +import IFrameRouterService from 'services/iframe'; import GUI from 'services/gui'; const { splitContextAndMethod } = require('utils'); diff --git a/src/services/iframe-editing.js b/src/services/iframe-editing.js deleted file mode 100644 index 01fd10ac6..000000000 --- a/src/services/iframe-editing.js +++ /dev/null @@ -1,347 +0,0 @@ -/** - * @file - * @since v3.6 - */ - -import GUI from 'services/gui'; - -const BasePluginService = require('core/iframe/services/plugins/service'); -const { base, inherit } = require('utils'); - -function EditingService() { - base(this); - this.pluginName = 'editing'; - this.subscribevents = []; - this.isRunning = false; - this.responseObject = { - cb: null, // resolve or reject promise method - qgs_layer_id : null, - error: null - }; - this.config = { - tools: { - add: { - disabled:[ - { - id: 'deletefeature' - }, - { - id: 'copyfeatures' - }, - { - id: 'editmultiattributes' - }, - { - id: 'deletePart' - }, - { - id: 'splitfeature' - }, - { - id: 'mergefeatures' - } - ] - }, - update: { - disabled: [ - { - id: 'addfeature' - }, - { - id: 'copyfeatures' - }, - { - id: 'deletefeature' - }, - { - id: 'editmultiattributes' - }, - { - id: 'deletePart' - }, - { - id: 'splitfeature' - }, - { - id: 'mergefeatures' - } - ] - }, - delete: { - enabled: [ - { - id:'deletefeature', - options: { - active:true - } - } - ] - } - } - }; - - // METHODS CALLED FROM EACH ACTION METHOD - // run before each action - this.startAction = async function({toolboxes, resolve, reject}){ - this.responseObject.cb = reject; - // set same mode autosave - this.dependencyApi.setSaveConfig({ - cb: { - done: toolbox => { - //set toolbox id - this.responseObject.cb = resolve; - this.responseObject.qgs_layer_id = toolbox.getId(); - this.responseObject.error = null; - // close panel that fire closeediting panel event - this.dependencyApi.hidePanel(); - }, // called when commit changes is done successuffly - error: (toolbox, error) => { - this.responseObject.cb = reject; - this.responseObject.qgs_layer_id = toolbox.getId(); - this.responseObject.error = error; - } // called whe commit change receive an error - } - }); - // set toolboxes visible base on value of qgs_layer_id - this.dependencyApi.showPanel({ - toolboxes - }); - this.isRunning = true; - }; - - //run after each action - this.stopAction = async function(options={}){ - const {qgs_layer_id} = options; - qgs_layer_id && await this.stopEditing(qgs_layer_id); - }; - - //// subscribers handlers - this.subscribersHandlers = { - canUndo:({activeTool, disableToolboxes=[]}) => bool => { - //set currenttoolbocx id in editing to null - if (bool === false) { - this.responseObject.qgs_layer_id = null; - this.responseObject.error = null; - } - activeTool.setEnabled(!bool); - disableToolboxes.forEach(toolbox => toolbox.setEditing(!bool)) - }, - canRedo:() =>{}, - cancelform:cb=> ()=>{cb()},//run callback - addfeature: ({properties, toolboxes}={}) => feature => { - Object.keys(properties).forEach(property => feature.set(property, properties[property])); - let activeTool; - const disableToolboxes = []; - toolboxes.forEach(toolbox => { - const addFeatureTool = toolbox.getToolById('addfeature'); - if (addFeatureTool.isActive()){ - addFeatureTool.setEnabled(false); - activeTool = addFeatureTool; - } else { - toolbox.setEditing(false); - disableToolboxes.push(toolbox) - } - }); - //just one time - if (this.subscribevents.find(eventObject => eventObject.event !== 'canUndo')) { - const handler = this.addSubscribeEvents('canUndo', { - activeTool, - disableToolboxes - }); - this.addSubscribeEvents('cancelform', handler); - } - }, - closeeditingpanel: ({qgs_layer_id})=> () => { - // response to router service - this.responseObject.cb({ - qgs_layer_id: this.responseObject.qgs_layer_id, - error: this.responseObject.error - }); - // stop action - this.stopAction({qgs_layer_id}) - } - }; - - // method to add subscribe refenrence - this.addSubscribeEvents = function(event, options={}){ - const handler = this.subscribersHandlers[event](options); - this.dependencyApi.subscribe(event, handler); - this.subscribevents.push({ - event, - handler - }); - return handler; - }; - - /** - * Reset subscriber editing plugin events - */ - this.resetSubscribeEvents = function(){ - this.subscribevents.forEach(({event, handler}) =>{ - this.dependencyApi.unsubscribe(event, handler); - }) - }; - - /** - * Method called whe we want add a feature - * @param options - * @returns {Promise} - */ - this.add = function(config={}){ - return new Promise(async (resolve, reject) => { - if (this.isRunning){ - reject(); - } else { - // extract qgslayerid from configuration message - const {qgs_layer_id:configQglLayerId, ...data} = config; - const { properties } = data; - const qgs_layer_id = this.getQgsLayerId({ - qgs_layer_id: configQglLayerId, - noValue: this.dependencyApi.getEditableLayersId() - }); - //call method common - await this.startAction({ - toolboxes: qgs_layer_id, - resolve, - reject - }); - - // create options - const options = { - tools: this.config.tools.add, - startstopediting: false, - action : 'add', - selected: qgs_layer_id.length === 1 - }; - // return all toolboxes - let toolboxes = await this.startEditing(qgs_layer_id, options); - toolboxes = toolboxes.filter(toolboxPromise => toolboxPromise.status === 'fulfilled').map(toolboxPromise => toolboxPromise.value); - !GUI.isSidebarVisible() && GUI.showSidebar(); - const toolbox = toolboxes.length === 1 && toolboxes[0]; - toolbox && toolbox.setActiveTool(toolbox.getToolById('addfeature')); - // // in case of no feature add avent subscribe - this.addSubscribeEvents('addfeature', {properties, toolboxes}); - this.addSubscribeEvents('closeeditingpanel', {qgs_layer_id}) - } - }) - }; - - /** - * Method called when we want update a know feature field - * @param config - * @returns {Promise} - */ - this.update = async function(config={}){ - return new Promise(async (resolve, reject)=>{ - if (this.isRunning){ - reject(); - } else { - const {qgs_layer_id: configQglLayerId, ...data} = config; - const {feature} = data; - const qgs_layer_id = this.getQgsLayerId({ - qgs_layer_id: configQglLayerId, - noValue: this.dependencyApi.getEditableLayersId() - }); - const response = await this.findFeaturesWithGeometry({ - qgs_layer_id, - feature, - zoom: true, - highlight: true, - selected: qgs_layer_id.length === 1 // set selected toolbox - }); - const {found} = response; - if (found) { - await this.startAction({ - toolboxes: [response.qgs_layer_id], - resolve, - reject - }); - - // create options - const options = { - feature, - tools: this.config.tools.update, - startstopediting: false, - action: 'update' - }; - - // return all toolboxes - await this.startEditing([response.qgs_layer_id], options); - !GUI.isSidebarVisible() && GUI.showSidebar(); - this.addSubscribeEvents('closeeditingpanel', { - qgs_layer_id: [response.qgs_layer_id] - }) - } else reject(); - } - }) - }; - - this.delete = function(){}; - - /** - * Start editing called when we want to start editing - * @param qgs_layer_id - * @param options - * @returns {Promise} - */ - this.startEditing = async function(qgs_layer_id=[], options={}) { - const {action= 'add', feature} = options; - const filter = {}; - options.filter = filter; - switch (action) { - case 'add': - filter.nofeatures = true; - break; - case 'update': - filter.field = `${feature.field}|eq|${feature.value}`; - break; - } - const startEditingPromise = []; - qgs_layer_id.forEach(layerid =>{ - startEditingPromise.push(this.dependencyApi.startEditing(layerid, options)) - }); - return await Promise.allSettled(startEditingPromise); - }; - - /** - * Stop editing - * @param qgs_layer_id - * @returns {Promise} - */ - this.stopEditing = async function(qgs_layer_id) { - const stopEditingPromises = []; - qgs_layer_id.forEach(layerid =>{ - stopEditingPromises.push(this.dependencyApi.stopEditing(layerid)) - }); - await Promise.allSettled(stopEditingPromises); - this.clear(); - }; - - this.stop = function(){ - return new Promise((resolve, reject)=>{ - this.dependencyApi.hidePanel(); - GUI.hideSidebar(); - this.once('clear', resolve); - }) - }; - - /** - * Method called wen we want to reset default editing plugin behaviour - * - * */ - this.clear = function(){ - this.dependencyApi.resetDefault(); - this.isRunning = false; - this.responseObject = { - cb: null, // resolve or reject promise method - qgs_layer_id : null, - error: null - }; - this.resetSubscribeEvents(); - this.emit('clear'); - } -} - -inherit(EditingService, BasePluginService); - -export default new EditingService(); \ No newline at end of file diff --git a/src/services/iframe-plugin.js b/src/services/iframe-plugin.js deleted file mode 100644 index 9fa02eb2e..000000000 --- a/src/services/iframe-plugin.js +++ /dev/null @@ -1,152 +0,0 @@ -/** - * @file - * @since v3.6 - */ - -import GUI from 'services/gui'; - -const { splitContextAndMethod, uniqueId } = require('utils'); - -function IframePluginService(options={}) { - //project is current project send by application service - this.pendingactions = {}; - this.init = async function({project}={}) { - await GUI.isReady(); - this.services = { - app: require('services/iframe-app').default, - editing: require('services/iframe-editing').default, - }; - //set eventResponse handler to alla services - this.eventResponseServiceHandler = ({action, response}) => { - this.postMessage({ - id: null, - action, - response - }) - }; - /* - get layer attributes from project layers state - */ - const layers = project.state.layers.map(layer =>({ - id: layer.id, - name: layer.name - })); - //initialize all service - const serviceNames = Object.keys(this.services); - for (let i=0; i < serviceNames.length; i++){ - const service = this.services[serviceNames[i]]; - // set common layer attribute service just one time - service.getLayers() === undefined && service.setLayers(layers); - await service.init(); - service.on('response', this.eventResponseServiceHandler); - } - /** - * Send post message is ready - */ - this.postMessage({ - id:null, - action:"app:ready", - response: { - result: true, - data: { - layers - } - } - }); - - if (window.addEventListener) window.addEventListener("message", this.getMessage, false); - else window.attachEvent("onmessage", this.getMessage); - }; - - /** - * Outputplace iframe get by DataRouteService - * @param dataPromise - * @param options - * @returns {Promise} - */ - this.outputDataPlace = async function(dataPromise, options={}){ - const {action='app:results'} = options; - let {result, data=[]} = await dataPromise; - const parser = new ol.format.GeoJSON(); - let outputData = []; - try { - outputData = data.map(({layer, features})=>({ - [layer.getId()]: { - features: parser.writeFeatures(features) - } - })); - } catch(err){ - result: false; - outputData: err; - } - this.postMessage({ - id: null, - action, - response: { - result, - data: outputData - } - }) - }; - - // method to post message to parent - this.postMessage = function (message={}) { - if (window.parent) window.parent.postMessage(message, "*") - }; - - this.stopPendingActions = async function(){ - const promises = []; - Object.keys(this.pendingactions).forEach(id => { - const {context} = this.pendingactions[id]; - promises.push(this.services[context].stop()); - delete this.pendingactions[id]; - }); - return Promise.allSettled(promises) - }; - - // method to handle all message from window - this.getMessage = async evt => { - if (evt && evt.data) { - const { id = uniqueId(), single=true, action, data:params } = evt.data; - const {context, method} = splitContextAndMethod(action); - let result = false; - let data; - try { - if (this.services[context].getReady()) { - single && await this.stopPendingActions(); - this.pendingactions[id] = { - context - }; - data = await this.services[context][method](params); - result = true; - } - } catch(err){ - result = false; - data = err; - } - this.postMessage({ - id, - action, - response: { - result, - data - } - }); - delete this.pendingactions[id]; - } - }; - - // Called when change map or clear - this.clear = function() { - const serviceNames = Object.keys(this.services); - for (let i=0; i < serviceNames.length; i++) { - const service = this.services[serviceNames[i]]; - service.off('response', this.eventResponseServiceHandler) - } - this.stopPendingActions(); - if (window.removeEventListener) window.removeEventListener("message", this.getMessage, false); - else window.detachEvent("onmessage", this.getMessage); - } -} - -export default new IframePluginService(); \ No newline at end of file diff --git a/src/services/iframe.js b/src/services/iframe.js new file mode 100644 index 000000000..2f581997d --- /dev/null +++ b/src/services/iframe.js @@ -0,0 +1,988 @@ +/** + * @file + * @since v3.6 + */ + +import GUI from 'services/gui'; +import ProjectsRegistry from 'store/projects'; +import PluginsRegistry from 'store/plugins'; + +import { + splitContextAndMethod, + uniqueId, + createFilterFormField, +} from 'utils'; + +const { normalizeEpsg } = require('utils/geo'); +const Projections = require('g3w-ol/projection/projections'); +const G3WObject = require('core/g3wobject'); + +/** + * @param epsg: Number Code of epsg Ex.4326 + * + * @returns String Normalize epsg: From number ex: 4326 to 'EPSG:4326' + * + * @since 3.9.1 + */ +async function _getEpsgFromParam(epsg) { + epsg = normalizeEpsg(epsg) + await Projections.registerProjection(epsg); + return epsg; +} + +/** + * @example template.html + * + * ```html + * + * + * + * + * Test Iframe + * + * + * + * + * + * + * ``` + */ +class IframePluginService { + + constructor() { + this.pendingactions = {}; + this.getMessage = this.getMessage.bind(this); + this.eventResponseServiceHandler = this.eventResponseServiceHandler.bind(this); + } + + /** + * @param { Object } opts + * @param opts.project current project send by application service + */ + async init({ + project, + } = {}) { + + await GUI.isReady(); + + this.services = { + app: new AppService(), + editing: new EditingService(), + }; + + // get layer attributes from project layers state + const layers = project.state.layers.map(layer => ({ id: layer.id, name: layer.name })); + + // initialize all service + const serviceNames = Object.keys(this.services); + + for (let i = 0; i < serviceNames.length; i++) { + const service = this.services[serviceNames[i]]; + // set common layer attribute service just one time + if (undefined === service.getLayers()) { + service.setLayers(layers); + } + await service.init(); + service.on('response', this.eventResponseServiceHandler); + } + + // Send post message is ready + this.postMessage({ + id: null, + action: 'app:ready', + response: { result: true, data: { layers } }, + }); + + window.addEventListener('message', this.getMessage, false); + } + + // set eventResponse handler to alla services + eventResponseServiceHandler({ action, response }) { + this.postMessage({ id: null, action, response }) + } + + /** + * Outputplace iframe get by DataRouteService + * + * @param dataPromise + * @param options + * + * @returns { Promise } + */ + async outputDataPlace(dataPromise, options = {}){ + const { action = 'app:results' } = options; + let { result, data = [] } = await dataPromise; + const parser = new ol.format.GeoJSON(); + let outputData = []; + + try { + outputData = data.map(({ layer, features }) => ({ [layer.getId()]: { features: parser.writeFeatures(features) } })); + } catch(err) { + result = false; + outputData = err; + } + + this.postMessage({ + id: null, + action, + response: { result, data: outputData } + }) + } + + /** + * post message to parent + */ + postMessage(message = {}) { + if (window.parent) { + window.parent.postMessage(message, '*'); + } + } + + async stopPendingActions() { + const promises = []; + Object + .keys(this.pendingactions) + .forEach(id => { + promises.push(this.services[this.pendingactions[id].context].stop()); + delete this.pendingactions[id]; + }); + return Promise.allSettled(promises) + }; + + /** + * handle all message from window + */ + async getMessage(evt) { + if (evt && evt.data) { + const { id = uniqueId(), single=true, action, data:params } = evt.data; + const { context, method } = splitContextAndMethod(action); + let result = false; + let data; + try { + const is_ready = this.services[context].getReady(); + if (is_ready && single) { + await this.stopPendingActions(); + } + if (is_ready) { + this.pendingactions[id] = { context }; + data = await this.services[context][method](params); + result = true; + } + } catch(err) { + result = false; + data = err; + } + this.postMessage({ + id, + action, + response: { result, data }, + }); + delete this.pendingactions[id]; + } + } + + /** + * Called when change map or clear + */ + clear() { + const serviceNames = Object.keys(this.services); + for (let i = 0; i < serviceNames.length; i++) { + this.services[serviceNames[i]].off('response', this.eventResponseServiceHandler) + } + this.stopPendingActions(); + window.removeEventListener('message', this.getMessage, false); + } + +} + +/** + * ORIGINAL SOURCE: src/app/core/iframe/services/baseservice.js@3.9.0 + */ +class BaseIframeService extends G3WObject { + + constructor(options = {}) { + + super(); + + /** + * @type { boolean } + */ + this.ready = false; + + /** + * Map service + */ + this.mapService = GUI.getService('map'); + + /** + * Current project + */ + this.project = ProjectsRegistry.getCurrentProject(); + + /** + * @type { Array | undefined } + */ + this.layers = undefined; + + // common attributes between plugin service + + /** + * ORIGINAL SOURCE: src/app/core/iframe/services/plugins/service.js@3.9.0 + * + * @since 3.9.1 + */ + this.pluginName; + + /** + * ORIGINAL SOURCE: src/app/core/iframe/services/plugins/service.js@3.9.0 + * + * @since 3.9.1 + */ + this.dependencyApi = {}; + + } + + /** + * ORIGINAL SOURCE: src/app/core/iframe/services/plugins/service.js@3.9.0 + * + * @virtual method need to be implemented by subclasses + * + * @since 3.9.1 + */ + async init({ + layers = {} + } = {}) { + this.layers = layers; + // skip when plugin is not in configuration + if (!PluginsRegistry.isPluginInConfiguration(this.pluginName)) { + return; + } + const plugin = PluginsRegistry.getPlugin(this.pluginName); + if (plugin) { + this.setDependencyApi(plugin.getApi()); + this.setReady(true); + } else { + PluginsRegistry.onafter('registerPlugin', async plugin => { + await plugin.isReady(); + if (plugin.getName() === this.pluginName) { + this.setDependencyApi(plugin.getApi()); + this.setReady(true); + } + }) + } + } + + /** + * ORIGINAL SOURCE: src/app/core/iframe/services/plugins/service.js@3.9.0 + * + * @virtual method need to be implemented by subclasses + * + * @since 3.9.1 + */ + setDependencyApi(api = {}) { + this.dependencyApi = api; + } + + /** + * ORIGINAL SOURCE: src/app/core/iframe/services/plugins/service.js@3.9.0 + * + * @virtual method need to be implemented by subclasses + * + * @since 3.9.1 + */ + getDependecyApi() { + return this.dependencyApi; + } + + /** + * Return a qgs_layer_id array based on passed qgis_layer_id + * + * @param { Object } opts + * @param { string | string[] | null | undefined } opts.qgs_layer_id + * @param { Array } noValue + * + * @returns { string[] } qgs_layer_id + * + * @private + */ + getQgsLayerId({ + qgs_layer_id, + noValue = this.layers.map(layer => layer.id), + }) { + return qgs_layer_id ? [].concat(qgs_layer_id) : noValue; + }; + + /** + * getFeature from DataProvider + * + * @private + */ + async searchFeature({ + layer, + feature, + }) { + const search_endpoint = this.project.getSearchEndPoint(); + const { field, value } = feature; + const DataRouterService = require('services/data').default; + const { data = [] } = await DataRouterService.getData('search:features', { + inputs: { + layer, + search_endpoint, + filter: createFilterFormField({ layer, search_endpoint, field, value }) + }, + outputs: false + }); + return data; + }; + + /** + * Search feature(s) by field and value + * + * @param { Object } opts + * @param opts.qgs_layer_id + * @param opts.feature + * @param opts.zoom + * @param opts.highlight + * + * @returns { Promise<{ qgs_layer_id: null, features: [], found: boolean }>} + */ + async findFeaturesWithGeometry({ + feature, + qgs_layer_id = [], + zoom = false, + highlight = false, + } = {}) { + const response = { + found: false, + features: [], + qgs_layer_id: null + }; + let layersCount = qgs_layer_id.length; + let i = 0; + while (!response.found && i < layersCount) { + const layer = this.project.getLayerById(qgs_layer_id[i]); + try { + const data = layer && await this.searchFeature({ layer, feature }); + const features = data.length && data[0].features; + response.found = features && features.length > 0 && !!features.find(f => f.getGeometry()); + if (!features || !response.found) { + throw 'invalid response'; + } + response.features = features; + response.qgs_layer_id = qgs_layer_id[i]; + if (zoom) { + this.mapService.zoomToFeatures(features, { highlight }); + } + } catch(err) { + i++; + } + } + // in case of no response zoom too initial extent + if (!response.found) { + this.mapService.zoomToProjectInitExtent(); + } + return response; + } + + /** + * Set layer function + * + * @param layers + */ + setLayers(layers = []) { + this.layers = layers; + } + + getLayers() { + return this.layers; + } + + /** + * Set ready service + * + * @param bool + */ + setReady(bool = false) { + this.ready = bool; + } + + getReady() { + return this.ready; + } + + /** + * Overwrite single service: Usefult to stop eventually running action + * + * @virtual method need to be implemented by subclasses + * + * @returns { Promise } + */ + async stop() {} + + /** + * Overwrite each single service + * + * @virtual method need to be implemented by subclasses + */ + clear() {} + +} + +/** + * ORIGINAL SOURCE: src/services/iframe-app.js@3.9.0 + */ +class AppService extends BaseIframeService { + + constructor() { + super(); + this.mapControls = { + screenshot: { control: null }, + changeMap: { control: null }, + }; + } + + /** + * Init service + * + * @returns { Promise } + */ + init() { + return new Promise((resolve, reject) => { + this.mapService.once('ready', () => { + this._map = this.mapService.getMap(); + this._mapCrs = this.mapService.getCrs(); + this.mapControls.screenshot.control = this.mapService.getMapControlByType({ type: 'screenshot' }); + this.setReady(true); + resolve(); + }); + }) + } + + /** + * @returns { Promise } + */ + async results({ + capture = true, + }) { + const DataRouterService = require('services/data').default; + if (capture) { + DataRouterService.setOutputPlaces(['iframe']) + } else { + DataRouterService.resetDefaultOutput(); + } + return []; + } + + /** + * @returns { Promise } + */ + async screenshot({ + capture = true, + }) { + // skip when .. + if (!capture) { + this.mapControls.screenshot.control.resetOriginalOnClickEvent(); + return; + } + + this.mapControls.screenshot.control.overwriteOnClickEvent(async() => { + let response; + try { + response = { result: true, data: await this.mapService.createMapImage() }; + } catch(err) { + response = { result: false, data: err }; + } finally { + this.emit('response', { response, action: 'app:screenshot' }); + } + }); + } + + /** + * Eventually send as param the projection in which we would like get center of map + * + * @param { Object } params + * @param params.epsg since 3.9.1 + * + * @returns { Promise } + */ + async getcenter(params = {}) { + const center = this.mapService.getCenter(); + if (undefined !== params.epsg) { + return ol.proj.transform( + center, + this.mapService.getEpsg(), + await _getEpsgFromParam(params.epsg) + ); + } + return center; + } + + /** + * Zoom to coordinates + * + * @param { Object } params + * @param { Array } params.coordinates + * @param params.epsg since 3.9.1 + * + * @returns { Promise } + */ + async zoomtocoordinates(params = {}) { + let { + coordinates = [], + epsg, + } = params; + // skip when .. + if (!(coordinates && Array.isArray(coordinates) && 2 === coordinates.length)) { + return Promise.reject(coordinates); + } + if (undefined !== epsg) { + // normalized psg code + epsg = await _getEpsgFromParam(epsg); + coordinates = ol.proj.transform(coordinates, epsg, this.mapService.getEpsg()); + } + this.mapService.zoomTo(coordinates); + return coordinates; + } + + /** + * Eventually send as param the projection in which we would like get center of map + * + * @param { Object } params + * @param params.epsg since 3.9.1 + * + * @returns { Promise } + */ + async getextent(params = {}) { + const extent = this.mapService.getMapExtent(); + /** @FIXME add description */ + if (undefined !== params.epsg) { + return ol.proj.transformExtent( + extent, + this.mapService.getEpsg(), + await _getEpsgFromParam(params.epsg) + ); + } + return extent; + } + + /** + * @param { Object } params + * @param { Array } params.extent + * @param params.epsg since 3.9.1 + * + * @returns { Promise } + */ + async zoomtoextent(params = {}) { + let { extent=[], epsg } = params; + // skip when .. + if (!(extent && Array.isArray(extent) && 4 === extent.length)) { + return Promise.reject(extent); + } + /** @FIXME add description */ + if (undefined !== epsg) { + epsg = _getEpsgFromParam(epsg); + extent = ol.proj.transformExtent(extent, epsg, this.mapService.getEpsg()); + } else { + this.mapService.goToBBox(extent); + } + return extent; + }; + + /** + * Zoom to features + * + * @param { Object } params + * @param params.qgs_layer_id + * @param params.feature + * @param { boolean } params.highlight + * + * @returns { Promise } qgs_layer_id + */ + async zoomtofeature(params = {}) { + return new Promise(async (resolve, reject) => { + let { + qgs_layer_id, + feature, + highlight = false, + } = params; + + qgs_layer_id = this.getQgsLayerId({ qgs_layer_id }); + + const response = await this.findFeaturesWithGeometry({ + qgs_layer_id, + feature, + zoom: true, + highlight, + }); + + resolve(response.qgs_layer_id); + }); + } + +} + +/** + * ORIGINAL SOURCE: src/services/iframe-editing.js@3.9.0 + */ +class EditingService extends BaseIframeService { + + constructor() { + super(); + + this.pluginName = 'editing'; + + this.subscribevents = []; + + this.isRunning = false; + + this.responseObject = { + cb: null, // resolve or reject promise method + qgs_layer_id: null, + error: null, + }; + + this.config = { + tools: { + add: { + disabled:[ + { id: 'deletefeature' }, + { id: 'copyfeatures' }, + { id: 'editmultiattributes' }, + { id: 'deletePart' }, + { id: 'splitfeature' }, + { id: 'mergefeatures' }, + ] + }, + update: { + disabled: [ + { id: 'addfeature' }, + { id: 'copyfeatures' }, + { id: 'deletefeature' }, + { id: 'editmultiattributes' }, + { id: 'deletePart' }, + { id: 'splitfeature' }, + { id: 'mergefeatures' }, + ] + }, + delete: { + enabled: [ + { id:'deletefeature', options: { active: true } }, + ] + } + } + }; + + /** + * subscribers handlers + */ + this.subscribersHandlers = { + + canUndo:({ activeTool, disableToolboxes = [] }) => bool => { + //set currenttoolbocx id in editing to null + if (false === bool) { + this.responseObject.qgs_layer_id = null; + this.responseObject.error = null; + } + activeTool.setEnabled(!bool); + disableToolboxes.forEach(toolbox => toolbox.setEditing(!bool)) + }, + + canRedo:() => {}, + + //run callback + cancelform:cb => () => { cb() }, + + addfeature: ({ properties, toolboxes } = {}) => feature => { + + Object + .keys(properties) + .forEach(p => feature.set(p, properties[p])); + + let activeTool; + const disableToolboxes = []; + + toolboxes + .forEach(t => { + const tool = t.getToolById('addfeature'); + if (tool.isActive()) { + tool.setEnabled(false); + activeTool = tool; + } else { + t.setEditing(false); + disableToolboxes.push(t) + } + }); + + // just one time + if (this.subscribevents.find(e => 'canUndo' !== e.event)) { + this.addSubscribeEvents('cancelform', this.addSubscribeEvents('canUndo', { activeTool, disableToolboxes })); + } + }, + + closeeditingpanel: ({ qgs_layer_id }) => () => { + // response to router service + this.responseObject.cb({ + qgs_layer_id: this.responseObject.qgs_layer_id, + error: this.responseObject.error, + }); + // stop action + this.stopAction({ qgs_layer_id }); + }, + + }; + + } + + // METHODS CALLED FROM EACH ACTION METHOD + + /** + * run before each action + */ + async startAction({ + toolboxes, + resolve, + reject, + }) { + + this.responseObject.cb = reject; + + // set same mode autosave + this.dependencyApi.setSaveConfig({ + cb: { + // called when commit changes is done successuffly + done: toolbox => { + //set toolbox id + this.responseObject.cb = resolve; + this.responseObject.qgs_layer_id = toolbox.getId(); + this.responseObject.error = null; + // close panel that fire closeediting panel event + this.dependencyApi.hidePanel(); + }, + // called whe commit change receive an error + error: (toolbox, error) => { + this.responseObject.cb = reject; + this.responseObject.qgs_layer_id = toolbox.getId(); + this.responseObject.error = error; + }, + } + }); + + // set toolboxes visible base on value of qgs_layer_id + this.dependencyApi.showPanel({ toolboxes }); + + this.isRunning = true; + } + + /** + * run after each action + */ + async stopAction(opts = {}) { + if (opts.qgs_layer_id) { + await this.stopEditing(opts.qgs_layer_id); + } + } + + /** + * add subscribe refenrence + */ + addSubscribeEvents(event, options = {}) { + const handler = this.subscribersHandlers[event](options); + this.dependencyApi.subscribe(event, handler); + this.subscribevents.push({ event, handler }); + return handler; + }; + + /** + * Reset subscriber editing plugin events + */ + resetSubscribeEvents() { + this.subscribevents.forEach((d) => { this.dependencyApi.unsubscribe(d.event, d.handler); }); + }; + + /** + * Called whe we want add a feature + * + * @param { Object } config + * @param config.qgs_layer_id + * @param config.properties + * + * @returns { Promise } + */ + add(config = {}) { + return new Promise(async (resolve, reject) => { + // skip when .. + if (this.isRunning) { + return reject(); + } + + // extract `qgs_layer_id9` from configuration message + const { qgs_layer_id: configQglLayerId, ...data } = config; + const { properties } = data; + + const qgs_layer_id = this.getQgsLayerId({ + qgs_layer_id: configQglLayerId, + noValue: this.dependencyApi.getEditableLayersId(), + }); + + // call method common + await this.startAction({ toolboxes: qgs_layer_id, resolve, reject }); + + // return all toolboxes + const toolboxes = ( + await this.startEditing(qgs_layer_id, { + tools: this.config.tools.add, + startstopediting: false, + action : 'add', + selected: 1 === qgs_layer_id.length, + }) + ) + .filter(p => 'fulfilled' === p.status) + .map(p => p.value); + + /** @FIXME add description */ + if (!GUI.isSidebarVisible()) { + GUI.showSidebar(); + } + + /** @FIXME add description */ + if (1 === toolboxes.length && toolboxes[0]) { + toolboxes[0].setActiveTool(toolboxes[0].getToolById('addfeature')); + } + + // in case of no feature add avent subscribe + this.addSubscribeEvents('addfeature', { properties, toolboxes }); + this.addSubscribeEvents('closeeditingpanel', { qgs_layer_id }) + }); + } + + /** + * Called when we want update a know feature field + * + * @param config + * + * @returns { Promise } + */ + async update(config = {}) { + return new Promise(async (resolve, reject) => { + // skip when .. + if (this.isRunning) { + return reject(); + } + + const { qgs_layer_id: configQglLayerId, ...data } = config; + const { feature } = data; + const qgs_layer_id = this.getQgsLayerId({ + qgs_layer_id: configQglLayerId, + noValue: this.dependencyApi.getEditableLayersId() + }); + + const response = await this.findFeaturesWithGeometry({ + qgs_layer_id, + feature, + zoom: true, + highlight: true, + selected: 1 === qgs_layer_id.length // set selected toolbox + }); + + // skip when .. + if (!response.found) { + return reject(); + } + + await this.startAction({ toolboxes: [response.qgs_layer_id], resolve, reject }); + + // return all toolboxes + await this.startEditing([response.qgs_layer_id], { + feature, + tools: this.config.tools.update, + startstopediting: false, + action: 'update', + }); + + if (!GUI.isSidebarVisible()) { + GUI.showSidebar(); + } + + this.addSubscribeEvents('closeeditingpanel', { qgs_layer_id: [response.qgs_layer_id] }); + }); + } + + /** + * @virtual method need to be implemented by subclasses + */ + delete() {} + + /** + * Called when we want to start editing + * + * @param { Array } qgs_layer_id + * @param { Object } options + * + * @returns { Promise< unknown | void > } + */ + async startEditing(qgs_layer_id = [], options = {}) { + const { action = 'add', feature } = options; + const filter = {}; + options.filter = filter; + switch (action) { + case 'add': filter.nofeatures = true; break; + case 'update': filter.field = `${feature.field}|eq|${feature.value}`; break; + } + const promises = []; + qgs_layer_id.forEach(layerid => { promises.push(this.dependencyApi.startEditing(layerid, options)) }); + return await Promise.allSettled(promises); + } + + /** + * Stop editing + * + * @param qgs_layer_id + * + * @returns { Promise } + */ + async stopEditing(qgs_layer_id) { + const promises = []; + qgs_layer_id.forEach(layerid => { promises.push(this.dependencyApi.stopEditing(layerid)); }); + await Promise.allSettled(promises); + this.clear(); + } + + stop() { + return new Promise((resolve, reject) => { + this.dependencyApi.hidePanel(); + GUI.hideSidebar(); + this.once('clear', resolve); + }); + } + + /** + * Called wen we want to reset default editing plugin behaviour + */ + clear() { + this.dependencyApi.resetDefault(); + this.isRunning = false; + this.responseObject = { + cb: null, // resolve or reject promise method + qgs_layer_id: null, + error: null, + }; + this.resetSubscribeEvents(); + this.emit('clear'); + } + +} + +export default new IframePluginService(); \ No newline at end of file diff --git a/src/services/index.js b/src/services/index.js index 143d7ce7b..2a26f1363 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -12,7 +12,7 @@ import GUI from './gui'; import HistoryService from './history'; import AppService from './iframe-app'; import EditingService from './iframe-editing'; -import IframePluginService from './iframe-plugin'; +import IframeService from './iframe'; import QueryBuilderService from './querybuilder'; import RelationsService from './relations'; import RouterService from './router'; @@ -38,7 +38,7 @@ export { HistoryService, AppService, EditingService, - IframePluginService, + IframeService, QueryBuilderService, RelationsService, RouterService, From 3a289670fb1c1414437ba459999b9b5da92ca713 Mon Sep 17 00:00:00 2001 From: Raruto Date: Wed, 20 Dec 2023 14:18:22 +0100 Subject: [PATCH 4/4] remove orphans --- src/app/core/iframe/services/baseservice.js | 185 ------------------- src/services/iframe-app.js | 194 -------------------- 2 files changed, 379 deletions(-) delete mode 100644 src/app/core/iframe/services/baseservice.js delete mode 100644 src/services/iframe-app.js diff --git a/src/app/core/iframe/services/baseservice.js b/src/app/core/iframe/services/baseservice.js deleted file mode 100644 index 5dc4d7729..000000000 --- a/src/app/core/iframe/services/baseservice.js +++ /dev/null @@ -1,185 +0,0 @@ -import DataRouterService from 'services/data'; -import ProjectsRegistry from 'store/projects'; -import GUI from 'services/gui'; - -const { - base, - inherit, - createFilterFormField -} = require('utils'); -const G3WObject = require('core/g3wobject'); - -function BaseIframeService(options={}) { - - base(this); - - /** - * @type { boolean } - */ - this.ready = false; - - /** - * Map service - */ - this.mapService = GUI.getService('map'); - - /** - * Current project - */ - this.project = ProjectsRegistry.getCurrentProject(); - - /** - * @type { Array | undefined } - */ - this.layers = undefined; - - this.init = function() { - // overwrite each service - }; - -} - -inherit(BaseIframeService, G3WObject); - -const proto = BaseIframeService.prototype; - -/** - * Return a qgs_layer_id array based on passed qgis_layer_id - * - * @param { Object } opts - * @param { string | string[] | null | undefined } opts.qgs_layer_id - * @param { Array } noValue - * - * @returns { string[] } qgs_layer_id - * - * @private - */ -proto.getQgsLayerId = function({ - qgs_layer_id, - noValue = this.layers.map(layer => layer.id) -}) { - return qgs_layer_id ? - ( - Array.isArray(qgs_layer_id) ? - qgs_layer_id : - [qgs_layer_id] - ) : - noValue; -}; - -/** - * getFeature from DataProvider - * - * @private - */ -proto.searchFeature = async function({layer, feature}) { - const search_endpoint = this.project.getSearchEndPoint(); - const { field, value } = feature; - const { data = [] } = await DataRouterService.getData('search:features', { - inputs: { - layer, - search_endpoint, - filter: createFilterFormField({ layer, search_endpoint, field, value }) - }, - outputs: false - }); - return data; -}; - -/** - * Search feature(s) by field and value - * - * @param { Object } opts - * @param opts.qgs_layer_id - * @param opts.feature - * @param opts.zoom - * @param opts.highlight - * - * @returns { Promise<{ qgs_layer_id: null, features: [], found: boolean }>} - */ -proto.findFeaturesWithGeometry = async function({ - feature, - qgs_layer_id = [], - zoom = false, - highlight = false, -}={}) { - const response = { - found: false, - features: [], - qgs_layer_id: null - }; - let layersCount = qgs_layer_id.length; - let i = 0; - while (!response.found && i < layersCount) { - const layer = this.project.getLayerById(qgs_layer_id[i]); - try { - const data = layer && await this.searchFeature({ layer, feature }); - if (data.length) { - const features = data[0].features; - response.found = features.length > 0 && !!features.find(feature => feature.getGeometry()); - if (response.found) { - response.features = features; - response.qgs_layer_id = qgs_layer_id[i]; - zoom && this.mapService.zoomToFeatures(features, { - highlight - }); - } else { - i++; - } - } else { - i++; - } - } catch(err) { - i++; - } - } - // in case of no response zoom too initial extent - if (!response.found) { - this.mapService.zoomToProjectInitExtent(); - } - return response; -}; - -/** - * Set layer function - * - * @param layers - */ -proto.setLayers = function(layers=[]) { - proto.layers = layers; -}; - -proto.getLayers = function() { - return proto.layers; -}; - -/** - * Set ready service - * - * @param bool - */ -proto.setReady = function(bool=false) { - this.ready = bool; -}; - -proto.getReady = function() { - return this.ready; -}; - -/** - * Overwrite single service: Usefult to stop eventually running action - * - * @virtual method need to be implemented by subclasses - * - * @returns { Promise } - */ -proto.stop = async function() {}; - -/** - * Overwrite each single service - * - * @virtual method need to be implemented by subclasses - */ -proto.clear = function() {}; - -module.exports = BaseIframeService; \ No newline at end of file diff --git a/src/services/iframe-app.js b/src/services/iframe-app.js deleted file mode 100644 index cb6c53d17..000000000 --- a/src/services/iframe-app.js +++ /dev/null @@ -1,194 +0,0 @@ -/** - * @file - * @since v3.6 - */ - -import DataRouterService from 'services/data'; - -const { base, inherit } = require('utils'); -const { normalizeEpsg } = require('utils/geo'); -const BaseService = require('core/iframe/services/baseservice'); -const Projections = require('g3w-ol/projection/projections'); - - -function AppService() { - - base(this); - - this.mapControls = { - screenshot: { - control: null - }, - changeMap: { - control: null - } - }; - - /** - * Init service - * @returns {Promise} - */ - this.init = function() { - return new Promise((resolve, reject) => { - this.mapService.once('ready', () => { - this._map = this.mapService.getMap(); - this._mapCrs = this.mapService.getCrs(); - this.mapControls.screenshot.control = this.mapService.getMapControlByType({ - type: 'screenshot' - }); - this.setReady(true); - resolve(); - }); - }) - - }; - /** - * - * @returns {Promise} - */ - this.results = async function({capture=true}){ - capture ? - DataRouterService.setOutputPlaces(['iframe']) : - DataRouterService.resetDefaultOutput(); - return []; - }; - - /** - * - * @param capture - * @returns {Promise} - */ - this.screenshot = async function({capture=true}) { - const action = 'app:screenshot'; - capture ? - this.mapControls.screenshot.control.overwriteOnClickEvent(async() => { - try { - const blob = await this.mapService.createMapImage(); - this.emit('response', { - action, - response: { - result: true, - data: blob - } - }) - } catch(err) { - this.emit('response', { - action, - response: { - result: false, - data: err - } - }) - } - }) : - this.mapControls.screenshot.control.resetOriginalOnClickEvent(); - }; - - /** - * @since v3.7.1 - * @param epsg: Number Code of epsg Ex.4326 - * @returns String Normalize epsg: From number ex: 4326 to 'EPSG:4326' - * @private - */ - this._getEpsgFromParam = async function(epsg) { - epsg = normalizeEpsg(epsg) - await Projections.registerProjection(epsg); - return epsg; - } - - /** - * Eventually send as param the projection in which we would like get center of map - * @param params - * @returns {Promise} - */ - this.getcenter = async function(params={}) { - const center = this.mapService.getCenter(); - if (undefined === params.epsg) { - return center; - } else { - const epsg = await this._getEpsgFromParam(params.epsg); - return ol.proj.transform(center, this.mapService.getEpsg(), epsg); - } - }; - - /** - * Zoom to coordinates - * @param params - * @returns {Promise<[]>} - */ - this.zoomtocoordinates = async function(params={}) { - let {coordinates=[], highlight=false, epsg} = params; - if (coordinates && Array.isArray(coordinates) && coordinates.length === 2) { - if (undefined === epsg) { - this.mapService.zoomTo(coordinates); - } else { - //normalizated psg code - epsg = await this._getEpsgFromParam(epsg); - coordinates = ol.proj.transform(coordinates, epsg, this.mapService.getEpsg()); - this.mapService.zoomTo(coordinates); - } - return coordinates; - } else { - return Promise.reject(coordinates); - } - }; - - /** - * Eventually send as param the projection in which we would like get center of map - * @param params - * @returns {Promise} - */ - this.getextent = async function(params={}) { - const extent = this.mapService.getMapExtent(); - if (undefined === params.epsg) { - return extent; - } else { - const epsg = await this._getEpsgFromParam(params.epsg); - return ol.proj.transformExtent(extent, this.mapService.getEpsg(), epsg); - } - }; - - /** - * - * @param params - * @returns {Promise<[]>} - */ - this.zoomtoextent = async function(params={}) { - let {extent=[], epsg} = params; - if (extent && Array.isArray(extent) && extent.length === 4) { - if (undefined === epsg) { - this.mapService.goToBBox(extent); - } else { - epsg = this._getEpsgFromParam(epsg); - extent = ol.proj.transformExtent(extent, epsg, this.mapService.getEpsg()); - } - return extent; - } else { - return Promise.reject(extent); - } - }; - - - //method to zoom to features - this.zoomtofeature = async function(params={}) { - return new Promise(async (resolve, reject) => { - let {qgs_layer_id, feature, highlight=false} = params; - qgs_layer_id = this.getQgsLayerId({ - qgs_layer_id - }); - - const response = await this.findFeaturesWithGeometry({ - qgs_layer_id, - feature, - zoom:true, - highlight - }); - - resolve(response.qgs_layer_id); - }) - }; -} - -inherit(AppService, BaseService); - -export default new AppService(); \ No newline at end of file