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 file-logo">
Sébastien DA ROCHA
committed
<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="levelPermissionsArc"
Sébastien DA ROCHA
committed
: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>
<a
class="
ui
small
button
circular
compact
absolute-right
icon
teal
"
data-tooltip="Consulter la documentation"
data-position="right center"
data-variation="mini"
href="https://geocontrib.readthedocs.io/fr/latest/documentation_fonctionnelle/feature_editing/"
target="_blank"
rel="noopener"
>
<i class="question icon" />
</a>
<div class="fields grouped">
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
<div class="field">
<label for="feature_browsing">Configuration du parcours de signalement</label>
</div>
<div
id="feature_browsing_filter"
class="field inline"
>
<label for="feature_browsing_default_filter">Filtrer sur</label>
<Dropdown
:options="featureBrowsingOptions.filter"
:selected="form.feature_browsing_default_filter.name"
:selection.sync="form.feature_browsing_default_filter"
/>
</div>
<div
id="feature_browsing_sort"
class="field inline"
>
<label for="feature_browsing_default_sort">Trier par</label>
<Dropdown
:options="featureBrowsingOptions.sort"
:selected="form.feature_browsing_default_sort.name"
:selection.sync="form.feature_browsing_default_sort"
/>
</div>
</div>
Sébastien DA ROCHA
committed
<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: [],
},
featureBrowsingOptions: {
filter: [{
name: 'Désactivé',
value: ''
},
{
name: 'Type de signalement',
value: 'feature_type_slug',
}],
sort: [{
name: 'Date de création',
value: '-created_on',
},
{
name: 'Date de modification',
value: '-updated_on'
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,
feature_browsing_default_filter: '',
feature_browsing_default_sort: '-created_on'
Sébastien DA ROCHA
committed
},
'1:500 000 000',
'1:250 000 000',
'1:150 000 000',
'1:70 000 000',
'1:35 000 000',
'1:15 000 000',
'1:10 000 000',
'1:4 000 000',
'1:2 000 000',
'1:1 000 000',
'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,
feature_browsing_default_sort: this.form.feature_browsing_default_sort.value,
feature_browsing_default_filter: this.form.feature_browsing_default_filter.value,
Sébastien DA ROCHA
committed
};
Sébastien DA ROCHA
committed
await axios
.put((`${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}v2/projects/${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;
});
let url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}v2/projects/`;
url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.project.slug}/duplicate/`;
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;
});
}
},
//* create a new object to avoid modifying original one
this.form = { ...this.project };
//* if duplication of project, generate new name
if (this.action === 'create_from') {

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;
}
//* transform string values to objects used with dropdowns
// fill dropdown current selection for archived feature viewing permission
if (this.levelPermissionsArc) {
const accessLevelArc = this.levelPermissionsArc.find(
(el) => el.name === this.project.access_level_arch_feature
if (accessLevelArc) {
this.form.access_level_arch_feature = {
name: this.project.access_level_arch_feature,
value: accessLevelArc.value ,
// fill dropdown current selection for published feature viewing permission
if (this.levelPermissionsPub) {
const accessLevelPub = this.levelPermissionsPub.find(
(el) => el.name === this.project.access_level_pub_feature
if (accessLevelPub) {
this.form.access_level_pub_feature = {
name: this.project.access_level_pub_feature,
value: accessLevelPub.value ,
// fill dropdown current selection for feature browsing default filtering
const default_filter = this.featureBrowsingOptions.filter.find(
(el) => el.value === this.project.feature_browsing_default_filter
);
if (default_filter) {
this.form.feature_browsing_default_filter = default_filter;
}
// fill dropdown current selection for feature browsing default sorting
const default_sort = this.featureBrowsingOptions.sort.find(
(el) => el.value === this.project.feature_browsing_default_sort
);
if (default_sort) {
this.form.feature_browsing_default_sort = default_sort;
}
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;
}
.file-logo {
min-height: calc(150px + 2.4285em);
display: flex;
flex-direction: column;
justify-content: space-between;
}
Sébastien DA ROCHA
committed
.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;
}
.checkboxes {
padding-left: .5em;
.absolute-right.ui.compact.icon.button {
position: absolute;
right: -2.75em;
top: calc(50% - 1em);
padding: .4em;
i.icon, i.icons {
font-size: .9em;
}
}
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
}
.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;
}
}
}
label[for=feature_browsing] {
padding-left: 2em;
}
label[for=feature_browsing_default_filter],
label[for=feature_browsing_default_sort] {
min-width: 4em;
}
#feature_browsing_filter,
#feature_browsing_sort {
margin-left: 2.5rem;
@media only screen and (min-width: 1100px) {
#feature_browsing_filter {
margin-top: -2.25em;
}
#feature_browsing_filter,
#feature_browsing_sort {
float: right;
}