<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> <h1 v-else-if=" feature_type && currentRouteName === 'ajouter-signalement' " > Création d'un signalement <small>[{{ feature_type.title }}]</small> </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> <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" @blur="updateStore" /> {{ form.title.errors }} </div> <div class="required field"> <label :for="form.status.id_for_label">{{ form.status.label }}</label> <Dropdown :options="form.status.field.choices" :selected="selected_status" :selection.sync="selected_status" /> {{ form.status.errors }} </div> </div> <div class="field"> <label :for="form.description.id_for_label">{{ form.description.label }}</label> <textarea :name="form.description.html_name" rows="5" v-model="form.description.value" @blur="updateStore" ></textarea> {{ form.description.errors }} </div> <!-- Geom Field --> <div class="field"> <label :for="form.geom.id_for_label">{{ form.geom.label }}</label> <!-- Import GeoImage --> <div v-frag v-if="feature_type && feature_type.geom_type === 'point'"> <p> <button 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> <p> <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" style="display: none"> <div class="ui negative message"> <div class="header"> Une erreur est survenue avec la fonctionnalité de géolocalisation </div> <p id="erreur-geolocalisation-message"></p> </div> <br /> </span> </div> {{ form.geom.errors }} <!-- Map --> <input type="hidden" :name="form.geom.html_name" :id="form.geom.id_for_label" v-model="form.geom.value" @blur="updateStore" /> <div class="ui tab active map-container" data-tab="map"> <div id="map"></div> <!-- // todo: ajouter v-if --> <!-- {% if serialized_base_maps|length > 0 %} {% include "geocontrib/map-layers/sidebar-layers.html" with basemaps=serialized_base_maps layers=serialized_layers project=project.slug%} {% endif %} --> <SidebarLayers v-if="baseMaps && map"/> </div> </div> <!-- Extra Fields --> <div class="ui horizontal divider">DONNÉES MÉTIER</div> <!-- // Todo: Récupérer les "extra_form" de l'API --> <div v-for="(field, index) in extra_form_with_values" :key="field.field_type + index" class="field" > <div v-frag v-if="field.field_type === 'char'"> <label for="field.name">{{ field.label }}</label> <input type="text" :name="field.name" :id="field.name" v-model="field.value" @blur="updateStore_extra_form" /> </div> <div v-frag v-else-if="field.field_type === 'list'"> <label for="field.name">{{ field.label }}</label> <Dropdown :options="field.choices" :selected="selected_extra_form_list" :selection.sync="selected_extra_form_list" /> </div> <div v-frag v-else-if="field.field_type === 'integer'"> <label for="field.name">{{ field.label }}</label> <div class="ui input"> <!-- //* si click sur fléche dans champ input, pas de focus, donc pas de blur, donc utilisation de @change --> <input type="number" :name="field.name" :id="field.name" v-model.number="field.value" @change="updateStore_extra_form" /> </div> </div> <div v-frag v-else-if="field.field_type === 'boolean'"> <div class="ui checkbox"> <input type="checkbox" :checked="field.value" :name="field.name" :id="field.name" @change="updateStore_extra_form" /> <label for="field.name">{{ field.label }}</label> </div> </div> <div v-frag v-else-if="field.field_type === 'date'"> <label for="field.name">{{ field.label }}</label> <input type="date" :name="field.name" :id="field.name" v-model="field.value" @blur="updateStore_extra_form" /> </div> <div v-frag v-else-if="field.field_type === 'decimal'"> <label for="field.name">{{ field.label }}</label> <div class="ui input"> <input type="number" step=".01" :name="field.name" :id="field.name" v-model.number="field.value" @change="updateStore_extra_form" /> </div> </div> <div v-frag v-else-if="field.field_type === 'text'"> <label :for="field.name">{{ field.label }}</label> <textarea :name="field.name" rows="3" v-model="field.value" @blur="updateStore_extra_form" ></textarea> </div> {{ field.errors }} </div> <!-- Pièces jointes --> <div class="ui horizontal divider">PIÈCES JOINTES</div> <!-- {{ attachment_formset.non_form_errors }} --> <div id="formsets-attachment"> <!-- {{ attachment_formset.management_form }} --> <FeatureAttachmentForm v-for="form in attachmentFormset" :key="form.dataKey" :attachmentForm="form" /> </div> <button @click="add_attachement_formset" 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 }} --> <div id="formsets-link"> <!-- {{ linked_formset.management_form }} --> <FeatureLinkedForm v-for="form in linkedFormset" :key="form.dataKey" :linkedForm="form" :features="features" /> </div> <button @click="add_linked_formset" 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"> <i class="white save icon"></i> Enregistrer les changements </button> </form> </div> </div> </template> <script> import frag from "vue-frag"; import { mapGetters, mapState } from "vuex"; import FeatureAttachmentForm from "@/components/feature/FeatureAttachmentForm"; import FeatureLinkedForm from "@/components/feature/FeatureLinkedForm"; 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' export default { name: "Feature_edit", directives: { frag, }, components: { FeatureAttachmentForm, FeatureLinkedForm, Dropdown, SidebarLayers, }, computed: { ...mapState(["project"]), ...mapState("feature", [ "attachmentFormset", "linkedFormset", "features", "extra_form", ]), ...mapGetters("feature_type", ["feature_type"]), currentRouteName() { return this.$route.name; }, baseMaps(){ return this.$store.state.map.basemaps; }, 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(); }, }, extra_form_with_values: function () { return this.extra_form.map((el) => { return { ...el, value: el.value ? el.value : null }; }); }, selected_extra_form_list: { get() { return this.extra_form_with_values.find( (el) => el.field_type === "list" ).value; }, set(newValue) { const index = this.extra_form_with_values.findIndex( (el) => el.field_type === "list" ); this.extra_form_with_values[index].value = newValue; this.updateStore_extra_form(); }, }, }, watch: { feature_type(newValue) { this.onFeatureTypeLoaded(newValue); } }, data() { return { attachmentDataKey: 0, linkedDataKey: 0, form: { title: { errors: null, id_for_label: "name", field: { max_length: 30, }, html_name: "name", label: "Nom", value: "", }, status: { errors: null, id_for_label: "status", field: { choices: ["Brouillon", "Publié", "Archivé"], }, html_name: "status", label: "Statut", value: "Brouillon", }, description: { errors: null, id_for_label: "description", html_name: "description", label: "Description", value: "", }, geom: { label: "Localisation", }, }, }; }, methods: { add_attachement_formset() { 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 }, add_linked_formset() { 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 }, updateStore() { this.$store.commit("feature/UPDATE_FORM", { title: this.form.title.value, status: this.form.status.value, description: this.form.description, // ? geom ? }); }, updateStore_extra_form() { this.$store.commit( "feature/UPDATE_EXTRA_FORM", this.extra_form_with_values ); }, postForm() { if (this.form.title.value) { this.form.title.errors = null; this.$store.dispatch("feature/POST_FEATURE"); } else { this.form.title.errors = "Veuillez compléter ce champ."; } }, onFeatureTypeLoaded(){ var geomLeaflet = { 'point': 'circlemarker', 'linestring': 'polyline', 'polygon': 'polygon' } console.log(this.feature_type) var geomType = this.feature_type.geom_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' }, finish: { title: 'Terminer le dessin', text: 'Terminer' }, undo: { title: 'Supprimer le dernier point dessiné', text: 'Supprimer le dernier point' }, buttons: { polyline: 'Dessiner une polyligne', polygon: 'Dessiner un polygone', rectangle: 'Dessiner un rectangle', circle: 'Dessiner un cercle', marker: 'Dessiner une balise', circlemarker: 'Dessiner un point' } }, handlers: { circle: { tooltip: { start: 'Cliquer et glisser pour dessiner le cercle.' }, radius: 'Rayon' }, circlemarker: { tooltip: { start: 'Cliquer sur la carte pour placer le point.' } }, marker: { tooltip: { start: 'Cliquer sur la carte pour placer la balise.' } }, polygon: { tooltip: { start: 'Cliquer pour commencer à dessiner.', cont: 'Cliquer pour continuer à dessiner.', end: 'Cliquer sur le premier point pour terminer le dessin.' } }, polyline: { error: '<strong>Error:</strong> shape edges cannot cross!', tooltip: { start: 'Cliquer pour commencer à dessiner.', cont: 'Cliquer pour continuer à dessiner.', end: 'Cliquer sur le dernier point pour terminer le dessin.' } }, rectangle: { tooltip: { start: 'Cliquer et glisser pour dessiner le rectangle.' } }, simpleshape: { tooltip: { end: 'Relâcher la souris pour terminer de dessiner.' } } } }, edit: { toolbar: { actions: { save: { title: 'Sauver les modifications', text: 'Sauver' }, cancel: { title: 'Annuler la modification, annule toutes les modifications', text: 'Annuler' }, clearAll: { title: 'Effacer l\'objet', text: 'Effacer' } }, buttons: { edit: 'Modifier l\'objet', editDisabled: 'Aucun objet à modifier', remove: 'Supprimer l\'objet', removeDisabled: 'Aucun objet à supprimer' } }, handlers: { edit: { tooltip: { text: 'Faites glisser les marqueurs ou les balises pour modifier l\'élément.', subtext: 'Cliquez sur Annuler pour annuler les modifications..' } }, remove: { tooltip: { 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', edit: { featureGroup: this.drawnItems }, draw: drawConfig, }) this.drawControlEditOnly = new L.Control.Draw({ position: 'topright', edit: { featureGroup: this.drawnItems }, draw: false }); 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"){ document.getElementById('create-point-geoposition').style.display = "inline" document.getElementById('erreur-geolocalisation').style.display = "none" } }).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()) } else { this.map.setView(this.$store.state.configuration.DEFAULT_MAP_VIEW.center,this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom) } }, updateGeomField(newGeom){ this.geometry=newGeom; }, initMap(){ //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 }); // mapUtil.addLayers(layers, serviceMap, optionsMap); 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) .then((response) => { const features = response.data.features; if (features) { const allFeaturesExceptCurrent = features.filter(feat => feat.id !== currentFeatureId); mapUtil.addFeatures(allFeaturesExceptCurrent); 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"){ document.getElementById('create-point-geoposition').style.display = "none" document.getElementById('erreur-geolocalisation').style.display = "none" } } }, created() { if (!this.project) { this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug); } console.log("SET_CURRENT_FEATURE_TYPE_SLUG") this.$store.commit( "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG", this.$route.params.slug_type_signal ); }, mounted() { console.log(this.currentRouteName) console.log(this.$route.params.slug_type_signal) console.log(this.$store.state.feature_type.feature_types.length) console.log(this.$store.state.feature_type.current_feature_type_slug) console.log(this.$store.state.feature_type.feature_type) console.log(this.feature_type) if (this.currentRouteName === "editer-signalement") { for (let el in this.feature) { if (el && this.form[el]) this.form[el].value = this.feature[el]; } } this.initMap(); }, }; // TODO : add script from django and convert: </script> <style> #map { height: 70vh; width: 100%; border: 1px solid grey; } @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; } /* // ! margin écrasé par class last-child first-child, pas normal ... */ .ui.segment { margin: 1rem 0 !important; } </style>