Skip to content

Commit

Permalink
Allow to store custom map themes (authenticathed users) (#540)
Browse files Browse the repository at this point in the history
* Clean code

* Clean code

* Add layerstree prop to CatalogChangeMapThemes.vue component

* Add save section to CatalogChangeMapThemes.vue

* Create constant with node and group attributes to send to server

* Clean code

* Clean code

* Clean code

* Clean code

* Add i18nLabel in input state object to translate label of icon changing language

* Add input label translation of new map theme

* Remove padding of input text component

* Start dev replication of user map theme (DEV)

* Add name and expandedattribute for save map theme of user

* Clean code

* On save send layerstree and style to server. TO DO

* 🌐 Add translation

* Clean code - Readability

* DEV - print params to send to server api

* 🌐 Add map theme save, delete and question. Need to translate se, de, fi, ro, fr.

* ✨ Add delete XHR method

* ✨ Add saveTheme and deleteTheme. Consider map_themes as object contain project and custom attribute

* ✨ Take in account map_theme object structure

* 💄  less shadow

* 💄  add calss to align sidebar button

* Remove unuseful canSave attribute. Use valid intead of input

* Add cursor: pointer to sidebar-button class

* 💄 change style

* 💄 change style

* jsdoc

* invalid jsdoc: `@return` → `@returns`

* decrease box-shadow: `.editbtn`

* remove unusued method: `mounted`

* code indentation

* 💄 reduce padding

* i18n

* refactor `CatalogChangeMapThemes.vue`

* inline foor loop

* add `v-t-tooltip`

* Add categories to save parameters and onec save custom map theme set activated

* Change name more concise

* In case of anonymous user, custom map theme section is hide and user can't save custom map theme

* ✨ Update custom map theme

* 🐛 Fix show map theme cutom #540 (comment)

* `save` → `saveTheme`

* avoid caching `config.layerstree`

* ♻️ store update layerstree custom map theme to avoit to do a new server request

* 🎨 Readibility

* ✨ Add error message validato for name map theme and translations

* ✨ Add new check validator type (bigint) and fix change language validator error message

* ✨ Add chart and varchar validator type

* ✨ Change i18n errors message translation on change language

* 🎨 Clean code. Readibility

* `chart` → `char`

* wrong variable: `this.state.atlas_values` → `this.atlas_values`

* re-initialize select2 fields after changing template

* 🐛 Fix atlas template. Disable print button if no value is provided.

Stop loading afet download print layout from server

* 🐛 Fix base layer custom icon missing

---------

Co-authored-by: Raruto <[email protected]>
  • Loading branch information
volterra79 and Raruto authored Apr 29, 2024
1 parent e2b0244 commit b2f7024
Show file tree
Hide file tree
Showing 28 changed files with 862 additions and 222 deletions.
6 changes: 4 additions & 2 deletions src/app/core/layers/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,14 @@ function Layer(config={}, options={}) {
*/
stylesfeaturecount: config.featurecount && defaultstyle && {
[defaultstyle]: config.featurecount
}
},
name: config.name, /** since 3.10.0 **/
expanded: config.expanded, /** since 3.10.0 **/

};

/**
* Store all selection features `fids`
* Store all selections feature `fids`
*/
this.selectionFids = new Set();

Expand Down
19 changes: 11 additions & 8 deletions src/app/core/layers/layersstore.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ proto.getLayersTree = function() {
proto.setLayersTree = function(layerstree=[], name, expanded=true) {
const [minx, miny, maxx, maxy] = this.getInitExtent();

// Root group project that contain all layerstree of qgis project
// Root group project that contains all layerstree of qgis project
const rootGroup = {
title: name || this.config.id,
root: true,
Expand All @@ -264,7 +264,8 @@ proto.setLayersTree = function(layerstree=[], name, expanded=true) {
* Used by external plugins to build layerstree
*
* @param {string} groupName is a ProjectName
* @param {unknown} [options.layerstree = null ]
* @param options
* @param {Object} [options.layerstree = null ]
* @param {boolean} [options.expanded = false]
* @param {boolean} [options.full = false]
*/
Expand Down Expand Up @@ -314,20 +315,22 @@ proto._traverseLightLayersTree = function(nodes, layerstree, tocLayersId) {
let lightlayer = null;

// case TOC has layer ID
if (null !== node.id && "undefined" !== typeof node.id && tocLayersId.find(id => id === node.id)) {
if (null !== node.id && undefined !== node.id && tocLayersId.find(id => id === node.id)) {
lightlayer = ({ ...lightlayer, ...node });
}

// case group
if (null !== node.nodes && "undefined" !== typeof node.nodes) {
if (null !== node.nodes && undefined !== node.nodes) {
lightlayer = ({
...lightlayer,
name: node.name, /** @since 3.10.0 **/
title: node.name,
groupId: uniqueId(),
root: false,
nodes: [],
checked: node.checked,
mutually_exclusive: node["mutually-exclusive"]
mutually_exclusive: node["mutually-exclusive"],
'mutually-exclusive': node["mutually-exclusive"], /** @since 3.10.0 */
});
this._traverseLightLayersTree(node.nodes, lightlayer.nodes, tocLayersId); // recursion step
}
Expand All @@ -346,14 +349,14 @@ proto._traverseLightLayersTree = function(nodes, layerstree, tocLayersId) {
proto._traverseLayersTree = function(nodes, parentGroup) {
nodes.forEach((node, index) => {
// substitute node layer with layer state
if ("undefined" !== typeof node.id) {
if (undefined !== node.id) {
nodes[index] = this.getLayerById(node.id).getState();
}
// case of layer substitute node with layer state
if ("undefined" !== typeof node.id) {
if (undefined !== node.id) {
nodes[index] = this.getLayerById(node.id).getState();
// pass bbox and epsg of layer
if ("undefined" !== typeof nodes[index].bbox) {
if (undefined !== nodes[index].bbox) {
this._setLayersTreeGroupBBox(parentGroup, { bbox: nodes[index].bbox, epsg: nodes[index].epsg });
}
}
Expand Down
92 changes: 76 additions & 16 deletions src/app/core/project/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const Projections = require('g3w-ol/projection/projections');
* @param config.query_point_tolerance
* @param config.wps array of wps service
* @param config.bookmarks array of bookmarks
* @param config.map_themes Object {project:[], custom:[]}
* @param { 'POST' | 'GET' } config.ows_method
* @param { boolean } config.wms_use_layer_ids
* @param { 'tab' | 'toc' } config.legend_position
Expand Down Expand Up @@ -160,22 +161,45 @@ proto.getQueryFeatureCount = function() {
return this.state.feature_count || 5;
};

/**
* @param mapcontrol
*
* @returns { boolean }
*/
proto.isQueryMultiLayers = function(mapcontrol) {
return this.state.querymultilayers && -1 !== this.state.querymultilayers.indexOf(mapcontrol);
};

/**
* @returns {*}
*/
proto.getRelations = function() {
return this.state.relations;
};

/**
* @param relationId
*
* @returns {*}
*/
proto.getRelationById = function(relationId) {
return this.state.relations.find(relation => relation.id === relationId);
};

/**
* @param { Object } opts
* @param opts.layerId
* @param opts.type
*
* @returns {*}
*/
proto.getRelationsByLayerId = function({layerId, type}={}) {
return this.state.relations.filter(relation => relation.referencedLayer === layerId && (type ? relation.type === type : true))
};

/**
* @returns {"POST"|"GET"}
*/
proto.getOwsMethod = function() {
return this.state.ows_method;
};
Expand Down Expand Up @@ -284,6 +308,7 @@ proto.getBaseLayers = function() {
* Get configuration layers an array from server config
*
* @param filter property layer config to filter
*
* @returns {*}
*/
proto.getConfigLayers = function({ key } = {}) {
Expand Down Expand Up @@ -366,18 +391,30 @@ proto.getQgisVersion = function({ type } = {}) {
return -1 === index ? this.state.qgis_version : +this.state.qgis_version.split('.')[index];
};

/**
* @returns {*}
*/
proto.getProjection = function() {
return this._projection;
};

/**
* @returns {*}
*/
proto.getWmsUrl = function() {
return this.state.WMSUrl;
};

/**
* @returns {string}
*/
proto.getInfoFormat = function() {
return 'application/vnd.ogc.gml';
};

/**
* @returns {*}
*/
proto.getLayersStore = function() {
return this._layersStore;
};
Expand All @@ -394,18 +431,13 @@ proto.setLayersTreePropertiesFromMapTheme = async function({
map_theme,
layerstree = this.state.layerstree
}) {
/**
* mapThemeConfig contain map_theme attributes coming from project map_themes attribute config
* plus layerstree of map_theme get from api map theme
*/
const mapThemeConfig = await this.getMapThemeFromThemeName(map_theme);
// extract layerstree
const { layerstree:mapThemeLayersTree } = mapThemeConfig;
/** map theme config */
const theme = await this.getMapThemeFromThemeName(map_theme);
// create a chages need to apply map_theme changes to map and TOC
const changes = {layers: {} }; // key is the layer id and object has style, visibility change (Boolean)
const promises = [];
/**
* Function to traverse current layerstree of toc anche get changes with the new one related to map_theme choose
* Traverse current layerstree of TOC and get changes with the new one related to map_theme choose
* @param mapThemeLayersTree // new mapLayerTree
* @param layerstree // current layerstree
*/
Expand All @@ -421,7 +453,7 @@ proto.setLayersTreePropertiesFromMapTheme = async function({
traverse(node.nodes, layerstree[index].nodes, checked && node.checked);
} else {
// case of layer
node.style = mapThemeConfig.styles[node.id]; // set style from map_theme
node.style = theme.styles[node.id]; // set style from map_theme
if (layerstree[index].checked !== node.visible) {
changes.layers[node.id] = {
visibility: true,
Expand All @@ -448,27 +480,55 @@ proto.setLayersTreePropertiesFromMapTheme = async function({
}
});
};
traverse(mapThemeLayersTree, layerstree);
traverse(theme.layerstree, layerstree);

await Promise.allSettled(promises);

// all groups checked after layer checked so is set checked but not visible
groups.forEach(({ group, node: { checked, expanded }}) => {
group.checked = checked;
group.expanded = expanded;
});

return changes // eventually, information about changes (for example style etc..)
};

/**
* get map Theme_configuration
*/
proto.getMapThemeFromThemeName = async function(map_theme) {
proto.getMapThemeFromThemeName = async function(theme) {
// get map theme configuration from map_themes project config
const mapThemeConfig = this.state.map_themes.find(map_theme_config => map_theme_config.theme === map_theme);
// check if mapThemeConfig exist and if it has layerstree (property gets from server with a specific api)
if (mapThemeConfig && undefined === mapThemeConfig.layerstree ) {
mapThemeConfig.layerstree = await this.getMapThemeConfiguration(map_theme);
const config = Object.values(this.state.map_themes).flat().find(c => theme === c.theme );
if (config && undefined === config.layerstree) {
config.layerstree = await this.getMapThemeConfiguration(theme);
}
return mapThemeConfig;
return config;
};

/**
* Save custom user map theme
*
* @since 3.10
*/
proto.saveMapTheme = function(theme, params = {}) {
//In case of no name provide skip
if (!theme) { return Promise.reject() }
return XHR.post({
url: `${this.urls.map_themes}${encodeURIComponent(theme)}/`,
contentType: 'application/json',
data: JSON.stringify(params),
});
};

/**
* @param theme
*
* @since 3.10.0
*/
proto.deleteMapTheme = async function(theme) {
//In case of no name provide skip
if (!theme) { return Promise.reject() }
return XHR.delete({url:`${this.urls.map_themes}${encodeURIComponent(theme)}/`});
};

/**
Expand Down
29 changes: 22 additions & 7 deletions src/app/gui/inputs/input.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ApplicationState from 'store/application-state';
import BaseInputComponent from 'components/InputBase.vue'
import ApplicationState from 'store/application-state';
import BaseInputComponent from 'components/InputBase.vue'
import { baseInputMixin as BaseInputMixin } from 'mixins';

const InputServices = require('./services');

const Input = {
Expand All @@ -10,11 +11,11 @@ const Input = {
'baseinput': BaseInputComponent
},
watch: {
'notvalid'(notvalid){
notvalid && this.service.setErrorMessage(this.state)
'notvalid'(notvalid) {
notvalid && this.service.setErrorMessage()
},
'state.value'(){
if ("undefined" !== typeof this.state.input.options.default_expression) {
if (undefined !== this.state.input.options.default_expression) {
// need to postpone state.value watch parent that use mixin
setTimeout(() => this.change());
}
Expand All @@ -24,8 +25,22 @@ const Input = {
this.service = new InputServices[this.state.input.type]({
state: this.state,
});
this.$watch(() => ApplicationState.language, () => this.service.setErrorMessage(this.state));
this.state.editable && this.state.validate.required && this.service.validate();

this.$watch(
() => ApplicationState.language,
async () => {
if (this.state.visible) {
this.state.visible = false;
this.service.setErrorMessage();
await this.$nextTick();
this.state.visible = true;
}
});

if (this.state.editable && this.state.validate.required) {
this.service.validate();
}

this.$emit('addinput', this.state);
/**
* in case of input value is fill with default value option we need to emit changeinput event
Expand Down
Loading

0 comments on commit b2f7024

Please sign in to comment.