<template> <div class="project-header ui grid stackable"> <div class="row"> <div class="three wide middle aligned column"> <div class="margin-bottom"> <img class="ui small centered image" alt="Thumbnail du projet" :src=" project.thumbnail.includes('default') ? require('@/assets/img/default.png') : DJANGO_BASE_URL + project.thumbnail + refreshId() " > </div> <div class="centered"> <div class="ui basic teal label tiny-margin" data-tooltip="Membres" > <i class="user icon" aria-hidden="true" />{{ project.nb_contributors }} </div> <div class="ui basic teal label tiny-margin" data-tooltip="Signalements publiés" > <i class="map marker icon" aria-hidden="true" />{{ project.nb_published_features }} </div> <div class="ui basic teal label tiny-margin" data-tooltip="Commentaires" > <i class="comment icon" aria-hidden="true" />{{ project.nb_published_features_comments }} </div> </div> </div> <div class="nine wide column"> <h1 class="ui header margin-bottom"> {{ project.title }} </h1> <div class="sub header"> <!-- {{ project.description }} --> <div id="preview" /> <textarea id="editor" v-model="project.description" data-preview="#preview" hidden /> </div> </div> <div class="four wide column right-column"> <div class="ui icon right compact buttons"> <a v-if=" user && permissions && permissions.can_view_project && isOnline " id="subscribe-button" class="ui button button-hover-green tiny-margin" data-tooltip="Gérer mon abonnement au projet" data-position="bottom center" data-variation="mini" @click="OPEN_PROJECT_MODAL('subscribe')" > <i class="inverted grey envelope icon" aria-hidden="true" /> </a> <router-link v-if=" permissions && permissions.can_update_project && isOnline " id="edit-project" :to="{ name: 'project_edit', params: { slug } }" class="ui button button-hover-orange tiny-margin" data-tooltip="Modifier le projet" data-position="bottom center" data-variation="mini" > <i class="inverted grey pencil alternate icon" aria-hidden="true" /> </router-link> <a v-if="isProjectAdmin && isOnline" id="delete-button" class="ui button button-hover-red tiny-margin" data-tooltip="Supprimer le projet" data-position="bottom right" data-variation="mini" @click="OPEN_PROJECT_MODAL('deleteProject')" > <i class="inverted grey trash icon" aria-hidden="true" /> </a> <div v-if="isProjectAdmin && !isSharedProject" id="share-button" class="ui dropdown button compact tiny-margin" data-tooltip="Partager le projet" data-position="bottom right" data-variation="mini" @click="toggleShareOptions" > <i class="inverted grey share icon" aria-hidden="true" /> <div :class="['menu left transition', {'visible': showShareOptions}]" style="z-index: 9999" > <div v-if="project.generate_share_link" class="item" @click="copyLink" > Copier le lien de partage </div> <div class="item" @click="copyCode" > Copier le code du Web Component </div> </div> </div> </div> <Transition> <div v-if="confirmMsg" class="ui positive tiny-margin message" > <span> Le lien a été copié dans le presse-papier </span> <i class="close icon" aria-hidden="true" @click="confirmMsg = false" /> </div> </Transition> </div> <div v-if="arraysOffline.length > 0" class="centered" > {{ arraysOffline.length }} modification<span v-if="arraysOffline.length > 1">s</span> en attente <button :disabled="!isOnline" class="ui fluid labeled teal icon button" @click="sendOfflineFeatures" > <i class="upload icon" aria-hidden="true" /> Envoyer au serveur </button> </div> </div> </div> </template> <script> import TextareaMarkdown from 'textarea-markdown'; import { mapState, mapGetters, mapMutations } from 'vuex'; import featureAPI from '@/services/feature-api'; export default { name: 'ProjectHeader', props: { arraysOffline: { type: Array, default: () => { return []; } } }, data() { return { slug: this.$route.params.slug, confirmMsg: false, showShareOptions: false, }; }, computed: { ...mapState('projects', [ 'project' ]), ...mapState([ 'configuration', ]), ...mapState([ 'user', 'user_permissions', 'isOnline', ]), ...mapGetters([ 'permissions' ]), DJANGO_BASE_URL() { return this.configuration.VUE_APP_DJANGO_BASE; }, isProjectAdmin() { return this.user_permissions && this.user_permissions[this.slug] && this.user_permissions[this.slug].is_project_administrator; }, isSharedProject() { return this.$route.path.includes('projet-partage'); }, }, mounted() { let textarea = document.querySelector('textarea'); new TextareaMarkdown(textarea); window.addEventListener('mousedown', this.clickOutsideDropdown); }, destroyed() { window.removeEventListener('mousedown', this.clickOutsideDropdown); }, methods: { ...mapMutations('modals', [ 'OPEN_PROJECT_MODAL' ]), refreshId() { const crypto = window.crypto || window.msCrypto; var array = new Uint32Array(1); return '?ver=' + crypto.getRandomValues(array); // Compliant for security-sensitive use cases }, toggleShareOptions() { this.confirmMsg = false; this.showShareOptions = !this.showShareOptions; }, clickOutsideDropdown(e) { // If the user click outside of the dropdown, close it if (!e.target.closest('#share-button')) { this.showShareOptions = false; } }, copyLink() { const sharedLink = window.location.href.replace('projet', 'projet-partage'); navigator.clipboard.writeText(sharedLink).then(()=> { this.confirmMsg = true; setTimeout(() => { this.confirmMsg = false; }, 15000); }, (e) => console.error('Failed to copy link: ', e)); }, copyCode() { // Including <script> directly within template literals cause the JavaScript parser to raise syntax errors. // The only working workaround, but ugly, is to split and concatenate the <script> tag. const webComponent = ` <!-- Pour modifier la police, ajoutez l'attribut "font" avec le nom de la police souhaitée (par exemple: font="'Roboto Condensed', Lato, 'Helvetica Neue'"). --> <!-- Dans le cas où la police souhaitée ne serait pas déjà disponible dans la page affichant le web component, incluez également une balise <style> pour l'importer. --> <style>@import url('https://fonts.googleapis.com/css?family=Roboto Condensed:400,700,400italic,700italic&subset=latin');</style> <scr` + `ipt src="${this.configuration.VUE_APP_DJANGO_BASE}/geocontrib/static/wc/project-preview.js"></scr` + `ipt> <project-preview domain="${this.configuration.VUE_APP_DJANGO_BASE}" project-slug="${this.project.slug}" color="${this.configuration.VUE_APP_PRIMARY_COLOR}" font="${this.configuration.VUE_APP_FONT_FAMILY}" width="" ></project-preview>`; navigator.clipboard.writeText(webComponent).then(()=> { this.confirmMsg = true; setTimeout(() => { this.confirmMsg = false; }, 15000); }, (e) => console.error('Failed to copy link: ', e)); }, sendOfflineFeatures() { this.arraysOfflineErrors = []; const promises = this.arraysOffline.map((feature) => featureAPI.postOrPutFeature({ data: feature.geojson, feature_id: feature.featureId, project__slug: feature.project, feature_type__slug: feature.geojson.properties.feature_type, method: feature.type.toUpperCase(), }) .then((response) => { if (!response) { this.arraysOfflineErrors.push(feature); } }) .catch((error) => { console.error(error); this.arraysOfflineErrors.push(feature); }) ); this.$store.commit('DISPLAY_LOADER', 'Envoi des signalements en cours.'); Promise.all(promises).then(() => { this.$emit('updateLocalStorage'); this.$emit('retrieveInfo'); this.$store.commit('DISCARD_LOADER'); }); }, } }; </script> <style lang="less" scoped> .project-header { .row .right-column { display: flex; flex-direction: column; .ui.buttons { justify-content: flex-end; .ui.button { flex-grow: 0; /* avoid stretching buttons */ } } } .centered { margin: auto; text-align: center; } .ui.dropdown > .left.menu { display: block; overflow: hidden; opacity: 0; max-height: 0; &.transition { transition: all .5s ease; } &.visible { opacity: 1; max-height: 6em; } .menu { margin-right: 0 !important; .item { white-space: nowrap; } } } .v-enter-active, .v-leave-active { transition: opacity .5s ease; } .v-enter-from, .v-leave-to { opacity: 0; } } #preview { max-height: 10em; overflow-y: scroll; } @media screen and (max-width: 767px) { .middle.aligned.column { text-align: center; } } </style>