Newer
Older
Sébastien DA ROCHA
committed
<template>

Timothee P
committed
<div id="project-edit">
<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"
>
Sébastien DA ROCHA
committed
<h1>
<span
v-if="action === 'edit'"
>Édition du projet "{{ form.title }}"</span>
Sébastien DA ROCHA
committed
<span v-else-if="action === 'create'">Création d'un projet</span>
</h1>
<div class="ui horizontal divider">
INFORMATIONS
</div>
Sébastien DA ROCHA
committed
<div class="two fields">
<div class="required field">
<label for="title">Titre</label>
<input
Sébastien DA ROCHA
committed
type="text"
required
maxlength="128"
name="title"
>
<ul
id="errorlist-title"
class="errorlist"
>
<li
v-for="error in errors.title"
:key="error"
>
Sébastien DA ROCHA
committed
</div>
<div class="field">
<label>Illustration du projet</label>
<img
v-if="thumbnailFileSrc.length || form.thumbnail.length"
Sébastien DA ROCHA
committed
id="form-input-file-logo"
Sébastien DA ROCHA
committed
:src="
thumbnailFileSrc
? thumbnailFileSrc
: DJANGO_BASE_URL + form.thumbnail
"
>
<label
class="ui icon button"
for="thumbnail"
>
Sébastien DA ROCHA
committed
<span class="label">{{
form.thumbnail_name ? form.thumbnail_name : fileToImport.name
}}</span>
</label>
<input
Sébastien DA ROCHA
committed
class="file-selection"
type="file"
accept="image/jpeg, image/png"
style="display: none"
name="thumbnail"
<ul
v-if="errorThumbnail.length"
id="errorlist-thumbnail"
class="errorlist"
>
Sébastien DA ROCHA
committed
</div>
</div>
<div class="two fields">
<div class="field">
<label for="description">Description</label>
<textarea
id="editor"
v-model="form.description"
data-preview="#preview"
name="description"
rows="5"
/>
<!-- {{ form.description.errors }} -->
</div>
<div class="field">
<label for="preview">Aperçu</label>
<div
id="preview"
class="description preview"
name="preview"
/>
</div>
Sébastien DA ROCHA
committed
</div>
<div class="ui horizontal divider">
PARAMÈTRES
</div>
Sébastien DA ROCHA
committed
<div class="two fields">
<div
id="published-visibility"
class="required field"
>
<label
for="access_level_pub_feature"
>Visibilité des signalements publiés</label>
Sébastien DA ROCHA
committed
<Dropdown
Sébastien DA ROCHA
committed
: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"
>
Sébastien DA ROCHA
committed
</div>
<div
id="archived-visibility"
class="required field"
>
Sébastien DA ROCHA
committed
<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"
>
Sébastien DA ROCHA
committed
</div>
</div>
<div class="fields grouped checkboxes">
<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>
Sébastien DA ROCHA
committed
<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="field">
<div class="ui checkbox">
<input
id="fast_edition_mode"
v-model="form.fast_edition_mode"
class="hidden"
type="checkbox"
name="fast_edition_mode"
>
<label for="fast_edition_mode">Mode d'édition rapide de signalements</label>
</div>
</div>
Sébastien DA ROCHA
committed
<div class="field required">
<label>Niveau de zoom maximum de la carte</label>
<div class="map-maxzoom-selector">
<div class="range-container">
<input
v-model="form.map_max_zoom_level"
type="range"
min="0"
max="22"
step="1"
@input="zoomMap"
><output class="range-output-bubble">{{
scalesTable[form.map_max_zoom_level]
}}</output>
</div>
<div class="map-preview">
<label>Aperçu :</label>
<div
id="map"
ref="map"
/>
<div class="no-preview">
pas de fond de carte disponible à cette échelle
</div>
</div>

Timothee P
committed
</div>
</div>
Sébastien DA ROCHA
committed
id="send-project"
type="button"
class="ui teal icon button"
@click="postForm"
>
<i
class="white save icon"
aria-hidden="true"
/> Enregistrer les changements
Sébastien DA ROCHA
committed
</button>
</form>
</div>
</template>
<script>
import Dropdown from '@/components/Dropdown.vue';
import mapService from '@/services/map-service';
Sébastien DA ROCHA
committed
import TextareaMarkdown from 'textarea-markdown';
import { mapActions, mapState } from 'vuex';
Sébastien DA ROCHA
committed
export default {
Sébastien DA ROCHA
committed
components: {
Dropdown,
},
data() {
return {
Sébastien DA ROCHA
committed
fileToImport: {
Sébastien DA ROCHA
committed
size: 0,
},
errors: {
title: [],
access_level_pub_feature: [],
access_level_arch_feature: [],
},
Sébastien DA ROCHA
committed
form: {
title: '',
slug: '',
created_on: '',
updated_on: '',
description: '',
Sébastien DA ROCHA
committed
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)
Sébastien DA ROCHA
committed
creator: null,
access_level_pub_feature: { name: '', value: '' },
access_level_arch_feature: { name: '', value: '' },
Sébastien DA ROCHA
committed
archive_feature: 0,
delete_feature: 0,
Sébastien DA ROCHA
committed
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,

Timothee P
committed
fast_edition_mode: false,
Sébastien DA ROCHA
committed
},
scalesTable: [
'1:500 Mio',
'1:250 Mio',
'1:150 Mio',
'1:70 Mio',
'1:35 Mio',
'1:15 Mio',
'1:10 Mio',
'1:4 Mio',
'1:2 Mio',
'1:1 Mio',
'1:500 000',
'1:250 000',
'1:150 000',
'1:70 000',
'1:35 000',
'1:15 000',
'1:8 000',
'1:4 000',
'1:2 000',
'1:1 000',
'1:500',
'1:250',
'1:150',
]
Sébastien DA ROCHA
committed
};
},
computed: {
...mapState('projects', ['project']),
Sébastien DA ROCHA
committed
return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
},
const levels = new Array();
if(this.levelsPermissions) {
this.levelsPermissions.forEach((item) => {
name: this.translateRoleToFrench(item.user_type_id),
}
if (!this.form.moderation && item.user_type_id == 'moderator') {
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'
) {
name: this.translateRoleToFrench(item.user_type_id),
Sébastien DA ROCHA
committed
},
watch: {
'form.moderation': function (newValue){
if(!newValue && this.form.access_level_arch_feature.value === 'moderator') {
this.form.access_level_arch_feature = { name: '', value: '' };
}
}
},
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) => {
});
} else {
this.fillProjectForm();
}
let textarea = document.querySelector('textarea');
new TextareaMarkdown(textarea);
},
Sébastien DA ROCHA
committed
methods: {
...mapActions('map', [
'INITIATE_MAP'
]),
Sébastien DA ROCHA
committed
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';
Sébastien DA ROCHA
committed
}
},
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';
}
Sébastien DA ROCHA
committed
truncate(n, len) {
const ext = n.substring(n.lastIndexOf('.') + 1, n.length).toLowerCase();
Sébastien DA ROCHA
committed
if (filename.length <= len) {
return n;
}
filename = filename.substr(0, len) + (n.length > len ? '[...]' : '');
return filename + '.' + ext;
Sébastien DA ROCHA
committed
},
const url = window.URL || window.webkitURL;
const image = new Image();
image.onload = function () {
handleFile(true);
};
image.onerror = function () {
handleFile(false);
};
image.src = url.createObjectURL(files);
},
Sébastien DA ROCHA
committed
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);
}
Sébastien DA ROCHA
committed
},
if (!this.form.archive_feature) {
this.form.archive_feature = 0;
}
if (!this.form.delete_feature) {
this.form.delete_feature = 0;
}
Sébastien DA ROCHA
committed
goBackNrefresh(slug) {
this.$store.dispatch('GET_USER_LEVEL_PROJECTS'), //* refresh projects user levels
this.$store.dispatch('GET_USER_LEVEL_PERMISSIONS'), //* refresh projects permissions

Florent Lavelle
committed
this.$store.dispatch('projects/GET_PROJECT', slug), //* refresh current project
]).then(() =>
// * go back to project list
this.$router.push({
Sébastien DA ROCHA
committed
params: { slug },
Sébastien DA ROCHA
committed
);
},
postProjectThumbnail(projectSlug) {
//* send img to the backend when feature_type is created
if (this.fileToImport) {
formData.append('file', this.fileToImport);
const url =
this.$store.state.configuration.VUE_APP_DJANGO_API_BASE +
return axios
.put(url, formData, {
headers: {
},
})
.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;
});
}
Sébastien DA ROCHA
committed
},
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."
);
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;
},
Sébastien DA ROCHA
committed
async postForm() {
Sébastien DA ROCHA
committed
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,
map_max_zoom_level: this.form.map_max_zoom_level,
Sébastien DA ROCHA
committed
is_project_type: this.form.is_project_type,
generate_share_link: this.form.generate_share_link,

Timothee P
committed
fast_edition_mode: this.form.fast_edition_mode,
Sébastien DA ROCHA
committed
moderation: this.form.moderation,
};
let url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/`;
Sébastien DA ROCHA
committed
Sébastien DA ROCHA
committed
await axios
.put((url += `${this.project.slug}/`), projectData)
Sébastien DA ROCHA
committed
.then((response) => {
if (response && response.status === 200) {
//* send thumbnail after feature_type was updated
Sébastien DA ROCHA
committed
}
})
.catch((error) => {
if (error.response && error.response.data.title[0]) {
this.errors.title.push(error.response.data.title[0]);
}
Sébastien DA ROCHA
committed
throw error;
});
url += `${this.project.slug}/duplicate/`;
}
this.loading = true;
Sébastien DA ROCHA
committed
await axios
Sébastien DA ROCHA
committed
.then((response) => {
if (response && response.status === 201 && response.data) {
//* send thumbnail after feature_type was created
Sébastien DA ROCHA
committed
}
Sébastien DA ROCHA
committed
})
.catch((error) => {
if (error.response && error.response.data.title[0]) {
this.errors.title.push(error.response.data.title[0]);
}
Sébastien DA ROCHA
committed
throw error;
});
}
},

Timothee P
committed
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

Timothee P
committed
this.form.title =
this.project.title +
` (Copie-${new Date()
.toLocaleString()
.slice(0, -3)

Timothee P
committed
this.form.is_project_type = false;
}
Sébastien DA ROCHA
committed
//* transform string values to objects for dropdowns display (could be in a computed)
if (this.levelPermissionsPub) {
(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) {
(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 ,
};
}
}
this.initPreviewMap();
},
initPreviewMap () {
const map = mapService.getMap();
if (map) mapService.destroyMap();
this.INITIATE_MAP({
el: this.$refs.map,
zoom: this.project ? this.project.map_max_zoom_level : 22,
center: this.$store.state.configuration.MAP_PREVIEW_CENTER,
maxZoom: 22,
controls: [],
zoomControl: false,
});
zoomMap() {
mapService.zoom(this.form.map_max_zoom_level);
}
Sébastien DA ROCHA
committed
};
</script>
<style media="screen" lang="less">
Sébastien DA ROCHA
committed
#form-input-file-logo {
margin-left: auto;
margin-right: auto;
}
.close.icon:hover {
cursor: pointer;
}
textarea {
height: 10em;
}
.description.preview {
height: 10em;
overflow: scroll;
border: 1px solid rgba(34, 36, 38, .15);
padding: .78571429em 1em;
}
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
.checkboxes {
padding-left: .5em;
}
.map-maxzoom-selector {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
input, output {
height: fit-content;
}
output {
white-space: nowrap;
min-width: auto;
}
.range-container {
margin-bottom: 2rem;
}
.map-preview {
margin-top: -1rem;
display: flex;
position: relative;
label {
white-space: nowrap;
font-size: .95em;
margin-right: 1rem;
}
#map {
min-height: 80px;
height: 80px;
width: 150px;
max-width: 150px;
z-index: 1;
}
.no-preview {
position: absolute;
top: 25%;
left: 25%;
text-align: center;
font-size: .75em;
color: #656565;
}
}
}