<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 id="title" v-model="form.title" type="text" required maxlength="128" name="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 id="form-input-file-logo" class="ui small image" :src=" thumbnailFileSrc ? thumbnailFileSrc : DJANGO_BASE_URL + form.thumbnail " > <label class="ui icon button" for="thumbnail" > <i class="file icon" /> <span class="label">{{ form.thumbnail_name ? form.thumbnail_name : fileToImport.name }}</span> </label> <input id="thumbnail" class="file-selection" type="file" accept="image/jpeg, image/png" style="display: none" name="thumbnail" @change="onFileChange" > <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" /> <!-- {{ 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 id="archive_feature" v-model="form.archive_feature" type="number" min="0" oninput="validity.valid||(value=0);" style="padding: 1px 2px" name="archive_feature" @blur="checkEmpty" > <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 id="delete_feature" v-model="form.delete_feature" type="number" min="0" oninput="validity.valid||(value=0);" style="padding: 1px 2px" name="delete_feature" @blur="checkEmpty" > <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="levelPermissionsPub" :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 id="moderation" class="hidden" type="checkbox" v-model="form.moderation" name="moderation" > <label for="moderation">Modération</label> </div> </div> <div class="field"> <div class="ui checkbox"> <input id="is_project_type" class="hidden" type="checkbox" v-model="form.is_project_type" name="is_project_type" > <label for="is_project_type">Est un projet type</label> </div> </div> <div class="field"> <div class="ui checkbox"> <input id="generate_share_link" class="hidden" type="checkbox" v-model="form.generate_share_link" name="generate_share_link" /> <label for="generate_share_link">Génération d'un lien de partage externe</label> </div> </div> <div class="ui divider" /> <button type="button" class="ui teal icon button" @click="postForm" > <i class="white save icon" /> Enregistrer les changements </button> </form> </div> </template> <script> import axios from '@/axios-client.js'; import Dropdown from '@/components/Dropdown.vue'; import { mapState, mapGetters, mapActions } from "vuex"; export default { name: 'ProjectEdit', components: { Dropdown, }, data() { return { loading: false, action: 'create', 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, generate_share_link: false, }, thumbnailFileSrc: '', }; }, computed: { ...mapState([ 'levelsPermissions', ]), ...mapGetters(['project']), DJANGO_BASE_URL: function () { return this.$store.state.configuration.VUE_APP_DJANGO_BASE; }, levelPermissions(){ let self = this; let levels = new Array(); if(self.levelsPermissions){ self.levelsPermissions.map(function(item) { if (item.user_type_id != 'super_contributor') levels.push({ name: self.traslateRoleToFrench(item.user_type_id), value: item.user_type_id, }); if (!self.form.moderation && item.user_type_id == 'moderator'){ levels.pop(); } }); } return levels; }, levelPermissionsPub(){ let self = this; let levels = new Array(); if(self.levelsPermissions){ self.levelsPermissions.map(function(item) { if (item.user_type_id != 'super_contributor' && item.user_type_id != 'admin' && item.user_type_id != 'moderator'){ levels.push({ name: self.traslateRoleToFrench(item.user_type_id), value: item.user_type_id, }); } }); } return levels; } }, watch: { 'form.moderation': function (newValue){ if(!newValue){ this.form.access_level_pub_feature = { name: '', value: '' }; this.form.access_level_arch_feature = { name: '', value: '' }; } } }, created() { this.definePageType(); if (this.action === 'create') { this.thumbnailFileSrc = require('@/assets/img/default.png'); } else if (this.action === 'edit' || this.action === 'create_from') { this.fillProjectForm(); } }, methods: { ...mapActions('projects', [ 'GET_ALL_PROJECTS' ]), 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'; } }, traslateRoleToFrench(role){ if (role == 'admin') return 'Administrateur projet'; if (role == 'moderator') return 'Modérateur'; if (role == 'contributor') return 'Contributeur'; if (role == 'logged_user') return 'Utilisateur connecté'; if (role == 'anonymous') return 'Utilisateur anonyme'; }, 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; }, validateImgFile(files, handleFile) { let url = window.URL || window.webkitURL; let image = new Image(); image.onload = function () { handleFile(true); URL.revokeObjectURL(image.src); }; image.onerror = function () { handleFile(false); URL.revokeObjectURL(image.src); }; image.src = url.createObjectURL(files); }, onFileChange(e) { // * read image file const files = e.target.files || e.dataTransfer.files; const _this = this; //* 'this' is different in onload function function handleFile(isValid) { if (isValid) { _this.fileToImport = files[0]; //* store the file to post later let reader = new FileReader(); //* read the file to display in the page reader.onload = function (e) { _this.thumbnailFileSrc = e.target.result; }; reader.readAsDataURL(_this.fileToImport); _this.errorThumbnail = []; } else { _this.errorThumbnail.push( "Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu." ); } } if (files.length) { //* check if file is an image and pass callback to handle file this.validateImgFile(files[0], handleFile); } }, 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_PROJECTS"), //* refresh projects user levels this.$store.dispatch("GET_USER_LEVEL_PERMISSIONS"), //* refresh projects permissions this.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 if (this.fileToImport) { 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, generate_share_link: this.form.generate_share_link, moderation: this.form.moderation, }; let url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`; if (this.action === 'edit') { await axios .put((url += `${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 && error.response.data.title[0]) { this.errors.title.push(error.response.data.title[0]); } throw error; }); } else { if (this.action === 'create_from') { url += `${this.project.slug}/duplicate/`; } this.loading = true; await axios .post(url, 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 && error.response.data.title[0]) { this.errors.title.push(error.response.data.title[0]); } this.loading = false; throw error; }); } }, fillProjectForm() { 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) if(this.levelPermissionsPub){ let value = {}; value = this.levelPermissionsPub.find( (el) => el.name === this.project.access_level_pub_feature ); if(value){ this.form.access_level_pub_feature = { name: this.project.access_level_pub_feature, value: value.value , }; } } if(this.levelPermissions){ let value = {}; value = this.levelPermissions.find( (el) => el.name === this.project.access_level_arch_feature ); if(value){ this.form.access_level_arch_feature = { name: this.project.access_level_arch_feature, value: value.value , }; } } }, }, }; </script> <style media="screen"> #form-input-file-logo { margin-left: auto; margin-right: auto; } .close.icon:hover { cursor: pointer; } </style>