diff --git a/src/components/Map/LayerSelector.vue b/src/components/Map/LayerSelector.vue new file mode 100644 index 0000000000000000000000000000000000000000..967275a5e1d32604e57526b630f50c02fbffdca9 --- /dev/null +++ b/src/components/Map/LayerSelector.vue @@ -0,0 +1,176 @@ +<template> + <div class="basemaps-items ui accordion styled"> + <div + :class="['basemap-item title', { active }]" + @click="$emit('activateGroup', basemap.id)" + > + <i + class="map outline fitted icon" + aria-hidden="true" + /> + <span>{{ basemap.title }}</span> + </div> + <div + v-if="queryableLayersOptions.length > 0 && active" + :id="`queryable-layers-selector-${basemap.id}`" + > + <strong>Couche requêtable</strong> + <Dropdown + :options="queryableLayersOptions" + :selected="selectedQueryLayer" + :search="true" + @update:selection="onQueryLayerChange($event)" + /> + </div> + <div + :id="`list-${basemap.id}`" + :class="['content', { active }]" + :data-basemap-index="basemap.id" + > + <div + v-for="(layer, index) in basemap.layers" + :key="basemap.id + '-' + layer.id + '-' + index" + class="layer-item transition visible item list-group-item" + :data-id="layer.id" + > + <!-- layer id is used for retrieving layer when changing order --> + <p class="layer-handle-sort"> + <i + class="th icon" + aria-hidden="true" + /> + {{ layer.title }} + </p> + <label>Opacité <span>(%)</span></label> + <div class="range-container"> + <input + type="range" + min="0" + max="1" + :value="layer.opacity" + step="0.01" + @change="updateOpacity($event, layer)" + ><output class="range-output-bubble">{{ + getOpacity(layer.opacity) + }}</output> + </div> + <div class="ui divider" /> + </div> + </div> + </div> +</template> + +<script> +import Sortable from 'sortablejs'; + +import Dropdown from '@/components/Dropdown.vue'; +import mapService from '@/services/map-service'; + + +export default { + name: 'LayerSelector', + + components: { + Dropdown, + }, + + props: { + basemap: { + type: Object, + default: null, + }, + baseMaps: { + type: Array, + default: () => [], + }, + active: { + type: Boolean, + default: false, + }, + }, + + data() { + return { + baseMap: {}, + selectedQueryLayer: null, + sortable: null, + }; + }, + + computed: { + queryableLayersOptions() { + const queryableLayers = this.basemap.layers.filter((l) => l.queryable === true); + return queryableLayers.map((x) => { + return { + name: x.title, + value: x, + }; + }); + } + }, + + mounted() { + if (this.queryableLayersOptions.length > 0) { + this.selectedQueryLayer = this.queryableLayersOptions[0].name; + } + setTimeout(this.initSortable.bind(this), 1000); + }, + + methods: { + isQueryable(baseMap) { + const queryableLayer = baseMap.layers.filter((l) => l.queryable === true); + return queryableLayer.length > 0; + }, + + initSortable() { + const element = document.getElementById(`list-${this.basemap.id}`); + if (element) { + this.sortable = new Sortable(element, { + animation: 150, + handle: '.layer-handle-sort', // The element that is active to drag + ghostClass: 'blue-background-class', + dragClass: 'white-opacity-background-class', + onEnd: () => this.$emit('onlayerMove'), + }); + } else { + console.error(`list-${this.basemap.id} not found in dom`); + } + }, + + onQueryLayerChange(layer) { + this.selectedQueryLayer = layer.name; + const baseMap = this.baseMaps[0].layers.find((l) => l.title === layer.name); + if (baseMap) { + baseMap.query = true; + } else { + console.error('No such param \'query\' found among basemap[0].layers'); + } + layer.query = true; + }, + + getOpacity(opacity) { + return Math.round(parseFloat(opacity) * 100); + }, + + updateOpacity(event, layer) { + mapService.updateOpacity(layer.id, event.target.value); + layer.opacity = event.target.value; + }, + } +}; +</script> + +<style> +.basemap-item.title > i { + margin-left: -1em !important; +} +.basemap-item.title > span { + margin-left: .5em; +} +.queryable-layers-dropdown { + margin-bottom: 1em; +} +.queryable-layers-dropdown > label { + font-weight: bold; +} +</style> diff --git a/src/components/Map/SidebarLayers.vue b/src/components/Map/SidebarLayers.vue index c2faef528133167542f4fceb9e8b418b4c04f3bc..c70b60fe9b6c5435f7b5c7d42e1f2709fee34604 100644 --- a/src/components/Map/SidebarLayers.vue +++ b/src/components/Map/SidebarLayers.vue @@ -46,89 +46,37 @@ Fonds cartographiques </h4> </div> - - <div + <LayerSelector v-for="basemap in baseMaps" :key="`list-${basemap.id}`" - class="basemaps-items ui accordion styled" - > - <div - :class="['basemap-item title', { active: isActive(basemap) }]" - @click="activateGroup(basemap)" - > - {{ basemap.title }} - </div> - <div - v-if="isQueryable(basemap)" - :id="`queryable-layers-selector-${basemap.id}`" - > - <strong>Couche requêtable</strong> - <Dropdown - :options="getQueryableLayers(basemap)" - :selected="selectedQueryLayer" - :search="true" - @update:selection="onQueryLayerChange($event)" - /> - </div> - <div - :id="`list-${basemap.id}`" - :class="['content', { active: isActive(basemap) }]" - :data-basemap-index="basemap.id" - > - <div - v-for="(layer, index) in basemap.layers" - :key="basemap.id + '-' + layer.id + '-' + index" - class="layer-item transition visible item list-group-item" - :data-id="layer.id" - > - <!-- layer id is used for retrieving layer when changing order --> - <p class="layer-handle-sort"> - <i - class="th icon" - aria-hidden="true" - /> - {{ layer.title }} - </p> - <label>Opacité <span>(%)</span></label> - <div class="range-container"> - <input - type="range" - min="0" - max="1" - :value="layer.opacity" - step="0.01" - @change="updateOpacity($event, layer)" - ><output class="range-output-bubble">{{ - getOpacity(layer.opacity) - }}</output> - </div> - <div class="ui divider" /> - </div> - </div> - </div> + :basemap="basemap" + :base-maps="baseMaps" + :active="activeBasemap.id === basemap.id" + @addLayers="addLayers" + @activateGroup="activateGroup" + @onlayerMove="onlayerMove" + /> </div> </template> <script> import { mapState } from 'vuex'; -import Sortable from 'sortablejs'; -import Dropdown from '@/components/Dropdown.vue'; + +import LayerSelector from '@/components/Map/LayerSelector.vue'; import mapService from '@/services/map-service'; export default { name: 'SidebarLayers', components: { - Dropdown, + LayerSelector }, data() { return { - selectedQueryLayer: null, - activeBasemap: null, baseMaps: [], expanded: false, - sortable: null + projectSlug: this.$route.params.slug }; }, @@ -137,27 +85,29 @@ export default { 'isOnline', ]), ...mapState('map', [ - 'availableLayers' + 'availableLayers', ]), + activeBasemap() { + return this.baseMaps.find((baseMap) => baseMap.active); + }, }, mounted() { this.baseMaps = this.$store.state.map.basemaps; - const project = this.$route.params.slug; const mapOptions = JSON.parse(localStorage.getItem('geocontrib-map-options')) || {}; - if (mapOptions && mapOptions[project]) { + if (mapOptions && mapOptions[this.projectSlug]) { // If already in the storage, we need to check if the admin did some // modification in the basemaps on the server side. The rule is: if one layer has been added // or deleted in the server, then we reset the localstorage. - const baseMapsFromLocalstorage = mapOptions[project]['basemaps']; + const baseMapsFromLocalstorage = mapOptions[this.projectSlug]['basemaps']; const areChanges = this.areChangesInBasemaps( this.baseMaps, baseMapsFromLocalstorage ); if (areChanges) { - mapOptions[project] = { + mapOptions[this.projectSlug] = { 'map-options': this.baseMaps, 'current-basemap-index': 0, }; @@ -172,7 +122,6 @@ export default { if (this.baseMaps.length > 0) { this.baseMaps[0].active = true; - this.activeBasemap = this.baseMaps[0]; this.addLayers(this.baseMaps[0]); } else { mapService.addLayers( @@ -182,120 +131,13 @@ export default { this.$store.state.configuration.DEFAULT_BASE_MAP_SCHEMA_TYPE ); } - setTimeout(this.initSortable.bind(this), 1000); }, methods: { - isActive(basemap) { - return basemap.active !== undefined && basemap.active; - }, - toggleSidebar(value) { this.expanded = value !== undefined ? value : !this.expanded; }, - activateGroup(basemap) { - this.baseMaps.forEach((basemap) => (basemap.active = false)); - basemap.active = true; - this.activeBasemap = basemap; - basemap.title += ' '; //weird!! Force refresh - this.addLayers(basemap); - - let mapOptions = localStorage.getItem('geocontrib-map-options') || {}; - mapOptions = mapOptions.length ? JSON.parse(mapOptions) : {}; - const project = this.$route.params.slug; - mapOptions[project] = { - ...mapOptions[project], - 'current-basemap-index': basemap.id, - }; - localStorage.setItem( - 'geocontrib-map-options', - JSON.stringify(mapOptions) - ); - }, - - updateOpacity(event, layer) { - mapService.updateOpacity(layer.id, event.target.value); - layer.opacity = event.target.value; - }, - - getOpacity(opacity) { - return Math.round(parseFloat(opacity) * 100); - }, - - onQueryLayerChange(layer) { - this.selectedQueryLayer = layer.name; - if (this.baseMaps[0].layers.find((l) => l.title === layer.name)) { - this.baseMaps[0].layers.find((l) => l.title === layer.name).query = true; - } else { - console.error('No such param \'query\' found among basemap[0].layers'); - } - layer.query = true; - }, - - isQueryable(baseMap) { - const queryableLayer = baseMap.layers.filter((l) => l.queryable === true); - return queryableLayer.length > 0; - }, - - onlayerMove() { - // Get the names of the current layers in order. - const currentLayersNamesInOrder = Array.from( - document.getElementsByClassName('layer-item transition visible') - ).map((el) => parseInt(el.attributes['data-id'].value)); - // Create an array to put the layers in order. - let movedLayers = []; - - for (const layerName of currentLayersNamesInOrder) { - movedLayers.push( - this.activeBasemap.layers.filter((el) => el.id === layerName)[0] - ); - } - // Remove existing layers undefined - movedLayers = movedLayers.filter(function (x) { - return x !== undefined; - }); - const eventOrder = new CustomEvent('change-layers-order', { - detail: { - layers: movedLayers, - }, - }); - document.dispatchEvent(eventOrder); - // Save the basemaps options into the localstorage - this.setLocalstorageMapOptions(this.baseMaps); - }, - - setLocalstorageMapOptions(basemaps) { - let mapOptions = localStorage.getItem('geocontrib-map-options') || {}; - mapOptions = mapOptions.length ? JSON.parse(mapOptions) : {}; - const project = this.$route.params.slug; - mapOptions[project] = { - ...mapOptions[project], - basemaps: basemaps, - }; - localStorage.setItem( - 'geocontrib-map-options', - JSON.stringify(mapOptions) - ); - }, - - initSortable() { - this.baseMaps.forEach((basemap) => { - const element = document.getElementById(`list-${basemap.id}`); - if (element) { - this. sortable = new Sortable(element, { - animation: 150, - handle: '.layer-handle-sort', // The element that is active to drag - ghostClass: 'blue-background-class', - dragClass: 'white-opacity-background-class', - onEnd: this.onlayerMove.bind(this), - }); - } else { - console.error(`list-${basemap.id} not found in dom`); - } - - }); - }, // Check if there are changes in the basemaps settings. Changes are detected if: // - one basemap has been added or deleted // - one layer has been added or deleted to a basemap @@ -354,16 +196,6 @@ export default { return !(isSameBasemaps && isSameLayers && isSameTitles); }, - getQueryableLayers(baseMap) { - const queryableLayer = baseMap.layers.filter((l) => l.queryable === true); - return queryableLayer.map((x) => { - return { - name: x.title, - value: x, - }; - }); - }, - addLayers(baseMap) { baseMap.layers.forEach((layer) => { const layerOptions = this.availableLayers.find((l) => l.id === layer.id); @@ -375,15 +207,57 @@ export default { // Slice is done because reverse() changes the original array, so we make a copy first mapService.addLayers(baseMap.layers.slice().reverse(), null, null, null,); }, + + activateGroup(basemapId) { + //* to activate a basemap, we need to set the active property to true et set the others to false + this.baseMaps = this.baseMaps.map((bm) => { + return { ...bm, active: bm.id === basemapId ? true : false }; + }); + //* add the basemap that was clicked to the map + this.addLayers(this.baseMaps.find((bm) => bm.id === basemapId)); + //* to persist the settings, we set the localStorage mapOptions per project + this.setLocalstorageMapOptions({ 'current-basemap-index': basemapId }); + }, + + onlayerMove() { + // Get the names of the current layers in order. + const currentLayersNamesInOrder = Array.from( + document.getElementsByClassName('layer-item transition visible') + ).map((el) => parseInt(el.attributes['data-id'].value)); + // Create an array to put the layers in order. + let movedLayers = []; + + for (const layerName of currentLayersNamesInOrder) { + movedLayers.push( + this.activeBasemap.layers.find((el) => el.id === layerName) + ); + } + // Remove existing layers undefined + movedLayers = movedLayers.filter(function (x) { + return x !== undefined; + }); + const eventOrder = new CustomEvent('change-layers-order', { + detail: { + layers: movedLayers, + }, + }); + document.dispatchEvent(eventOrder); + // Save the basemaps options into the localstorage + this.setLocalstorageMapOptions({ basemaps: this.baseMaps }); + }, + + setLocalstorageMapOptions(newOptionObj) { + let mapOptions = localStorage.getItem('geocontrib-map-options') || {}; + mapOptions = mapOptions.length ? JSON.parse(mapOptions) : {}; + mapOptions[this.projectSlug] = { + ...mapOptions[this.projectSlug], + ...newOptionObj, + }; + localStorage.setItem( + 'geocontrib-map-options', + JSON.stringify(mapOptions) + ); + }, }, }; </script> - -<style> -.queryable-layers-dropdown { - margin-bottom: 1em; -} -.queryable-layers-dropdown > label { - font-weight: bold; -} -</style>