<template> <div class="fourteen wide column"> <div :class="{active: loading}" class="ui inverted dimmer" > <div class="ui text loader"> Projet en cours de création. Vous allez être redirigé. </div> </div> <form id="form-project-edit" class="ui form"> <h1> <span v-if="action === 'edit'" >Édition du projet "{{ form.title }}"</span > <span v-else-if="action === 'create'">Création d'un projet</span> </h1> <div class="ui horizontal divider">INFORMATIONS</div> <div class="two fields"> <div class="required field"> <label for="title">Titre</label> <input type="text" required maxlength="128" name="title" id="title" v-model="form.title" /> <ul id="errorlist-title" class="errorlist"> <li v-for="error in errors.title" :key="error"> {{ error }} </li> </ul> </div> <div class="field"> <label>Illustration du projet</label> <img class="ui small image" id="form-input-file-logo" :src=" thumbnailFileSrc ? thumbnailFileSrc : DJANGO_BASE_URL + form.thumbnail " /> <label class="ui icon button" for="thumbnail"> <i class="file icon"></i> <span class="label">{{ form.thumbnail_name ? form.thumbnail_name : fileToImport.name }}</span> </label> <input @change="onFileChange" class="file-selection" type="file" accept="image/jpeg, image/png" style="display: none" name="thumbnail" id="thumbnail" /> <ul v-if="errorThumbnail.length" id="errorlist-thumbnail" class="errorlist" > <li> {{ errorThumbnail[0] }} </li> </ul> </div> </div> <div class="field"> <label for="description">Description</label> <textarea v-model="form.description" name="description" rows="5" ></textarea> <!-- {{ form.description.errors }} --> </div> <div class="ui horizontal divider">PARAMÈTRES</div> <div class="four fields"> <div class="field"> <label for="archive_feature">Délai avant archivage</label> <div class="ui right labeled input"> <input type="number" min="0" oninput="validity.valid||(value=0);" style="padding: 1px 2px" name="archive_feature" id="archive_feature" @blur="checkEmpty" v-model="form.archive_feature" /> <div class="ui label">jour(s)</div> </div> <ul v-if="errors_archive_feature.length" id="errorlist-achivage" class="errorlist" > <li> {{ errors_archive_feature[0] }} </li> </ul> </div> <div class="field"> <label for="delete_feature">Délai avant suppression</label> <div class="ui right labeled input"> <input type="number" min="0" oninput="validity.valid||(value=0);" style="padding: 1px 2px" name="delete_feature" id="delete_feature" @blur="checkEmpty" v-model="form.delete_feature" /> <div class="ui label">jour(s)</div> </div> </div> <div class="required field"> <label for="access_level_pub_feature" >Visibilité des signalements publiés</label > <Dropdown :options="levelPermissions" :selected="form.access_level_pub_feature.name" :selection.sync="form.access_level_pub_feature" /> <ul id="errorlist-access_level_pub_feature" class="errorlist"> <li v-for="error in errors.access_level_pub_feature" :key="error"> {{ error }} </li> </ul> </div> <div class="required field"> <label for="access_level_arch_feature"> Visibilité des signalements archivés </label> <Dropdown :options="levelPermissions" :selected="form.access_level_arch_feature.name" :selection.sync="form.access_level_arch_feature" /> <ul id="errorlist-access_level_arch_feature" class="errorlist"> <li v-for="error in errors.access_level_arch_feature" :key="error"> {{ error }} </li> </ul> </div> </div> <div class="field"> <div class="ui checkbox"> <input type="checkbox" v-model="form.moderation" name="moderation" id="moderation" /> <label for="moderation">Modération</label> </div> <!-- {{ form.moderation.errors }} --> </div> <div class="field"> <div class="ui checkbox"> <input type="checkbox" v-model="form.is_project_type" name="is_project_type" id="is_project_type" /> <label for="is_project_type">Est un projet type</label> </div> <!-- {{ form.is_project_type.errors }} --> </div> <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> </template> <script> const axios = require("axios"); import Dropdown from "@/components/Dropdown.vue"; import { mapGetters } from "vuex"; 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"); export default { name: "Project_edit", components: { Dropdown, }, data() { return { loading: false, action: "create", levelPermissions: [ { name: "Utilisateur anonyme", value: "anonymous" }, { name: "Utilisateur connecté", value: "logged_user" }, { name: "Contributeur", value: "contributor" }, ], fileToImport: { name: "Sélectionner une image ...", size: 0, }, errors_archive_feature: [], errors: { title: [], access_level_pub_feature: [], access_level_arch_feature: [], }, errorThumbnail: [], form: { title: "", slug: "", created_on: "", updated_on: "", description: "", moderation: false, thumbnail: "", // todo : utiliser l'image par défaut thumbnail_name: "", // todo: delete after getting image in jpg or png instead of data64 (require post to django) creator: null, access_level_pub_feature: { name: "", value: "" }, access_level_arch_feature: { name: "", value: "" }, archive_feature: 0, delete_feature: 0, nb_features: 0, nb_published_features: 0, nb_comments: 0, nb_published_features_comments: 0, nb_contributors: 0, is_project_type: false, }, thumbnailFileSrc: "", }; }, computed: { ...mapGetters(["project"]), DJANGO_BASE_URL: function () { return this.$store.state.configuration.VUE_APP_DJANGO_BASE; }, }, methods: { definePageType() { if (this.$router.history.current.name === "project_create") { this.action = "create"; } else if (this.$router.history.current.name === "project_edit") { this.action = "edit"; } else if (this.$router.history.current.name === "project_create_from") { this.action = "create_from"; } }, truncate(n, len) { let ext = n.substring(n.lastIndexOf(".") + 1, n.length).toLowerCase(); let filename = n.replace("." + ext, ""); if (filename.length <= len) { return n; } filename = filename.substr(0, len) + (n.length > len ? "[...]" : ""); return filename + "." + ext; }, onFileChange(e) { // * read image file const files = e.target.files || e.dataTransfer.files; if (!files.length) return; //* abort if no file this.fileToImport = files[0]; //* stock the file to post later let reader = new FileReader(); //* read the file to display in the page let _this = this; //* 'this' is different in onload function reader.onload = function (e) { _this.thumbnailFileSrc = e.target.result; }; reader.readAsDataURL(this.fileToImport); }, checkEmpty() { //* forbid empty fields if (!this.form.archive_feature) this.form.archive_feature = 0; if (!this.form.delete_feature) this.form.delete_feature = 0; }, goBackNrefresh(slug) { Promise.all([ this.$store.dispatch("GET_USER_LEVEL_PERMISSIONS"), //* refresh projects permissions this.$store.dispatch("GET_ALL_PROJECTS"), //* & refresh project list ]).then(() => // * go back to project list this.$router.push({ name: "project_detail", params: { slug }, }) ); }, postProjectThumbnail(projectSlug) { //* send img to the backend when feature_type is created let formData = new FormData(); formData.append("file", this.fileToImport); const url = this.$store.state.configuration.VUE_APP_DJANGO_API_BASE + "projects/" + projectSlug + "/thumbnail/"; return axios .put(url, formData, { headers: { "Content-Type": "multipart/form-data", }, }) .then((response) => { if (response && response.status === 200) { this.goBackNrefresh(projectSlug); } }) .catch((error) => { let err_msg = "Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu."; if (error.response.data[0]) err_msg = error.response.data[0]; this.errorThumbnail.push(err_msg); throw error; }); }, checkForm() { if (this.form.archive_feature > this.form.delete_feature) { this.errors_archive_feature.push( "Le délais de suppression doit être supérieur au délais d'archivage." ); return false; } for (const key in this.errors) { if ((key === "title" && this.form[key]) || this.form[key].value) { this.errors[key] = []; } else if (!this.errors[key].length) { this.errors[key].push( key === "title" ? "Veuillez compléter ce champ." : "Sélectionnez un choix valide. Ce choix ne fait pas partie de ceux disponibles." ); document .getElementById(`errorlist-${key}`) .scrollIntoView({ block: "end", inline: "nearest" }); return false; } } return true; }, async postForm() { if (!this.checkForm()) return; const projectData = { title: this.form.title, description: this.form.description, access_level_arch_feature: this.form.access_level_arch_feature.value, access_level_pub_feature: this.form.access_level_pub_feature.value, archive_feature: this.form.archive_feature, delete_feature: this.form.delete_feature, is_project_type: this.form.is_project_type, moderation: this.form.moderation, }; if (this.action === "create" || this.action === "duplicate") { this.loading = true; await axios .post( `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`, projectData ) .then((response) => { if (response && response.status === 201 && response.data) { //* send thumbnail after feature_type was created if (this.fileToImport.size > 0) { this.postProjectThumbnail(response.data.slug); } else { this.goBackNrefresh(response.data.slug); } } this.loading = false; }) .catch((error) => { if (error.response.data.title[0]) { this.errors.title.push(error.response.data.title[0]); } this.loading = false; throw error; }); } else if (this.action === "edit") { await axios .put( `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.project.slug}/`, projectData ) .then((response) => { if (response && response.status === 200) { //* send thumbnail after feature_type was updated if (this.fileToImport.size > 0) { this.postProjectThumbnail(this.project.slug); } else { this.goBackNrefresh(this.project.slug); } } }) .catch((error) => { if (error.response.data.title[0]) { this.errors.title.push(error.response.data.title[0]); } throw error; }); } }, }, created() { this.definePageType(); console.log(this.action); if (this.action === "create") { this.thumbnailFileSrc = require("@/assets/img/default.png"); } else if (this.action === "edit" || this.action === "create_from") { if (!this.project) { this.$store.dispatch("GET_PROJECT_INFO", this.$route.params.slug); } this.form = { ...this.project }; //* create a new object to avoid modifying original one if (this.action === "create_from") { this.form.title = this.project.title + ` (Copie-${new Date() .toLocaleString() .slice(0, -3) .replace(",", "")})`; this.form.is_project_type = false; } //* transform string values to objects for dropdowns display (could be in a computed) this.form.access_level_pub_feature = { name: this.project.access_level_pub_feature, value: this.levelPermissions.find( (el) => (el.name = this.project.access_level_pub_feature) ).value, }; this.form.access_level_arch_feature = { name: this.project.access_level_arch_feature, value: this.levelPermissions.find( (el) => (el.name = this.project.access_level_arch_feature) ).value, }; } }, }; </script> <style media="screen"> #form-input-file-logo { margin-left: auto; margin-right: auto; } .close.icon:hover { cursor: pointer; } </style>