<template> <div> <div v-if="permissions && permissions.can_view_project && project" id="project-detail" > <div id="message" class="fullwidth" > <div v-if="tempMessage" class="ui positive message" > <p> <i class="check icon" aria-hidden="true" /> {{ tempMessage }} </p> </div> </div> <ProjectHeader :arrays-offline="arraysOffline" @retrieveInfo="retrieveProjectInfo" @updateLocalStorage="updateLocalStorage" /> <div class="ui grid stackable"> <div class="row"> <div class="eight wide column"> <ProjectFeatureTypes :loading="projectInfoLoading" :project="project" @delete="toggleDeleteFeatureTypeModal" /> </div> <div class="eight wide column block-map"> <div class="map-container"> <div id="map" ref="map" /> <div :class="{ active: mapLoading }" class="ui inverted dimmer" > <div class="ui text loader"> Chargement de la carte... </div> </div> <SidebarLayers v-if="basemaps && map" ref="sidebar" /> <div id="popup" class="ol-popup" > <a id="popup-closer" href="#" class="ol-popup-closer" /> <div id="popup-content" /> </div> </div> <router-link id="features-list" :to="{ name: 'liste-signalements', params: { slug: slug }, }" custom > <div class="ui button fluid teal" > <i class="ui icon arrow right" /> Voir tous les signalements </div> </router-link> </div> </div> <div class="row"> <div class="sixteen wide column"> <div class="ui two stackable cards"> <ProjectLastFeatures :loading="featuresLoading" /> <ProjectLastComments :loading="projectInfoLoading" /> </div> </div> </div> <div class="row"> <div class="sixteen wide column"> <ProjectParameters :project="project" /> </div> </div> </div> </div> <span v-else-if="!projectInfoLoading"> <i class="icon exclamation triangle" aria-hidden="true" /> <span>Vous ne disposez pas des droits nécessaires pour consulter ce projet.</span> </span> <ProjectModal :is-subscriber="is_suscriber" :feature-type-to-delete="featureTypeToDelete" @action="handleModalAction" /> </div> </template> <script> import mapService from '@/services/map-service'; import { mapGetters, mapState, mapActions, mapMutations } from 'vuex'; import projectAPI from '@/services/project-api'; import featureTypeAPI from '@/services/featureType-api'; import featureAPI from '@/services/feature-api'; import ProjectHeader from '@/components/Project/Detail/ProjectHeader'; import ProjectFeatureTypes from '@/components/Project/Detail/ProjectFeatureTypes'; import ProjectLastFeatures from '@/components/Project/Detail/ProjectLastFeatures'; import ProjectLastComments from '@/components/Project/Detail/ProjectLastComments'; import ProjectParameters from '@/components/Project/Detail/ProjectParameters'; import ProjectModal from '@/components/Project/Detail/ProjectModal'; import SidebarLayers from '@/components/Map/SidebarLayers'; export default { name: 'ProjectDetail', components: { ProjectHeader, ProjectFeatureTypes, ProjectLastFeatures, ProjectLastComments, ProjectParameters, ProjectModal, SidebarLayers, }, filters: { setDate(value) { const date = new Date(value); const d = date.toLocaleDateString('fr', { year: '2-digit', month: 'numeric', day: 'numeric', }); return d; }, }, props: { message: { type: String, default: '' } }, data() { return { infoMessage: '', importMessage: null, arraysOffline: [], arraysOfflineErrors: [], slug: this.$route.params.slug, is_suscriber: false, tempMessage: null, projectInfoLoading: true, featureTypeToDelete: null, featuresLoading: true, mapLoading: true, }; }, computed: { ...mapGetters([ 'permissions' ]), ...mapState('projects', [ 'project' ]), ...mapState([ 'configuration', ]), ...mapState('feature', [ 'features' ]), ...mapState('feature-type', [ 'feature_types' ]), ...mapState([ 'last_comments', 'user', 'user_permissions', 'reloadIntervalId', ]), ...mapState('map', [ 'map', 'basemaps', 'availableLayers', ]), API_BASE_URL() { return this.configuration.VUE_APP_DJANGO_API_BASE; }, }, created() { if (this.user) { projectAPI .getProjectSubscription({ baseUrl: this.$store.state.configuration.VUE_APP_DJANGO_API_BASE, projectSlug: this.$route.params.slug }) .then((data) => (this.is_suscriber = data.is_suscriber)); } this.$store.commit('feature/SET_FEATURES', []); //* empty features remaining in case they were in geojson format and will be fetch after map initialization anyway this.$store.commit('feature-type/SET_FEATURE_TYPES', []); //* empty feature_types remaining from previous project }, mounted() { this.retrieveProjectInfo(); if (this.message) { this.tempMessage = this.message; document .getElementById('message') .scrollIntoView({ block: 'end', inline: 'nearest' }); setTimeout(() => (this.tempMessage = null), 5000); //* hide message after 5 seconds } }, beforeDestroy() { this.$store.dispatch('CANCEL_CURRENT_SEARCH_REQUEST'); this.CLEAR_RELOAD_INTERVAL_ID(); this.CLOSE_PROJECT_MODAL(); }, methods: { ...mapMutations([ 'CLEAR_RELOAD_INTERVAL_ID', 'DISPLAY_MESSAGE', 'DISPLAY_LOADER', 'DISCARD_LOADER', ]), ...mapMutations('modals', [ 'OPEN_PROJECT_MODAL', 'CLOSE_PROJECT_MODAL' ]), ...mapActions('projects', [ 'GET_PROJECT_INFO', 'GET_PROJECT', ]), ...mapActions('map', [ 'INITIATE_MAP' ]), ...mapActions('feature', [ 'GET_PROJECT_FEATURES' ]), ...mapActions('feature-type', [ 'GET_IMPORTS' ]), retrieveProjectInfo() { this.DISPLAY_LOADER('Projet en cours de chargement.'); Promise.all([ this.GET_PROJECT(this.slug), this.GET_PROJECT_INFO(this.slug) ]) .then(() => { this.DISCARD_LOADER(); this.projectInfoLoading = false; this.$nextTick(() => { let map = mapService.getMap(); if (map) mapService.destroyMap(); this.initMap(); }); }) .catch((err) => { console.error(err); this.DISCARD_LOADER(); this.projectInfoLoading = false; }); }, checkForOfflineFeature() { let arraysOffline = []; const localStorageArray = localStorage.getItem('geocontrib_offline'); if (localStorageArray) { arraysOffline = JSON.parse(localStorageArray); this.arraysOffline = arraysOffline.filter( (x) => x.project === this.slug ); } }, updateLocalStorage() { let arraysOffline = []; const localStorageArray = localStorage.getItem('geocontrib_offline'); if (localStorageArray) { arraysOffline = JSON.parse(localStorageArray); } const arraysOfflineOtherProject = arraysOffline.filter( (x) => x.project !== this.slug ); this.arraysOffline = []; arraysOffline = arraysOfflineOtherProject.concat( this.arraysOfflineErrors ); localStorage.setItem('geocontrib_offline', JSON.stringify(arraysOffline)); }, subscribeProject() { projectAPI .subscribeProject({ baseUrl: this.$store.state.configuration.VUE_APP_DJANGO_API_BASE, suscribe: !this.is_suscriber, projectSlug: this.$route.params.slug, }) .then((data) => { this.is_suscriber = data.is_suscriber; this.CLOSE_PROJECT_MODAL(); if (this.is_suscriber) { this.DISPLAY_MESSAGE({ comment: 'Vous êtes maintenant abonné aux notifications de ce projet.', level: 'positive' }); } else { this.DISPLAY_MESSAGE({ comment: 'Vous ne recevrez plus les notifications de ce projet.', level: 'negative' }); } }); }, deleteProject() { projectAPI.deleteProject(this.API_BASE_URL, this.slug) .then((response) => { if (response === 'success') { this.$router.push('/'); this.DISPLAY_MESSAGE({ comment: `Le projet ${this.project.title} a bien été supprimé.`, level: 'positive' }); } else { this.DISPLAY_MESSAGE({ comment: `Une erreur est survenu lors de la suppression du projet ${this.project.title}.`, level: 'negative' }); } }); }, deleteFeatureType() { featureTypeAPI.deleteFeatureType(this.featureTypeToDelete.slug) .then((response) => { this.CLOSE_PROJECT_MODAL(); if (response === 'success') { this.GET_PROJECT(this.slug); this.retrieveProjectInfo(); this.DISPLAY_MESSAGE({ comment: `Le type de signalement ${this.featureTypeToDelete.title} a bien été supprimé.`, level: 'positive', }); } else { this.DISPLAY_MESSAGE({ comment: `Une erreur est survenu lors de la suppression du type de signalement ${this.featureTypeToDelete.title}.`, level: 'negative', }); } this.featureTypeToDelete = null; }) .catch(() => { this.DISPLAY_MESSAGE({ comment: `Une erreur est survenu lors de la suppression du type de signalement ${this.featureTypeToDelete.title}.`, level: 'negative', }); this.CLOSE_PROJECT_MODAL(); }); }, handleModalAction(e) { switch (e) { case 'subscribe': this.subscribeProject(); break; case 'deleteProject': this.deleteProject(); break; case 'deleteFeatureType': this.deleteFeatureType(); break; } }, toggleDeleteFeatureTypeModal(featureType) { this.featureTypeToDelete = featureType; this.OPEN_PROJECT_MODAL('deleteFeatureType'); }, async initMap() { if (this.project && this.permissions.can_view_project) { let layersToLoad; if (this.basemaps && this.basemaps.length > 0) { const basemapIndex = 0; layersToLoad = this.basemaps[basemapIndex].layers; layersToLoad.forEach((layerToLoad) => { this.availableLayers.forEach((layer) => { if (layer.id === layerToLoad.id) { layerToLoad = Object.assign(layerToLoad, layer); } }); }); layersToLoad.reverse(); } await this.INITIATE_MAP({ el: this.$refs.map, layersToLoad }); this.checkForOfflineFeature(); const project_id = this.$route.params.slug.split('-')[0]; const mvtUrl = `${this.API_BASE_URL}features.mvt`; mapService.addVectorTileLayer( mvtUrl, project_id, this.feature_types ); this.mapLoading = false; this.arraysOffline.forEach((x) => (x.geojson.properties.color = 'red')); const featuresOffline = this.arraysOffline.map((x) => x.geojson); this.GET_PROJECT_FEATURES({ project_slug: this.slug, ordering: '-created_on', limit: null, geojson: true, }) .then(() => { this.featuresLoading = false; console.log('addFeatures'); mapService.addFeatures({ features: [...this.features, ...featuresOffline], featureTypes: this.feature_types, addToMap: true, queryParams: { ordering: this.project.feature_browsing_default_sort, filter: this.project.feature_browsing_default_filter, } }); }) .catch((err) => { console.error(err); this.featuresLoading = false; }); featureAPI.getFeaturesBbox(this.slug).then((bbox) => { if (bbox) { mapService.fitBounds(bbox); } }); } }, }, }; </script> <style lang="less" scoped> .fullwidth { width: 100%; } .block-map { display: flex !important; flex-direction: column; .map-container { position: relative; height: 100%; #map { border: 1px solid grey; } } .button { margin-top: 0.5em; } } </style>