<template> <div v-if="isOnline" :class="['sidebar-container', { expanded }]" > <div class="layers-icon" @click="toggleSidebar()" > <!-- // ! svg point d'interrogation pas accepté par linter --> <!-- <?xml version="1.0" encoding="iso-8859-1"?> --> <svg id="Capa_1" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 491.203 491.203" style="enable-background: new 0 0 491.203 491.203" xml:space="preserve" > <g> <g> <!-- eslint-disable max-len --> <path d="M487.298,326.733l-62.304-37.128l62.304-37.128c2.421-1.443,3.904-4.054,3.904-6.872s-1.483-5.429-3.904-6.872 l-62.304-37.128l62.304-37.128c3.795-2.262,5.038-7.172,2.776-10.968c-0.68-1.142-1.635-2.096-2.776-2.776l-237.6-141.6 c-2.524-1.504-5.669-1.504-8.192,0l-237.6,141.6c-3.795,2.262-5.038,7.172-2.776,10.968c0.68,1.142,1.635,2.096,2.776,2.776 l62.304,37.128L3.905,238.733c-3.795,2.262-5.038,7.172-2.776,10.968c0.68,1.142,1.635,2.096,2.776,2.776l62.304,37.128 L3.905,326.733c-3.795,2.262-5.038,7.172-2.776,10.968c0.68,1.142,1.635,2.096,2.776,2.776l237.6,141.6 c2.526,1.494,5.666,1.494,8.192,0l237.6-141.6c3.795-2.262,5.038-7.172,2.776-10.968 C489.393,328.368,488.439,327.414,487.298,326.733z M23.625,157.605L245.601,25.317l221.976,132.288L245.601,289.893 L23.625,157.605z M23.625,245.605l58.208-34.68l159.672,95.2c2.524,1.504,5.668,1.504,8.192,0l159.672-95.2l58.208,34.68 L245.601,377.893L23.625,245.605z M245.601,465.893L23.625,333.605l58.208-34.68l159.672,95.2c2.524,1.504,5.668,1.504,8.192,0 l159.672-95.2l58.208,34.68L245.601,465.893z" /> <!--eslint-enable--> </g> </g> </svg> </div> <div class="basemaps-title"> <h4> Fonds cartographiques </h4> </div> <div 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> </div> </template> <script> import { mapState } from 'vuex'; import Sortable from 'sortablejs'; import Dropdown from '@/components/Dropdown.vue'; import mapService from '@/services/map-service'; export default { name: 'SidebarLayers', components: { Dropdown, }, data() { return { selectedQueryLayer: null, activeBasemap: null, baseMaps: [], expanded: false, sortable: null }; }, computed: { ...mapState([ 'isOnline', ]), ...mapState('map', [ 'availableLayers' ]), }, 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 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 areChanges = this.areChangesInBasemaps( this.baseMaps, baseMapsFromLocalstorage ); if (areChanges) { mapOptions[project] = { 'map-options': this.baseMaps, 'current-basemap-index': 0, }; localStorage.setItem( 'geocontrib-map-options', JSON.stringify(mapOptions) ); } else { this.baseMaps = baseMapsFromLocalstorage; } } if (this.baseMaps.length > 0) { this.baseMaps[0].active = true; this.activeBasemap = this.baseMaps[0]; this.addLayers(this.baseMaps[0]); } else { mapService.addLayers( null, this.$store.state.configuration.DEFAULT_BASE_MAP_SERVICE, this.$store.state.configuration.DEFAULT_BASE_MAP_OPTIONS, 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 areChangesInBasemaps(basemapFromServer, basemapFromLocalstorage = {}) { let isSameBasemaps = false; let isSameLayers = true; let isSameTitles = true; // Compare the length and the id values of the basemaps const idBasemapsServer = basemapFromServer.map((b) => b.id).sort(); const idBasemapsLocalstorage = basemapFromLocalstorage.length ? basemapFromLocalstorage.map((b) => b.id).sort() : {}; isSameBasemaps = idBasemapsServer.length === idBasemapsLocalstorage.length && idBasemapsServer.every( (value, index) => value === idBasemapsLocalstorage[index] ); // For each basemap, compare the length and id values of the layers outer_block: { for (const basemapServer of basemapFromServer) { const idLayersServer = basemapServer.layers.map((b) => b.id).sort(); if (basemapFromLocalstorage.length) { for (const basemapLocalstorage of basemapFromLocalstorage) { if (basemapServer.id === basemapLocalstorage.id) { const idLayersLocalstorage = basemapLocalstorage.layers .map((b) => b.id) .sort(); isSameLayers = idLayersServer.length === idLayersLocalstorage.length && idLayersServer.every( (value, index) => value === idLayersLocalstorage[index] ); if (!isSameLayers) { break outer_block; } } } } } // Compare basemaps titles const titlesBasemapsServer = basemapFromServer .map((b) => b.title) .sort(); const titlesBasemapsLocalstorage = basemapFromLocalstorage.length ? basemapFromLocalstorage.map((b) => b.title).sort() : {}; isSameTitles = titlesBasemapsServer.every( (title, index) => title === titlesBasemapsLocalstorage[index] ); if (!isSameTitles) { break outer_block; } } 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); layer = Object.assign(layer, layerOptions); layer.options.basemapId = baseMap.id; }); mapService.removeLayers(); // Reverse is done because the first layer in order has to be added in the map in last. // Slice is done because reverse() changes the original array, so we make a copy first mapService.addLayers(baseMap.layers.slice().reverse(), null, null, null,); }, }, }; </script> <style> .queryable-layers-dropdown { margin-bottom: 1em; } .queryable-layers-dropdown > label { font-weight: bold; } </style>