Something went wrong on our end
-
DESPRES Damien authoredDESPRES Damien authored
Project_detail.vue 23.80 KiB
<template>
<div v-frag>
<div v-frag v-if="permissions && permissions.can_view_project && project">
<div id="message" class="fullwidth">
<div v-if="tempMessage" class="ui positive message">
<!-- <i class="close icon"></i> -->
<!-- <div class="header">You are eligible for a reward</div> -->
<p><i class="check icon"></i> {{ tempMessage }}</p>
</div>
</div>
<div id="message_info" class="fullwidth">
<div
v-if="infoMessage"
class="ui info message"
style="text-align: left"
>
<div class="header">
<i class="info circle icon"></i> Informations
</div>
<ul class="list">
{{
infoMessage
}}
</ul>
</div>
</div>
<div class="row">
<div class="four wide middle aligned column">
<img
class="ui small spaced image"
:src="
project.thumbnail.includes('default')
? require('@/assets/img/default.png')
: DJANGO_BASE_URL + project.thumbnail + refreshId()
"
/>
<div class="ui hidden divider"></div>
<div class="ui basic teal label" data-tooltip="Membres">
<i class="user icon"></i>{{ project.nb_contributors }}
</div>
<div class="ui basic teal label" data-tooltip="Signalements">
<i class="map marker icon"></i>{{ project.nb_published_features }}
</div>
<div class="ui basic teal label" data-tooltip="Commentaires">
<i class="comment icon"></i
>{{ project.nb_published_features_comments }}
</div>
</div>
<div class="ten wide column">
<h1 class="ui header">
<div class="content">
{{ project.title }}
<div v-if="arraysOffline.length>0">{{arraysOffline.length}} modifications en attente
<button
:disabled="isOffline()"
@click="sendOfflineFeatures()"
class="ui fluid teal icon button"
>
<i class="upload icon"></i> Envoyer au serveur
</button>
</div>
<div class="ui icon right floated compact buttons">
<a
v-if="permissions && permissions.can_view_project && isOffline()!=true"
id="subscribe-button"
class="ui button button-hover-green"
data-tooltip="S'abonner au projet"
data-position="top center"
data-variation="mini"
@click="isModalOpen = true"
>
<i class="inverted grey envelope icon"></i>
</a>
<router-link
v-if="permissions && permissions.can_update_project && isOffline()!=true"
:to="{ name: 'project_edit', params: { slug: project.slug } }"
class="ui button button-hover-orange"
data-tooltip="Modifier le projet"
data-position="top center"
data-variation="mini"
>
<i class="inverted grey pencil alternate icon"></i>
</router-link>
</div>
<div class="ui hidden divider"></div>
<div class="sub header">
{{ project.description }}
</div>
</div>
</h1>
</div>
</div>
<div class="row">
<div class="seven wide column">
<h3 class="ui header">Types de signalements</h3>
<div class="ui middle aligned divided list">
<div
v-for="(type, index) in feature_types"
:key="type.title + '-' + index"
class="item"
>
<div class="middle aligned content">
<router-link
:to="{
name: 'details-type-signalement',
params: { feature_type_slug: type.slug },
}"
>
<img
v-if="type.geom_type === 'point'"
class="list-image-type"
src="@/assets/img/marker.png"
/>
<img
v-if="type.geom_type === 'linestring'"
class="list-image-type"
src="@/assets/img/line.png"
/>
<img
v-if="type.geom_type === 'polygon'"
class="list-image-type"
src="@/assets/img/polygon.png"
/>
{{ type.title }}
</router-link>
<!-- {% if project and feature_types and
permissions|lookup:'can_create_feature' %} -->
<!-- // ? should we get type.is_editable ? -->
<!-- v-if="
project &&
permissions.can_create_feature &&
type.is_editable
" -->
<router-link
v-if="
project && permissions && permissions.can_create_feature
"
:to="{
name: 'ajouter-signalement',
params: { slug_type_signal: type.slug },
}"
class="
ui
compact
small
icon
right
floated
button button-hover-green
"
data-tooltip="Ajouter un signalement"
data-position="left center"
data-variation="mini"
>
<i class="ui plus icon"></i>
</router-link>
<router-link
:to="{
name: 'dupliquer-type-signalement',
params: { slug_type_signal: type.slug },
}"
v-if="
project &&
permissions &&
permissions.can_create_feature_type && isOffline()!=true
"
class="
ui
compact
small
icon
right
floated
button button-hover-green
"
data-tooltip="Dupliquer un type de signalement"
data-position="left center"
data-variation="mini"
>
<i class="inverted grey copy alternate icon"></i>
</router-link>
<router-link
:to="{
name: 'editer-type-signalement',
params: { slug_type_signal: type.slug },
}"
v-if="
project &&
type.is_editable &&
permissions &&
permissions.can_create_feature_type && isOffline()!=true
"
class="
ui
compact
small
icon
right
floated
button button-hover-green
"
data-tooltip="Éditer le type de signalement"
data-position="left center"
data-variation="mini"
>
<i class="inverted grey pencil alternate icon"></i>
</router-link>
</div>
</div>
<div v-if="feature_types.length === 0">
<i> Le projet ne contient pas encore de type de signalements. </i>
</div>
</div>
<div class="nouveau-type-signalement">
<router-link
v-if="permissions && permissions.can_update_project && isOffline()!=true"
:to="{
name: 'ajouter-type-signalement',
params: { slug: project.slug },
}"
class="ui compact basic button button-hover-green"
>
<i class="ui plus icon"></i>Créer un nouveau type de signalement
</router-link>
</div>
<div class="nouveau-type-signalement">
<a
v-if="permissions && permissions.can_update_project && isOffline()!=true"
class="
ui
compact
basic
button button-hover-green
important-flex
align-center
text-left
"
>
<i class="ui plus icon"></i>
<label class="ui" for="json_file">
<span class="label"
>Créer un nouveau type de signalement à partir d'un
GeoJSON</span
>
</label>
<input
type="file"
accept="application/json, .json, .geojson"
style="display: none"
name="json_file"
id="json_file"
@change="onFileChange"
/>
</a>
<br />
<div id="button-import" v-if="fileToImport.size > 0">
<button
:disabled="fileToImport.size === 0"
@click="toNewFeatureType"
class="ui fluid teal icon button"
>
<i class="upload icon"></i> Lancer l'import avec le fichier
{{ fileToImport.name }}
</button>
</div>
</div>
</div>
<div class="seven wide column">
<div id="map"></div>
</div>
</div>
<div class="row">
<div class="fourteen wide column">
<div class="ui two stackable cards">
<div class="red card">
<div class="content">
<div class="center aligned header">Derniers signalements</div>
<div class="center aligned description">
<div class="ui relaxed list">
<div
v-for="(item, index) in last_features"
:key="item.title + index"
class="item"
>
<div class="content">
<div>
<router-link
:to="{
name: 'details-signalement',
params: {
slug: project.slug,
slug_type_signal: item.feature_type.slug,
slug_signal: item.feature_id,
},
}"
>{{ item.title || item.feature_id }}</router-link
>
</div>
<div class="description">
<i
>[{{ item.created_on | setDate
}}<span v-if="user && item.display_creator"
>, par {{ item.display_creator }}
</span>
]</i
>
</div>
</div>
</div>
<i v-if="last_features.length === 0"
>Aucun signalement pour le moment.</i
>
</div>
</div>
</div>
</div>
<div class="orange card">
<div class="content">
<div class="center aligned header">Derniers commentaires</div>
<div class="center aligned description">
<div class="ui relaxed list">
<div
v-for="(item, index) in last_comments"
:key="'comment ' + index"
class="item"
>
<div class="content">
<div>
<router-link :to="item.related_feature.feature_url"
>"{{ item.comment }}"</router-link
>
</div>
<div class="description">
<i
>[ {{ item.created_on
}}<span v-if="user && item.display_author"
>, par {{ item.display_author }}
</span>
]</i
>
</div>
</div>
</div>
<i v-if="!last_comments || last_comments.length === 0"
>Aucun commentaire pour le moment.</i
>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="fourteen wide column">
<div class="ui grey segment">
<h3 class="ui header">Paramètres du projet</h3>
<div class="ui five stackable cards">
<div class="card">
<div class="center aligned content">
<h4 class="ui center aligned icon header">
<i class="disabled grey archive icon"></i>
<div class="content">Délai avant archivage automatique</div>
</h4>
</div>
<div class="center aligned extra content">
{{ project.archive_feature }} jours
</div>
</div>
<div class="card">
<div class="content">
<h4 class="ui center aligned icon header">
<i class="disabled grey trash alternate icon"></i>
<div class="content">
Délai avant suppression automatique
</div>
</h4>
</div>
<div class="center aligned extra content">
{{ project.delete_feature }} jours
</div>
</div>
<div class="card">
<div class="content">
<h4 class="ui center aligned icon header">
<i class="disabled grey eye icon"></i>
<div class="content">
Visibilité des signalements publiés
</div>
</h4>
</div>
<div class="center aligned extra content">
{{ project.access_level_pub_feature }}
</div>
</div>
<div class="card">
<div class="content">
<h4 class="ui center aligned icon header">
<i class="disabled grey eye icon"></i>
<div class="content">
Visibilité des signalements archivés
</div>
</h4>
</div>
<div class="center aligned extra content">
{{ project.access_level_arch_feature }}
</div>
</div>
<div class="card">
<div class="content">
<h4 class="ui center aligned icon header">
<i class="disabled grey cogs icon"></i>
<div class="content">Modération</div>
</h4>
</div>
<div class="center aligned extra content">
{{ project.moderation ? "Oui" : "Non" }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<span v-else>
<i class="icon exclamation triangle"></i>
<span
>Vous ne disposez pas des droits nécessaires pour consulter ce
projet.</span
>
</span>
<div
v-if="isModalOpen"
class="ui dimmer modals page transition visible active"
style="display: flex !important"
>
<div
:class="[
'ui mini modal subscription',
{ 'transition visible active': isModalOpen },
]"
>
<i @click="isModalOpen = false" class="close icon"></i>
<div class="ui icon header">
<i class="envelope icon"></i>
Notifications du projet
</div>
<div class="content">
<button
@click="subsribeProject"
:class="['ui compact fluid button', is_suscriber ? 'red' : 'green']"
>
{{
is_suscriber
? "Se désabonner de ce projet"
: "S'abonner à ce projet"
}}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import frag from "vue-frag";
import { mapUtil } from "@/assets/js/map-util.js";
import { mapGetters, mapState } from "vuex";
import projectAPI from "@/services/project-api";
const axios = require("axios");
export default {
name: "Project_details",
props: ["message"],
directives: {
frag,
},
filters: {
setDate: function (value) {
let date = new Date(value);
let d = date.toLocaleDateString("fr", {
year: "2-digit",
month: "numeric",
day: "numeric",
});
return d;
},
},
data() {
return {
infoMessage: "",
arraysOffline: [],
geojsonImport: [],
fileToImport: { name: "", size: 0 },
slug: this.$route.params.slug,
isModalOpen: false,
is_suscriber: false,
tempMessage: null,
};
},
computed: {
...mapGetters(["project", "permissions"]),
...mapState("feature_type", ["feature_types"]),
...mapState("feature", ["features"]),
...mapState(["last_comments", "user"]),
DJANGO_BASE_URL: function () {
return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
},
last_features: function () {
// * limit to last five element of array (looks sorted chronologically, but not sure...)
return this.$store.state.feature.features.slice(-5);
},
},
methods: {
refreshId() {
return "?ver=" + Math.random();
},
isOffline(){
return navigator.onLine==false;
},
checkForOfflineFeature(){
let arraysOffline=[];
let localStorageArray=localStorage.getItem("geocontrib_offline");
if(localStorageArray){
arraysOffline=JSON.parse(localStorageArray);
this.arraysOffline=arraysOffline.filter(x=>x.project==this.project.slug);
}
},
sendOfflineFeatures(){
var promises = [];
this.arraysOffline.forEach((feature, index, object)=>{
console.log(feature);
if(feature.type=='post') {
promises.push(
axios
.post(`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}features/`, feature.geojson)
.then((response) => {
console.log(response)
if (response.status === 201 && response.data) {
object.splice(index, 1);
}
})
.catch((error) => {
console.log(error);
}));
}
else if(feature.type=='put') {
promises.push(
axios
.put(`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}features/${feature.featureId}`, feature.geojson)
.then((response) => {
console.log(response)
if (response.status === 200 && response.data) {
object.splice(index, 1);
}
})
.catch((error) => {
console.log(error);
}));
}
});
Promise.all(promises).then(() =>
this.updateLocalStorage()
);
},
updateLocalStorage(){
let arraysOffline=[];
let localStorageArray=localStorage.getItem("geocontrib_offline");
if(localStorageArray){
arraysOffline=JSON.parse(localStorageArray);
}
let arraysOfflineOtherProject = arraysOffline.filter(x=>x.project!=this.project.slug);
arraysOffline=arraysOfflineOtherProject.concat(this.arraysOffline);
localStorage.setItem("geocontrib_offline",JSON.stringify(arraysOffline));
},
toNewFeatureType() {
this.$router.push({
name: "ajouter-type-signalement",
params: {
geojson: this.geojsonImport,
fileToImport: this.fileToImport,
},
});
},
onFileChange(e) {
var files = e.target.files || e.dataTransfer.files;
if (!files.length) return;
this.fileToImport = files[0];
// TODO : VALIDATION IF FILE IS JSON
if (this.fileToImport.size > 0) {
const fr = new FileReader();
fr.onload = (e) => {
this.geojsonImport = JSON.parse(e.target.result);
};
fr.readAsText(this.fileToImport);
//* stock filename to import features afterward
this.$store.commit(
"feature_type/SET_FILE_TO_IMPORT",
this.fileToImport
);
}
},
subsribeProject() {
this.$store.state.configuration.VUE_APP_DJANGO_API_BASE;
projectAPI
.subscribeProject({
suscribe: !this.is_suscriber,
projectSlug: this.$route.params.slug,
})
.then((data) => {
this.is_suscriber = data.is_suscriber;
this.isModalOpen = false;
if (this.is_suscriber)
this.infoMessage =
"Vous êtes maintenant abonné aux notifications de ce projet.";
else
this.infoMessage =
"Vous ne recevrez plus les notifications de ce projet.";
setTimeout(
function () {
this.infoMessage = "";
}.bind(this),
3000
);
});
},
},
created() {
this.$store.dispatch("GET_PROJECT_INFO", this.slug);
if (this.user) {
projectAPI
.getProjectSubscription({ projectSlug: this.$route.params.slug })
.then((data) => (this.is_suscriber = data.is_suscriber));
}
},
mounted() {
if (this.project && this.permissions.can_view_project) {
this.$store.dispatch("map/INITIATE_MAP");
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
let self = this;
axios
.get(url)
.then((response) => {
const features = response.data.features;
const featureGroup = mapUtil.addFeatures(features);
if (featureGroup && featureGroup.getLayers().length > 0) {
mapUtil
.getMap()
.fitBounds(featureGroup.getBounds(), { padding: [25, 25] });
self.$store.commit("map/SET_GEOJSON_FEATURES", features);
}
})
.catch((error) => {
throw error;
});
this.checkForOfflineFeature();
}
if (this.message) {
this.tempMessage = this.message;
document
.getElementById("message")
.scrollIntoView({ block: "end", inline: "nearest" });
setTimeout(() => {
//* hide message after 5 seconds
this.tempMessage = null;
}, 5000);
}
},
};
</script>
<style>
@import "../../assets/resources/semantic-ui-2.4.2/semantic.min.css";
#map {
width: 100%;
height: 100%;
min-height: 250px;
}
.list-image-type {
margin-right: 5px;
height: 25px;
vertical-align: bottom;
}
/* // ! missing style in semantic.min.css, je ne comprends pas comment... */
.ui.right.floated.button {
float: right;
margin: 0 0 0 1em;
}
.nouveau-type-signalement {
padding-top: 1em;
}
#button-import {
padding-top: 0.5em;
}
.fullwidth {
width: 100%;
}
.important-flex {
display: flex !important;
}
.align-center {
align-items: center !important;
}
.text-left {
text-align: left !important;
}
</style>