diff --git a/src/components/ImportTask.vue b/src/components/ImportTask.vue index fb642ab722c761cc8c80988e6413576d0fd1f9e7..d7e008942c59ba2405acd96ee2611f5d5f0e4b58 100644 --- a/src/components/ImportTask.vue +++ b/src/components/ImportTask.vue @@ -23,7 +23,7 @@ <td> <span v-if="importFile.infos" - :data-tooltip="dataTooltipMsg(importFile.infos)" + :data-tooltip="importFile.infos" class="ui icon" > <i diff --git a/src/components/feature/FeatureAttachmentForm.vue b/src/components/feature/FeatureAttachmentForm.vue index 7e9ef3a1d2aa912bb6292cd6e6c8c7c82ddec024..eb9d0ef402eab0ff57f5623e7088c0c10637ee4b 100644 --- a/src/components/feature/FeatureAttachmentForm.vue +++ b/src/components/feature/FeatureAttachmentForm.vue @@ -1,14 +1,10 @@ <template> <div> - <!-- <span v-for="hidden in form.hidden_fields" :key="hidden"> - {{ hidden }} - </span> --> - <div class="ui teal segment"> <h4> Pièce jointe <button - @click="remove_attachment_formset(form.dataKey)" + @click="removeAttachmentFormset(form.dataKey)" class="ui small compact right floated icon button remove-formset" type="button" > @@ -27,33 +23,28 @@ :name="form.title.html_name" :id="form.title.id_for_label" v-model="form.title.value" - @blur="updateStore" + @change="updateStore" /> {{ form.title.errors }} </div> <div class="required field"> <label>Fichier (PDF, PNG, JPEG)</label> - <!-- // todo : mettre en place la sélection de fichier --> <label - @click="selectFile" class="ui icon button" - :for="form.attachment_file.id_for_label" + :for="'attachment_file' + attachmentForm.dataKey" > <i class="file icon"></i> <span v-if="form.attachment_file.value" class="label">{{ form.attachment_file.value }}</span> - <span v-else class="label">Sélectionner un fichier ...</span> + <span v-else class="label">Sélectionner un fichier ... </span> </label> - <!-- // todo: récupérer la valeur :accept="IMAGE_FORMAT" --> - <!-- @change="processImgData" --> <input + @change="onFileChange" type="file" style="display: none" :name="form.attachment_file.html_name" - class="image_file" - :id="form.attachment_file.id_for_label" - @blur="updateStore" + :id="'attachment_file' + attachmentForm.dataKey" /> {{ form.attachment_file.errors }} </div> @@ -64,7 +55,7 @@ name="form.info.html_name" rows="5" v-model="form.info.value" - @blur="updateStore" + @change="updateStore" ></textarea> {{ form.info.errors }} </div> @@ -81,6 +72,7 @@ export default { data() { return { + fileToImport: null, form: { title: { errors: null, @@ -94,7 +86,6 @@ export default { }, attachment_file: { errors: null, - id_for_label: "titre", html_name: "titre", label: "Titre", value: "", @@ -107,22 +98,50 @@ export default { }, }; }, + + watch: { + attachmentForm(newValue) { + this.initForm(newValue); + }, + }, + methods: { - remove_attachment_formset() { + initForm(attachmentForm) { + for (let el in attachmentForm) { + if (el && this.form[el]) { + this.form[el].value = + el === "attachment_file" + ? attachmentForm[el].split("/").pop() //* keep only the file name, not the path + : attachmentForm[el]; + } + } + }, + removeAttachmentFormset() { this.$store.commit( "feature/REMOVE_ATTACHMENT_FORM", this.attachmentForm.dataKey ); }, - selectFile() {}, updateStore() { this.$store.commit("feature/UPDATE_ATTACHMENT_FORM", { dataKey: this.attachmentForm.dataKey, title: this.form.title.value, attachment_file: this.form.attachment_file.value, info: this.form.info.value, + fileToImport: this.fileToImport, }); }, + onFileChange(e) { + const files = e.target.files || e.dataTransfer.files; + if (!files.length) return; + this.fileToImport = files[0]; //* store file to import + this.form.attachment_file.value = files[0].name; //* add name to the form for display, in order to match format return from API + this.updateStore(); + }, + }, + + mounted() { + this.initForm(this.attachmentForm); }, }; </script> \ No newline at end of file diff --git a/src/services/feature-api.js b/src/services/feature-api.js new file mode 100644 index 0000000000000000000000000000000000000000..0968431c33f4e194ecc4ca04a3ed7ad4e02b9203 --- /dev/null +++ b/src/services/feature-api.js @@ -0,0 +1,37 @@ +import axios from 'axios'; +import store from '../store' + +const baseUrl = store.state.configuration.VUE_APP_DJANGO_API_BASE; + +const featureAPI = { + async getFeatureAttachments(featureId) { + const response = await axios.get( + `${baseUrl}features/${featureId}/attachments/` + ); + if ( + response.status === 200 && + response.data + ) { + return response.data; + } else { + return null; + } + }, + + /* async subscribeProject({ projectSlug, suscribe }) { + const response = await axios.put( + `${baseUrl}projects/${projectSlug}/subscription/`, + { is_suscriber: suscribe } + ); + if ( + response.status === 200 && + response.data + ) { + return response.data; + } else { + return null; + } + }, */ +} + +export default featureAPI; diff --git a/src/store/modules/feature.js b/src/store/modules/feature.js index 07c33eef2583583f5aa911af5b58dc63bef7d752..df638cf266dfce9e82646000d260747c0adbc853 100644 --- a/src/store/modules/feature.js +++ b/src/store/modules/feature.js @@ -27,8 +27,8 @@ const feature = { SET_EXTRA_FORM(state, extra_form) { state.extra_form = extra_form; }, - ADD_ATTACHMENT_FORM(state, dataKey) { - state.attachmentFormset = [...state.attachmentFormset, { dataKey }]; + ADD_ATTACHMENT_FORM(state, attachmentFormset) { + state.attachmentFormset = [...state.attachmentFormset, attachmentFormset]; }, UPDATE_ATTACHMENT_FORM(state, payload) { const index = state.attachmentFormset.findIndex((el) => el.dataKey === payload.dataKey); @@ -37,6 +37,9 @@ const feature = { REMOVE_ATTACHMENT_FORM(state, payload) { state.attachmentFormset = state.attachmentFormset.filter(form => form.dataKey !== payload); }, + CLEAR_ATTACHMENT_FORM(state) { + state.attachmentFormset = []; + }, ADD_LINKED_FORM(state, dataKey) { state.linkedFormset = [...state.linkedFormset, { dataKey }]; }, @@ -55,19 +58,21 @@ const feature = { axios .get(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${project_slug}/feature/`) .then((response) => { - const features = response.data.features; - commit("SET_FEATURES", features); - //dispatch("map/ADD_FEATURES", null, { root: true }); //todo: should check if map was initiated + if (response.status === 200 && response.data) { + const features = response.data.features; + commit("SET_FEATURES", features); + //dispatch("map/ADD_FEATURES", null, { root: true }); //todo: should check if map was initiated + } }) .catch((error) => { throw error; }); }, - POST_FEATURE({ state, rootState }, routeName) { - let extraFormOject = {}; + SEND_FEATURE({ state, rootState, dispatch }, routeName) { + let extraFormObject = {}; //* prepare an object to be flatten in properties of geojson for (const field of state.extra_form) { - extraFormOject[field.name] = field.value; + extraFormObject[field.name] = field.value; } const geojson = { "id": state.form.feature_id, @@ -79,7 +84,7 @@ const feature = { "status": state.form.status.value, "project": rootState.project_slug, "feature_type": rootState.feature_type.current_feature_type_slug, - ...extraFormOject + ...extraFormObject } } @@ -87,7 +92,9 @@ const feature = { axios .put(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/${state.form.feature_id}/`, geojson) .then((response) => { - console.log(response, response.data) + if (response.status === 200 && response.data) { + console.log(response, response.data) + } }) .catch((error) => { throw error; @@ -96,28 +103,42 @@ const feature = { axios .post(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/`, geojson) .then((response) => { - console.log(response, response.data) + if (response.status === 201 && response.data) { + console.log(response, response.data) + dispatch("SEND_ATTACHMENTS", response.data.id) + } }) .catch((error) => { throw error; }); } }, + + SEND_ATTACHMENTS({ state }, featureId) { + for (let attacht of state.attachmentFormset) { + let formdata = new FormData(); + formdata.append("file", attacht.fileToImport, attacht.fileToImport.name); + const data = { + title: attacht.title, + info: attacht.info, + } + formdata.append("data", JSON.stringify(data)); + axios + .post(`${this.state.configuration.VUE_APP_DJANGO_API_BASE}features/${featureId}/attachments/`, formdata) + .then((response) => { + if (response.status === 200 && response.data) { + console.log(response, response.data) + return "La pièce jointe a bien été ajouté" + } + }) + .catch((error) => { + throw error; + }); + } + } //DELETE_FEATURE({ state }, feature_slug) { //console.log("Deleting feature:", feature_slug, state) - /* axios - .post(`${DJANGO_API_BASE}feature_type/`, data) - .then((response) => { - const routerHistory = router.options.routerHistory - commit("SET_USER", response.data.user); - router.push(routerHistory[routerHistory.length - 1] || "/") - dispatch("GET_USER_LEVEL_PROJECTS"); - }) - .catch(() => { - commit("SET_USER", false) - }); */ - // }, // POST_COMMENT({ state }, data) { //console.log("post comment", data, state) diff --git a/src/store/modules/feature_type.js b/src/store/modules/feature_type.js index b1c65d094d405450babbad5a0a782fca292f318f..1f0ced836f3359f7db4a331bba40fe3bc1c202b7 100644 --- a/src/store/modules/feature_type.js +++ b/src/store/modules/feature_type.js @@ -122,7 +122,7 @@ const feature_type = { } }, - POST_FEATURES_FROM_GEOJSON({ state, dispatch }, payload) { + SEND_FEATURES_FROM_GEOJSON({ state, dispatch }, payload) { const { feature_type_slug } = payload if (state.fileToImport.size > 0) { diff --git a/src/views/feature/Feature_detail.vue b/src/views/feature/Feature_detail.vue index 42eeec306a63a0e53b82e4f47796c02a5bfecdf8..37883fdb5fd2b49e24967cc22335c5c6374e35ab 100644 --- a/src/views/feature/Feature_detail.vue +++ b/src/views/feature/Feature_detail.vue @@ -181,20 +181,21 @@ <a class="ui tiny image" target="_blank" - :href="pj.attachment_file.url" + :href="DJANGO_BASE_URL + pj.attachment_file" > <img - v-if="pj.extension === '.pdf'" - src="{% static 'geocontrib/img/pdf.png' %}" + :src=" + pj.extension === '.pdf' + ? require('@/assets/img/pdf.png') + : DJANGO_BASE_URL + pj.attachment_file + " /> - <!-- // ? que faire ? --> - <img v-else :src="pj.attachment_file.url" /> </a> <div class="middle aligned content"> <a class="header" target="_blank" - :href="pj.attachment_file.url" + :href="DJANGO_BASE_URL + pj.attachment_file" >{{ pj.title }}</a > <div class="description"> @@ -274,10 +275,6 @@ method="POST" enctype="multipart/form-data" > - <!-- action="{% url 'geocontrib:add_comment' slug=feature.project.slug feature_type_slug=feature.feature_type.slug feature_id=feature.feature_id%}" --> - <!-- {% for hidden in comment_form.hidden_fields %} - {{ hidden }} - {% endfor %} --> <div v-if="comment_form.non_field_errors" class="alert alert-danger" @@ -382,6 +379,7 @@ import frag from "vue-frag"; import { mapState } from "vuex"; import { mapUtil } from "@/assets/js/map-util.js"; +import featureAPI from "@/services/feature-api"; const axios = require("axios"); export default { @@ -407,18 +405,8 @@ export default { }, }, */ ], - attachments: [ - // TODO : Récupérer depuis l'api - /* { - attachment_file: { - url: "http://localhost:8000/media/user_1/albinoscom.jpg", - }, - extension: "jpg", - title: "albinos", - info: "Drôle de bête", - }, */ - ], - // TODO : Récupérer depuis l'api + attachments: [], + // TODO : Récupérer events depuis l'api events: [], comment_form: { attachment_file: { @@ -444,6 +432,9 @@ export default { computed: { ...mapState(["user"]), + DJANGO_BASE_URL: function () { + return this.$store.state.configuration.VUE_APP_DJANGO_BASE; + }, feature: function () { return ( this.$store.state.feature.features.find( @@ -564,6 +555,9 @@ export default { "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG", this.$route.params.slug_type_signal ); + featureAPI + .getFeatureAttachments(this.$route.params.slug_signal) + .then((data) => (this.attachments = data)); }, mounted() { diff --git a/src/views/feature/Feature_edit.vue b/src/views/feature/Feature_edit.vue index aa803f96591f2cdddc4758b2485a18985b6652c1..0ef149bec65dde7066ed12658feec198ed81e2d3 100644 --- a/src/views/feature/Feature_edit.vue +++ b/src/views/feature/Feature_edit.vue @@ -65,7 +65,8 @@ <!-- Import GeoImage --> <div v-frag v-if="feature_type && feature_type.geom_type === 'point'"> <p> - <button @click="showGeoRef=true;" + <button + @click="showGeoRef = true" id="add-geo-image" type="button" class="ui compact button" @@ -75,25 +76,44 @@ 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> + <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> </div> <p v-if="showGeoPositionBtn"> - <button @click="create_point_geoposition()" + <button + @click="create_point_geoposition()" id="create-point-geoposition" type="button" class="ui compact button" @@ -102,13 +122,18 @@ signalement à partir de votre géolocalisation </button> </p> - <span id="erreur-geolocalisation" v-if="erreurGeolocalisationMessage"> + <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> + <p id="erreur-geolocalisation-message"> + {{ erreurGeolocalisationMessage }} + </p> </div> <br /> </span> @@ -130,7 +155,7 @@ "geocontrib/map-layers/sidebar-layers.html" with basemaps=serialized_base_maps layers=serialized_layers project=project.slug%} {% endif %} --> - <SidebarLayers v-if="baseMaps && map" /> + <SidebarLayers v-if="basemaps && map" /> </div> </div> @@ -207,6 +232,7 @@ 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"; @@ -231,12 +257,12 @@ export default { data() { return { - map:null, - file:null, - showGeoRef:false, - showGeoPositionBtn:true, - erreurGeolocalisationMessage:null, - erreurUploadMessage:null, + map: null, + file: null, + showGeoRef: false, + showGeoPositionBtn: true, + erreurGeolocalisationMessage: null, + erreurUploadMessage: null, attachmentDataKey: 0, linkedDataKey: 0, statusChoices: [ @@ -282,6 +308,7 @@ export default { computed: { ...mapState(["project"]), + ...mapState("map", ["basemaps"]), ...mapState("feature", [ "attachmentFormset", "linkedFormset", @@ -344,51 +371,52 @@ export default { } }, create_point_geoposition() { - function success(position) { - const latitude = position.coords.latitude - const longitude = position.coords.longitude + const latitude = position.coords.latitude; + const longitude = position.coords.longitude; - var layer = L.circleMarker([latitude, longitude]) - this.add_layer_call_back(layer) + 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 = err.message; } - this.erreurGeolocalisationMessage=null; + this.erreurGeolocalisationMessage = null; if (!navigator.geolocation) { - this.erreurGeolocalisationMessage="La géolocalisation n'est pas supportée par votre navigateur."; + this.erreurGeolocalisationMessage = + "La géolocalisation n'est pas supportée par votre navigateur."; } else { - navigator.geolocation.getCurrentPosition(success.bind(this), error.bind(this)); + 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); + console.log(">>>> 1st element in files array >>>> ", this.file); }, - georeferencement(){ + 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!!'); + 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!!'; + console.log("FAILURE!!"); + self.erreurUploadMessage = "FAILURE!!"; }); }, @@ -409,10 +437,26 @@ export default { }, add_attachement_formset() { - this.$store.commit("feature/ADD_ATTACHMENT_FORM", this.attachmentDataKey); // * create an object with the counter in store + this.$store.commit("feature/ADD_ATTACHMENT_FORM", { + dataKey: this.attachmentDataKey, + }); // * create an object with the counter in store this.attachmentDataKey += 1; // * increment counter for key in v-for }, + addExistingAttachementFormset(attachementFormset) { + for (const attachment of attachementFormset) { + console.log("attachment", attachment); + this.$store.commit("feature/ADD_ATTACHMENT_FORM", { + dataKey: this.attachmentDataKey, + title: attachment.title, + attachment_file: attachment.attachment_file, + info: attachment.info, + id: attachment.id, + }); + this.attachmentDataKey += 1; + } + }, + 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 @@ -431,7 +475,7 @@ export default { postForm() { if (this.form.title.value) { this.form.title.errors = null; - this.$store.dispatch("feature/POST_FEATURE", this.currentRouteName); + this.$store.dispatch("feature/SEND_FEATURE", this.currentRouteName); } else { this.form.title.errors = "Veuillez compléter ce champ."; } @@ -614,8 +658,8 @@ export default { this.drawControlFull.addTo(this.map); this.updateGeomField(""); if (geomType === "point") { - this.showGeoPositionBtn=true; - this.erreurGeolocalisationMessage=""; + this.showGeoPositionBtn = true; + this.erreurGeolocalisationMessage = ""; } }.bind(this) ); @@ -687,15 +731,11 @@ export default { 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) { @@ -706,8 +746,8 @@ export default { //this.updateGeomField(wellknown.stringify(layer.toGeoJSON())) this.updateGeomField(layer.toGeoJSON()); if (this.feature_type.geomType === "point") { - this.showGeoPositionBtn=false; - this.erreurGeolocalisationMessage=""; + this.showGeoPositionBtn = false; + this.erreurGeolocalisationMessage = ""; } }, }, @@ -720,6 +760,15 @@ export default { "feature_type/SET_CURRENT_FEATURE_TYPE_SLUG", this.$route.params.slug_type_signal ); + // todo : mutualize in store with feature_detail.vue + + if (this.$route.params.slug_signal) { + featureAPI + .getFeatureAttachments(this.$route.params.slug_signal) + .then((data) => this.addExistingAttachementFormset(data)); + } else { //* be sure that previous attachemntFormset has been cleared for creation + this.$store.commit("feature/CLEAR_ATTACHMENT_FORM"); + } }, mounted() { diff --git a/src/views/feature_type/Feature_type_detail.vue b/src/views/feature_type/Feature_type_detail.vue index a595e6ea9760b71202e6fd9ad3ad5227858cc54f..cd18f5ec2fa0f784d0cf5452bbaef8081649ba32 100644 --- a/src/views/feature_type/Feature_type_detail.vue +++ b/src/views/feature_type/Feature_type_detail.vue @@ -242,7 +242,7 @@ export default { }, importGeoJson() { - this.$store.dispatch("feature_type/POST_FEATURES_FROM_GEOJSON", { + this.$store.dispatch("feature_type/SEND_FEATURES_FROM_GEOJSON", { slug: this.$route.params.slug, feature_type_slug: this.$route.params.feature_type_slug, fileToImport: this.fileToImport, diff --git a/src/views/feature_type/Feature_type_edit.vue b/src/views/feature_type/Feature_type_edit.vue index 7b198c7f03a260048a4fbe23e2d451ec315cc14b..8b81dd4148a43bd90cb330e00373e11d8654a387 100644 --- a/src/views/feature_type/Feature_type_edit.vue +++ b/src/views/feature_type/Feature_type_edit.vue @@ -371,7 +371,7 @@ export default { postFeatures(feature_type_slug) { this.$store - .dispatch("feature_type/POST_FEATURES_FROM_GEOJSON", { + .dispatch("feature_type/SEND_FEATURES_FROM_GEOJSON", { slug: this.$route.params.slug, feature_type_slug, })