├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── PULL_REQUEST_TEMPLATE.md ├── release.yml └── workflows │ └── backport.yml ├── .gitignore ├── .npmrc ├── AUTHORS.txt ├── COPYING.txt ├── README.md ├── config.template.js ├── gulpfile.js ├── jsconfig.json ├── package.json └── src ├── app ├── README.md └── gui │ ├── fields │ ├── fields.js │ └── fieldsservice.js │ └── inputs │ ├── checkbox │ ├── service.js │ └── vue │ │ └── checkbox.js │ ├── color │ └── vue │ │ └── color.js │ ├── datetimepicker │ ├── service.js │ └── vue │ │ └── datetimepicker.js │ ├── float │ ├── service.js │ └── vue │ │ └── float.js │ ├── input.js │ ├── integer │ ├── service.js │ └── vue │ │ └── integer.js │ ├── lonlat │ ├── service.js │ └── vue │ │ └── lonlat.js │ ├── media │ ├── service.js │ └── vue │ │ └── media.js │ ├── picklayer │ ├── service.js │ └── vue │ │ └── picklayer.js │ ├── radio │ ├── service.js │ └── vue │ │ └── radio.js │ ├── range │ ├── service.js │ └── vue │ │ └── range.js │ ├── select │ ├── service.js │ └── vue │ │ └── select.js │ ├── service.js │ ├── services.js │ ├── sliderrange │ ├── service.js │ └── vue │ │ └── sliderrange.js │ ├── table │ └── vue │ │ └── table.js │ ├── text │ └── vue │ │ └── text.js │ ├── textarea │ └── vue │ │ └── textarea.js │ ├── texthtml │ └── vue │ │ └── texthtml.js │ └── unique │ ├── service.js │ └── vue │ └── unique.js ├── assets ├── app.css ├── cursors │ ├── mCapturePoint.svg │ ├── mIdentify.svg │ ├── mZoomIn.svg │ └── mZoomOut.svg ├── fonts │ ├── titillium-web-latin-400-italic.woff2 │ ├── titillium-web-latin-400-normal.woff2 │ ├── titillium-web-latin-700-italic.woff2 │ ├── titillium-web-latin-700-normal.woff2 │ ├── titillium-web-latin-ext-400-italic.woff2 │ ├── titillium-web-latin-ext-400-normal.woff2 │ ├── titillium-web-latin-ext-700-italic.woff2 │ └── titillium-web-latin-ext-700-normal.woff2 ├── geocoding-providers │ ├── bing_places.js │ ├── bing_streets.js │ ├── google.js │ └── nominatim.js └── images │ ├── FakeProjectThumb.png │ ├── baselayers │ ├── bingaerial.png │ ├── bingaerialwithlabels.png │ ├── bingstreets.png │ ├── nobaselayer.png │ └── osm.png │ ├── compass.svg │ ├── controls │ ├── addlayer.svg │ ├── camera.svg │ ├── mActionAddBasicCircle.svg │ ├── mActionAddBasicRectangle.svg │ ├── mActionAddPoint.svg │ ├── mActionAddPolygon.svg │ ├── mActionAddPolyline.svg │ ├── mActionCreateAnnotationLayer.svg │ ├── mActionIdentify.svg │ ├── mActionIdentifyByFreehand.svg │ ├── mActionIdentifyByPolygon.svg │ ├── mActionIdentifyByRadius.svg │ ├── mActionIdentifyByRectangle.svg │ ├── mActionMeasure.svg │ ├── mActionText.svg │ ├── mActionTextAnnotation.svg │ ├── mActionZoomFullExtent.svg │ ├── mActionZoomIn.svg │ ├── mActionZoomOut.svg │ ├── mActionZoomToArea.svg │ ├── my_location.svg │ └── streetview.svg │ ├── g3wsuite_logo.png │ ├── loader.svg │ ├── logo_gis3w_156_85.png │ ├── mapcentermarker.svg │ ├── pushpin.svg │ └── streetviewarrow.png ├── components ├── App.vue ├── Catalog.vue ├── CatalogChangeMapThemes.vue ├── CatalogContextMenu.vue ├── CatalogLayerLegend.vue ├── CatalogTristateTree.vue ├── ContextMenu.vue ├── ContextMenuItem.vue ├── Field.vue ├── FieldG3W.vue ├── FieldGeo.vue ├── FieldImage.vue ├── FieldLink.vue ├── FieldMedia.vue ├── FieldText.vue ├── FieldVue.vue ├── Form.vue ├── FormAddon.vue ├── FormAddons.vue ├── FormBody.vue ├── FormFooter.vue ├── FormHeader.vue ├── GlobalBarLoader.vue ├── GlobalDateTime.vue ├── GlobalDivider.vue ├── GlobalGeo.vue ├── GlobalHelpDiv.vue ├── GlobalProgressBar.vue ├── GlobalRange.vue ├── GlobalTabs.vue ├── GlobalTabsNode.vue ├── InputBase.vue ├── InputCheckbox.vue ├── InputColor.vue ├── InputDateTimePicker.vue ├── InputFloat.vue ├── InputG3W.vue ├── InputG3WFormInputs.vue ├── InputInteger.vue ├── InputLonLat.vue ├── InputMedia.vue ├── InputPickLayer.vue ├── InputRadio.vue ├── InputRange.vue ├── InputSelect.vue ├── InputSliderRange.vue ├── InputTable.vue ├── InputTableBody.vue ├── InputTableHeader.vue ├── InputText.vue ├── InputTextArea.vue ├── InputTextHtml.vue ├── InputUnique.vue ├── Map.vue ├── MapControlGeocoding.vue ├── MapControlZoomHistory.vue ├── ModalAddLayer.vue ├── ModalChangeMap.vue ├── ModalLogin.vue ├── ModalMetadata.vue ├── Print.vue ├── PrintPage.vue ├── ProjectsMenu.vue ├── QueryBuilder.vue ├── QueryResults.vue ├── QueryResultsAction.vue ├── QueryResultsActionChooseLayer.vue ├── QueryResultsActionDownloadFormats.vue ├── QueryResultsActionInfoFormats.vue ├── QueryResultsActionQueryPolygonCSVAttributes.vue ├── QueryResultsActions.vue ├── QueryResultsHeaderFeatureActionsBody.vue ├── QueryResultsHeaderFeatureBody.vue ├── QueryResultsTableAttributeFieldValue.vue ├── Relation.vue ├── Relations.vue ├── RelationsPage.vue ├── Search.vue ├── SearchPanel.vue ├── SidebarItem.vue ├── SpatialBookMarks.vue ├── Table.vue ├── Tool.vue ├── UserMessage.vue ├── g3w-alerts.js ├── g3w-form.js └── g3w-search.js ├── directives ├── utils.js ├── v-disabled.js ├── v-download.js ├── v-select2.js ├── v-t-html.js ├── v-t-plugin.js ├── v-t-tooltip.js └── v-t.js ├── g3w-component.js ├── g3w-constants.js ├── g3w-eventbus.js ├── g3w-globals.js ├── g3w-i18n.js ├── g3w-object.js ├── g3w-panel.js ├── g3w-plugin.js ├── g3w-vendors.js ├── index.dev.js ├── index.prod.js ├── locales ├── de.js ├── en.js ├── fi.js ├── fr.js ├── index.js ├── it.js ├── pl.js ├── pt.js ├── ro.js ├── se.js └── uk.js ├── map ├── controls │ ├── annotationcontrol.js │ ├── geolocationcontrol.js │ ├── interactioncontrol.js │ ├── measurecontrol.js │ ├── queryby.js │ ├── scalecontrol.js │ ├── screenshotcontrol.js │ └── streetviewcontrol.js ├── interactions │ ├── pickcoordinatesinteraction.js │ └── pickfeatureinteraction.js └── layers │ ├── feature.js │ ├── featuresstore.js │ ├── imagelayer.js │ ├── layer.js │ ├── layersstore.js │ ├── tablelayer.js │ └── vectorlayer.js ├── mixins ├── autocomplete.js ├── base-input.js ├── click.js ├── fields.js ├── form-inputs.js ├── geo.js ├── index.js ├── media.js ├── resize.js ├── select.js └── select2.js ├── plugins └── README.md ├── services ├── application.js ├── data.js ├── gui.js ├── iframe.js ├── map.js ├── queryresults.js └── tasks.js ├── store ├── application.js ├── plugins.js └── projections.js └── utils ├── XHR.js ├── addZValue.js ├── areCoordinatesEqual.js ├── base.js ├── convertQGISDateTimeFormatToMoment.js ├── convertSingleMultiGeometry.js ├── copyUrl.js ├── createFilterFormInputs.js ├── createFilterFromString.js ├── createMeasureTooltip.js ├── createRelationsUrl.js ├── createSelectedStyle.js ├── createVectorLayerFromFile.js ├── debounce.js ├── dissolve.js ├── distance.js ├── flattenObject.js ├── getAlphanumericPropertiesFromFeature.js ├── getCatalogLayerById.js ├── getCatalogLayers.js ├── getDataForSearchInput.js ├── getDefaultExpression.js ├── getFilterExpression.js ├── getListableProjects.js ├── getMapLayersByFilter.js ├── getOLGeometry.js ├── getPlugin.js ├── getProject.js ├── getProjectConfigByGid.js ├── getProjectUrl.js ├── getRelationLayerById.js ├── getResolutionFromScale.js ├── getScaleFromResolution.js ├── getUniqueDomId.js ├── get_legend_params.js ├── groupBy.js ├── inherit.js ├── intersects.js ├── is3DGeometry.js ├── isLineGeometryType.js ├── isMultiGeometry.js ├── isPointGeometryType.js ├── isPolygonGeometryType.js ├── noop.js ├── normalizeEpsg.js ├── parsers └── index.js ├── printAtlas.js ├── promisify.js ├── prompt.js ├── removeZValue.js ├── resolve.js ├── reverseGeometry.js ├── sameOrigin.js ├── sanitizeFidFeature.js ├── saveBlob.js ├── splitFeature.js ├── throttle.js ├── toRawType.js ├── waitFor.js └── within.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization, 2 | # when the file has been committed with CRLF, no conversion is done. 3 | * text=auto -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug report 2 | description: Report a bug/issue 3 | labels: [bug] 4 | assignees: [volterra79] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Checklist 9 | description: > 10 | Please confirm the following: 11 | options: 12 | - label: I've searched through the existing [issues](https://github.com/g3w-suite/g3w-client/issues) and this bug has never been reported before 13 | required: false 14 | - type: textarea 15 | attributes: 16 | label: Subject of the issue 17 | description: Describe your issue here. 18 | placeholder: You can attach images or log files by clicking this area to highlight it and then dragging files in. 19 | validations: 20 | required: true 21 | - type: textarea 22 | attributes: 23 | label: Steps to reproduce 24 | description: Steps to reproduce the behavior. 25 | placeholder: | 26 | 1. Go to '...' 27 | 2. Click on '....' 28 | 3. Scroll down to '....' 29 | 4. See error 30 | validations: 31 | required: true 32 | - type: textarea 33 | attributes: 34 | label: Environment 35 | description: | 36 | Please provide as much information as possible in order to resolve your issue. Need help? [Find out your version](https://github.com/g3w-suite/g3w-admin#version). 37 | value: | 38 | - g3w-admin: __version__ 39 | - g3w-client: __version__ 40 | - browser: __name__ 41 | - operating system: __name and version (desktop or mobile)__ 42 | validations: 43 | required: true 44 | - type: input 45 | attributes: 46 | label: Link to your project 47 | description: A public URL that can help others to check your issue 48 | placeholder: eg. https://dev.g3wsuite.it/en/map/demo-34/ 49 | validations: 50 | required: false 51 | - type: textarea 52 | attributes: 53 | label: Additional info 54 | description: | 55 | Add any other useful information that could allow others to replicate your issue, for example: 56 | - **installed plugins** 57 | - **project structure** 58 | - **configuration files** 59 | - **self-explanatory pictures** 60 | placeholder: You can attach images or log files by clicking this area to highlight it and then dragging files in. 61 | validations: 62 | required: false 63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | 3 | contact_links: 4 | - name: 📄 Improve documentation 5 | url: https://github.com/g3w-suite/g3w-suite-documentation/issues 6 | about: Suggest new additions or updates to existing documentation related to https://g3w-suite.readthedocs.io/ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: ☝️ Feature request 2 | description: Suggest an idea for this project 3 | labels: 'feature' 4 | assignees: [volterra79] 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Checklist 9 | description: > 10 | Please confirm the following: 11 | options: 12 | - label: I've searched through the [current issues](https://github.com/g3w-suite/g3w-client/issues) to make sure this feature hasn't been requested already. 13 | required: false 14 | 15 | - type: textarea 16 | attributes: 17 | label: Motivation 18 | description: Is your feature request related to a problem? Provide a clear and concise description of what the problem is. 19 | placeholder: e.g. "I'm always frustrated when [...]" 20 | validations: 21 | required: true 22 | 23 | - type: textarea 24 | attributes: 25 | label: Suggested solution 26 | description: Provide a clear and concise description of what you want to happen. 27 | placeholder: You can attach images or log files by clicking this area to highlight it and then dragging files in. 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | attributes: 33 | label: Alternatives considered 34 | description: Please describe any alternative solutions or features you've considered. 35 | placeholder: Screenshots, video, links, etc. 36 | validations: 37 | required: false 38 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Closes: # 2 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | authors: 6 | - octocat 7 | categories: 8 | - title: 🚀 New Features 9 | labels: 10 | - feature 11 | - title: 🐞 Bug fixed 12 | labels: 13 | - bug 14 | - title: ✨ Improvements 15 | labels: 16 | - ux 17 | - title: 📝 Documentation 18 | labels: 19 | - docs 20 | - title: 👨‍🚀 Translations 21 | labels: 22 | - i18n 23 | - title: 🐙 Github 24 | labels: 25 | - github 26 | - title: 🧪 Tests 27 | labels: 28 | - tests 29 | - title: 🛠️ Config 30 | labels: 31 | - config 32 | - title: ♻️ API changes 33 | labels: 34 | - refactoring 35 | - title: ⚠️ Breaking changes 36 | labels: 37 | - breaking 38 | - title: ⚓ Other changes 39 | labels: 40 | - "*" 41 | -------------------------------------------------------------------------------- /.github/workflows/backport.yml: -------------------------------------------------------------------------------- 1 | name: 🔙 Backport 2 | on: 3 | pull_request_target: 4 | types: 5 | - closed 6 | - labeled 7 | 8 | jobs: 9 | backport: 10 | name: Backport 11 | runs-on: ubuntu-latest 12 | # Only react to merged PRs for security reasons. 13 | # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. 14 | if: > 15 | github.event.pull_request.merged 16 | && ( 17 | github.event.action == 'closed' 18 | || ( 19 | github.event.action == 'labeled' 20 | && contains(github.event.label.name, 'backport') 21 | ) 22 | ) 23 | steps: 24 | - uses: tibdex/backport@v2 25 | with: 26 | github_token: ${{ secrets.GITHUB_TOKEN }} 27 | body_template: "Backport <%= mergeCommitSha %> from #<%= number %> to <%= base %>.\n\n<%= body %>" 28 | label_pattern: "^backport to (?([^ ]+))$" 29 | # Include the original labels from the merged PR (minus any matching label_pattern) 30 | labels_template: "<% print(JSON.stringify(labels)) %>" 31 | title_template: "🔙 from #<%= number %> - <%= title %>" 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .idea 3 | .vscode 4 | package-lock.json 5 | /build 6 | /config.js 7 | /cypress.env.json 8 | /deploy/ 9 | /dist 10 | /documento.odt 11 | /g3w-admin/ 12 | /node_modules 13 | /npm-debug.log 14 | /test/ 15 | /test/config/ 16 | /src/app/__dev__/ 17 | /src/app/core/iframe/test/ 18 | /src/app/dev/ 19 | /src/config/ 20 | /src/assets/style/less/g3w-skins-custom/ 21 | /src/libs/sdk/config/dev/ 22 | /src/libs/sdk/g3w-ol3/config/config.js 23 | /src/plugins/** 24 | !/src/plugins/README.md 25 | !/src/plugins/_version.js -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # If set to true, npm will refuse to install any package 2 | # not compatible with the current Node version (see "engines" in package.json) 3 | engine-strict=true -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Francesco Boccacci 2 | Giovanni Allegri 3 | Walter Lorenzetti 4 | Leonardo Lami 5 | -------------------------------------------------------------------------------- /config.template.js: -------------------------------------------------------------------------------- 1 | const { version } = require('./package.json'); 2 | 3 | const G3W_PLUGINS = [ // override "initConfig->group->plugins" attribute for custom plugin development 4 | // "your-plugin-folder-name-1", 5 | // "your-plugin-folder-name-2", 6 | // "your-plugin-folder-name-3", 7 | ]; 8 | 9 | const G3W_KEYS = { 10 | // google: '', 11 | // bing: '' 12 | }; 13 | 14 | let conf = { 15 | pluginsFolder: './src/plugins', // path to G3W-CLIENT plugins folder 16 | admin_plugins_folder: '../g3w-admin/g3w-admin', // path to G3W-ADMIN plugins folder 17 | admin_overrides_folder: '../g3w-suite-docker/config/g3w-suite/overrides', // path to G3W-SUITE overrides folder 18 | docker_plugins_folder: '../g3w-suite-docker/shared-volume/plugins', // path to G3W-SUITE plugins folder 19 | plugins: G3W_PLUGINS, 20 | devConfig() { 21 | g3wsdk.core.ApplicationService.once('ready', () => { }); 22 | g3wsdk.core.ApplicationService.once('initconfig', () => { 23 | initConfig.group.vendorkeys = Object.assign(initConfig.group.vendorkeys || {}, G3W_KEYS); 24 | 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 }}), {})); 25 | }); 26 | //Every time a new iframe is created, listen for messages 27 | g3wsdk.gui.GUI.on('iframe:message', (w, e) => { 28 | //Once app is ready, send a message to the iframe 29 | if (e.data.action === 'app:ready') { 30 | w.postMessage({ // test MESSAGE sent to "Open in iframe" map control 31 | id: null, 32 | action: 'app:getcenter', // or 'app:getextent' 33 | data: { epsg: 4326 } 34 | }, '*') 35 | } 36 | }); 37 | g3wsdk.gui.GUI.once('ready', () => { console.log('ready'); }); 38 | } 39 | }; 40 | 41 | // backward compatibilities (v3.x) 42 | if (version < '4') { 43 | conf.assetsFolder = (version.localeCompare('3.7.0', undefined, { numeric: true, sensitivity: 'case' }) < 0 ? './assets' : './src/assets'); 44 | conf.plugins = conf.plugins.reduce((a, v) => ({ ...a, [v]: { gid: 'qdjango:1', baseurl: './dist' }}), {}); 45 | conf.host = '127.0.0.1'; 46 | conf.port = '3000'; 47 | conf.proxy = { host: '127.0.0.1', url: 'http://127.0.0.1:8000/' }; 48 | conf.test = { path: '/test/config/groups/' }; 49 | } 50 | 51 | module.exports = conf; -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "moduleResolution": "bundler", 5 | "baseUrl": "src", 6 | "paths": { 7 | "*": [ 8 | "*", 9 | "app/*", 10 | "plugins/*" 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "g3w-client", 3 | "version": "4.0.0-alpha.0", 4 | "description": "Gis3W WebGIS Client", 5 | "main": "index.js", 6 | "scripts": { 7 | "preinstall": "", 8 | "build": "gulp version && gulp build", 9 | "dev": "gulp dev", 10 | "docker": "docker compose --env-file ../g3w-suite-docker/.env --file ../g3w-suite-docker/docker-compose-dev.yml --project-name g3w-suite-docker --project-directory ../g3w-suite-docker", 11 | "docker:pull": "npm run docker pull", 12 | "docker:up": "npm run docker up -- -d", 13 | "docker:down": "npm run docker down", 14 | "docker:config": "npm run docker config", 15 | "docker:logs": "npm run docker logs", 16 | "test": "", 17 | "preversion": "npm run test", 18 | "version": "npm run build && git add -A", 19 | "postversion": "git push && git push --tags" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/g3w-suite/g3w-client.git" 24 | }, 25 | "author": "", 26 | "license": "MPL-2.0", 27 | "homepage": "https://github.com/g3w-suite/g3w-client.git", 28 | "dependencies": { 29 | "@fortawesome/fontawesome-free": "^5.15.4", 30 | "@mapbox/shp-write": "^0.4.3", 31 | "@ungap/with-resolvers": "^0.1.0", 32 | "bootstrap": "^3.3.7", 33 | "datatables.net-dt": "^1.10.16", 34 | "eonasdan-bootstrap-datetimepicker": "^4.17.49", 35 | "i18next": "^18.0.1", 36 | "ismobilejs": "^1.1.1", 37 | "jquery": "^2.2.1", 38 | "jsts": "^2.11.3", 39 | "jszip": "^3.10.1", 40 | "localforage": "^1.10.0", 41 | "lodash.clonedeep": "^4.5.0", 42 | "moment": "^2.19.1", 43 | "ol": "^10.4.0", 44 | "proj4": "^2.14.0", 45 | "quill": "^2.0.0-dev.4", 46 | "scriptjs": "^2.5.9", 47 | "select2": "^4.0.4", 48 | "shpjs": "^6.1.0", 49 | "util-deprecate": "^1.0.2", 50 | "vue": "2.7.16", 51 | "vue-color": "^2.8.1", 52 | "vue-cookie": "^1.1.4", 53 | "vue-cookie-law": "^1.13.3", 54 | "vue2-teleport": "^1.1.4", 55 | "wolfy87-eventemitter": "^5.2.9" 56 | }, 57 | "devDependencies": { 58 | "del": "^2.2.2", 59 | "esbuild": "^0.24.0", 60 | "esbuild-vue": "^1.2.2", 61 | "gulp": "^4.0.2", 62 | "gulp-flatten": "^0.2.0", 63 | "gulp-prompt": "^1.2.0", 64 | "gulp-rename": "^1.2.2", 65 | "jshint": "^2.9.1", 66 | "jshint-stylish": "^2.1.0", 67 | "uglify-js": "^2.6.2", 68 | "vue-template-compiler": "2.7.16" 69 | }, 70 | "engines": { 71 | "node": ">=22.9.0", 72 | "npm": ">=10.8.3" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/app/README.md: -------------------------------------------------------------------------------- 1 | # 🚨 GUI folder will be deleted in upcoming releases 2 | 3 | Ref: https://github.com/g3w-suite/g3w-client/issues/193 -------------------------------------------------------------------------------- /src/app/gui/fields/fields.js: -------------------------------------------------------------------------------- 1 | import Text from 'components/FieldText.vue'; 2 | import Link from 'components/FieldLink.vue'; 3 | import Image from 'components/FieldImage.vue' 4 | import Geo from 'components/FieldGeo.vue'; 5 | import Media from 'components/FieldMedia.vue'; 6 | import VueField from 'components/FieldVue.vue'; 7 | 8 | module.exports = { 9 | simple_field: Text, 10 | text_field: Text, 11 | link_field: Link, 12 | image_field: Image, 13 | geo_field: Geo, 14 | photo_field: Image, 15 | media_field: Media, 16 | vue_field: VueField 17 | }; -------------------------------------------------------------------------------- /src/app/gui/fields/fieldsservice.js: -------------------------------------------------------------------------------- 1 | import { toRawType } from 'utils/toRawType'; 2 | const Fields = require('./fields'); 3 | 4 | module.exports = { 5 | /** 6 | * Get Type field from field value 7 | * field: Object contains the value of the field 8 | * @param field 9 | * @returns {string} 10 | */ 11 | getType(field) { 12 | let type = field.type; 13 | if ('vue' !== type) { 14 | const fieldValue = field.value; 15 | const value = fieldValue && 'Object' === toRawType(fieldValue) && !fieldValue.coordinates && !fieldValue.vue ? fieldValue.value : fieldValue; 16 | if (!value) { 17 | type = 'simple'; 18 | } else if (value && 'object' === typeof value) { 19 | if (value.coordinates) { 20 | type = 'geo'; 21 | } else if (value.vue) { 22 | type = 'vue'; 23 | } 24 | } else if (value && Array.isArray(value)) { 25 | if (value.length && value[0].photo) { 26 | type = 'photo'; 27 | } else { 28 | type = 'simple' 29 | } 30 | } else if (value.toString().toLowerCase().match(/[^\s]+.(png|jpg|jpeg|gif)$/g)) { 31 | type = 'photo'; 32 | } else if (value.toString().match(/^(https?:\/\/[^\s]+)/g)) { 33 | type = 'link'; 34 | } else { 35 | type = 'simple'; 36 | } 37 | } 38 | return `${type}_field`; 39 | }, 40 | isSimple(field) { 41 | return 'simple_field' === this.getType(field); 42 | }, 43 | isLink(field) { 44 | return 'link_field' === this.getType(field); 45 | }, 46 | isImage(field) { 47 | return 'image_field' === this.getType(field); 48 | }, 49 | isPhoto(field) { 50 | return 'photo_field' === this.getType(field); 51 | }, 52 | isVue(field) { 53 | return 'vue_field' === this.getType(field); 54 | }, 55 | /** 56 | * Method to add a new field type to Fields 57 | * @param type 58 | * @param field 59 | */ 60 | add({type, field}) { 61 | Fields[type] = field; 62 | }, 63 | /** 64 | * Remove field from a Fields list 65 | * @param type 66 | */ 67 | remove(type) { 68 | delete Fields[type]; 69 | }, 70 | }; -------------------------------------------------------------------------------- /src/app/gui/inputs/checkbox/service.js: -------------------------------------------------------------------------------- 1 | const Service = require('gui/inputs/service'); 2 | module.exports = class CheckBoxService extends Service { 3 | constructor(opts = {}) { 4 | opts.validatorOptions = { 5 | values: opts.state.input.options.values.map(v => v) 6 | }; 7 | super(opts); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/gui/inputs/checkbox/vue/checkbox.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputCheckbox.vue'; 2 | 3 | const CheckBoxInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = CheckBoxInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/color/vue/color.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputColor.vue'; 2 | 3 | const TextInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = TextInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/datetimepicker/service.js: -------------------------------------------------------------------------------- 1 | import { convertQGISDateTimeFormatToMoment } from 'utils/convertQGISDateTimeFormatToMoment'; 2 | 3 | const Service = require('gui/inputs/service'); 4 | 5 | module.exports = class DateTimePickerService extends Service { 6 | constructor(opts = {}) { 7 | super(opts); 8 | 9 | this.validatorOptions = {}; 10 | } 11 | 12 | getLocale() { 13 | return window.initConfig.user.i18n ? window.initConfig.user.i18n : 'en'; 14 | }; 15 | 16 | convertQGISDateTimeFormatToMoment(datetimeformat) { 17 | return convertQGISDateTimeFormatToMoment(datetimeformat); 18 | }; 19 | 20 | setValidatorOptions(opts = {}) { 21 | this.validatorOptions = opts; 22 | }; 23 | }; -------------------------------------------------------------------------------- /src/app/gui/inputs/datetimepicker/vue/datetimepicker.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputDateTimePicker.vue'; 2 | 3 | const DateTimePickerInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = DateTimePickerInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/float/service.js: -------------------------------------------------------------------------------- 1 | const Service = require('gui/inputs/service'); 2 | module.exports = class FloatService extends Service { 3 | constructor(opts = {}) { 4 | super(opts); 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/app/gui/inputs/float/vue/float.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputFloat.vue'; 2 | 3 | const FloatInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = FloatInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/input.js: -------------------------------------------------------------------------------- 1 | import ApplicationState from 'store/application'; 2 | import BaseInputComponent from 'components/InputBase.vue' 3 | import { baseInputMixin as BaseInputMixin } from 'mixins'; 4 | 5 | const InputServices = require('./services'); 6 | 7 | const Input = { 8 | props: ['state'], 9 | mixins: [BaseInputMixin], 10 | components: { 11 | 'baseinput': BaseInputComponent 12 | }, 13 | watch: { 14 | 'notvalid'(notvalid) { 15 | if (notvalid) { this.service.setErrorMessage() } 16 | }, 17 | 'state.value'() { 18 | if (undefined !== this.state.input.options.default_expression) { 19 | // need to postpone state.value watch parent that use mixin 20 | setTimeout(() => this.change()); 21 | } 22 | } 23 | }, 24 | created() { 25 | this.service = new InputServices[this.state.input.type]({ state: this.state }); 26 | 27 | this.$watch( 28 | () => ApplicationState.language, 29 | async () => { 30 | if (this.state.visible) { 31 | this.state.visible = false; 32 | this.service.setErrorMessage(); 33 | await this.$nextTick(); 34 | this.state.visible = true; 35 | } 36 | }); 37 | 38 | if (this.state.editable && this.state.validate.required) { 39 | this.service.validate(); 40 | } 41 | 42 | this.$emit('addinput', this.state); 43 | /** 44 | * in case of input value is fill with default value option we need to emit changeinput event 45 | * without check validation. Example: 46 | * { 47 | "name": "id", 48 | "type": "integer", 49 | "label": "id", 50 | "editable": false, 51 | "validate": { 52 | "required": true, 53 | "unique": true 54 | }, 55 | "pk": true, 56 | "default": "nextval('g3wsuite.zone_id_seq'::regclass)", 57 | "input": { 58 | "type": "text", 59 | "options": {} 60 | } 61 | } 62 | in this case if we start a validation, it fail because default value is a string while input is interger 63 | */ 64 | if (this.state.value_from_default_value) { this.$emit('changeinput', this.state) } 65 | }, 66 | destroyed() { 67 | // emit remove input to form (in case for example tab visibility condition) 68 | this.$emit('removeinput', this.state); 69 | } 70 | }; 71 | 72 | module.exports = Input; 73 | -------------------------------------------------------------------------------- /src/app/gui/inputs/integer/service.js: -------------------------------------------------------------------------------- 1 | const Service = require('gui/inputs/service'); 2 | 3 | module.exports = class IntegerService extends Service { 4 | constructor(opts = {}) { 5 | super(opts); 6 | } 7 | }; -------------------------------------------------------------------------------- /src/app/gui/inputs/integer/vue/integer.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputInteger.vue'; 2 | 3 | const IntegerInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = IntegerInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/lonlat/service.js: -------------------------------------------------------------------------------- 1 | import GUI from 'services/gui'; 2 | 3 | const Service = require('gui/inputs/service'); 4 | module.exports = class LonLatService extends Service { 5 | constructor(opts = {}) { 6 | super(opts); 7 | this.coordinatebutton; 8 | this.mapService = GUI.getService('map'); 9 | this.mapEpsg = this.mapService.getCrs(); 10 | 11 | this.mapControlToggleEventHandler = evt => { 12 | if (evt.target.isToggled() && evt.target.isClickMap()) { 13 | this.coordinatebutton.active && this.toggleGetCoordinate(); 14 | } 15 | }; 16 | this.map = this.mapService.getMap(); 17 | this.outputEpsg = this.state.epsg || this.mapEpsg; 18 | //Store event map key 19 | this.eventMapKey; 20 | } 21 | 22 | setCoordinateButtonReactiveObject(coordinatebutton) { 23 | this.coordinatebutton = coordinatebutton; 24 | }; 25 | 26 | validate() { 27 | if (this.state.values.lon < -180) { this.state.values.lon = -180} 28 | else if (this.state.values.lon > 180) { this.state.values.lon = 180 } 29 | if (this.state.values.lat < -90) { this.state.values.lon = -90 } 30 | else if (this.state.values.lat > 90) { this.state.values.lon = 90 } 31 | 32 | this.state.validate.valid = !Number.isNaN(1*this.state.values.lon); 33 | }; 34 | 35 | toggleGetCoordinate() { 36 | this.coordinatebutton.active = !this.coordinatebutton.active; 37 | this.coordinatebutton.active ? this.startToGetCoordinates() : this.stopToGetCoordinates(); 38 | }; 39 | 40 | startToGetCoordinates() { 41 | this.mapService.deactiveMapControls(); 42 | this.mapService.on('mapcontrol:toggled', this.mapControlToggleEventHandler); 43 | this.eventMapKey = this.map.on('click', evt =>{ 44 | evt.originalEvent.stopPropagation(); 45 | evt.preventDefault(); 46 | const coordinate = this.mapEpsg !== this.outputEpsg ? ol.proj.transform(evt.coordinate, this.mapEpsg, this.outputEpsg) : evt.coordinate; 47 | this.state.value = [coordinate]; 48 | const [lon, lat] = coordinate; 49 | this.state.values.lon = lon; 50 | this.state.values.lat = lat; 51 | }) 52 | }; 53 | 54 | stopToGetCoordinates() { 55 | ol.Observable.unByKey(this.eventMapKey); 56 | this.mapService.off('mapcontrol:toggled', this.mapControlToggleEventHandler) 57 | }; 58 | 59 | clear() { 60 | this.stopToGetCoordinates(); 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /src/app/gui/inputs/lonlat/vue/lonlat.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputLonLat.vue'; 2 | 3 | const LatLontInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = LatLontInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/media/service.js: -------------------------------------------------------------------------------- 1 | const Service = require('gui/inputs/service'); 2 | module.exports = class MediaService extends Service { 3 | constructor(opts = {}) { 4 | super(opts); 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /src/app/gui/inputs/media/vue/media.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputMedia.vue'; 2 | 3 | const MediaInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = MediaInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/picklayer/vue/picklayer.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputPickLayer.vue'; 2 | 3 | const PickLayerInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = PickLayerInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/radio/service.js: -------------------------------------------------------------------------------- 1 | const Service = require('gui/inputs/service'); 2 | 3 | module.exports = class RadioService extends Service { 4 | constructor(opts = {}) { 5 | super(opts); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/app/gui/inputs/radio/vue/radio.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputRadio.vue'; 2 | 3 | const RadioInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = RadioInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/range/service.js: -------------------------------------------------------------------------------- 1 | const Service = require('gui/inputs/service'); 2 | 3 | module.exports = class RangeService extends Service { 4 | constructor(opts = {}) { 5 | const { min, max } = opts.state.input.options.values[0]; 6 | opts.state.info = `[MIN: ${min} - MAX: ${max}]`; 7 | super(opts); 8 | 9 | this.setValidator({ 10 | validate(value) { 11 | value = 1 * value; 12 | return value >= 1*min && value <= 1*max; 13 | } 14 | }); 15 | } 16 | isValueInRange(value, min, max) { 17 | return value <= max && value >= min; 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /src/app/gui/inputs/range/vue/range.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputRange.vue'; 2 | 3 | const RangeInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = RangeInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/select/service.js: -------------------------------------------------------------------------------- 1 | import { getCatalogLayerById } from 'utils/getCatalogLayerById'; 2 | 3 | const Service = require('gui/inputs/service'); 4 | 5 | module.exports = class SelectService extends Service { 6 | constructor(opts = {}) { 7 | super(opts); 8 | this.layer = null; 9 | } 10 | 11 | _getLayerById(layer_id) { 12 | return getCatalogLayerById(layer_id); 13 | }; 14 | 15 | addValue(value) { 16 | this.state.input.options.values.push(value); 17 | }; 18 | 19 | sortValues() { 20 | const { orderbyvalue } = this.state.input.options; 21 | this.state.input.options.values.sort((a, b) => { 22 | const val1 = a[orderbyvalue ? 'value' : 'key']; 23 | const val2 = b[orderbyvalue ? 'value' : 'key']; 24 | if ( val1 < val2 ) { 25 | return -1; 26 | } 27 | if ( val1 > val2) { 28 | return 1; 29 | } 30 | return 0; 31 | }); 32 | } 33 | 34 | getKeyByValue({ search } = {}) { 35 | const { value, key, } = this.state.input.options; 36 | return new Promise((resolve, reject) => { 37 | this.getData({ 38 | key, 39 | value, 40 | search 41 | }).then(values => { 42 | values.forEach(({ $value : key, text: value }) => { 43 | this.addValue({ 44 | key, 45 | value 46 | }) 47 | }) 48 | this.sortValues(); 49 | resolve(this.state.input.options.values); 50 | }).catch(e => { console.warn(e); reject(e); }); 51 | }) 52 | }; 53 | 54 | /** 55 | * 56 | * @param layer_id 57 | * @param key 58 | * @param value 59 | * @param search 60 | * @return {Promise} 61 | */ 62 | getData({ 63 | layer_id = this.state.input.options.layer_id, 64 | key = this.state.input.options.key, 65 | value = this.state.input.options.value, 66 | search, 67 | } = {}) { 68 | 69 | return new Promise((resolve, reject) => { 70 | if (!this._layer) { this._layer = this._getLayerById(layer_id) } 71 | this._layer.getDataTable({ 72 | [ Array.isArray(search) ? 'field' : 'suggest' ] : Array.isArray(search) //take in account multiselect value 73 | ? search 74 | .map((_, j) => [].concat(search[j]).map(v => `${key}|eq|${encodeURIComponent(v)}`).join(`|null,`)) 75 | .join('|OR,') || '' 76 | : `${key}|${search}`.trim(), 77 | ordering: this.state.input.options.orderbyvalue ? value : key, //@since 3.11.0 78 | }).then(response => { 79 | const values = response.features.map(f =>({ 80 | text: f.properties[key], 81 | id: f.properties[value], 82 | $value: f.properties[value] 83 | })) 84 | resolve(values); 85 | }).fail(e => { console.warn(e); reject(e) }); 86 | }); 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /src/app/gui/inputs/select/vue/select.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputSelect.vue'; 2 | 3 | const SelectInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = SelectInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/services.js: -------------------------------------------------------------------------------- 1 | const InputsServices = { 2 | 'text': require('./service'), 3 | 'textarea': require('./service'), 4 | 'texthtml': require('./service'), 5 | 'integer': require('./integer/service'), 6 | 'string': require('./service'), 7 | 'float': require('./float/service'), 8 | 'radio': require('./radio/service'), 9 | 'check': require('./checkbox/service'), 10 | 'range': require('./range/service'), 11 | 'datetimepicker': require('./datetimepicker/service'), 12 | 'unique': require('./unique/service'), 13 | 'select': require('./select/service'), 14 | 'media': require('./media/service'), 15 | 'select_autocomplete': require('./select/service'), 16 | 'picklayer': require('./service'), 17 | 'color': require('./service'), 18 | 'slider': require('./sliderrange/service'), 19 | 'lonlat': require('./lonlat/service'), 20 | }; 21 | 22 | module.exports = InputsServices; 23 | -------------------------------------------------------------------------------- /src/app/gui/inputs/sliderrange/service.js: -------------------------------------------------------------------------------- 1 | const Service = require('gui/inputs/service'); 2 | 3 | class SliderRangeService extends Service { 4 | constructor(opts = {}) { 5 | const { state } = opts; 6 | opts.state.info = `[MIN: ${state.input.options.min} - MAX: ${state.input.options.max}]`; 7 | super(opts); 8 | this.setValidator({ 9 | validate(value) { 10 | value = 1 * value; 11 | return value >= (1 * opts.state.input.options.min) && value <= (1 * opts.state.input.options.max); 12 | } 13 | }); 14 | } 15 | 16 | validate() { 17 | this.state.value = 1*this.state.value; 18 | this.state.validate.valid = this.state.value >= this.state.input.options.min || this.state.value <= this.state.input.options.max; 19 | } 20 | 21 | changeInfoMessage() { 22 | this.state.info = `[MIN: ${this.state.input.options.min} - MAX: ${this.state.input.options.max}]`; 23 | }; 24 | } 25 | 26 | module.exports = SliderRangeService; -------------------------------------------------------------------------------- /src/app/gui/inputs/sliderrange/vue/sliderrange.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputSliderRange.vue'; 2 | 3 | const RangeInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = RangeInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/table/vue/table.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputTable.vue'; 2 | 3 | const TableInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = TableInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/text/vue/text.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputText.vue'; 2 | 3 | const TextInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = TextInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/textarea/vue/textarea.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputTextArea.vue'; 2 | 3 | const TextAreaInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = TextAreaInput; 6 | -------------------------------------------------------------------------------- /src/app/gui/inputs/texthtml/vue/texthtml.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputTextHtml.vue'; 2 | 3 | const TextHtmlInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = TextHtmlInput; -------------------------------------------------------------------------------- /src/app/gui/inputs/unique/service.js: -------------------------------------------------------------------------------- 1 | const Service = require('gui/inputs/service'); 2 | module.exports = class UniqueService extends Service { 3 | constructor(opts = {}) { 4 | super(opts); 5 | } 6 | }; -------------------------------------------------------------------------------- /src/app/gui/inputs/unique/vue/unique.js: -------------------------------------------------------------------------------- 1 | import vueComponentOptions from 'components/InputUnique.vue'; 2 | 3 | const UniqueInput = Vue.extend(vueComponentOptions); 4 | 5 | module.exports = UniqueInput; 6 | -------------------------------------------------------------------------------- /src/assets/cursors/mZoomIn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/cursors/mZoomOut.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/fonts/titillium-web-latin-400-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/fonts/titillium-web-latin-400-italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/titillium-web-latin-400-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/fonts/titillium-web-latin-400-normal.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/titillium-web-latin-700-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/fonts/titillium-web-latin-700-italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/titillium-web-latin-700-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/fonts/titillium-web-latin-700-normal.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/titillium-web-latin-ext-400-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/fonts/titillium-web-latin-ext-400-italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/titillium-web-latin-ext-400-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/fonts/titillium-web-latin-ext-400-normal.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/titillium-web-latin-ext-700-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/fonts/titillium-web-latin-ext-700-italic.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/titillium-web-latin-ext-700-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/fonts/titillium-web-latin-ext-700-normal.woff2 -------------------------------------------------------------------------------- /src/assets/geocoding-providers/bing_places.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since 3.9.0 4 | */ 5 | 6 | (function() { 7 | 8 | const geocoding = initConfig.mapcontrols.geocoding || {}; 9 | const provider = document.currentScript.src.split('/').reverse()[0].replace('.js', '') || 'bing_places'; 10 | 11 | // skip when disabled 12 | if (!provider in geocoding.providers) { 13 | return; 14 | } 15 | 16 | /** 17 | * @example https://dev.virtualearth.net/REST/v1/LocalSearch/?query={query}&userMapView={lat,lon,lat,lon}&key={BingMapsKey} 18 | * 19 | * @see https://learn.microsoft.com/en-us/bingmaps/rest-services/locations/local-search 20 | */ 21 | geocoding.providers[provider].fetch = async function(opts) { 22 | const { XHR } = g3wsdk.core.utils; 23 | const { vendorkeys } = g3wsdk.core.ApplicationState.keys; 24 | 25 | // fallback to generic bing vendor key 26 | vendorkeys[provider] = vendorkeys[provider] || vendorkeys.bing; 27 | 28 | const key = undefined !== vendorkeys[provider] ? vendorkeys[provider] : opts && (new URL(opts.url)).searchParams.get('key'); 29 | 30 | // whether can fetch data from Bing Local Search API 31 | if (!opts || !key) { 32 | return Promise.reject(); 33 | } 34 | 35 | const url = opts.url || 'https://dev.virtualearth.net/REST/v1/LocalSearch/'; 36 | const params = { 37 | query: opts.query, // textual search 38 | userMapView: [opts.extent[1], opts.extent[0], opts.extent[3], opts.extent[2]].join(','), 39 | }; 40 | 41 | // get fallback key from url 42 | if (undefined === vendorkeys.bing) { 43 | params.key = key; 44 | } 45 | 46 | const response = await XHR.get({ url, params }); 47 | 48 | return { 49 | provider, 50 | label: 'Bing Places', 51 | icon: undefined !== opts.icon ? opts.icon : 'poi', 52 | results: 200 === response.statusCode 53 | ? response.resourceSets[0].resources 54 | .filter(({ point: { coordinates } })=> ol.extent.containsXY(opts.extent, coordinates[1], coordinates[0])) 55 | .map(result => { 56 | return { 57 | name: result.name, 58 | lon: result.point.coordinates[1], 59 | lat: result.point.coordinates[0], 60 | type: result.entityType, 61 | address: { 62 | road: result.Address.addressLine, 63 | postcode: result.Address.postalCode, 64 | city: result.Address.locality, 65 | state: result.Address.adminDistrict, 66 | country: result.Address.countryRegion, 67 | formatted: result.Address.formattedAddress, 68 | }, 69 | bing: result, 70 | }; 71 | }) 72 | : [], 73 | }; 74 | 75 | }; 76 | 77 | })(); -------------------------------------------------------------------------------- /src/assets/geocoding-providers/bing_streets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since 3.9.0 4 | */ 5 | 6 | (function() { 7 | 8 | const geocoding = initConfig.mapcontrols.geocoding || {}; 9 | const provider = document.currentScript.src.split('/').reverse()[0].replace('.js', '') || 'bing_streets'; 10 | 11 | // skip when disabled 12 | if (!provider in geocoding.providers) { 13 | return; 14 | } 15 | 16 | /** 17 | * @example https://dev.virtualearth.net/REST/v1/Locations/?query={query}&userMapView={lat,lon,lat,lon}&key={BingMapsKey} 18 | * 19 | * @see https://learn.microsoft.com/en-us/bingmaps/rest-services/locations/find-a-location-by-query 20 | */ 21 | geocoding.providers[provider].fetch = async function(opts) { 22 | const { XHR } = g3wsdk.core.utils; 23 | const { vendorkeys } = g3wsdk.core.ApplicationState.keys; 24 | 25 | // fallback to generic bing vendor key 26 | vendorkeys[provider] = vendorkeys[provider] || vendorkeys.bing; 27 | 28 | const key = undefined !== vendorkeys.bing ? vendorkeys.bing : opts && (new URL(opts.url)).searchParams.get('key'); 29 | 30 | // whether can fetch data from Bing Locations API 31 | if (!opts || !key /*|| !active*/) { 32 | return Promise.reject(); 33 | } 34 | 35 | const url = opts.url || 'https://dev.virtualearth.net/REST/v1/Locations/'; 36 | const params = { 37 | query: opts.query, // textual search 38 | userMapView: [opts.extent[1], opts.extent[0], opts.extent[3], opts.extent[2]].join(','), 39 | }; 40 | 41 | // get fallback key from url 42 | if (undefined === vendorkeys.bing) { 43 | params.key = key; 44 | } 45 | 46 | const response = await XHR.get({ url, params }); 47 | 48 | return { 49 | provider, 50 | label: 'Bing Streets', 51 | icon: undefined !== opts.icon ? opts.icon : 'road', 52 | results: 200 === response.statusCode 53 | ? response.resourceSets[0].resources 54 | .filter(({ point: { coordinates } })=> ol.extent.containsXY(opts.extent, coordinates[1], coordinates[0])) 55 | .map(result => { 56 | return { 57 | name: result.name, 58 | lon: result.point.coordinates[1], 59 | lat: result.point.coordinates[0], 60 | type: result.entityType, 61 | address: { 62 | road: result.address.addressLine, 63 | postcode: result.address.postalCode, 64 | city: result.address.locality, 65 | state: result.address.adminDistrict, 66 | country: result.address.countryRegion, 67 | formatted: result.address.formattedAddress, 68 | }, 69 | bing: result, 70 | }; 71 | }) 72 | : [], 73 | }; 74 | }; 75 | 76 | })(); -------------------------------------------------------------------------------- /src/assets/geocoding-providers/google.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since 3.9.0 4 | */ 5 | 6 | (function() { 7 | 8 | const geocoding = initConfig.mapcontrols.geocoding || {}; 9 | const provider = document.currentScript.src.split('/').reverse()[0].replace('.js', '') || 'google'; 10 | 11 | // skip when disabled 12 | if (!provider in geocoding.providers) { 13 | return; 14 | } 15 | 16 | let active = true; 17 | 18 | geocoding.providers[provider].fetch = async function(opts) { 19 | 20 | const { XHR } = g3wsdk.core.utils; 21 | const { vendorkeys } = g3wsdk.core.ApplicationState.keys; 22 | 23 | // fallback to generic google vendor key 24 | vendorkeys[provider] = vendorkeys[provider] || vendorkeys.google; 25 | 26 | const key = undefined !== vendorkeys[provider] ? vendorkeys[provider] : opts && (new URL(opts.url)).searchParams.get('key'); 27 | 28 | // whether can fetch data from Google Geocode API 29 | if (!opts || !active || !key) { 30 | return Promise.reject(); 31 | } 32 | 33 | const url = opts.url || 'https://maps.googleapis.com/maps/api/geocode/json'; 34 | const params = { 35 | address: opts.query, // textual search 36 | bounds: [opts.extent[1], opts.extent[0], opts.extent[3], opts.extent[2]].join(','), 37 | language: opts.lang, 38 | }; 39 | 40 | // get fallback key from url 41 | if (undefined === vendorkeys.bing) { 42 | params.key = key; 43 | } 44 | 45 | const response = await XHR.get({ url, params }); 46 | 47 | // disable google provider on invalid API key 48 | if (response.status === 'REQUEST_DENIED') { 49 | active = false; 50 | return Promise.reject(); 51 | } 52 | 53 | return { 54 | provider, 55 | label: 'Google', 56 | icon: undefined !== opts.icon ? opts.icon : 'poi', 57 | results: 'OK' === response.status 58 | ? response.results 59 | .filter(({ geometry: { location } })=> ol.extent.containsXY(opts.extent, location.lng, location.lat)) 60 | .map(result => { 61 | let name, city, country; 62 | result.address_components.forEach(({ types, long_name }) => { 63 | if (types.find(t => 'route' === t)) name = long_name; 64 | else if (types.find( t => 'locality' === t)) city = long_name; 65 | else if (types.find( t => 'country' === t)) country = long_name 66 | }); 67 | return { 68 | lon : result.geometry.location.lng, 69 | lat : result.geometry.location.lat, 70 | address: { 71 | name, 72 | road: undefined, 73 | postcode: '', 74 | city, 75 | state: undefined, 76 | country, 77 | formatted: result.display_name, 78 | }, 79 | google: result, 80 | }; 81 | }) 82 | : [], 83 | }; 84 | 85 | }; 86 | 87 | })(); -------------------------------------------------------------------------------- /src/assets/geocoding-providers/nominatim.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since 3.9.0 4 | */ 5 | 6 | (function() { 7 | 8 | const geocoding = initConfig.mapcontrols.geocoding || {}; 9 | const provider = document.currentScript.src.split('/').reverse()[0].replace('.js', '') || 'nominatim'; 10 | 11 | // skip when disabled 12 | if (!provider in geocoding.providers) { 13 | return; 14 | } 15 | 16 | geocoding.providers[provider].fetch = async function(opts) { 17 | 18 | const { XHR } = g3wsdk.core.utils; 19 | 20 | if (!opts) { 21 | return Promise.reject(); 22 | } 23 | 24 | return { 25 | provider, 26 | label: 'Nominatim (OSM)', 27 | icon: undefined !== opts.icon ? opts.icon : 'road', 28 | results: 29 | ( 30 | await XHR.get({ 31 | url: opts.url || 'https://nominatim.openstreetmap.org/search', 32 | params: { 33 | q: opts.query, // textual search 34 | format: 'json', 35 | addressdetails: 1, 36 | limit: opts.limit || 10, 37 | viewbox: opts.extent.join(','), 38 | bounded: 1, 39 | } 40 | }) 41 | ) 42 | .filter(place => ol.extent.containsXY(opts.extent, place.lon, place.lat)) 43 | .map(result => ({ 44 | name: result.name, 45 | lon: result.lon, 46 | lat: result.lat, 47 | type: result.type, 48 | address: { 49 | name: result.address.neighbourhood || '', 50 | road: result.address.road || '', 51 | city: result.address.city || result.address.town, 52 | postcode: result.address.postcode, 53 | state: result.address.state, 54 | country: result.address.country, 55 | formatted: result.display_name, 56 | }, 57 | nominatim: result, 58 | }) 59 | ), 60 | }; 61 | 62 | }; 63 | 64 | })(); -------------------------------------------------------------------------------- /src/assets/images/FakeProjectThumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/images/FakeProjectThumb.png -------------------------------------------------------------------------------- /src/assets/images/baselayers/bingaerial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/images/baselayers/bingaerial.png -------------------------------------------------------------------------------- /src/assets/images/baselayers/bingaerialwithlabels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/images/baselayers/bingaerialwithlabels.png -------------------------------------------------------------------------------- /src/assets/images/baselayers/bingstreets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/images/baselayers/bingstreets.png -------------------------------------------------------------------------------- /src/assets/images/baselayers/nobaselayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/images/baselayers/nobaselayer.png -------------------------------------------------------------------------------- /src/assets/images/baselayers/osm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/images/baselayers/osm.png -------------------------------------------------------------------------------- /src/assets/images/compass.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/addlayer.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/camera.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionAddBasicCircle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionAddBasicRectangle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionAddPoint.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 33 | 36 | 44 | 47 | 55 | 63 | 68 | 69 | 70 | 77 | 78 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionAddPolygon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionAddPolyline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionIdentify.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionIdentifyByPolygon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionIdentifyByRadius.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionIdentifyByRectangle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionMeasure.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionText.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionTextAnnotation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionZoomFullExtent.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionZoomIn.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionZoomOut.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/mActionZoomToArea.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/my_location.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/controls/streetview.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/g3wsuite_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/images/g3wsuite_logo.png -------------------------------------------------------------------------------- /src/assets/images/logo_gis3w_156_85.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/images/logo_gis3w_156_85.png -------------------------------------------------------------------------------- /src/assets/images/mapcentermarker.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/streetviewarrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g3w-suite/g3w-client/826792536c4b353c3c3e6235d268cceb8ebc1567/src/assets/images/streetviewarrow.png -------------------------------------------------------------------------------- /src/components/ContextMenu.vue: -------------------------------------------------------------------------------- 1 | 13 | 22 | 23 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/ContextMenuItem.vue: -------------------------------------------------------------------------------- 1 | 7 | 15 | 16 | 31 | 32 | -------------------------------------------------------------------------------- /src/components/Field.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 21 | 22 | 28 | 29 | 55 | -------------------------------------------------------------------------------- /src/components/FieldG3W.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/FieldGeo.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 15 | 16 | 34 | 35 | 43 | -------------------------------------------------------------------------------- /src/components/FieldLink.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 17 | 18 | 45 | 46 | 51 | -------------------------------------------------------------------------------- /src/components/FieldMedia.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 19 | 20 | 37 | 38 | 41 | -------------------------------------------------------------------------------- /src/components/FieldText.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 26 | 27 | 35 | -------------------------------------------------------------------------------- /src/components/FieldVue.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/FormAddon.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 15 | 16 | 39 | 40 | 50 | -------------------------------------------------------------------------------- /src/components/FormAddons.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 18 | 19 | 34 | -------------------------------------------------------------------------------- /src/components/FormBody.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 38 | 39 | -------------------------------------------------------------------------------- /src/components/FormHeader.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 36 | 37 | -------------------------------------------------------------------------------- /src/components/GlobalBarLoader.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | 15 | 30 | -------------------------------------------------------------------------------- /src/components/GlobalDivider.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/GlobalGeo.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 15 | 16 | 34 | 35 | 43 | -------------------------------------------------------------------------------- /src/components/GlobalHelpDiv.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | 13 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/GlobalProgressBar.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 20 | 21 | 27 | -------------------------------------------------------------------------------- /src/components/InputColor.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/InputFloat.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/InputG3WFormInputs.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 33 | 34 | 66 | 67 | 80 | -------------------------------------------------------------------------------- /src/components/InputInteger.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/InputPickLayer.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/InputRadio.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/InputRange.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 24 | 25 | -------------------------------------------------------------------------------- /src/components/InputSliderRange.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 25 | 26 | -------------------------------------------------------------------------------- /src/components/InputTable.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 24 | 25 | -------------------------------------------------------------------------------- /src/components/InputTableBody.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 18 | 19 | -------------------------------------------------------------------------------- /src/components/InputTableHeader.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/InputText.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/InputTextArea.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | 24 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/InputUnique.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/MapControlZoomHistory.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 33 | 34 | 35 | 92 | 93 | -------------------------------------------------------------------------------- /src/components/ModalLogin.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 35 | 36 | -------------------------------------------------------------------------------- /src/components/QueryResultsAction.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | 24 | -------------------------------------------------------------------------------- /src/components/QueryResultsActionChooseLayer.vue: -------------------------------------------------------------------------------- 1 | 5 | 38 | 39 | -------------------------------------------------------------------------------- /src/components/QueryResultsActionQueryPolygonCSVAttributes.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 38 | 39 | 77 | 78 | -------------------------------------------------------------------------------- /src/components/QueryResultsActions.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 15 | 16 | 47 | 48 | -------------------------------------------------------------------------------- /src/components/QueryResultsHeaderFeatureActionsBody.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 21 | 22 | 69 | 70 | -------------------------------------------------------------------------------- /src/components/QueryResultsHeaderFeatureBody.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 29 | 30 | 70 | 71 | -------------------------------------------------------------------------------- /src/components/QueryResultsTableAttributeFieldValue.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 13 | 14 | 38 | 39 | -------------------------------------------------------------------------------- /src/components/Tool.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 57 | 58 | 80 | 81 | -------------------------------------------------------------------------------- /src/directives/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @TODO refactor stateful directives (eg. "v-t") in order to delete this file: "src/directives/utils.js" 3 | */ 4 | import { getUniqueDomId } from 'utils/getUniqueDomId'; 5 | 6 | /** 7 | * Internal state 8 | */ 9 | const vm = new Vue(); 10 | const directives = {}; 11 | 12 | export const watch = ({ el, attr, watcher, immediate = true } = {}) => { 13 | const unique_attr_id = getUniqueDomId(); 14 | el.setAttribute(attr, unique_attr_id); 15 | const dir = directives[unique_attr_id] = {}; 16 | if (watcher) { 17 | dir.unwatch = vm.$watch(watcher[0], watcher[1], watcher[2] || { immediate }); 18 | dir.handler = watcher[1]; 19 | } 20 | return unique_attr_id; 21 | }; 22 | 23 | export const unwatch = ({ el, attr } = {}) => { 24 | const unique_attr_id = el.getAttribute(attr); 25 | if (unique_attr_id) { 26 | directives[unique_attr_id].unwatch(); 27 | delete directives[unique_attr_id]; 28 | } 29 | }; 30 | 31 | export const trigger = ({el, attr, data}) => directives[el.getAttribute(attr)].handler(data); 32 | 33 | -------------------------------------------------------------------------------- /src/directives/v-disabled.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | export default (el, binding) => { 7 | el.classList.toggle('g3w-disabled', binding.value); 8 | }; -------------------------------------------------------------------------------- /src/directives/v-download.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | import ApplicationState from 'store/application'; 7 | import { watch, unwatch } from 'directives/utils'; 8 | 9 | const attr = 'g3w-v-download-id'; 10 | 11 | export default { 12 | bind(el, binding) { 13 | if ('boolean' === typeof binding.value ? binding.value : true) { 14 | watch({ 15 | el, 16 | attr, 17 | watcher: [ 18 | () => ApplicationState.download, 19 | bool => { 20 | const className = binding.modifiers && binding.modifiers.show && 'hide' || 'disabled'; 21 | el.classList.toggle(`g3w-${className}`, className === 'hide' ? !bool : bool) 22 | } 23 | ] 24 | }); 25 | } 26 | }, 27 | unbind: el => unwatch({ el, attr }) 28 | }; -------------------------------------------------------------------------------- /src/directives/v-t-html.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | import ApplicationState from 'store/application'; 7 | import { watch, unwatch } from 'directives/utils'; 8 | import { t } from 'g3w-i18n'; 9 | 10 | const attr = 'g3w-v-t-html-id'; 11 | 12 | export default { 13 | bind(el, binding) { 14 | watch({ 15 | el, 16 | attr, 17 | watcher: [ 18 | () => ApplicationState.language, 19 | () => { el.innerHTML = `${t(binding.value)}`; } 20 | ] 21 | }); 22 | }, 23 | update(el, binding) { 24 | if (binding.value !== binding.oldValue) { 25 | el.innerHTML = `${t(binding.value)}`; 26 | } 27 | }, 28 | unbind: el => unwatch({ el, attr }) 29 | }; -------------------------------------------------------------------------------- /src/directives/v-t-plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | import ApplicationState from 'store/application'; 7 | import { watch, unwatch } from 'directives/utils'; 8 | import { tPlugin } from 'g3w-i18n'; 9 | 10 | const attr = 'g3w-v-t-plugin-id'; 11 | 12 | export default { 13 | bind(el, binding) { 14 | const innerHTML = el.innerHTML; 15 | watch({ 16 | el, 17 | attr, 18 | watcher: [ 19 | () => ApplicationState.language, 20 | () => { 21 | const value = null !== binding.value ? tPlugin(binding.value) : ''; 22 | switch(binding.arg ? binding.arg : 'post') { 23 | case 'pre': el.innerHTML = `${value} ${innerHTML}`; break; 24 | case 'post': el.innerHTML = `${innerHTML} ${value}`; break; 25 | } 26 | } 27 | ] 28 | }); 29 | }, 30 | unbind: el => unwatch({ el, attr }) 31 | }; -------------------------------------------------------------------------------- /src/directives/v-t-tooltip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | import ApplicationState from 'store/application'; 7 | import { watch, unwatch, trigger } from 'directives/utils'; 8 | import { t, tPlugin } from 'g3w-i18n'; 9 | 10 | const attr = 'g3w-v-t-tooltip-id'; 11 | 12 | export default { 13 | bind(_el, binding) { 14 | // Automatically create tooltip 15 | if (binding.modifiers.create) { 16 | if (binding.arg) { 17 | _el.setAttribute('data-placement', binding.arg); 18 | _el.classList.add(`skin-tooltip-${binding.arg}`); 19 | } 20 | _el.setAttribute('data-container',"body"); 21 | $(_el) 22 | .tooltip({ trigger : ApplicationState.ismobile ? 'click': 'hover', html: true }) 23 | // hide tooltip on mobile after click 24 | .on('shown.bs.tooltip', () => { ApplicationState.ismobile && setTimeout(()=>$(_el).tooltip('hide'), 600) }); 25 | } 26 | watch({ 27 | el: _el, 28 | attr, 29 | watcher: [ 30 | () => ApplicationState.language, 31 | ({el = _el}) => { 32 | let value = el.getAttribute('current-tooltip'); 33 | if (null === value) { value = binding.value; } 34 | el.setAttribute('data-original-title', binding.modifiers.text ? value : ('plugin' === binding.arg ? tPlugin : t)(value)); 35 | } 36 | ] 37 | }); 38 | }, 39 | componentUpdated(el, oldVnode) { 40 | const value = el.getAttribute('current-tooltip'); 41 | //in case of null or empty value, need to hide tooltip 42 | if ([null, ''].includes(value)) { 43 | $(el).tooltip('hide'); 44 | } 45 | if (null != value && value !== oldVnode.oldValue) { 46 | trigger({ el, attr, data: { el } }); 47 | } 48 | }, 49 | unbind: el => { $(el).tooltip('hide'); unwatch({ el, attr }); } 50 | }; -------------------------------------------------------------------------------- /src/directives/v-t.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | import ApplicationState from 'store/application'; 7 | import { watch, unwatch } from 'directives/utils'; 8 | import { t } from 'g3w-i18n'; 9 | 10 | const attr = 'g3w-v-t-id'; 11 | 12 | /** 13 | * @since 3.8.7 14 | */ 15 | const handleInnerHTML = ({ el } = {}) => { 16 | const value = null === el.__currentBinding.value ? '' : t(el.__currentBinding.value); 17 | switch(el.__currentBinding.arg ? el.__currentBinding.arg : 'post') { 18 | case 'pre': el.innerHTML = `${value} ${el.__innerHTML}`; break; 19 | case 'post': el.innerHTML = `${el.__innerHTML} ${value}`; break; 20 | } 21 | } 22 | 23 | export default { 24 | bind(el, binding) { 25 | /** 26 | * @since 3.8.7 27 | */ 28 | // set init innerHTML value of element 29 | el.__innerHTML = el.innerHTML; 30 | //set current binging 31 | el.__currentBinding = binding; 32 | watch({ 33 | el, 34 | attr, 35 | watcher: [ 36 | () => ApplicationState.language, 37 | () => handleInnerHTML({ el }) 38 | ] 39 | }); 40 | }, 41 | /** 42 | * @since 3.8.7 43 | */ 44 | componentUpdated(el, binding) { 45 | if (el.__currentBinding.value !== binding.value) { 46 | // reset currentBinding to get last value; 47 | el.__currentBinding = binding; 48 | handleInnerHTML({ el }) 49 | } 50 | }, 51 | 52 | unbind: el => unwatch({ el, attr }) 53 | } -------------------------------------------------------------------------------- /src/g3w-eventbus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file common vue instances used to watch object changes or to emit events 3 | * 4 | * NB: node.js modules are singletons by default. 5 | * 6 | * @see https://medium.com/@lazlojuly/are-node-js-modules-singletons-764ae97519af 7 | * 8 | * @since 3.11.0 9 | */ 10 | 11 | /** 12 | * ORIGINAL SOURCE: src/app/g3w-ol/constants.js@3.8.6 13 | */ 14 | export const VM = new Vue(); -------------------------------------------------------------------------------- /src/g3w-i18n.js: -------------------------------------------------------------------------------- 1 | import ApplicationState from 'store/application'; 2 | 3 | export const i18next = require('i18next'); 4 | 5 | export const getAppLanguage = () => window.initConfig.user.i18n || "en"; 6 | /* function to translate */ 7 | export const t = text => i18next.t(text); 8 | 9 | /* function to translate plugins */ 10 | export const tPlugin = text => i18next.t(`plugins.${text}`); 11 | 12 | export const addI18n = i18nObject => { 13 | for (const lang in i18nObject) { 14 | for (const key in i18nObject[lang]) { 15 | i18next.addResource(lang, 'translation', key, i18nObject[lang][key]) 16 | } 17 | } 18 | }; 19 | 20 | export const addI18nPlugin = ({ name, config }) => { 21 | for (const lang in config) { 22 | if (ApplicationState.i18n.plugins[lang]) { 23 | ApplicationState.i18n.plugins[lang].plugins[name] = config[lang] 24 | } 25 | } 26 | for (const lang in ApplicationState.i18n.plugins) { 27 | for (const key in ApplicationState.i18n.plugins[lang]) { 28 | i18next.addResource(lang, 'translation', key, ApplicationState.i18n.plugins[lang][key]) 29 | } 30 | } 31 | }; 32 | 33 | export default { 34 | getAppLanguage, 35 | t, 36 | tPlugin, 37 | addI18n, 38 | addI18nPlugin, 39 | }; 40 | -------------------------------------------------------------------------------- /src/g3w-panel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ORIGINAL SOURCE: src/app/core/g3w-panel.js@v3.10.2 3 | * @since 3.11.0 4 | */ 5 | 6 | import GUI from 'services/gui'; 7 | import G3WObject from 'g3w-object'; 8 | import { $promisify } from 'utils/promisify'; 9 | 10 | /** 11 | * ORIGINAL SOURCE: src/app/gui/panel.js@v3.9.3 12 | */ 13 | export default class Panel extends G3WObject { 14 | 15 | constructor (opts = {}) { 16 | super(); 17 | 18 | this.id = opts.id || null; 19 | 20 | this.title = opts.title || ''; 21 | 22 | this.service = opts.service; 23 | 24 | if (opts.vueComponentObject) { 25 | this.internalPanel = new (Vue.extend(opts.vueComponentObject))({ service: this.service }); 26 | } else { 27 | this.internalPanel = opts.panel || opts.internalPanel || null; 28 | } 29 | 30 | if (true === opts.show && this.internalPanel) { 31 | this.show(); 32 | } 33 | } 34 | 35 | getId() { 36 | return this.id; 37 | } 38 | 39 | getTitle() { 40 | return this.title; 41 | } 42 | 43 | getService() { 44 | return this.service; 45 | } 46 | 47 | setService(service) { 48 | this.service = service; 49 | } 50 | 51 | getInternalPanel() { 52 | return this.internalPanel; 53 | } 54 | 55 | setInternalPanel(internalPanel) { 56 | this.internalPanel = internalPanel; 57 | } 58 | 59 | show() { 60 | GUI.showPanel(this); 61 | } 62 | 63 | close() { 64 | GUI.closePanel(); 65 | } 66 | 67 | mount(parent) { 68 | const panel = this.internalPanel; 69 | const vueComp = panel.$mount(); 70 | $(parent).append(vueComp.$el); 71 | vueComp.$nextTick(() => { 72 | if (panel.onShow) { panel.onShow();} 73 | }); 74 | return $promisify(Promise.resolve(true)); 75 | } 76 | 77 | unmount() { 78 | const panel = this.internalPanel; 79 | panel.$destroy(true); 80 | $(panel.$el).remove(); 81 | if (panel.onClose) { panel.onClose();} 82 | this.internalComponent = null; 83 | if (this.service && this.service.clear) { 84 | this.service.clear(); 85 | } 86 | return $promisify(Promise.resolve()); 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /src/locales/index.js: -------------------------------------------------------------------------------- 1 | import it from './it'; 2 | import en from './en'; 3 | import fi from './fi'; 4 | import se from './se'; 5 | import fr from './fr'; 6 | import de from './de'; 7 | import ro from './ro'; 8 | import pl from './pl'; 9 | import uk from './uk'; 10 | import pt from './pt'; 11 | 12 | const translations = { 13 | it, 14 | en, 15 | fi, 16 | se, 17 | fr, 18 | de, 19 | ro, 20 | pl, 21 | uk, 22 | pt, 23 | }; 24 | 25 | export default translations; 26 | -------------------------------------------------------------------------------- /src/map/interactions/pickcoordinatesinteraction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ORIGINAL SOURCE: src/app/g3w-ol/interactions/pickcoordinatesinteraction.js@v3.10.2 3 | * @since 3.11.0 4 | */ 5 | 6 | export default class PickCoordinatesInteraction extends ol.interaction.Pointer { 7 | constructor(opts = {}) { 8 | super({ 9 | handleDownEvent(e) { 10 | this._centerMap = e.map.getView().getCenter(); 11 | // set timeout to avoid blocking pan 12 | setTimeout(() => { 13 | if (this._centerMap === e.map.getView().getCenter()) { 14 | this.handleUpEvent(e); 15 | } 16 | }, 300); 17 | // return false to avoid start of drag event 18 | return false 19 | }, 20 | handleUpEvent(e) { 21 | this.dispatchEvent({ 22 | type: 'picked', 23 | coordinate: e.coordinate, 24 | }) 25 | // it used to stop drag event 26 | return false; 27 | }, 28 | handleMoveEvent(e) { 29 | e.map.getViewport().classList.add(this._cursor); 30 | return true; 31 | }, 32 | ...opts 33 | }); 34 | this._cursor = opts.cursor || 'ol-pointer'; 35 | // this.previousCursor_ = null; 36 | this._centerMap = null; 37 | } 38 | 39 | shouldStopEvent() { return false } 40 | 41 | setActive(bool) { 42 | const map = this.getMap(); 43 | if (map) { 44 | map.getViewport().classList.remove(this._cursor); 45 | } 46 | super.setActive(bool); 47 | }; 48 | 49 | setMap(map) { 50 | if (!map) { 51 | this.getMap().getViewport().classList.remove(this._cursor); 52 | } 53 | super.setMap(map); 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /src/map/interactions/pickfeatureinteraction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ORIGINAL SOURCE: src/app/g3w-ol/interactions/pickfeatureinteraction.js@v3.10.2 3 | * @since 3.11.0 4 | */ 5 | 6 | export default class PickFeatureInteraction extends ol.interaction.Pointer { 7 | constructor(opts = {}) { 8 | super({ 9 | handleDownEvent(e) { 10 | this.pickedFeature_ = this.featuresAtPixel_(e.pixel, e.map); 11 | return this.pickedFeature_; 12 | }, 13 | handleUpEvent(e) { 14 | if (this.pickedFeature_) { 15 | this.dispatchEvent({ 16 | type: 'picked', 17 | feature: this.pickedFeature_, 18 | coordinate: e.coordinate, 19 | layer: this.pickedLayer_, 20 | }) 21 | } 22 | return true; 23 | }, 24 | handleMoveEvent(e) { 25 | e.map.getTargetElement().style.cursor = this.featuresAtPixel_(e.pixel, e.map) ? 'pointer': ''; 26 | }, 27 | ...opts 28 | }) 29 | 30 | const { features } = opts; 31 | this.features_ = (Array.isArray(features) && features.length > 0) ? features : null; 32 | this.layers_ = opts.layers || null; 33 | this.pickedFeature_ = null; 34 | this.pickedLayer_ = null; 35 | } 36 | 37 | layerFilter_(layer) { 38 | const include = (this.layers_ || []).includes(layer); 39 | this.pickedLayer_ = include && layer; 40 | return include; 41 | } 42 | 43 | featuresAtPixel_(pixel, map) { 44 | let featureFound = null; 45 | const intersectingFeature = map.forEachFeatureAtPixel(pixel, feature => { 46 | if (this.features_) { 47 | if (this.features_.includes(feature)) { return feature } 48 | else { return null } 49 | } 50 | return feature; 51 | }, { 52 | layerFilter: this.layerFilter_.bind(this), 53 | hitTolerance: (isMobile && isMobile.any) ? 10 : 0 54 | }); 55 | if (intersectingFeature) { featureFound = intersectingFeature } 56 | return featureFound; 57 | } 58 | 59 | shouldStopEvent() { return false } 60 | 61 | setMap(map) { 62 | if (!map) { this.getMap().getTargetElement().style.cursor = ''} 63 | super.setMap(map); 64 | } 65 | }; -------------------------------------------------------------------------------- /src/mixins/autocomplete.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | import { getCatalogLayerById } from 'utils/getCatalogLayerById'; 7 | 8 | export default { 9 | methods: { 10 | async autocompleteRequest({ layerId, field, value } = {}) { 11 | let data = []; 12 | try { 13 | data = await getCatalogLayerById(layerId).getFilterData({ 14 | suggest: `${field}|${value}`, 15 | unique: field 16 | }) 17 | } catch(e) { 18 | console.warn(e); 19 | } 20 | return data.map(value => ({ id: value, text: value })) 21 | } 22 | } 23 | }; -------------------------------------------------------------------------------- /src/mixins/base-input.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | export default { 7 | computed: { 8 | tabIndex() { 9 | return this.editable ? 0 : -1; 10 | }, 11 | notvalid() { 12 | return false === this.state.validate.valid; 13 | }, 14 | editable() { 15 | return this.state.editable; 16 | }, 17 | showhelpicon() { 18 | return this.state.help && this.state.help.message.trim(); 19 | }, 20 | disabled() { 21 | return !this.editable || ['loading', 'error'].includes(this.loadingState); 22 | }, 23 | loadingState() { 24 | return this.state.input.options.loading ? this.state.input.options.loading.state : null; 25 | } 26 | }, 27 | methods: { 28 | /** 29 | * @since v3.9.1 30 | * @param bool 31 | */ 32 | setLoading(bool) { 33 | this.state.input.options.loading.state = bool ? 'loading' : 'ready'; 34 | }, 35 | showHideHelp() { 36 | this.state.help.visible = !this.state.help.visible 37 | }, 38 | // used to text input to listen to mobile changes 39 | mobileChange(event) { 40 | this.state.value = event.target.value; 41 | this.change(); 42 | }, 43 | // called when input value change 44 | change() { 45 | this.service.setEmpty(); 46 | // validate input every time on change 47 | // because can be inserted a text where state.input.type (widget) is text but state.type is integer 48 | this.service.validate(); 49 | //after check if is valid need to set update 50 | this.service.setUpdate(); 51 | // emit change input 52 | this.$emit('changeinput', this.state); 53 | }, 54 | isVisible() {} 55 | } 56 | }; -------------------------------------------------------------------------------- /src/mixins/click.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.8 4 | */ 5 | 6 | export default { 7 | 8 | created() { 9 | /** 10 | * Store `click` and `doubleclick` events on a single vue element. 11 | * 12 | * @see https://stackoverflow.com/q/41303982 13 | */ 14 | this.__CLICK_EVENT = { 15 | count: 0, // count click events 16 | timeoutID: null // timeoutID return by setTimeout Function 17 | }; 18 | }, 19 | 20 | methods: { 21 | 22 | /** 23 | * @param {{ '1': () => {}, '2': () => {}}} callbacks hashmap of click event handlers ('1' = click, '2' = double click) 24 | * @param context 25 | */ 26 | handleClick(callbacks = {}, context) { 27 | if (!this.__CLICK_EVENT) { 28 | console.warn('click mixin not initialized on context:', context); 29 | return; 30 | } 31 | this.__CLICK_EVENT.count += 1; // increment click count 32 | if (!this.__CLICK_EVENT.timeoutID) { // skip and wait for timeout in order to detect double click 33 | this.__CLICK_EVENT.timeoutID = setTimeout(() => { 34 | if (undefined !== callbacks[this.__CLICK_EVENT.count]) { 35 | callbacks[this.__CLICK_EVENT.count].call(context); 36 | } 37 | this.__resetClickMixin(); 38 | }, 300); 39 | } 40 | }, 41 | 42 | __resetClickMixin() { 43 | this.__CLICK_EVENT.count = 0; 44 | this.__CLICK_EVENT.timeoutID = null; 45 | }, 46 | 47 | __clearClickMixin() { 48 | this.__resetClickMixin(); 49 | this.__CLICK_EVENT = null; 50 | } 51 | 52 | }, 53 | 54 | beforeDestroy() { 55 | this.__clearClickMixin(); 56 | } 57 | 58 | }; -------------------------------------------------------------------------------- /src/mixins/fields.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | export default { 7 | methods: { 8 | getFieldService() { 9 | if (undefined === this._fieldsService) { 10 | this._fieldsService = require('gui/fields/fieldsservice'); 11 | } 12 | return this._fieldsService; 13 | }, 14 | getFieldType(field) { 15 | return this.getFieldService().getType(field); 16 | }, 17 | isSimple(field) { 18 | return this.getFieldService().isSimple(field); 19 | }, 20 | isLink(field) { 21 | return this.getFieldService().isLink(field); 22 | }, 23 | isImage(field) { 24 | return this.getFieldService().isImage(field); 25 | }, 26 | isPhoto(field) { 27 | return this.getFieldService().isPhoto(field); 28 | }, 29 | isVue(field) { 30 | return this.getFieldService().isVue(field); 31 | }, 32 | sanitizeFieldValue(value) { 33 | return (Array.isArray(value) && !value.length) ? '' : value; 34 | } 35 | } 36 | }; -------------------------------------------------------------------------------- /src/mixins/geo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | import GUI from 'services/gui'; 7 | 8 | export default { 9 | methods: { 10 | showLayer() { 11 | this.visible = !this.visible; 12 | this.layer.setVisible(this.visible); 13 | } 14 | }, 15 | created() { 16 | const data = this.data; 17 | const mapProjection = GUI.getService('map').getProjection().getCode(); 18 | let style; 19 | switch (data.type) { 20 | case 'Point': 21 | case 'MultiPoint': 22 | style = [new ol.style.Style({ 23 | image: new ol.style.Circle({ 24 | radius: 6, 25 | fill: new ol.style.Fill({ color: [255,255,255,1.0] }), 26 | stroke: new ol.style.Stroke({ color: [0,0,0,1.0], width: 2, }) 27 | }) 28 | }), 29 | new ol.style.Style({ 30 | image: new ol.style.Circle({ 31 | radius: 2, 32 | fill: new ol.style.Fill({ color: [255,255,255,1.0] }), 33 | stroke: new ol.style.Stroke({ color: [0,0,0,1.0], width: 2, }) 34 | }) 35 | })]; 36 | break; 37 | case 'Line': 38 | case 'MultiLineString': 39 | case 'Polygon': 40 | case 'MultiPolygon': 41 | style = new ol.style.Style({ 42 | fill: new ol.style.Fill({ color: 'rgba(255, 255, 255, 0.3)', }), 43 | stroke: new ol.style.Stroke({ color: [0,0,0,1.0], width: 2, }) 44 | }); 45 | break; 46 | } 47 | this.layer = new ol.layer.Vector({ 48 | source: new ol.source.Vector({ 49 | features: new ol.format.GeoJSON().readFeatures(data, { featureProjection: mapProjection }) 50 | }), 51 | visible: !!this.visible, 52 | style: style 53 | }); 54 | GUI.getService('map').getMap().addLayer(this.layer); 55 | }, 56 | beforeDestroy() { 57 | GUI.getService('map').getMap().removeLayer(this.layer); 58 | } 59 | }; -------------------------------------------------------------------------------- /src/mixins/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | import autocompleteMixin from 'mixins/autocomplete'; 6 | import fieldsMixin from 'mixins/fields'; 7 | import mediaMixin from 'mixins/media'; 8 | import geoMixin from 'mixins/geo'; 9 | import resizeMixin from 'mixins/resize'; 10 | import selectMixin from 'mixins/select'; 11 | import select2Mixin from 'mixins/select2'; 12 | import formInputsMixins from 'mixins/form-inputs'; 13 | import baseInputMixin from 'mixins/base-input'; 14 | 15 | const mixins = { 16 | autocompleteMixin, 17 | fieldsMixin, 18 | mediaMixin, 19 | geoMixin, 20 | resizeMixin, 21 | selectMixin, 22 | select2Mixin, 23 | formInputsMixins, 24 | baseInputMixin, 25 | }; 26 | export { fieldsMixin }; 27 | export { mediaMixin }; 28 | export { geoMixin }; 29 | export { resizeMixin }; 30 | export { selectMixin }; 31 | export { select2Mixin }; 32 | export { formInputsMixins }; 33 | export { baseInputMixin }; 34 | export { autocompleteMixin }; 35 | 36 | export { mixins }; 37 | export default mixins; -------------------------------------------------------------------------------- /src/mixins/media.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | export default { 7 | computed: { 8 | filename() { 9 | return this.value ? this.value.split('/').pop() : this.value; 10 | } 11 | }, 12 | methods: { 13 | isMedia(value) { 14 | if (value && 'object' === typeof value && Object === value.constructor) { 15 | return !!value.mime_type; 16 | } 17 | return false; 18 | }, 19 | getMediaType(mime_type) { 20 | const media = { 21 | type: null, 22 | options: {} 23 | }; 24 | 25 | switch(mime_type) { 26 | case 'image/gif': 27 | case 'image/png': 28 | case 'image/jpeg': 29 | case 'image/bmp': 30 | media.type = 'image'; 31 | break; 32 | case 'application/pdf': 33 | media.type = 'pdf'; 34 | break; 35 | case 'video/mp4': 36 | case 'video/ogg': 37 | case 'video/x-ms-wmv': 38 | case 'video/x-msvideo': 39 | case 'video/quicktime': 40 | media.type = 'video'; 41 | media.options.format = mime_type; 42 | break; 43 | case 'application/gzip': 44 | case 'application/zip': 45 | media.type = 'zip'; 46 | break; 47 | case 'application/msword': 48 | case 'application/vnd.oasis.opendocument.text': 49 | media.type = 'text'; 50 | break; 51 | case 'application/vnd.ms-office': 52 | case 'application/vnd.oasis.opendocument.spreadsheet': 53 | media.type = 'excel'; 54 | break; 55 | case 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 56 | case 'application/vnd.ms-powerpoint': 57 | case 'application/vnd.oasis.opendocument.presentation': 58 | media.type = 'ppt'; 59 | break; 60 | default: 61 | media.type = 'unknow'; 62 | } 63 | return media; 64 | } 65 | } 66 | }; -------------------------------------------------------------------------------- /src/mixins/resize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | import GUI from 'services/gui'; 7 | import { throttle } from 'utils/throttle'; 8 | import { debounce } from 'utils/debounce'; 9 | 10 | const DELAY_TYPE = { 11 | throttle, 12 | debounce, 13 | }; 14 | 15 | export default { 16 | created() { 17 | const delayWrapper = this.delayType && DELAY_TYPE[this.delayType] || DELAY_TYPE.throttle; 18 | this.delayResize = this.resize ? delayWrapper(this.resize.bind(this), this.delayTime): null; 19 | GUI.on('resize', this.delayResize); 20 | }, 21 | async mounted() { 22 | await this.$nextTick(); 23 | if (this.resize) { this.resize(); } 24 | }, 25 | beforeDestroy() { 26 | GUI.off('resize', this.delayResize); 27 | this.delayResize = null; 28 | this.delayTime = null; 29 | } 30 | }; -------------------------------------------------------------------------------- /src/mixins/select.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | export default { 7 | methods: { 8 | getLanguage() { 9 | return window.initConfig.user.i18n || "en"; 10 | }, 11 | async changeSelect(value) { 12 | this.state.value = 'null' === value ? null : value; 13 | //need to be waited in case of autocomplete 14 | await this.$nextTick(); 15 | this.change(); 16 | }, 17 | getValue(value) { 18 | return null === value ? 'null' : value; 19 | }, 20 | resetValues() { 21 | this.state.input.options.values.splice(0); 22 | } 23 | }, 24 | computed: { 25 | autocomplete() { 26 | return 'select_autocomplete' === this.state.input.type && this.state.input.options.usecompleter; 27 | }, 28 | 29 | }, 30 | watch:{ 31 | async notvalid(value) { 32 | await this.$nextTick(); 33 | if (this.select2) { 34 | this.select2.data('select2').$container[value ? "addClass" : "removeClass"]("input-error-validation") 35 | } 36 | } 37 | } 38 | }; -------------------------------------------------------------------------------- /src/mixins/select2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.7 4 | */ 5 | 6 | import ApplicationState from 'store/application'; 7 | import resizeMixin from 'mixins/resize'; 8 | 9 | export default { 10 | mixins: [resizeMixin], 11 | methods: { 12 | setValue() { 13 | this.select2.val(this.state.value).trigger('change'); 14 | }, 15 | resize() { 16 | if (this.select2 && !ApplicationState.ismobile) { 17 | this.select2.select2('close'); 18 | } 19 | } 20 | }, 21 | beforeDestroy() { 22 | //destroy a select2 dom element 23 | if (this.select2) { 24 | this.select2.select2('destroy'); 25 | // remove all events 26 | this.select2.off(); 27 | this.select2 = null; 28 | } 29 | } 30 | }; -------------------------------------------------------------------------------- /src/services/application.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @since v3.6 4 | */ 5 | import { APP_VERSION } from 'g3w-constants'; 6 | import G3WObject from 'g3w-object'; 7 | 8 | const ApplicationService = new G3WObject({ setters: { online(){}, offline(){} }}); 9 | ApplicationService.version = APP_VERSION; 10 | 11 | export default ApplicationService; -------------------------------------------------------------------------------- /src/store/plugins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Store G3W-CLIENT plugins (editing, qplotly, qtimeseries, ...) 3 | * @since v3.6 4 | */ 5 | 6 | import G3WObject from 'g3w-object'; 7 | 8 | /** 9 | * Object where store plugin 10 | * key = plugin name 11 | * value = plugin instance 12 | * 13 | * @since 3.11.0 14 | */ 15 | const PLUGINS = {}; 16 | 17 | export default Object.assign(new G3WObject, { setters: { 18 | /** store plugin into registry (if not already registered) */ 19 | registerPlugin(plugin) { PLUGINS[plugin.name] = PLUGINS[plugin.name] || plugin; }, 20 | }, 21 | /** @returns Plugin instance */ 22 | getPlugin(name) { 23 | return PLUGINS[name]; 24 | }, 25 | }); -------------------------------------------------------------------------------- /src/store/projections.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * 4 | * @since 3.11.0 5 | */ 6 | import { normalizeEpsg } from 'utils/normalizeEpsg'; 7 | import proj4 from 'proj4'; 8 | 9 | /** 10 | * ORIGINAL SOURCE: src/app/g3w-ol/projection/projection.js@v3.10.1 11 | * ORIGINAL SOURCE: src/app/g3w-ol/projection/projections.js@v3.10.1 12 | */ 13 | export default { 14 | 15 | get(crs = {}) { 16 | let p = ol.proj.get(normalizeEpsg(crs.epsg)); 17 | const proj = !p && { 18 | code: crs.epsg, 19 | extent: crs.extent, 20 | axisOrientation: crs.axisinverted ? 'neu' : 'enu', 21 | units: crs.geographic ? 'degrees' : 'm' 22 | }; 23 | 24 | // crs not yet registered 25 | if (!p) { 26 | p = new ol.proj.Projection(proj); 27 | p.getAxisOrientation = () => proj.axisOrientation; 28 | ol.proj.addProjection(p); 29 | } 30 | 31 | // crs is a proj4 object 32 | if (proj && crs.proj4) { 33 | proj4.defs(crs.epsg, crs.proj4); 34 | ol.proj.proj4.register(proj4); 35 | } 36 | 37 | // crs has no extent 38 | if (crs.extent && !p.getExtent()){ 39 | p.setExtent(crs.extent); 40 | } 41 | 42 | return p; 43 | }, 44 | 45 | /** 46 | * Check and register epsg 47 | * 48 | * @param epsg : "EPSG:" Ex. "EPSG:4326" 49 | * 50 | * @returns { Promise } 51 | * 52 | * @since v3.8 53 | */ 54 | async registerProjection(epsg) { 55 | let p = ol.proj.get(epsg) || undefined; 56 | 57 | // check if already registered 58 | if (!p) { 59 | const { result, data } = await (await fetch(`/crs/${epsg.split(':')[1]}/`)).json(); 60 | if (result) { 61 | data.epsg = normalizeEpsg(data.epsg); 62 | p = this.get(data); 63 | ol.proj.proj4.register(proj4); 64 | return p; 65 | } 66 | } 67 | 68 | return p; 69 | } 70 | }; -------------------------------------------------------------------------------- /src/utils/areCoordinatesEqual.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { Array } coordinates1 3 | * @param { Array } coordinates2 4 | * 5 | * @returns { boolean } 6 | */ 7 | export function areCoordinatesEqual(coordinates1 = [], coordinates2 = []) { 8 | return (coordinates1[0] === coordinates2[0] && coordinates1[1] === coordinates2[1]); 9 | } -------------------------------------------------------------------------------- /src/utils/base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on google closure library implementation 3 | */ 4 | export function base(target) { 5 | 6 | console.warn('[G3W-CLIENT] g3wsdk.core.utils.base is deprecated'); 7 | console.trace(); 8 | 9 | // reference to previous function (caller) 10 | const caller = arguments.callee.caller; 11 | 12 | // call superclass constructor (that inherits from superClass_) 13 | if (caller.superClass_) { 14 | if ('Function' === caller.superClass_.constructor.name) { 15 | return caller.superClass_.constructor.apply(target, Array.prototype.slice.call(arguments, 1)); 16 | } 17 | return Object.assign( 18 | target, 19 | Reflect.construct(caller.superClass_.constructor, Array.prototype.slice.call(arguments, 1), target.constructor) 20 | ); 21 | } 22 | 23 | let foundCaller = false; 24 | 25 | // traverse prototype chain 26 | for (let ctor = target.constructor; ctor; ctor = ctor.superClass_?.constructor) { 27 | if (ctor.prototype[arguments[1]] === caller) { 28 | foundCaller = true; 29 | } else if (foundCaller) { 30 | return ctor.prototype[arguments[1]].apply(target, Array.prototype.slice.call(arguments, 2)); 31 | } 32 | } 33 | 34 | // caller is an instance method 35 | if (target[arguments[1]] === caller) { 36 | return target.constructor.prototype[arguments[1]].apply(target, Array.prototype.slice.call(arguments, 2)); 37 | } 38 | 39 | // method was called by wrong caller 40 | throw Error('base called from a method of one name to a method of a different name'); 41 | } -------------------------------------------------------------------------------- /src/utils/convertQGISDateTimeFormatToMoment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Covert datetime format from Qgis format to Moment 3 | * 4 | * @param datetimeformat 5 | * 6 | * @returns {*} 7 | */ 8 | export function convertQGISDateTimeFormatToMoment(datetimeformat) { 9 | datetimeformat = datetimeformat.replace(/y/g, 'Y'); 10 | const matchDayInDate = datetimeformat.match(/d/g); 11 | if (matchDayInDate && matchDayInDate.length < 3) { datetimeformat = datetimeformat.replace(/d/g, 'D') } 12 | return datetimeformat 13 | } -------------------------------------------------------------------------------- /src/utils/convertSingleMultiGeometry.js: -------------------------------------------------------------------------------- 1 | import { GEOMETRY_TYPES } from 'g3w-constants'; 2 | import { isMultiGeometry } from 'utils/isMultiGeometry'; 3 | 4 | /** 5 | * Convert geometry to geometryType (from Single to Multi or viceversa) 6 | * 7 | * @param { ol.geom } geometry current OL geometry 8 | * @param { string } toGeometryType 9 | */ 10 | export function convertSingleMultiGeometry(geometry, toGeometryType) { 11 | const from_type = geometry.getType(); 12 | 13 | if (!toGeometryType || toGeometryType === from_type) { 14 | return geometry; 15 | } 16 | 17 | const from_multi = isMultiGeometry(from_type); 18 | const to_multi = isMultiGeometry(toGeometryType); 19 | 20 | if (from_multi && !to_multi) { 21 | switch (geometry.getType()) { 22 | case GEOMETRY_TYPES.MULTIPOLYGON: return geometry.getPolygons(); 23 | case GEOMETRY_TYPES.MULTILINE: return geometry.getLineStrings(); 24 | case GEOMETRY_TYPES.MULTILINESTRING: return geometry.getLineStrings(); 25 | case GEOMETRY_TYPES.MULTIPOINT: return geometry.getPoints(); 26 | default: console.warn('invalid geometry type', geometry.getType()); 27 | } 28 | return []; 29 | } 30 | 31 | if (!from_multi && to_multi) { 32 | return new ol.geom[`Multi${from_type}`]([geometry.getCoordinates()]); 33 | } 34 | 35 | return geometry; 36 | } -------------------------------------------------------------------------------- /src/utils/copyUrl.js: -------------------------------------------------------------------------------- 1 | export function copyUrl(url) { 2 | const tempinput = document.createElement('input'); 3 | document.body.appendChild(tempinput); 4 | tempinput.value = url; 5 | tempinput.select(); 6 | document.execCommand('copy'); 7 | document.body.removeChild(tempinput); 8 | }; -------------------------------------------------------------------------------- /src/utils/createFilterFormInputs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param layer single layer or an array of layers 3 | * @param inputs 4 | * 5 | * @returns {*} 6 | */ 7 | export function createFilterFormInputs({ 8 | layer, 9 | inputs = [], 10 | }) { 11 | const filter = inputs.map((input, i) => Array.isArray(input.attribute) 12 | // multi key relation fields 13 | ? input.attribute.map((attr, j) => [].concat(input.value[j]).map(v => `${attr}|${(input.operator || 'eq').toLowerCase()}|${encodeURIComponent(v)}`).join(`|null,`)).join('|AND,') 14 | // input logic operator 15 | : `${i > 0 ? `|${inputs[i-1].logicop},` : ''}${'in' === input.operator 16 | ? `${input.attribute}|${input.operator}|(${[].concat(input.value).map(v => encodeURIComponent(v)).join(',')})` 17 | : [].concat(input.value).map(v => `${input.attribute}|${(input.operator || 'eq').toLowerCase()}|${encodeURIComponent(v)}`).join(`|${undefined !== input.logicop ? input.logicop : 'OR'},`)}` 18 | ).join('') || undefined; 19 | 20 | // check if is a single layer of an array of layers 21 | return Array.isArray(layer) ? layer.map(() => filter) : filter; 22 | } -------------------------------------------------------------------------------- /src/utils/createFilterFromString.js: -------------------------------------------------------------------------------- 1 | import { FILTER_EXPRESSION_OPERATORS } from 'g3w-constants'; 2 | 3 | const operators = Object.entries(FILTER_EXPRESSION_OPERATORS); 4 | 5 | export function createFilterFromString({ filter = '' }) { 6 | filter = operators 7 | .reduce((acc, [_, op]) => acc 8 | .replace(new RegExp(`\\s+${op}\\s+`, 'g'), `${op}`) // remove all blank space between operators 9 | .replace(new RegExp(`'${op}`, 'g'), `${op}`) // leading single quote 10 | .replace(new RegExp(`${op}'`, 'g'), `${op}`) // trailing single quote 11 | , filter) 12 | .replace(/'$/g, '') 13 | .replace(/"/g, ''); 14 | filter = operators 15 | .reduce((acc, [k, op]) => acc.replace(new RegExp(op, 'g'), ['AND', 'OR'].includes(op) ? `|${k},` : `|${k}|`), filter) 16 | // encode value 17 | .split('|') 18 | .map((v, i) => (0 === (i+1) % 3) ? encodeURIComponent(v) : v) 19 | .join('|'); 20 | 21 | return filter; 22 | } -------------------------------------------------------------------------------- /src/utils/createRelationsUrl.js: -------------------------------------------------------------------------------- 1 | import ApplicationState from 'store/application' 2 | import { sanitizeFidFeature } from 'utils/sanitizeFidFeature'; 3 | 4 | /** 5 | * ORIGINAL SOURCE: src/services/relations.js@v3.10.2 6 | */ 7 | export function createRelationsUrl({ 8 | layer = {}, 9 | relation = {}, 10 | fid, 11 | type = 'data', // 12 | }) { 13 | return `${ApplicationState.project.getLayerById( 14 | undefined === relation.father 15 | ? (layer.id === relation.referencedLayer ? relation.referencingLayer : relation.referencedLayer) 16 | : (layer.id === relation.father ? relation.child : relation.father) 17 | ).getUrl(type)}?relationonetomany=${relation.id}|${sanitizeFidFeature(fid)}`; 18 | } -------------------------------------------------------------------------------- /src/utils/createSelectedStyle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { Object } style 3 | * @param style.geometryType 4 | * @param style.color 5 | * @param style.fill 6 | * 7 | * @returns { ol.style.Style | null } style 8 | */ 9 | export function createSelectedStyle({ 10 | geometryType, 11 | color = 'rgb(255,255,0)', 12 | fill = true, 13 | } = {}) { 14 | switch(geometryType) { 15 | 16 | case 'LineString': 17 | case 'MultiLineString': 18 | return new ol.style.Style({ 19 | stroke: new ol.style.Stroke({ color, width: 4 }) 20 | }); 21 | 22 | case 'Point': 23 | case 'MultiPoint': 24 | return new ol.style.Style({ 25 | image: new ol.style.Circle({ 26 | radius: 6, 27 | fill: fill && new ol.style.Fill({ color }), 28 | stroke: !fill && new ol.style.Stroke({ color, width: 4 }), 29 | }), 30 | zIndex: Infinity, 31 | }); 32 | 33 | case 'MultiPolygon': 34 | case 'Polygon': 35 | return new ol.style.Style({ 36 | stroke: new ol.style.Stroke({ color, width: 4 }), 37 | fill: fill && new ol.style.Fill({ color: ol.color.asString([...ol.color.asArray(color)].splice(0, 3).concat(.25)) }) // force rgba color transparency (alpha = .25) 38 | }); 39 | 40 | default: 41 | console.warn('invalid geometry type', geometryType); 42 | return null; 43 | 44 | } 45 | } -------------------------------------------------------------------------------- /src/utils/debounce.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * build debounce function 4 | */ 5 | export function debounce(func, delay = 500) { 6 | let timeout; 7 | return function (...args) { 8 | clearTimeout(timeout); 9 | timeout = setTimeout(() => func(...args), delay); 10 | }; 11 | } -------------------------------------------------------------------------------- /src/utils/dissolve.js: -------------------------------------------------------------------------------- 1 | import 'jsts/dist/jsts.min.js'; 2 | 3 | /** 4 | * 5 | * @param { Object } opts 6 | * @param { Array } opts.features 7 | * @param { number } opts.index 8 | * @param { boolean } opts.clone 9 | * 10 | * @returns dissolved feature 11 | */ 12 | export function dissolve({ 13 | features = [], 14 | index = 0, 15 | clone = false, 16 | } = {}) { 17 | 18 | const parser = new jsts.io.OL3Parser(); 19 | const featuresLength = features.length; 20 | 21 | 22 | /** In case no features to dissolve */ 23 | if (0 === featuresLength) { 24 | return null; 25 | } 26 | 27 | /** In the case of single feature, return feature */ 28 | if (1 === featuresLength) { 29 | return features[0]; 30 | } 31 | 32 | let jstsdissolvedFeatureGeometry; 33 | 34 | const baseFeature = clone ? features[index].clone() : features[index]; 35 | const baseFeatureGeometry = baseFeature.getGeometry(); 36 | const baseFeatureGeometryType = baseFeatureGeometry.getType(); 37 | 38 | // check if it can build a LineString 39 | if ('LineString' === baseFeatureGeometryType) { 40 | const lineMerger = new jsts.operation.linemerge.LineMerger(); 41 | for (let i = 0; i < featuresLength; i++) { 42 | lineMerger.addLineString( 43 | new jsts.geom.GeometryFactory().createLineString(parser.read(features[i].getGeometry()).getCoordinates()) 44 | ); 45 | } 46 | const mergedLineString = lineMerger.getMergedLineStrings(); 47 | jstsdissolvedFeatureGeometry = 1 === mergedLineString.size() ? mergedLineString.toArray()[0] : null; 48 | } 49 | 50 | if ('LineString' !== baseFeatureGeometryType) { 51 | jstsdissolvedFeatureGeometry = parser.read(baseFeatureGeometry); 52 | for (let i = 0; i < featuresLength ; i++) { 53 | if (index !== i) { 54 | jstsdissolvedFeatureGeometry = jstsdissolvedFeatureGeometry.union(parser.read(features[i].getGeometry())) 55 | } 56 | } 57 | } 58 | 59 | /** In case of no dissolved geometry */ 60 | if (!jstsdissolvedFeatureGeometry) { 61 | return null; 62 | } 63 | 64 | const dissolvedFeatureGeometry = parser.write(jstsdissolvedFeatureGeometry); 65 | const dissolvedFeatureGeometryType = dissolvedFeatureGeometry.getType(); 66 | const dissolvedFeatureGeometryCoordinates = dissolvedFeatureGeometryType === baseFeatureGeometryType 67 | ? dissolvedFeatureGeometry.getCoordinates() 68 | : -1 !== baseFeatureGeometryType.indexOf('Multi') && dissolvedFeatureGeometryType === baseFeatureGeometryType.replace('Multi', '') 69 | ? [dissolvedFeatureGeometry.getCoordinates()] 70 | : null; 71 | 72 | /** In the case of null feature dissolved coordinates */ 73 | if (null === dissolvedFeatureGeometryCoordinates) { 74 | return null; 75 | } 76 | 77 | baseFeature.getGeometry().setCoordinates(dissolvedFeatureGeometryCoordinates); 78 | 79 | return baseFeature; 80 | } -------------------------------------------------------------------------------- /src/utils/distance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * core/geometry/geom::distance@v3.4 3 | * core/geometry/geom::squaredDistance@v3.4 4 | */ 5 | export function distance(c1, c2) { 6 | return Math.sqrt( 7 | Math.pow(c2[0] - c1[0], 2) + 8 | Math.pow(c2[1] - c1[1], 2) 9 | ); 10 | } -------------------------------------------------------------------------------- /src/utils/flattenObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ORIGINAL SOURCE: https://stackoverflow.com/a/56253298 3 | * 4 | * @example 5 | * 6 | * ### Sample Input 7 | * 8 | * ``` 9 | * const obj = { 10 | * name: "test", 11 | * address: { 12 | * personal: "abc", 13 | * office: { 14 | * building: 'random', 15 | * street: 'some street' 16 | * } 17 | * } 18 | * } 19 | * ``` 20 | * 21 | * ### Expected Output 22 | * 23 | * ``` 24 | * { 25 | * name : "test", 26 | * address_personal: "abc" 27 | * address_office_building: "random" 28 | * address_office_street: "some street" 29 | * } 30 | * ``` 31 | * 32 | * @since 3.9.0 33 | */ 34 | export function flattenObject(obj, parent, res = {}) { 35 | for (let key in obj) { 36 | let propName = parent ? parent + '_' + key : key; 37 | if ('object' === typeof obj[key]) { 38 | flattenObject(obj[key], propName, res); 39 | } else { 40 | res[propName] = obj[key]; 41 | } 42 | } 43 | return res; 44 | } -------------------------------------------------------------------------------- /src/utils/getAlphanumericPropertiesFromFeature.js: -------------------------------------------------------------------------------- 1 | import { GEOMETRY_FIELDS } from 'g3w-constants'; 2 | 3 | /** 4 | * @param { Array } properties 5 | * 6 | * @returns { Array } 7 | */ 8 | export function getAlphanumericPropertiesFromFeature(properties = []) { 9 | return (Array.isArray(properties) ? properties : Object.keys(properties)).filter(p => !GEOMETRY_FIELDS.includes(p)); 10 | } -------------------------------------------------------------------------------- /src/utils/getCatalogLayerById.js: -------------------------------------------------------------------------------- 1 | import ApplicationState from 'store/application'; 2 | 3 | /** 4 | * ORIGINAL SOURCE: src/app/core/layers/layersstoreregistry.js@v3.10.2 5 | */ 6 | export function getCatalogLayerById(id) { 7 | return Object.values(ApplicationState.catalog).map(s => s.getLayerById(id)).find(l => l); 8 | } -------------------------------------------------------------------------------- /src/utils/getCatalogLayers.js: -------------------------------------------------------------------------------- 1 | import ApplicationState from 'store/application'; 2 | 3 | /** 4 | * ORIGINAL SOURCE: src/app/core/layers/layersstoreregistry.js@v3.10.2 5 | */ 6 | export function getCatalogLayers(filter, options = {}) { 7 | return Object.values(ApplicationState.catalog).flatMap(s => s.getLayers(filter, options)); 8 | } -------------------------------------------------------------------------------- /src/utils/getDataForSearchInput.js: -------------------------------------------------------------------------------- 1 | import { SEARCH_ALLVALUE } from 'g3w-constants'; 2 | 3 | /** 4 | * @returns { Array } of unique values from field 5 | */ 6 | export async function getDataForSearchInput({ state, field, filter, suggest }) { 7 | 8 | try { 9 | // get unique value from each layers 10 | return ( 11 | await Promise.allSettled(state.search_layers.map(l => l.getFilterData({ 12 | suggest, 13 | fformatter: field, 14 | ordering: field, 15 | field: filter || getDataForSearchInput.field({ 16 | state, 17 | //in the case of suggested parameter set (case autocomplete field), need to use current field 18 | field: suggest ? field : (state.forminputs.find(i => field === i.attribute) || {}).dependance || field, 19 | fields: [] 20 | }), 21 | }))) 22 | ) 23 | .filter(d => 'fulfilled' === d.status) 24 | .reduce((acc, d, i) => 0 === i 25 | ? acc.concat(d.value.data || []) // for first layer get all uninques values 26 | : [...new Set([...(d.value.data || []), ...acc].map(JSON.stringify))].map(JSON.parse), // ensure uniques values (search performed on multiple serach_layers) 27 | [] 28 | ) 29 | .map(([value, key]) => ({ key, value })); 30 | 31 | } catch(e) { console.warn(e); } 32 | 33 | return []; 34 | } 35 | 36 | /** 37 | * Traverse field dependecies 38 | */ 39 | getDataForSearchInput.field = ({ state, field, fields = [] } = {}) => { 40 | field = state.forminputs.find(i => i.attribute === field); // current input 41 | const parent = state.forminputs.find(i => i.attribute === field.dependance); // current input dependance (parent field) 42 | // get all values (un-filtered) 43 | if (!parent || [].concat(parent.value).find(v => v === SEARCH_ALLVALUE)) { 44 | return (fields || []).join() || undefined; 45 | } 46 | 47 | // filter by parent field 48 | if (undefined !== parent.value) { 49 | //Take in account in operator (array values) 50 | fields.unshift(`${parent.attribute}|${parent.operator.toLowerCase()}|${'in' === parent.operator ? `(${[].concat(parent.value).map(v => encodeURIComponent(v)).join(',')})` : `${encodeURIComponent(parent.value)}`}` + (fields.length ? `|${parent.logicop}` : '')); 51 | } 52 | 53 | // recursion step 54 | return getDataForSearchInput.field({ state, fields, field: parent.attribute }); 55 | } -------------------------------------------------------------------------------- /src/utils/getDefaultExpression.js: -------------------------------------------------------------------------------- 1 | import DataRouterService from 'services/data'; 2 | 3 | /** 4 | * ORIGINAL SOURCE: src/app/core/expression/inputservice.js@3.8.6 5 | * 6 | * @param expr.field related field 7 | * @param expr.feature feature to transform in form_data 8 | * @param expr.qgs_layer_id layer id owner of the feature data 9 | * @param expr.parentData 10 | * 11 | * @returns { void | Promise } 12 | * 13 | * @since 3.9.0 14 | */ 15 | export async function getDefaultExpression({ 16 | field, 17 | feature, 18 | qgs_layer_id, 19 | parentData, 20 | } = {}) { 21 | 22 | const { 23 | layer_id = qgs_layer_id, 24 | default_expression, 25 | loading, 26 | default: default_value, 27 | } = field.input.options; 28 | 29 | /** 30 | * @FIXME should return Promise.reject('some error message') ? 31 | */ 32 | if (!default_expression) { 33 | return; 34 | } 35 | 36 | loading.state = 'loading'; 37 | 38 | // Call `expression:expression_eval` to get value from expression and set it to field 39 | try { 40 | 41 | const value = await DataRouterService.getData('expression:expression_eval', { 42 | inputs: { 43 | field_name: field.name, 44 | layer_id, // 45 | qgs_layer_id, //layer id owner of the data 46 | form_data: (new ol.format.GeoJSON()).writeFeatureObject(feature), 47 | formatter: 0, 48 | expression: default_expression.expression, 49 | parent: parentData && { 50 | form_data: (new ol.format.GeoJSON()).writeFeatureObject(parentData.feature), 51 | qgs_layer_id: parentData.qgs_layer_id, 52 | formatter: 0 53 | } 54 | }, 55 | outputs: false 56 | }); 57 | 58 | field.value = value; 59 | 60 | return value; 61 | 62 | } catch(e) { 63 | if (undefined !== default_value) { 64 | field.value = default_value 65 | } 66 | console.warn(e); 67 | return Promise.reject(e); 68 | } finally { 69 | loading.state = 'ready'; 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /src/utils/getFilterExpression.js: -------------------------------------------------------------------------------- 1 | import DataRouterService from 'services/data'; 2 | 3 | /** 4 | * ORIGINAL SOURCE: src/app/core/expression/inputservice.js@3.8.6 5 | * 6 | * @param expr.field related field 7 | * @param expr.feature feature to transform in form_data 8 | * @param expr.qgs_layer_id layer id owner of the feature data 9 | * @param expr.parentData 10 | * 11 | * @returns { void | Promise } 12 | * 13 | * @since 3.9.0 14 | */ 15 | export async function getFilterExpression({ 16 | field, 17 | feature, 18 | qgs_layer_id, 19 | parentData, 20 | } = {}) { 21 | let { 22 | key, 23 | value, 24 | layer_id = qgs_layer_id, 25 | filter_expression, 26 | loading, 27 | orderbyvalue 28 | } = field.input.options; 29 | 30 | /** 31 | * @FIXME should return Promise.reject('some error message') ? 32 | */ 33 | if (!filter_expression) { 34 | return; 35 | } 36 | 37 | loading.state = 'loading'; 38 | 39 | try { 40 | 41 | const features = await DataRouterService.getData('expression:expression', { 42 | inputs: { 43 | field_name: field.name, 44 | layer_id, 45 | qgs_layer_id, 46 | form_data: (new ol.format.GeoJSON()).writeFeatureObject(feature), 47 | parent: parentData && ({ 48 | form_data: (new ol.format.GeoJSON()).writeFeatureObject(parentData.feature), 49 | qgs_layer_id: parentData.qgs_layer_id, 50 | formatter: 0, 51 | }), 52 | formatter: 0, 53 | expression: filter_expression.expression, 54 | ordering: [undefined, false].includes(orderbyvalue) ? key : value, //@since 3.11.0 55 | }, 56 | outputs: false, 57 | }); 58 | 59 | if ('select_autocomplete' === field.input.type) { 60 | field.input.options.values = []; 61 | // temporary array to sort the keys 62 | const values = []; 63 | for (let i = 0; i < features.length; i++) { 64 | values.push({ 65 | key: features[i].properties[value], 66 | value: features[i].properties[key] 67 | }) 68 | } 69 | 70 | field.input.options.values = values; 71 | } 72 | 73 | return features; 74 | 75 | } catch(e) { 76 | console.warn(e); 77 | return Promise.reject(e); 78 | } finally { 79 | loading.state = 'ready'; 80 | } 81 | 82 | } -------------------------------------------------------------------------------- /src/utils/getListableProjects.js: -------------------------------------------------------------------------------- 1 | import ApplicationState from 'store/application'; 2 | 3 | /** used by the following plugins: "iframe", "archiweb" */ 4 | export function getListableProjects() { 5 | window.initConfig.projects 6 | return window.initConfig.projects.filter(p => { 7 | if (![null, undefined].includes(p.listable)) { 8 | return p.listable; 9 | } 10 | if ( 11 | p.id === ApplicationState.project.getId() || 12 | (window.initConfig.overviewproject && p.gid === window.initConfig.overviewproject) 13 | ) { 14 | return false; 15 | } 16 | return p; 17 | }).sort((a, b) => (a.title || '').localeCompare(b.title)); 18 | } -------------------------------------------------------------------------------- /src/utils/getMapLayersByFilter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param filter defaults `GEOLAYER: true` 3 | * @param options 4 | * 5 | * @returns { Array } map layers based on filtered properties of layer (eg. `GEOLAYER : true`) 6 | */ 7 | export function getMapLayersByFilter(filter = {}, options = {}) { 8 | const { MapLayersStoresRegistry } = require('services/map').default; 9 | return MapLayersStoresRegistry 10 | .getQuerableLayersStores() 11 | .flatMap(s => s.getLayers({ 12 | GEOLAYER: true, 13 | /** @TODO check if it could be used to remove the subsequent call to: `store.isQueryable()` */ 14 | // QUERYABLE: true, 15 | ...(filter || {}) 16 | }, options)); 17 | } -------------------------------------------------------------------------------- /src/utils/getOLGeometry.js: -------------------------------------------------------------------------------- 1 | import { GEOMETRY_TYPES } from 'g3w-constants'; 2 | 3 | /** 4 | * core/geometry/geometry::getOLGeometry@v3.4 5 | */ 6 | export function getOLGeometry(geometryType) { 7 | 8 | switch (geometryType) { 9 | 10 | case GEOMETRY_TYPES.LINESTRINGZ: 11 | case GEOMETRY_TYPES.LINESTRINGM: 12 | case GEOMETRY_TYPES.LINESTRINGZM: 13 | case GEOMETRY_TYPES.LINESTRING25D: 14 | case GEOMETRY_TYPES.LINE: 15 | case GEOMETRY_TYPES.LINEZ: 16 | case GEOMETRY_TYPES.LINEM: 17 | case GEOMETRY_TYPES.LINEZM: 18 | case GEOMETRY_TYPES.LINE25D: 19 | return 'LineString'; 20 | 21 | case GEOMETRY_TYPES.MULTILINESTRINGZ: 22 | case GEOMETRY_TYPES.MULTILINESTRINGM: 23 | case GEOMETRY_TYPES.MULTILINESTRINGZM: 24 | case GEOMETRY_TYPES.MULTILINESTRING25D: 25 | case GEOMETRY_TYPES.MULTILINE: 26 | case GEOMETRY_TYPES.MULTILINEZ: 27 | case GEOMETRY_TYPES.MULTILINEM: 28 | case GEOMETRY_TYPES.MULTILINEZM: 29 | case GEOMETRY_TYPES.MULTILINE25D: 30 | return 'MultiLineString'; 31 | 32 | case GEOMETRY_TYPES.POINT: 33 | case GEOMETRY_TYPES.POINTZ: 34 | case GEOMETRY_TYPES.POINTM: 35 | case GEOMETRY_TYPES.POINTZM: 36 | case GEOMETRY_TYPES.POINT25D: 37 | return 'Point'; 38 | 39 | case GEOMETRY_TYPES.MULTIPOINT: 40 | case GEOMETRY_TYPES.MULTIPOINTZ: 41 | case GEOMETRY_TYPES.MULTIPOINTM: 42 | case GEOMETRY_TYPES.MULTIPOINTZM: 43 | case GEOMETRY_TYPES.MULTIPOINT25D: 44 | return 'MultiPoint'; 45 | 46 | case GEOMETRY_TYPES.POLYGON: 47 | case GEOMETRY_TYPES.POLYGONZ: 48 | case GEOMETRY_TYPES.POLYGONM: 49 | case GEOMETRY_TYPES.POLYGONZM: 50 | case GEOMETRY_TYPES.POLYGON25D: 51 | return 'Polygon'; 52 | 53 | case GEOMETRY_TYPES.MULTIPOLYGON: 54 | case GEOMETRY_TYPES.MULTIPOLYGONZ: 55 | case GEOMETRY_TYPES.MULTIPOLYGONM: 56 | case GEOMETRY_TYPES.MULTIPOLYGONZM: 57 | case GEOMETRY_TYPES.MULTIPOLYGON25D: 58 | return 'MultiPolygon'; 59 | 60 | default: 61 | console.warn('invalid geometry type: ', geometryType); 62 | return geometryType; 63 | } 64 | } -------------------------------------------------------------------------------- /src/utils/getPlugin.js: -------------------------------------------------------------------------------- 1 | import ApplicationState from 'store/application'; 2 | 3 | /** 4 | * @param name 5 | * 6 | * @returns Plugin instance 7 | */ 8 | export function getPlugin(name) { 9 | return ApplicationState._plugins[name]; 10 | } -------------------------------------------------------------------------------- /src/utils/getProjectConfigByGid.js: -------------------------------------------------------------------------------- 1 | /** used by the following plugins: "iframe", "archiweb" */ 2 | export function getProjectConfigByGid(gid) { 3 | return window.initConfig.projects.find(p => gid === p.gid); 4 | } -------------------------------------------------------------------------------- /src/utils/getProjectUrl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param gid 3 | * 4 | * @returns {string} 5 | */ 6 | export function getProjectUrl(gid) { 7 | const project = window.initConfig.projects.find(p => gid === p.gid); 8 | try { 9 | return `${(new URL(window.initConfig.urls.baseurl))}${project.url}`; 10 | } catch(e) { 11 | console.warn(e); 12 | return `${location.origin}${window.initConfig.urls.baseurl}${project.url}`; 13 | } 14 | } -------------------------------------------------------------------------------- /src/utils/getRelationLayerById.js: -------------------------------------------------------------------------------- 1 | import ApplicationState from 'store/application'; 2 | 3 | /** 4 | * @since 3.11.8 5 | */ 6 | export function getRelationLayerById(relationid) { 7 | return ApplicationState.project.getLayerById((ApplicationState.project.getRelationById(relationid) || {}).referencedLayer); 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/getResolutionFromScale.js: -------------------------------------------------------------------------------- 1 | import { INCHES_PER_UNIT, DOTS_PER_INCH } from 'g3w-constants'; 2 | 3 | export function getResolutionFromScale(scale, units = 'm') { 4 | // just to prevent that scale is passed as 1:10000 or 0.0001 5 | return 1 / (((scale >= 1.0) ? (1.0 / scale) : scale) * INCHES_PER_UNIT[units] * DOTS_PER_INCH); 6 | } -------------------------------------------------------------------------------- /src/utils/getScaleFromResolution.js: -------------------------------------------------------------------------------- 1 | import { INCHES_PER_UNIT, DOTS_PER_INCH } from 'g3w-constants'; 2 | 3 | export function getScaleFromResolution(resolution, units = 'm') { 4 | return Math.round(resolution * INCHES_PER_UNIT[units] * DOTS_PER_INCH); 5 | } -------------------------------------------------------------------------------- /src/utils/getUniqueDomId.js: -------------------------------------------------------------------------------- 1 | let _uid = 0; 2 | 3 | export function getUniqueDomId() { 4 | return `${++_uid}_${Date.now()}`; 5 | } -------------------------------------------------------------------------------- /src/utils/get_legend_params.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param layer 3 | * 4 | * @returns {{ LEGEND_ON: undefined | string, LEGEND_OFF: undefined | string }} 5 | */ 6 | export function get_legend_params(layer) { 7 | let LEGEND_ON, LEGEND_OFF; 8 | (layer.getCategories() || []) 9 | .forEach(({ 10 | checked, // new Value 11 | _checked, // old Value 12 | ruleKey, 13 | }) => { 14 | // skip when there's no difference from original `checked` status (_checked) and current changed by toc categories (checked) 15 | if (checked === _checked) { 16 | return; 17 | } 18 | if (checked) { 19 | LEGEND_ON = (undefined === LEGEND_ON ? `${layer.getWMSLayerName()}:` : `${LEGEND_ON},`) + ruleKey; 20 | } else { 21 | LEGEND_OFF = (undefined === LEGEND_OFF ? `${layer.getWMSLayerName()}:` : `${LEGEND_OFF},`) + ruleKey; 22 | } 23 | }); 24 | return { 25 | LEGEND_ON, 26 | LEGEND_OFF, 27 | }; 28 | } -------------------------------------------------------------------------------- /src/utils/groupBy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Almost the same as lodash@v4.0.0 groupBy 3 | * 4 | * @since 3.10.0 5 | */ 6 | export function groupBy(array, keyFn) { 7 | return array.reduce((result, item) => { 8 | const key = keyFn(item); 9 | if (!result[key]) { 10 | result[key] = []; 11 | } 12 | result[key].push(item); 13 | return result; 14 | }, {}); 15 | } -------------------------------------------------------------------------------- /src/utils/inherit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * google closure library impememtation 3 | */ 4 | export function inherit(childCtor, parentCtor) { 5 | console.warn('[G3W-CLIENT] g3wsdk.core.utils.inherit is deprecated'); 6 | console.trace(); 7 | 8 | function tempCtor() {} 9 | tempCtor.prototype = parentCtor.prototype; 10 | childCtor.superClass_ = parentCtor.prototype; 11 | childCtor.prototype = new tempCtor(); 12 | childCtor.prototype.constructor = childCtor; 13 | } -------------------------------------------------------------------------------- /src/utils/intersects.js: -------------------------------------------------------------------------------- 1 | import 'jsts/dist/jsts.min.js'; 2 | 3 | /** 4 | * @param {ol.geometry} geometry 5 | * @param {ol.geometry} geometryToCheck 6 | * 7 | * @returns {boolean} whether `geometry` interesects `geometryToCheck` 8 | * 9 | * @since 3.8.0 10 | */ 11 | export function intersects(geometry, geometryToCheck) { 12 | const parser = new jsts.io.OL3Parser(); 13 | parser.inject( 14 | ol.geom.Point, 15 | ol.geom.LineString, 16 | ol.geom.LinearRing, 17 | ol.geom.Polygon, 18 | ol.geom.MultiPoint, 19 | ol.geom.MultiLineString, 20 | ol.geom.MultiPolygon, 21 | ); 22 | return parser.read(geometry).intersects(parser.read(geometryToCheck)); 23 | } -------------------------------------------------------------------------------- /src/utils/is3DGeometry.js: -------------------------------------------------------------------------------- 1 | import { GEOMETRY_TYPES } from 'g3w-constants'; 2 | 3 | export function is3DGeometry(geometryType) { 4 | return [ 5 | GEOMETRY_TYPES.POINTZ, 6 | GEOMETRY_TYPES.POINTM, 7 | GEOMETRY_TYPES.POINTZM, 8 | GEOMETRY_TYPES.POINT25D, 9 | GEOMETRY_TYPES.MULTIPOINTZ, 10 | GEOMETRY_TYPES.MULTIPOINTM, 11 | GEOMETRY_TYPES.MULTIPOINTZM, 12 | GEOMETRY_TYPES.MULTIPOINT25D, 13 | GEOMETRY_TYPES.LINESTRINGZ, 14 | GEOMETRY_TYPES.LINESTRINGM, 15 | GEOMETRY_TYPES.LINESTRINGZM, 16 | GEOMETRY_TYPES.LINESTRING25D, 17 | GEOMETRY_TYPES.MULTILINESTRINGZ, 18 | GEOMETRY_TYPES.MULTILINESTRINGM, 19 | GEOMETRY_TYPES.MULTILINESTRINGZM, 20 | GEOMETRY_TYPES.MULTILINESTRING25D, 21 | GEOMETRY_TYPES.LINEZ, 22 | GEOMETRY_TYPES.LINEM, 23 | GEOMETRY_TYPES.LINEZM, 24 | GEOMETRY_TYPES.LINE25D, 25 | GEOMETRY_TYPES.MULTILINEZ, 26 | GEOMETRY_TYPES.MULTILINEM, 27 | GEOMETRY_TYPES.MULTILINEZM, 28 | GEOMETRY_TYPES.MULTILINE25D, 29 | GEOMETRY_TYPES.POLYGONZ, 30 | GEOMETRY_TYPES.POLYGONM, 31 | GEOMETRY_TYPES.POLYGONZM, 32 | GEOMETRY_TYPES.POLYGON25D, 33 | GEOMETRY_TYPES.MULTIPOLYGONZ, 34 | GEOMETRY_TYPES.MULTIPOLYGONM, 35 | GEOMETRY_TYPES.MULTIPOLYGONZM, 36 | GEOMETRY_TYPES.MULTIPOLYGON25D 37 | ].find(type3D => type3D === geometryType); 38 | } -------------------------------------------------------------------------------- /src/utils/isLineGeometryType.js: -------------------------------------------------------------------------------- 1 | import { GEOMETRY_TYPES } from 'g3w-constants'; 2 | 3 | /** 4 | * core/geometry/geometry::isLineGeometryType@v3.4 5 | * core/geometry/geometry::getAllLineGeometryTypes@v3.4 6 | */ 7 | export function isLineGeometryType(geometryType) { 8 | return [ 9 | GEOMETRY_TYPES.LINESTRING, 10 | GEOMETRY_TYPES.LINESTRINGZ, 11 | GEOMETRY_TYPES.LINESTRINGM, 12 | GEOMETRY_TYPES.LINESTRINGZM, 13 | GEOMETRY_TYPES.LINESTRING25D, 14 | GEOMETRY_TYPES.MULTILINESTRING, 15 | GEOMETRY_TYPES.MULTILINESTRINGZ, 16 | GEOMETRY_TYPES.MULTILINESTRINGM, 17 | GEOMETRY_TYPES.MULTILINESTRINGZM, 18 | GEOMETRY_TYPES.MULTILINESTRING25D, 19 | GEOMETRY_TYPES.LINE, 20 | GEOMETRY_TYPES.LINEZ, 21 | GEOMETRY_TYPES.LINEM, 22 | GEOMETRY_TYPES.LINEZM, 23 | GEOMETRY_TYPES.LINE25D, 24 | GEOMETRY_TYPES.MULTILINE, 25 | GEOMETRY_TYPES.MULTILINEZ, 26 | GEOMETRY_TYPES.MULTILINEM, 27 | GEOMETRY_TYPES.MULTILINEZM, 28 | GEOMETRY_TYPES.MULTILINE25D, 29 | ].includes(geometryType); 30 | } -------------------------------------------------------------------------------- /src/utils/isMultiGeometry.js: -------------------------------------------------------------------------------- 1 | import { GEOMETRY_TYPES } from 'g3w-constants'; 2 | 3 | /** 4 | * core/geometry/geometry::isMultiGeometry@v3.4 5 | */ 6 | export function isMultiGeometry(geometryType) { 7 | return [ 8 | GEOMETRY_TYPES.MULTIPOINT, 9 | GEOMETRY_TYPES.MULTIPOINTZ, 10 | GEOMETRY_TYPES.MULTIPOINTZM, 11 | GEOMETRY_TYPES.MULTIPOINTM, 12 | GEOMETRY_TYPES.MULTIPOINT25D, 13 | GEOMETRY_TYPES.MULTILINESTRING, 14 | GEOMETRY_TYPES.MULTILINESTRINGZ, 15 | GEOMETRY_TYPES.MULTILINESTRINGM, 16 | GEOMETRY_TYPES.MULTILINESTRINGZM, 17 | GEOMETRY_TYPES.MULTILINESTRING25D, 18 | GEOMETRY_TYPES.MULTILINE, 19 | GEOMETRY_TYPES.MULTILINEZ, 20 | GEOMETRY_TYPES.MULTILINEM, 21 | GEOMETRY_TYPES.MULTILINEZM, 22 | GEOMETRY_TYPES.MULTILINE25D, 23 | GEOMETRY_TYPES.MULTIPOLYGON, 24 | GEOMETRY_TYPES.MULTIPOLYGONZ, 25 | GEOMETRY_TYPES.MULTIPOLYGONM, 26 | GEOMETRY_TYPES.MULTIPOLYGONZM, 27 | GEOMETRY_TYPES.MULTIPOLYGON25D, 28 | ].includes(geometryType); 29 | } -------------------------------------------------------------------------------- /src/utils/isPointGeometryType.js: -------------------------------------------------------------------------------- 1 | import { GEOMETRY_TYPES } from 'g3w-constants'; 2 | 3 | /** 4 | * core/geometry/geometry::isPointGeometryType@v3.4 5 | * core/geometry/geometry::getAllPointGeometryTypes@v3.4 6 | */ 7 | export function isPointGeometryType(geometryType) { 8 | return [ 9 | GEOMETRY_TYPES.POINT, 10 | GEOMETRY_TYPES.POINTZ, 11 | GEOMETRY_TYPES.POINTM, 12 | GEOMETRY_TYPES.POINTZM, 13 | GEOMETRY_TYPES.POINT25D, 14 | GEOMETRY_TYPES.MULTIPOINT, 15 | GEOMETRY_TYPES.MULTIPOINTZ, 16 | GEOMETRY_TYPES.MULTIPOINTM, 17 | GEOMETRY_TYPES.MULTIPOINTZM, 18 | GEOMETRY_TYPES.MULTIPOINT25D, 19 | ].includes(geometryType); 20 | } -------------------------------------------------------------------------------- /src/utils/isPolygonGeometryType.js: -------------------------------------------------------------------------------- 1 | import { GEOMETRY_TYPES } from 'g3w-constants'; 2 | 3 | /** 4 | * core/geometry/geometry::isPolygonGeometryType@v3.4 5 | * core/geometry/geometry::getAllPolygonGeometryTypes@v3.4 6 | */ 7 | export function isPolygonGeometryType(geometryType) { 8 | return [ 9 | GEOMETRY_TYPES.POLYGON, 10 | GEOMETRY_TYPES.POLYGONZ, 11 | GEOMETRY_TYPES.POLYGONM, 12 | GEOMETRY_TYPES.POLYGONZM, 13 | GEOMETRY_TYPES.POLYGON25D, 14 | GEOMETRY_TYPES.MULTIPOLYGON, 15 | GEOMETRY_TYPES.MULTIPOLYGONZ, 16 | GEOMETRY_TYPES.MULTIPOLYGONM, 17 | GEOMETRY_TYPES.MULTIPOLYGONZM, 18 | GEOMETRY_TYPES.MULTIPOLYGON25D, 19 | ].includes(geometryType); 20 | } -------------------------------------------------------------------------------- /src/utils/noop.js: -------------------------------------------------------------------------------- 1 | export function noop() {} -------------------------------------------------------------------------------- /src/utils/normalizeEpsg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param { number | string } epsg 3 | * 4 | * @returns { string | undefined } 5 | */ 6 | export function normalizeEpsg(epsg) { 7 | if ('number' === typeof epsg) { 8 | return `EPSG:${epsg}`; 9 | } 10 | epsg = epsg.replace(/[^\d\.\-]/g, ""); 11 | if ('' !== epsg) { 12 | return `EPSG:${parseInt(epsg)}`; 13 | } 14 | } -------------------------------------------------------------------------------- /src/utils/promisify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Migrate your consumer code away from jQuery promises. 3 | * Covert a jQuery promise into an ES6 Promise 4 | * 5 | * @param promise jquery promise 6 | */ 7 | export function promisify(promise) { 8 | if (promise instanceof Promise) { 9 | return promise; 10 | } 11 | if (!promise || !promise.then) { 12 | console.trace(promise); 13 | return Promise.reject('not a promise'); 14 | } 15 | return new Promise((resolve, reject) => { 16 | promise.then(resolve).fail(reject); 17 | }); 18 | } 19 | 20 | /** 21 | * Migrate your consumer code away from jQuery promises. 22 | * Convert ES6 Promise into jQuery 23 | * 24 | * @param promise async function or ES6 promise 25 | */ 26 | export function $promisify(promise) { 27 | if (undefined === promise) { 28 | console.trace(); 29 | return $.Deferred(d => d.reject('not a promise')).promise(); 30 | } 31 | if (promise.always) { 32 | return promise; 33 | } 34 | return $.Deferred(async d => { 35 | try { d.resolve(await (promise instanceof Promise ? promise : promise())); } 36 | catch (e) { console.trace(e); d.reject(e); } 37 | }).promise(); 38 | } -------------------------------------------------------------------------------- /src/utils/prompt.js: -------------------------------------------------------------------------------- 1 | import GUI from 'services/gui'; 2 | import { getUniqueDomId } from 'utils/getUniqueDomId'; 3 | 4 | /** 5 | * @TODO make it simpler (native HTML dialogs, Vue SFC components, ..) 6 | * 7 | * Similar to `window.prompt` 8 | * 9 | * @since 3.9.0 10 | */ 11 | export async function prompt({ 12 | value, 13 | label, 14 | callback, 15 | }) { 16 | 17 | // Reactive vue object (input instance) 18 | let data = { 19 | value, 20 | id: getUniqueDomId() 21 | }; 22 | 23 | let vueInput = new Vue({ 24 | template: /* html */ ` 25 |
26 | 27 | 34 |
`, 35 | data() { 36 | return data; 37 | }, 38 | }); 39 | 40 | let prompt; // store dialog modal window 41 | 42 | ( 43 | new Promise((resolve, reject) => { 44 | // modal window with input name 45 | prompt = GUI.showModalDialog({ 46 | message: vueInput.$mount().$el, 47 | closeButton: false, 48 | buttons: { 49 | ok: { label: 'Ok', className: 'btn-success', callback: () => resolve(data.value) }, 50 | cancel: { label: 'Cancel', className: 'btn-danger', callback: () => reject() }, 51 | }, 52 | }); 53 | // conditionally disable confirm button (based on input value) 54 | const okBtn = prompt.find('button.btn-success'); 55 | okBtn.prop('disabled', 0 === data.value.trim().length); 56 | vueInput.$watch('value', value => { okBtn.prop('disabled', 0 === value.trim().length) }); 57 | }) 58 | ) 59 | .then(callback) 60 | .catch(e => console.warn(e)) 61 | .finally(() => { 62 | vueInput.$destroy(); 63 | vueInput = null; 64 | data = null; 65 | prompt = null; 66 | }) 67 | } -------------------------------------------------------------------------------- /src/utils/removeZValue.js: -------------------------------------------------------------------------------- 1 | import { GEOMETRY_TYPES } from 'g3w-constants'; 2 | 3 | /** 4 | * Remove Z values from geometry coordinates 5 | */ 6 | export function removeZValue({ feature } = {}) { 7 | 8 | const geometry = feature.getGeometry(); 9 | 10 | // skip when feature has no geometry (alphanumerical feature) 11 | if (!geometry) { 12 | return feature; 13 | } 14 | 15 | const coords = geometry.getCoordinates(); 16 | 17 | switch (geometry.getType()) { 18 | 19 | // POINT: [x, y] 20 | case GEOMETRY_TYPES.POINT: 21 | coords.splice(2); 22 | geometry.setCoordinates(coords); 23 | break; 24 | 25 | // MULTIPOINT: [ [x1, y1], [x2, y2] ] 26 | case GEOMETRY_TYPES.MULTIPOINT: 27 | // LINE: [ [x1, y1], [x2, y2] ] 28 | case GEOMETRY_TYPES.LINESTRING: 29 | case GEOMETRY_TYPES.LINE: 30 | coords.forEach(c => c.splice(2)); 31 | geometry.setCoordinates(coords); 32 | break; 33 | 34 | // MULTILINE: [ 35 | // [ [x1, y1], [x2, y2] ], 36 | // [ [x3, y3], [x4, y4] ] 37 | // ] 38 | case GEOMETRY_TYPES.MULTILINESTRING: 39 | case GEOMETRY_TYPES.MULTILINE: 40 | coords.forEach(line => line.forEach(c => c.splice(2))); 41 | geometry.setCoordinates(coords); 42 | break; 43 | 44 | // POLYGON: [ 45 | // [ [x1, y1], [x2, y2], [x3, y3], [x1, y1] ] 46 | // ] 47 | case GEOMETRY_TYPES.POLYGON: 48 | coords[0].forEach(c => c.splice(2)); 49 | geometry.setCoordinates(coords); 50 | break; 51 | 52 | // MULTIPOLYGON: [ 53 | // [ [x1, y1], [x2, y2], [x3, y3], [x1, y1] ], 54 | // [ [xa, ya], [xb, yb], [xc, yc], [xa, ya] ] 55 | // ] 56 | case GEOMETRY_TYPES.MULTIPOLYGON: 57 | coords.forEach(poly => poly[0].forEach(c => c.splice(2))); 58 | geometry.setCoordinates(coords); 59 | break; 60 | 61 | default: 62 | console.warn('unsupported geometry type: ' + geometry.getType()); 63 | 64 | } 65 | 66 | return feature; 67 | } -------------------------------------------------------------------------------- /src/utils/resolve.js: -------------------------------------------------------------------------------- 1 | import { $promisify } from 'utils/promisify'; 2 | 3 | export function resolve(value) { 4 | return $promisify(Promise.resolve(value)); 5 | } -------------------------------------------------------------------------------- /src/utils/reverseGeometry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param geometry 3 | */ 4 | export function reverseGeometry(geometry) { 5 | geometry.setCoordinates(_reverseCoords(geometry.getCoordinates())); 6 | return geometry 7 | } 8 | 9 | function _reverseCoords(coords) { 10 | coords.find(c => { 11 | if (!Array.isArray(c)) { 12 | const [y, x] = coords; coords[0] = x; coords[1] = y; 13 | return true; 14 | } 15 | _reverseCoords(c); 16 | }); 17 | return coords; 18 | } -------------------------------------------------------------------------------- /src/utils/sameOrigin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} url1 3 | * @param {string} url2 4 | * 5 | * @returns {boolean} whether URLs have same origin. 6 | * 7 | * @since 3.8.0 8 | */ 9 | export function sameOrigin(url1, url2) { 10 | try { 11 | return new URL(url1).origin === new URL(url2).origin; 12 | } catch(e) { 13 | console.warn(e); 14 | return false; 15 | } 16 | } -------------------------------------------------------------------------------- /src/utils/sanitizeFidFeature.js: -------------------------------------------------------------------------------- 1 | export function sanitizeFidFeature(fid) { 2 | if ('string' === typeof fid && Number.isNaN(1*fid)) { 3 | fid = fid.split('.'); 4 | fid = fid.at(2 === fid.length ? 1 : 0); 5 | } 6 | return fid; 7 | } -------------------------------------------------------------------------------- /src/utils/saveBlob.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Save blob file to disk, same as: `Content-Disposition: attachment; filename="file name.jpg"` response header 3 | */ 4 | export function saveBlob(blob, filename = 'filename=g3w_file') { 5 | Object.assign(document.createElement('a'), { 6 | href: URL.createObjectURL(blob), 7 | download: filename.split('filename=').at(-1) 8 | }).click(); 9 | URL.revokeObjectURL(blob); 10 | } -------------------------------------------------------------------------------- /src/utils/throttle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * build throttle function 3 | */ 4 | export function throttle(fnc, delay = 500) { 5 | let lastCall; 6 | return function (...args) { 7 | let previousCall = lastCall; 8 | lastCall = Date.now(); 9 | if (previousCall === undefined // function is being called for the first time 10 | || (lastCall - previousCall) > delay) { // throttle time has elapsed 11 | fnc(...args); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/utils/toRawType.js: -------------------------------------------------------------------------------- 1 | export function toRawType(value) { 2 | return Object.prototype.toString.call(value).slice(8, -1) 3 | } -------------------------------------------------------------------------------- /src/utils/waitFor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Function to wait for predicates. 3 | * 4 | * @param { () => Boolean } predicate - A function that returns a bool 5 | * @param { number } [timeout] - Optional maximum waiting time in ms after rejected 6 | * 7 | * @see https://gist.github.com/chrisjhoughton/7890239?permalink_comment_id=4411125#gistcomment-4411125 8 | */ 9 | export function waitFor(predicate, timeout) { 10 | return new Promise((resolve, reject) => { 11 | const check = () => { 12 | if (!predicate()) { 13 | return 'invalid predicate'; 14 | } 15 | clearInterval(interval); 16 | resolve('predicate'); 17 | }; 18 | const interval = setInterval(check, 100); 19 | check(); 20 | if (timeout) { 21 | setTimeout(() => { clearInterval(interval); reject('timeout'); }, timeout); 22 | } 23 | }); 24 | } -------------------------------------------------------------------------------- /src/utils/within.js: -------------------------------------------------------------------------------- 1 | import 'jsts/dist/jsts.min.js'; 2 | 3 | /** 4 | * @param { ol.geometry } geometry 5 | * @param { ol.geometry } geometryToCheck 6 | * @returns { boolean } whether `geometry` contains `geometryToCheck` 7 | * 8 | * @since 3.8.0 9 | */ 10 | export function within(geometry, geometryToCheck) { 11 | const parser = new jsts.io.OL3Parser(); 12 | parser.inject( 13 | ol.geom.Point, 14 | ol.geom.LineString, 15 | ol.geom.LinearRing, 16 | ol.geom.Polygon, 17 | ol.geom.MultiPoint, 18 | ol.geom.MultiLineString, 19 | ol.geom.MultiPolygon, 20 | ); 21 | return parser.read(geometryToCheck).within(parser.read(geometry)) 22 | } --------------------------------------------------------------------------------