Newer
Older

Timothee P
committed
<template>
<div v-frag>
<script
type="application/javascript"
:src="
baseUrl +
'/resources/leaflet-control-geocoder-1.13.0/Control.Geocoder.js'
"
></script>

Timothee P
committed
<div class="fourteen wide column">
<h1 v-if="feature && currentRouteName === 'editer-signalement'">
Mise à jour du signalement "{{ feature.title || feature.feature_id }}"
<h1
v-else-if="feature_type && currentRouteName === 'ajouter-signalement'"
>
Création d'un signalement <small>[{{ feature_type.title }}]</small>

Timothee P
committed
</h1>
<form
id="form-feature-edit"
action=""
method="post"
enctype="multipart/form-data"
class="ui form"
>
<!-- Feature Fields -->
<div class="two fields">
<label :for="form.title.id_for_label">{{ form.title.label }}</label>

Timothee P
committed
<input
type="text"
required
:maxlength="form.title.field.max_length"
:name="form.title.html_name"
:id="form.title.id_for_label"
v-model="form.title.value"

Timothee P
committed
@blur="updateStore"

Timothee P
committed
/>
<ul id="errorlist-title" class="errorlist">
<li v-for="error in form.title.errors" :key="error">
{{ error }}
</li>
</ul>

Timothee P
committed
</div>
<div class="required field">
<label :for="form.status.id_for_label">{{
form.status.label

Timothee P
committed
}}</label>

Timothee P
committed
:selection.sync="selected_status"

Timothee P
committed
</div>
</div>
<div class="field">
<label :for="form.description.id_for_label">{{
form.description.label

Timothee P
committed
}}</label>
<textarea

Timothee P
committed
rows="5"

Timothee P
committed
@blur="updateStore"

Timothee P
committed
></textarea>
</div>
<!-- Geom Field -->
<div class="field">
<label :for="form.geom.id_for_label">{{ form.geom.label }}</label>

Timothee P
committed
<!-- Import GeoImage -->
<div v-frag v-if="feature_type && feature_type.geom_type === 'point'">
<button
@click="showGeoRef = true"
id="add-geo-image"
type="button"
class="ui compact button"
>
<i class="file image icon"></i>Importer une image géoréférencée
</button>
Vous pouvez utiliser une image géoréférencée pour localiser le
signalement.
</p>
<div v-if="showGeoRef">
<p>
Attention, si vous avez déjà saisi une géométrie, celle issue de
l'image importée l'écrasera.
</p>
<label>Image (png ou jpeg)</label>
<label class="ui icon button" for="image_file">
<i class="file icon"></i>
<span class="label">{{ geoRefFileLabel }}</span>
</label>
<input
type="file"
accept="image/jpeg, image/png"
style="display: none"
ref="file"
v-on:change="handleFileUpload()"
name="image_file"
class="image_file"
id="image_file"
/>
<p class="error-message" style="color: red">
{{ erreurUploadMessage }}
</p>
</div>
<button
@click="georeferencement()"
id="get-geom-from-image-file"
type="button"
class="ui positive right labeled icon button"
>
Importer
<i class="checkmark icon"></i>
</button>
<button
@click="create_point_geoposition()"
id="create-point-geoposition"
type="button"
class="ui compact button"
>
<i class="ui map marker alternate icon"></i>Positionner le
signalement à partir de votre géolocalisation
</button>
</p>
<span
id="erreur-geolocalisation"
v-if="erreurGeolocalisationMessage"
>
<div class="ui negative message">
<div class="header">
Une erreur est survenue avec la fonctionnalité de
géolocalisation
</div>
<p id="erreur-geolocalisation-message">
{{ erreurGeolocalisationMessage }}
</p>

Timothee P
committed
</div>
<ul id="errorlist-geom" class="errorlist">
<li v-for="error in form.geom.errors" :key="error">
{{ error }}
</li>
</ul>

Timothee P
committed
<!-- Map -->
<input
type="hidden"
:name="form.geom.html_name"
:id="form.geom.id_for_label"
v-model="form.geom.value"

Timothee P
committed
@blur="updateStore"

Timothee P
committed
/>
<div class="ui tab active map-container" data-tab="map">
<div id="map"></div>
<!-- {% if serialized_base_maps|length > 0 %} {% include

Timothee P
committed
"geocontrib/map-layers/sidebar-layers.html" with
basemaps=serialized_base_maps layers=serialized_layers
project=project.slug%} {% endif %} -->
<SidebarLayers v-if="basemaps && map" />

Timothee P
committed
</div>
</div>
<!-- Extra Fields -->
<div class="ui horizontal divider">DONNÉES MÉTIER</div>
v-for="(field, index) in extra_form"
:key="field.field_type + index"
class="field"
>
<FeatureExtraForm :field="field" />

Timothee P
committed
{{ field.errors }}
</div>
<!-- Pièces jointes -->
<div class="ui horizontal divider">PIÈCES JOINTES</div>
<div id="formsets-attachment">

Timothee P
committed
v-for="form in attachmentFormset"
:key="form.dataKey"
:attachmentForm="form"
ref="attachementForm"

Timothee P
committed
/>

Timothee P
committed

Timothee P
committed
<button

Timothee P
committed
id="add-attachment"
type="button"
class="ui compact basic button button-hover-green"
>
<i class="ui plus icon"></i>Ajouter une pièce jointe
</button>
<!-- Signalements liés -->
<div class="ui horizontal divider">SIGNALEMENTS LIÉS</div>

Timothee P
committed
<div id="formsets-link">
<FeatureLinkedForm
v-for="form in linkedFormset"
:key="form.dataKey"
:linkedForm="form"
:features="features"
ref="linkedForm"

Timothee P
committed
<button

Timothee P
committed
id="add-link"
type="button"
class="ui compact basic button button-hover-green"
>
<i class="ui plus icon"></i>Ajouter une liaison
</button>
<div class="ui divider"></div>
<button @click="postForm" type="button" class="ui teal icon button">

Timothee P
committed
<i class="white save icon"></i> Enregistrer les changements
</button>
</form>
</div>
</div>
</template>
<script>
import frag from "vue-frag";
import { mapState, mapGetters } from "vuex";
import FeatureAttachmentForm from "@/components/feature/FeatureAttachmentForm";
import FeatureLinkedForm from "@/components/feature/FeatureLinkedForm";
import FeatureExtraForm from "@/components/feature/FeatureExtraForm";
import Dropdown from "@/components/Dropdown.vue";
import SidebarLayers from "@/components/map-layers/SidebarLayers";
import featureAPI from "@/services/feature-api";
import L from "leaflet";
import "leaflet-draw";
import { mapUtil } from "@/assets/js/map-util.js";
const axios = require("axios");
import flip from "@turf/flip";

Timothee P
committed
axios.defaults.headers.common["X-CSRFToken"] = ((name) => {
var re = new RegExp(name + "=([^;]+)");
var value = re.exec(document.cookie);
return value != null ? unescape(value[1]) : null;
})("csrftoken");

Timothee P
committed
export default {
name: "Feature_edit",

Timothee P
committed
directives: {
frag,
},
components: {
FeatureAttachmentForm,
FeatureLinkedForm,

Timothee P
committed
SidebarLayers,

Timothee P
committed
data() {
return {
map: null,
feature_type: null,
baseUrl: this.$store.state.configuration.BASE_URL,
file: null,
showGeoRef: false,
showGeoPositionBtn: true,
erreurGeolocalisationMessage: null,
erreurUploadMessage: null,
attachmentDataKey: 0,
linkedDataKey: 0,
statusChoices: [
{
name: "Brouillon",
value: "draft",
},

Timothee P
committed
title: {

Timothee P
committed
field: {
max_length: 30,
},
html_name: "name",
label: "Nom",
value: "",

Timothee P
committed
},
status: {
id_for_label: "status",
html_name: "status",
label: "Statut",
value: {
value: "draft",
name: "Brouillon",
},

Timothee P
committed
},
description: {
id_for_label: "description",
html_name: "description",
label: "Description",
value: "",

Timothee P
committed
},
geom: {

Timothee P
committed
},
},
};
},
...mapGetters(["project"]),
...mapState(["user", "USER_LEVEL_PROJECTS"]),
...mapState("map", ["basemaps"]),
...mapState("feature", [
"attachmentFormset",
"linkedFormset",
"features",
"extra_form",

Timothee P
committed
"linked_features",
field_title() {
if (this.feature_type) {
if (this.feature_type.title_optional) {
return "field";
currentRouteName() {
return this.$route.name;
},
Sébastien DA ROCHA
committed
feature: function () {
return this.$store.state.feature.features.find(
(el) => el.feature_id === this.$route.params.slug_signal
);
},
return this.file.name;
}
return "Sélectionner une image ...";
selected_status: {
get() {
return this.form.status.value;
},
set(newValue) {
this.form.status.value = newValue;
this.updateStore();
},
},
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
allowedStatusChoices() {
if (this.project) {
const isModerate = this.project.moderation;
const userStatus = this.USER_LEVEL_PROJECTS[this.project.slug];
//* si admin ou modérateur, statuts toujours disponible : Brouillon, Publié, Archivé
if (
userStatus === "Modérateur" ||
userStatus === "Administrateur projet"
) {
return this.statusChoices.filter((el) => el.value !== "pending");
} else if (userStatus === "Contributeur") {
if (
this.currentRouteName === "editer-signalement" &&
this.project.creator === this.user.id
) {
//* IF création de project
return this.statusChoices.filter((el) => el.value === "draft");
} else {
//* ELSE édition de projet
return isModerate
? this.statusChoices.filter(
(el) => el.value === "draft" || el.value === "pending"
)
: this.statusChoices.filter(
(el) => el.value === "draft" || el.value === "published"
);
}
}
}
return []
},
feature(newValue) {
if (this.$route.name === "editer-signalement") {
this.initForm();
this.initExtraForms(newValue);
}

Timothee P
committed
methods: {
initForm() {
if (this.currentRouteName === "editer-signalement") {
for (let key in this.feature) {
if (key && this.form[key]) {
if (key === "status") {
const value = this.feature[key];
this.form[key].value = this.statusChoices.find(
(key) => key.value === value
this.form[key].value = this.feature[key];
create_point_geoposition() {
function success(position) {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
var layer = L.circleMarker([latitude, longitude]);
this.add_layer_call_back(layer);
this.map.setView([latitude, longitude]);
}
function error(err) {
this.erreurGeolocalisationMessage = err.message;
this.erreurGeolocalisationMessage = null;
this.erreurGeolocalisationMessage =
"La géolocalisation n'est pas supportée par votre navigateur.";
navigator.geolocation.getCurrentPosition(
success.bind(this),
error.bind(this)
);
handleFileUpload() {
this.file = this.$refs.file.files[0];
},
georeferencement() {
console.log("georeferencement");
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}exif-geom-reader/`;
let formData = new FormData();
console.log(">> formData >> ", formData);
let self = this;
axios
.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
console.log("SUCCESS!!", response.data);
if (response.data.geom.indexOf("POINT") >= 0) {
let regexp = /POINT\s\((.*)\s(.*)\)/;
let json = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [parseFloat(arr[1]), parseFloat(arr[2])],
self.addAttachment({
title: "Localisation",
info: "",
id: "loc",
attachment_file: self.file.name,
fileToImport: self.file,
});
console.log("FAILURE!!");
initExtraForms(feature) {
function findCurrentValue(label) {
const field = feature.feature_data.find((el) => el.label === label);
return field ? field.value : null;
let extraForm = this.feature_type.customfield_set.map((field) => {
return {
...field,
//* add value field to extra forms from feature_type and existing values if feature is defined
value: feature ? findCurrentValue(field.label) : null,
};
});
this.$store.commit("feature/SET_EXTRA_FORM", extraForm);
this.$store.commit("feature/ADD_ATTACHMENT_FORM", {
dataKey: this.attachmentDataKey,
}); // * create an object with the counter in store

Timothee P
committed
this.attachmentDataKey += 1; // * increment counter for key in v-for
console.log(attachment);
this.$store.commit("feature/ADD_ATTACHMENT_FORM", {
dataKey: this.attachmentDataKey,
title: attachment.title,
attachment_file: attachment.attachment_file,
info: attachment.info,
fileToImport: attachment.fileToImport,
id: attachment.id,
});
this.attachmentDataKey += 1;
},
addExistingAttachementFormset(attachementFormset) {
for (const attachment of attachementFormset) {
this.addAttachment(attachment);
}
},

Timothee P
committed
this.$store.commit("feature/ADD_LINKED_FORM", {
dataKey: this.linkedDataKey,
}); // * create an object with the counter in store
this.linkedDataKey += 1; // * increment counter for key in v-for

Timothee P
committed
addExistingLinkedFormset(linkedFormset) {
for (const linked of linkedFormset) {
this.$store.commit("feature/ADD_LINKED_FORM", {
dataKey: this.linkedDataKey,
relation_type: linked.relation_type,
feature_to: linked.feature_to,
});
this.linkedDataKey += 1;
}
},

Timothee P
committed
updateStore() {
this.$store.commit("feature/UPDATE_FORM", {
title: this.form.title.value,
status: this.form.status.value,
description: this.form.description,
geometry: this.form.geom.value,
feature_id: this.feature ? this.feature.feature_id : "",

Timothee P
committed
});
checkFormTitle() {
this.form.title.errors = [];
!this.form.title.errors.includes("Veuillez compléter ce champ.")
) {
this.form.title.errors.push("Veuillez compléter ce champ.");
document
.getElementById("errorlist-title")
.scrollIntoView({ block: "end", inline: "nearest" });
}
return false;
},
checkFormGeom() {
if (this.form.geom.value) {
this.form.geom.errors = [];
return true;
} else if (
!this.form.geom.errors.includes("Valeur géométrique non valide.")
) {
this.form.geom.errors.push("Valeur géométrique non valide.");
document
.getElementById("errorlist-geom")
.scrollIntoView({ block: "end", inline: "nearest" });
checkAddedForm() {
let isValid = true; //* fallback if all customForms returned true
if (this.$refs.attachementForm) {
for (const attachementForm of this.$refs.attachementForm) {
if (attachementForm.checkForm() === false) {
isValid = false;
}
}
}
if (this.$refs.linkedForm) {
for (const linkedForm of this.$refs.linkedForm) {
if (linkedForm.checkForm() === false) {
isValid = false;
}
}
}
return isValid;
},
let is_valid = true;
if (!this.feature_type.title_optional) {
is_valid =
this.checkFormTitle() &&
this.checkFormGeom() &&
this.checkAddedForm();
} else {
is_valid = this.checkFormGeom() && this.checkAddedForm();
this.$store.dispatch("feature/SEND_FEATURE", this.currentRouteName);
}
//* ************* MAP *************** *//
onFeatureTypeLoaded() {
point: "circlemarker",
linestring: "polyline",
polygon: "polygon",
};
var drawConfig = {
polygon: false,
marker: false,
polyline: false,
rectangle: false,
circle: false,
circlemarker: false,
};
drawConfig[geomLeaflet[geomType]] = true;
L.drawLocal = {
draw: {
toolbar: {
actions: {
title: "Annuler le dessin",
text: "Annuler",
title: "Terminer le dessin",
text: "Terminer",
title: "Supprimer le dernier point dessiné",
text: "Supprimer le dernier point",
polyline: "Dessiner une polyligne",
polygon: "Dessiner un polygone",
rectangle: "Dessiner un rectangle",
circle: "Dessiner un cercle",
marker: "Dessiner une balise",
circlemarker: "Dessiner un point",
},
start: "Cliquer et glisser pour dessiner le cercle.",
start: "Cliquer sur la carte pour placer le point.",
},
start: "Cliquer sur la carte pour placer la balise.",
},
start: "Cliquer pour commencer à dessiner.",
cont: "Cliquer pour continuer à dessiner.",
end: "Cliquer sur le premier point pour terminer le dessin.",
},
error: "<strong>Error:</strong> shape edges cannot cross!",
start: "Cliquer pour commencer à dessiner.",
cont: "Cliquer pour continuer à dessiner.",
end: "Cliquer sur le dernier point pour terminer le dessin.",
},
start: "Cliquer et glisser pour dessiner le rectangle.",
},
end: "Relâcher la souris pour terminer de dessiner.",
},
},
},
},
edit: {
toolbar: {
actions: {
save: {
title: "Sauver les modifications",
text: "Sauver",
title:
"Annuler la modification, annule toutes les modifications",
text: "Annuler",
title: "Effacer l'objet",
text: "Effacer",
},
edit: "Modifier l'objet",
editDisabled: "Aucun objet à modifier",
remove: "Supprimer l'objet",
removeDisabled: "Aucun objet à supprimer",
},
text: "Faites glisser les marqueurs ou les balises pour modifier l'élément.",
subtext: "Cliquez sur Annuler pour annuler les modifications..",
},
text: "Cliquez sur un élément pour le supprimer.",
},
},
},
},
this.drawnItems = new L.FeatureGroup();
this.map.addLayer(this.drawnItems);
this.drawControlFull = new L.Control.Draw({
position: "topright",
featureGroup: this.drawnItems,
this.drawControlEditOnly = new L.Control.Draw({
position: "topright",
featureGroup: this.drawnItems,
});
if (this.currentRouteName === "editer-signalement") {
this.map.addControl(this.drawControlEditOnly);
} else this.map.addControl(this.drawControlFull);
this.map.on(
"draw:created",
function (e) {
var layer = e.layer;
this.add_layer_call_back(layer);
}.bind(this)
);
//var wellknown;// TODO Remplacer par autre chose
this.map.on(
"draw:edited",
function (e) {
var layers = e.layers;
let self = this;
layers.eachLayer(function (layer) {
//this.updateGeomField(wellknown.stringify(layer.toGeoJSON()))
self.updateGeomField(layer.toGeoJSON());
});
}.bind(this)
);
this.map.on(
"draw:deleted",
function () {
this.drawControlEditOnly.remove(this.map);
this.drawControlFull.addTo(this.map);
this.updateGeomField("");
if (geomType === "point") {
this.showGeoPositionBtn = true;
this.erreurGeolocalisationMessage = "";
}
}.bind(this)
);
updateMap(geomFeatureJSON) {
if (this.drawnItems) this.drawnItems.clearLayers();
console.log("update map");
var geomType = this.feature_type.geom_type;
if (geomFeatureJSON) {
var geomJSON = flip(geomFeatureJSON.geometry); //turf.flip(geomFeatureJSON)
if (geomType === "point") {
L.circleMarker(geomJSON.coordinates).addTo(this.drawnItems);
} else if (geomType === "linestring") {
L.polyline(geomJSON.coordinates).addTo(this.drawnItems);
} else if (geomType === "polygon") {
L.polygon(geomJSON.coordinates).addTo(this.drawnItems);
this.map.fitBounds(this.drawnItems.getBounds());
this.map.setView(
this.$store.state.configuration.DEFAULT_MAP_VIEW.center,
this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom
);
updateGeomField(newGeom) {
//this.geometry = newGeom;
this.form.geom.value = newGeom.geometry;
this.updateStore();
var mapDefaultViewCenter =
this.$store.state.configuration.DEFAULT_MAP_VIEW.center;
var mapDefaultViewZoom =
this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom;
// Create the map, then init the layers and features
this.map = mapUtil.createMap({
mapDefaultViewCenter,
mapDefaultViewZoom,
});
const currentFeatureId = this.$route.params.slug_signal;
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
axios
.get(url)
const features = response.data.features;
if (features) {
const allFeaturesExceptCurrent = features.filter(
(feat) => feat.id !== currentFeatureId
);
if (this.currentRouteName === "editer-signalement") {
const currentFeature = features.filter(
(feat) => feat.id === currentFeatureId
)[0];
this.updateMap(currentFeature);
}
})
.catch((error) => {
throw error;
});
document.addEventListener("change-layers-order", (event) => {
// 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.updateOrder(event.detail.layers.slice().reverse());
});
},
add_layer_call_back(layer) {
layer.addTo(this.drawnItems);
this.drawControlFull.remove(this.map);
this.drawControlEditOnly.addTo(this.map);
//var wellknown;// TODO Remplacer par autre chose
//this.updateGeomField(wellknown.stringify(layer.toGeoJSON()))
this.updateGeomField(layer.toGeoJSON());
if (this.feature_type.geomType === "point") {
this.showGeoPositionBtn = false;
this.erreurGeolocalisationMessage = "";
getFeatureAttachments() {
featureAPI
.getFeatureAttachments(this.$route.params.slug_signal)
.then((data) => this.addExistingAttachementFormset(data));
},

Timothee P
committed
getLinkedFeatures() {
featureAPI
.getFeatureLinks(this.$route.params.slug_signal)
.then((data) => this.addExistingLinkedFormset(data));
},

Timothee P
committed
},
/* if (!this.project) {
console.log(this.$store.state.projects);
this.project = this.$store.state.projects.find(
(project) => project.slug === this.$store.state.project_slug
);
this.filterStatusChoicesF();
} */
this.$store.commit(
"feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
this.$route.params.slug_type_signal
);
if (this.$route.params.slug_signal) {

Timothee P
committed
this.getLinkedFeatures();
let ftSlug = this.$route.params.slug_type_signal;
this.$store
.dispatch("GET_PROJECT_INFO", this.$route.params.slug)
.then((data) => {
console.log(data);
console.log(this.$store.state.feature_type.feature_types);
this.feature_type = this.$store.state.feature_type.feature_types.find(
(el) => el.slug === ftSlug
);
setTimeout(
function () {
mapUtil.addGeocoders(this.$store.state.configuration);
}.bind(this),
1000
);
});
},
destroyed() {
//* be sure that previous Formset have been cleared for creation
this.$store.commit("feature/CLEAR_ATTACHMENT_FORM");
this.$store.commit("feature/CLEAR_LINKED_FORM");
},

Timothee P
committed
};
</script>
<style>
#map {
height: 70vh;
width: 100%;

Timothee P
committed
border: 1px solid grey;

Timothee P
committed
@media only screen and (max-width: 767px) {
#map {
height: 80vh;