Newer
Older

Timothee P
committed
<template>
<div v-frag>
<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">
<div class="required field">
<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
/>

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

Timothee P
committed
}}</label>
:options="statusChoices"
:selected="selected_status.name"

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>

Timothee P
committed
</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'">
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>
<div class="field">
<label>Image (png ou jpeg)</label>
<label class="ui icon button" for="image_file">
<i class="file icon"></i>
<span class="label">Sélectionner une image ...</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>
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>

Timothee P
committed

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>

Timothee P
committed
<!-- {{ attachment_formset.non_form_errors }} -->

Timothee P
committed
<div id="formsets-attachment">

Timothee P
committed
<!-- {{ attachment_formset.management_form }} -->

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

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>
<!-- {{ linked_formset.non_form_errors }} -->

Timothee P
committed
<div id="formsets-link">
<!-- {{ linked_formset.management_form }} -->
<FeatureLinkedForm
v-for="form in linkedFormset"
:key="form.dataKey"
:linkedForm="form"
:features="features"
/>

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";

Timothee P
committed
import { mapGetters, mapState } 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 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
export default {
name: "Feature_edit",

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

Timothee P
committed
SidebarLayers,

Timothee P
committed
data() {
return {
erreurGeolocalisationMessage:null,
erreurUploadMessage:null,
attachmentDataKey: 0,
linkedDataKey: 0,
statusChoices: [
{
name: "Brouillon",
value: "draft",
},
{ name: "Publié", value: "published" },
{ name: "Archivé", value: "archived" },
],

Timothee P
committed
title: {
errors: null,
id_for_label: "name",

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

Timothee P
committed
},
status: {
errors: null,
id_for_label: "status",
html_name: "status",
label: "Statut",
value: "Brouillon",

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

Timothee P
committed
},
geom: {

Timothee P
committed
},
},
};
},
computed: {
...mapState(["project"]),
...mapState("feature", [
"attachmentFormset",
"linkedFormset",
"features",
"extra_form",
]),
...mapGetters("feature_type", ["feature_type"]),
Sébastien DA ROCHA
committed
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
);
},
selected_status: {
get() {
return this.form.status.value;
},
set(newValue) {
this.form.status.value = newValue;
this.updateStore();
},
},
},
watch: {
this.onFeatureTypeLoaded();
},
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 el in this.feature) {
if (el && this.form[el]) {
if (el === "status") {
const value = this.feature[el];
this.form[el].value = this.statusChoices.find(
(el) => el.value === value
);
} else {
this.form[el].value = this.feature[el];
}
}
}
this.updateStore();
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
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;
if (!navigator.geolocation) {
this.erreurGeolocalisationMessage="La géolocalisation n'est pas supportée par votre navigateur.";
} else {
navigator.geolocation.getCurrentPosition(success.bind(this), error.bind(this));
}
},
handleFileUpload() {
this.file = this.$refs.file.files[0];
console.log('>>>> 1st element in files array >>>> ', this.file);
},
georeferencement(){
console.log("georeferencement");
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}exif-geom-reader/`;
let formData = new FormData();
formData.append('file', this.file);
console.log('>> formData >> ', formData);
let self=this;
axios.post(url,
formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}
).then(function () {
console.log('SUCCESS!!');
})
.catch(function () {
console.log('FAILURE!!');
self.erreurUploadMessage='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);

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

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
});
postForm() {
if (this.form.title.value) {
this.form.title.errors = null;
this.$store.dispatch("feature/POST_FEATURE", this.currentRouteName);
} else {
this.form.title.errors = "Veuillez compléter ce champ.";
}
},
onFeatureTypeLoaded() {
point: "circlemarker",
linestring: "polyline",
polygon: "polygon",
};
// console.log(this.feature_type)
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(map, geomFeatureJSON) {
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();
//console.log(drawnItems);
//console.log(configuration);
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(this.map, 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="";

Timothee P
committed
},
created() {
if (!this.project) {

Timothee P
committed
this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug);
this.$store.commit(
"feature_type/SET_CURRENT_FEATURE_TYPE_SLUG",
this.$route.params.slug_type_signal
);
},

Timothee P
committed
};
// TODO : add script from django and convert:
</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;
}
}
/* // ! missing style in semantic.min.css, je ne comprends pas comment... */
.ui.right.floated.button {
float: right;
margin-right: 0;
margin-left: 0.25em;
}

Timothee P
committed
/* // ! margin écrasé par class last-child first-child, pas normal ... */
.ui.segment {
margin: 1rem 0 !important;
}