<template> <div :class="['sidebar-container', { expanded }]"> <!-- <div class="sidebar-layers"></div> --> <div @click="expanded = !expanded" class="layers-icon"> <!-- // ! svg point d'interrogation pas accepté par linter --> <!-- <?xml version="1.0" encoding="iso-8859-1"?> --> <svg version="1.1" id="Capa_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> <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" /> </g> </g> </svg> </div> <div class="basemaps-title"> <h4> Fonds cartographiques <!-- <span data-tooltip="Il est possible pour chaque fond cartographique de modifier l'ordre des couches" data-position="bottom left"> <i class="question circle outline icon"></i> </span> --> </h4> </div> <div v-for="basemap in baseMaps" :key="`list-${basemap.id}`" class="basemaps-items ui accordion styled" > <div :class="{ active: isActive(basemap) }" class="basemap-item title" @click="activateGroup(basemap)" > {{ basemap.title }} </div> <div :id="`queryable-layers-selector-${basemap.id}`" v-if="isQueryable(basemap)"> <b>Couche requêtable</b> <Dropdown @update:selection="onQueryLayerChange($event)" :options="getQueryableLayers(basemap)" :selected="selectedQueryLayer" :search="true" /> </div> <div :class="{ active: isActive(basemap) }" class="content" :id="`list-${basemap.id}`" :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" > <p class="layer-handle-sort"> <i class="th icon"></i>{{ layer.title }} </p> <label>Opacité <span>(%)</span></label> <div class="range-container"> <!-- // todo : rendre réactif les valeurs et connectés avec store/Map --> <input @change="updateOpacity($event, layer)" type="range" min="0" max="1" :value="layer.opacity" step="0.01" /><output class="range-output-bubble">{{ getOpacity(layer.opacity) }}</output> </div> <div class="ui divider"></div> </div> </div> </div> </div> </template> <script> import { mapUtil } from "@/assets/js/map-util.js"; import Dropdown from "@/components/Dropdown.vue"; import Sortable from 'sortablejs'; export default { name: "SidebarLayers", components: { Dropdown, }, data() { return { selectedQueryLayer:null, activeBasemap:null, baseMaps: [], layers: [], expanded: false, }; }, methods: { isActive(basemap) { return basemap.active !== undefined && basemap.active; }, 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) : {}; let 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) { console.log(event.target.value, layer); mapUtil.updateOpacity(layer.id, event.target.value); layer.opacity = event.target.value; }, getOpacity(opacity) { return Math.round(parseFloat(opacity) * 100); }, onQueryLayerChange(layer){ console.log(layer); this.selectedQueryLayer=layer.name; }, isQueryable(baseMap){ let queryableLayer=baseMap.layers.filter(l => l.queryable === true); return queryableLayer.length>0; }, onlayerMove(event){ console.log(event) // Get the names of the current layers in order. const currentLayersNamesInOrder = Array.from(document.getElementsByClassName('layer-item transition visible')).map(el => el.children[0].innerText); // Create an array to put the layers in order. let movedLayers = []; for (const layerName of currentLayersNamesInOrder) { movedLayers.push(this.activeBasemap.layers.filter(el => el.title === 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 console.log(this.baseMaps) this.setLocalstorageMapOptions(this.baseMaps) }, setLocalstorageMapOptions(basemaps) { let mapOptions = localStorage.getItem('geocontrib-map-options') || {}; mapOptions = mapOptions.length ? JSON.parse(mapOptions) : {}; let project=this.$route.params.slug; mapOptions[project] = { ...mapOptions[project], 'basemaps': basemaps }; localStorage.setItem('geocontrib-map-options', JSON.stringify(mapOptions)); }, initSortable(){ this.baseMaps.forEach((basemap) => { new Sortable(document.getElementById(`list-${basemap.id}`), { 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) }); }); }, // 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(let basemapServer of basemapFromServer) { let idLayersServer = basemapServer.layers.map(b => b.id).sort(); if (basemapFromLocalstorage.length){ for (let basemapLocalstorage of basemapFromLocalstorage) { if (basemapServer.id === basemapLocalstorage.id) { let 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){ let queryableLayer=baseMap.layers.filter(l => l.queryable === true); return queryableLayer.map(x=>{ return { name:x.title, value:x } }); }, addLayers(baseMap) { baseMap.layers.forEach((layer) => { var layerOptions = this.layers.find((l) => l.id === layer.id); console.log(layerOptions); layer = Object.assign(layer, layerOptions); layer.options.basemapId = baseMap.id; }); mapUtil.removeLayers(mapUtil.getMap()); // 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 mapUtil.addLayers(baseMap.layers.slice().reverse(), null, null); }, }, mounted() { this.baseMaps = this.$store.state.map.basemaps; let 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; } } this.layers = this.$store.state.map.layers; if (this.baseMaps.length > 0) { this.baseMaps[0].active = true; this.activeBasemap=this.baseMaps[0]; this.addLayers(this.baseMaps[0]); } else { mapUtil.addLayers( null, this.$store.state.configuration.DEFAULT_BASE_MAP.SERVICE, this.$store.state.configuration.DEFAULT_BASE_MAP.OPTIONS ); } setTimeout(this.initSortable.bind(this),1000) }, }; </script> <style> @import "../../assets/styles/sidebar-layers.css"; .queryable-layers-dropdown { margin-bottom: 1em; } .queryable-layers-dropdown > label { font-weight: bold; } </style>