<template> <div id="project-edit" class="page" > <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="two 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" v-model="form.moderation" class="hidden" type="checkbox" name="moderation" > <label for="moderation">Modération</label> </div> </div> <div class="field"> <div class="ui checkbox"> <input id="is_project_type" v-model="form.is_project_type" class="hidden" type="checkbox" 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" v-model="form.generate_share_link" class="hidden" type="checkbox" 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, 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', ]), ...mapState('projects', ['project']), DJANGO_BASE_URL: function () { return this.$store.state.configuration.VUE_APP_DJANGO_BASE; }, levelPermissions(){ const levels = new Array(); if(this.levelsPermissions) { this.levelsPermissions.forEach((item) => { if (item.user_type_id !== 'super_contributor') { levels.push({ name: this.translateRoleToFrench(item.user_type_id), value: item.user_type_id, }); } if (!this.form.moderation && item.user_type_id == 'moderator') { levels.pop(); } }); } return levels; }, levelPermissionsPub(){ const levels = new Array(); if (this.levelsPermissions) { this.levelsPermissions.forEach((item) => { if ( item.user_type_id !== 'super_contributor' && item.user_type_id !== 'admin' && item.user_type_id !== 'moderator' ) { levels.push({ name: this.translateRoleToFrench(item.user_type_id), value: item.user_type_id, }); } }); } return levels; } }, watch: { 'form.moderation': function (newValue){ if(!newValue && this.form.access_level_arch_feature.value === 'moderator') { 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') { if (!this.project) { this.$store.dispatch('projects/GET_PROJECT', this.$route.params.slug) .then((projet) => { if (projet) { this.fillProjectForm(); } }); } else { 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'; } }, translateRoleToFrench(role){ switch (role) { case 'admin': return 'Administrateur projet'; case 'moderator': return 'Modérateur'; case 'contributor': return 'Contributeur'; case 'logged_user': return 'Utilisateur connecté'; case 'anonymous': return 'Utilisateur anonyme'; } }, truncate(n, len) { const 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) { const url = window.URL || window.webkitURL; const 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 const 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) { const 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() { this.form = { ...this.project }; //* create a new object to avoid modifying original one if (this.action === 'create_from') { //* if duplication of project, generate new name 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) { const 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) { const 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>