diff --git a/src/App.vue b/src/App.vue index af053b2d87e67b5a9deebc44bcec4e30cd73fdf9..e69824047105b97d90a13045d55c99e14c1611cc 100644 --- a/src/App.vue +++ b/src/App.vue @@ -65,7 +65,7 @@ <router-link v-if=" - project && + project && isOnline && (user.is_administrator || user.is_superuser || isAdmin) " :to="{ @@ -78,7 +78,7 @@ </router-link> <router-link v-if=" - project && + project && isOnline && (user.is_administrator || user.is_superuser || isAdmin) " :to="{ @@ -92,8 +92,12 @@ <div class="mobile"> <router-link + :is="isOnline ? 'router-link' : 'span'" v-if="user" - :to="{name: 'my_account', params: { slug: $route.params.slug ? $route.params.slug : '-' }}" + :to="{ + name: 'my_account', + params: { slug: isSharedProject && $route.params.slug ? $route.params.slug : null } + }" class="item" > {{ userFullname || user.username || "Utilisateur inconnu" }} @@ -145,8 +149,12 @@ <div class="desktop flex push-right-desktop"> <router-link + :is="isOnline ? 'router-link' : 'span'" v-if="user" - :to="{name: 'my_account', params: { slug: $route.params.slug ? $route.params.slug : '-' }}" + :to="{ + name: 'my_account', + params: { slug: isSharedProject && $route.params.slug ? $route.params.slug : null } + }" class="item" > {{ userFullname || user.username || "Utilisateur inconnu" }} @@ -291,6 +299,9 @@ export default { 'projects', 'project', ]), + ...mapState([ + 'isOnline', + ]), APPLICATION_NAME() { return this.configuration.VUE_APP_APPLICATION_NAME; }, diff --git a/src/assets/styles/base.css b/src/assets/styles/base.css index 5e5a50703d8e64157be1f60c5f476588c9a67753..f77d223bc0e28657f22ab8017e15c4ed3f490920 100644 --- a/src/assets/styles/base.css +++ b/src/assets/styles/base.css @@ -32,6 +32,12 @@ main { .important-flex { display: flex !important; } +.pointer:hover { + cursor: pointer; +} +.dimmer-anchor { + position: relative; +} /* ---------------------------------- */ /* MAIN */ /* ---------------------------------- */ diff --git a/src/components/Pagination.vue b/src/components/Pagination.vue index d0f730bab725b56ccaab46e4c02835e7c197da65..2afc85dfff08547813bd8d477c290efe1632ddd3 100644 --- a/src/components/Pagination.vue +++ b/src/components/Pagination.vue @@ -8,7 +8,7 @@ > <a class="page-link" - href="#" + :href="currentLocation" @click="page -= 1" > <i class="ui icon big angle left" /> @@ -26,7 +26,7 @@ > <a class="page-link" - href="#" + :href="currentLocation" @click="changePage(index)" > {{ index }} @@ -45,7 +45,7 @@ > <a class="page-link" - href="#" + :href="currentLocation" @click="page = index" > {{ index }} @@ -58,7 +58,7 @@ > <a class="page-link" - href="#" + :href="currentLocation" @click="page += 1" > <i class="ui icon big angle right" /> @@ -89,7 +89,8 @@ export default { data() { return { - page: 1 + page: 1, + currentLocation: window.location.origin + window.location.pathname + '#', }; }, diff --git a/src/components/feature/FeatureListMassToggle.vue b/src/components/feature/FeatureListMassToggle.vue new file mode 100644 index 0000000000000000000000000000000000000000..02aaa775df28a1cb77c6dbbf47ff1111b31a5e16 --- /dev/null +++ b/src/components/feature/FeatureListMassToggle.vue @@ -0,0 +1,48 @@ +<template> + <div + class="switch-buttons pointer" + :data-tooltip="`Passer en mode ${massMode === 'modify' ? 'suppression':'édition'}`" + @click="switchMode" + > + <div><i :class="['icon pencil', {disabled: massMode !== 'modify'}]" /></div> + <span class="grey">| </span> + <div><i :class="['icon trash', {disabled: massMode !== 'delete'}]" /></div> + </div> +</template> + +<script> +import { mapMutations, mapState } from 'vuex'; +export default { + name: 'FeatureListMassToggle', + + computed: { + ...mapState('feature', ['massMode']) + }, + + methods: { + ...mapMutations('feature', [ + 'TOGGLE_MASS_MODE', + 'UPDATE_CHECKED_FEATURES', + 'UPDATE_CLICKED_FEATURES']), + + switchMode() { + this.TOGGLE_MASS_MODE(this.massMode === 'modify' ? 'delete' : 'modify'); + this.UPDATE_CLICKED_FEATURES([]); + this.UPDATE_CHECKED_FEATURES([]); + } + }, +}; +</script> + +<style scoped> +.switch-buttons { + display: flex; + justify-content: center; + align-items: baseline; +} + +.grey { + color: #bbbbbb; +} + +</style> \ No newline at end of file diff --git a/src/components/feature/FeatureListTable.vue b/src/components/feature/FeatureListTable.vue index fe00d41609a0292f08ef064b0494760a691d6665..e892519aba5b3f83ce0ea1a3a27770028f3cc58f 100644 --- a/src/components/feature/FeatureListTable.vue +++ b/src/components/feature/FeatureListTable.vue @@ -1,312 +1,310 @@ <template> - <div - data-tab="list" - class="dataTables_wrapper no-footer" - > - <table - id="table-features" - class="ui compact table dataTable" + <div> + <div class="table-mobile-buttons left-align"> + <FeatureListMassToggle /> + </div> + <div + data-tab="list" + class="dataTables_wrapper no-footer" > - <thead> - <tr> - <th class="dt-center"> - <div - class="switch-buttons pointer" - :data-tooltip="`Passer en mode ${mode === 'modify' ? 'suppression':'édition'}`" - @click="switchMode" - > - <div><i :class="['icon pencil', {disabled: mode !== 'modify'}]" /></div> - <span class="grey">| </span> - <div><i :class="['icon trash', {disabled: mode !== 'delete'}]" /></div> - </div> - </th> - - <th class="dt-center"> - <div - class="pointer" - @click="changeSort('status')" - > - Statut - <i - :class="{ - down: isSortedAsc('status'), - up: isSortedDesc('status'), - }" - class="icon sort" - /> - </div> - </th> - <th class="dt-center"> - <div - class="pointer" - @click="changeSort('feature_type')" - > - Type - <i - :class="{ - down: isSortedAsc('feature_type'), - up: isSortedDesc('feature_type'), - }" - class="icon sort" - /> - </div> - </th> - <th class="dt-center"> - <div - class="pointer" - @click="changeSort('title')" + <table + id="table-features" + class="ui compact table unstackable dataTable" + > + <thead> + <tr> + <th class="dt-center"> + <FeatureListMassToggle /> + </th> + + <th class="dt-center"> + <div + class="pointer" + @click="changeSort('status')" + > + Statut + <i + :class="{ + down: isSortedAsc('status'), + up: isSortedDesc('status'), + }" + class="icon sort" + /> + </div> + </th> + <th class="dt-center"> + <div + class="pointer" + @click="changeSort('feature_type')" + > + Type + <i + :class="{ + down: isSortedAsc('feature_type'), + up: isSortedDesc('feature_type'), + }" + class="icon sort" + /> + </div> + </th> + <th class="dt-center"> + <div + class="pointer" + @click="changeSort('title')" + > + Nom + <i + :class="{ + down: isSortedAsc('title'), + up: isSortedDesc('title'), + }" + class="icon sort" + /> + </div> + </th> + <th class="dt-center"> + <div + class="pointer" + @click="changeSort('updated_on')" + > + Dernière modification + <i + :class="{ + down: isSortedAsc('updated_on'), + up: isSortedDesc('updated_on'), + }" + class="icon sort" + /> + </div> + </th> + <th + v-if="user" + class="dt-center" > - Nom - <i - :class="{ - down: isSortedAsc('title'), - up: isSortedDesc('title'), - }" - class="icon sort" - /> - </div> - </th> - <th class="dt-center"> - <div - class="pointer" - @click="changeSort('updated_on')" + <div + class="pointer" + @click="changeSort('display_creator')" + > + Auteur + <i + :class="{ + down: isSortedAsc('display_creator'), + up: isSortedDesc('display_creator'), + }" + class="icon sort" + /> + </div> + </th> + <th + v-if="user" + class="dt-center" > - Dernière modification - <i - :class="{ - down: isSortedAsc('updated_on'), - up: isSortedDesc('updated_on'), - }" - class="icon sort" - /> - </div> - </th> - <th - v-if="user" - class="dt-center" + <div + class="pointer" + @click="changeSort('display_last_editor')" + > + Dernier éditeur + <i + :class="{ + down: isSortedAsc('display_last_editor'), + up: isSortedDesc('display_last_editor'), + }" + class="icon sort" + /> + </div> + </th> + </tr> + </thead> + <tbody> + <tr + v-for="(feature, index) in paginatedFeatures" + :key="index" > - <div - class="pointer" - @click="changeSort('display_creator')" - > - Auteur - <i - :class="{ - down: isSortedAsc('display_creator'), - up: isSortedDesc('display_creator'), + <td class="dt-center"> + <div + :class="['ui checkbox', {disabled: !checkRights(feature)}]" + > + <input + :id="feature.id" + v-model="checked" + type="checkbox" + :value="feature.id" + :disabled="!checkRights(feature)" + name="select" + @input="storeClickedFeature(feature)" + > + <label for="select" /> + </div> + </td> + + <td class="dt-center"> + <div v-if="feature.properties.status.value === 'archived'"> + <span data-tooltip="Archivé"> + <i class="grey archive icon" /> + </span> + </div> + <div v-else-if="feature.properties.status.value === 'pending'"> + <span data-tooltip="En attente de publication"> + <i class="teal hourglass outline icon" /> + </span> + </div> + <div v-else-if="feature.properties.status.value === 'published'"> + <span data-tooltip="Publié"> + <i class="olive check icon" /> + </span> + </div> + <div v-else-if="feature.properties.status.value === 'draft'"> + <span data-tooltip="Brouillon"> + <i class="orange pencil alternate icon" /> + </span> + </div> + </td> + <td class="dt-center"> + <router-link + :to="{ + name: 'details-type-signalement', + params: { + feature_type_slug: feature.properties.feature_type.slug, + }, }" - class="icon sort" - /> - </div> - </th> - <th - v-if="user" - class="dt-center" - > - <div - class="pointer" - @click="changeSort('display_last_editor')" - > - Dernier éditeur - <i - :class="{ - down: isSortedAsc('display_last_editor'), - up: isSortedDesc('display_last_editor'), + > + {{ feature.properties.feature_type.title }} + </router-link> + </td> + <td class="dt-center"> + <router-link + :to="{ + name: 'details-signalement', + params: { + slug_type_signal: feature.properties.feature_type.slug, + slug_signal: feature.properties.slug || feature.id, + }, }" - class="icon sort" - /> - </div> - </th> - </tr> - </thead> - <tbody> - <tr - v-for="(feature, index) in paginatedFeatures" - :key="index" - > - <td class="dt-center"> - <div - :class="['ui checkbox', {disabled: !checkRights(feature)}]" - > - <input - :id="feature.id" - v-model="checked" - type="checkbox" - :value="feature.id" - :disabled="!checkRights(feature)" - name="select" - @input="storeClickedFeature(feature)" > - <label for="select" /> - </div> - </td> - - <td class="dt-center"> - <div - v-if="feature.properties.status.value === 'archived'" - data-tooltip="Archivé" - > - <i class="grey archive icon" /> - </div> - <div - v-else-if="feature.properties.status.value === 'pending'" - data-tooltip="En attente de publication" - > - <i class="teal hourglass outline icon" /> - </div> - <div - v-else-if="feature.properties.status.value === 'published'" - data-tooltip="Publié" - > - <i class="olive check icon" /> - </div> - <div - v-else-if="feature.properties.status.value === 'draft'" - data-tooltip="Brouillon" + {{ getFeatureDisplayName(feature) }} + </router-link> + </td> + <td class="dt-center"> + {{ feature.properties.updated_on }} + </td> + <td + v-if="user" + class="dt-center" > - <i class="orange pencil alternate icon" /> - </div> - </td> - <td class="dt-center"> - <router-link - :to="{ - name: 'details-type-signalement', - params: { - feature_type_slug: feature.properties.feature_type.slug, - }, - }" + {{ getUserName(feature) }} + </td> + <td + v-if="user" + class="dt-center" > - {{ feature.properties.feature_type.title }} - </router-link> - </td> - <td class="dt-center"> - <router-link - :to="{ - name: 'details-signalement', - params: { - slug_type_signal: feature.properties.feature_type.slug, - slug_signal: feature.properties.slug || feature.id, - }, - }" - > - {{ getFeatureDisplayName(feature) }} - </router-link> - </td> - <td class="dt-center"> - {{ feature.properties.updated_on }} - </td> - <td - v-if="user" - class="dt-center" - > - {{ getUserName(feature) }} - </td> - <td - v-if="user" - class="dt-center" - > - {{ feature.properties.display_last_editor }} - </td> - </tr> - <tr - v-if="featuresCount === 0" - class="odd" - > - <td - colspan="5" - class="dataTables_empty" - valign="top" + {{ feature.properties.display_last_editor }} + </td> + </tr> + <tr + v-if="featuresCount === 0" + class="odd" > - Aucune donnée disponible - </td> - </tr> - </tbody> - </table> - <div - v-if="pageNumbers.length > 1" - id="table-features_info" - class="dataTables_info" - role="status" - aria-live="polite" - > - Affichage de l'élément {{ pagination.start + 1 }} à - {{ displayedPageEnd }} - sur {{ featuresCount }} éléments - </div> - <div - v-if="pageNumbers.length > 1" - id="table-features_paginate" - class="dataTables_paginate paging_simple_numbers" - > - <a - id="table-features_previous" - :class="[ - 'paginate_button previous', - { disabled: pagination.currentPage === 1 }, - ]" - aria-controls="table-features" - data-dt-idx="0" - tabindex="0" - @click="$emit('update:page', 'previous')" - >Précédent</a> - <span> - <span v-if="pagination.currentPage >= 5"> - <a - key="page1" - class="paginate_button" - aria-controls="table-features" - data-dt-idx="1" - tabindex="0" - @click="$emit('update:page', 1)" - >{{ 1 }}</a> - <span class="ellipsis">…</span> - </span> + <td + colspan="5" + class="dataTables_empty" + valign="top" + > + Aucune donnée disponible + </td> + </tr> + </tbody> + </table> + <div + v-if="pageNumbers.length > 1" + id="table-features_info" + class="dataTables_info" + role="status" + aria-live="polite" + > + Affichage de l'élément {{ pagination.start + 1 }} à + {{ displayedPageEnd }} + sur {{ featuresCount }} éléments + </div> + <div + v-if="pageNumbers.length > 1" + id="table-features_paginate" + class="dataTables_paginate paging_simple_numbers" + > <a - v-for="pageNumber in displayedPageNumbers" - :key="'page' + pageNumber" + id="table-features_previous" :class="[ - 'paginate_button', - { current: pageNumber === pagination.currentPage }, + 'paginate_button previous', + { disabled: pagination.currentPage === 1 }, ]" aria-controls="table-features" - data-dt-idx="1" + data-dt-idx="0" tabindex="0" - @click="$emit('update:page', pageNumber)" - >{{ pageNumber }}</a> - <span v-if="(lastPageNumber - pagination.currentPage) >= 4"> - <span class="ellipsis">…</span> + @click="$emit('update:page', 'previous')" + >Précédent</a> + <span> + <span v-if="pagination.currentPage >= 5"> + <a + key="page1" + class="paginate_button" + aria-controls="table-features" + data-dt-idx="1" + tabindex="0" + @click="$emit('update:page', 1)" + >{{ 1 }}</a> + <span class="ellipsis">…</span> + </span> <a - :key="'page' + lastPageNumber" - class="paginate_button" + v-for="pageNumber in displayedPageNumbers" + :key="'page' + pageNumber" + :class="[ + 'paginate_button', + { current: pageNumber === pagination.currentPage }, + ]" aria-controls="table-features" data-dt-idx="1" tabindex="0" - @click="$emit('update:page', lastPageNumber)" - >{{ lastPageNumber }}</a> + @click="$emit('update:page', pageNumber)" + >{{ pageNumber }}</a> + <span v-if="(lastPageNumber - pagination.currentPage) >= 4"> + <span class="ellipsis">…</span> + <a + :key="'page' + lastPageNumber" + class="paginate_button" + aria-controls="table-features" + data-dt-idx="1" + tabindex="0" + @click="$emit('update:page', lastPageNumber)" + >{{ lastPageNumber }}</a> + </span> </span> - </span> - <a - id="table-features_next" - :class="[ - 'paginate_button next', - { disabled: pagination.currentPage === pageNumbers.length }, - ]" - aria-controls="table-features" - data-dt-idx="7" - tabindex="0" - @click="$emit('update:page', 'next')" - >Suivant</a> + <a + id="table-features_next" + :class="[ + 'paginate_button next', + { disabled: pagination.currentPage === pageNumbers.length }, + ]" + aria-controls="table-features" + data-dt-idx="7" + tabindex="0" + @click="$emit('update:page', 'next')" + >Suivant</a> + </div> </div> </div> </template> <script> +import { mapState, mapGetters, mapMutations } from 'vuex'; +import FeatureListMassToggle from '@/components/feature/FeatureListMassToggle'; -import { mapState, mapGetters } from 'vuex'; export default { name: 'FeatureListTable', + components: { + FeatureListMassToggle, + }, + props: { paginatedFeatures: { type: Array, @@ -320,10 +318,6 @@ export default { type: Array, default: null, }, - clickedFeatures: { - type: Array, - default: null, - }, featuresCount: { type: Number, default: 0, @@ -336,16 +330,13 @@ export default { type: Object, default: null, }, - mode: { - type: String, - default: null, - } }, computed: { ...mapGetters(['permissions']), ...mapState(['user', 'USER_LEVEL_PROJECTS']), ...mapState('projects', ['project']), + ...mapState('feature', ['clickedFeatures', 'massMode']), userStatus() { return this.USER_LEVEL_PROJECTS[this.$route.params.slug]; @@ -390,12 +381,20 @@ export default { }, destroyed() { - this.$store.commit('feature/UPDATE_CHECKED_FEATURES', []); + this.UPDATE_CHECKED_FEATURES([]); }, methods: { + ...mapMutations('feature', [ + 'UPDATE_CLICKED_FEATURES', + 'UPDATE_CHECKED_FEATURES', + ]), + storeClickedFeature(feature) { - this.$emit('update:clickedFeatures', [...this.clickedFeatures, { feature_id: feature.id, feature_type: feature.properties.feature_type.slug }]); + this.UPDATE_CLICKED_FEATURES([ + ...this.clickedFeatures, + { feature_id: feature.id, feature_type: feature.properties.feature_type.slug } + ]); }, canDeleteFeature(feature) { @@ -422,7 +421,7 @@ export default { }, checkRights(feature) { - switch (this.mode) { + switch (this.massMode) { case 'modify': return this.canEditFeature(feature); case 'delete': @@ -430,12 +429,6 @@ export default { } }, - switchMode() { - this.$emit('update:mode', this.mode === 'modify' ? 'delete' : 'modify'); - this.$emit('update:clickedFeatures', []); - this.$store.commit('feature/UPDATE_CHECKED_FEATURES', []); - }, - getUserName(feature) { if (!feature.properties.creator) { return ' ---- '; @@ -559,15 +552,6 @@ table.dataTable th.dt-center, table.dataTable td.dt-center, table.dataTable td.d i.icon.sort:not(.down):not(.up) { color: rgb(220, 220, 220); } -.pointer:hover { - cursor: pointer; -} - -.switch-buttons { - display: flex; - justify-content: center; - align-items: baseline; -} .grey { color: #bbbbbb; @@ -576,13 +560,28 @@ i.icon.sort:not(.down):not(.up) { .ui.dropdown .menu .left.menu, .ui.dropdown > .left.menu .menu { margin-right: 0 !important; } + +.table-mobile-buttons { + margin-bottom: 1em; +} +@media only screen and (min-width: 761px) { + .table-mobile-buttons { + display: none !important; + } +} /* Max width before this PARTICULAR table gets nasty This query will take effect for any screen smaller than 760px and also iPads specifically. */ -@media only screen and (max-width: 760px), - (min-device-width: 768px) and (max-device-width: 1024px) { +@media only screen and (max-width: 760px) { + .table-mobile-buttons { + display: flex !important; + } + /* hide table border */ + .ui.table { + border: none !important; + } /* Force table to not be like tables anymore */ table, thead, @@ -600,8 +599,12 @@ and also iPads specifically. left: -9999px; } - tr { + tr { /* style as a card */ border: 1px solid #ccc; + border-radius: 7px; + margin-bottom: 3vh; + padding: 0 1vw .5em 1vw; + box-shadow: rgba(50, 50, 50, 0.1) 2px 5px 10px ; } td { @@ -611,7 +614,19 @@ and also iPads specifically. position: relative; padding-left: 50%; } - + .ui.table tr td { + border-top: none; + } + .ui.compact.table td { + padding: .2em; + } + td:nth-of-type(1) { + border: none !important; + padding: .25em !important; + } + td:nth-of-type(7) { + border-bottom: none !important; + } td:before { /* Now like a table header */ position: absolute; @@ -622,7 +637,6 @@ and also iPads specifically. padding-right: 10px; white-space: nowrap; } - /* Label the data */ @@ -644,20 +658,29 @@ and also iPads specifically. td:nth-of-type(6):before { content: "Auteur"; } + td:nth-of-type(7):before { + content: "Dernier éditeur"; + } table.dataTable th.dt-center, table.dataTable td.dt-center, table.dataTable td.dataTables_empty { text-align: right; } - #table-features { - margin-left: 1em; - width: calc(100% - 1em); - } - .ui.checkbox { position: absolute; - left: -1.75em; - top: 5em; + left: calc(-1vw - .75em); + top: -.75em; + } + .dataTables_wrapper .dataTables_info, + .dataTables_wrapper .dataTables_paginate { + width: 100%; + text-align: center; + margin: .5em 0; + } +} +@media only screen and (max-width: 410px) { + .ui.table tr td { + border: none; } } </style> \ No newline at end of file diff --git a/src/components/map-layers/SidebarLayers.vue b/src/components/map-layers/SidebarLayers.vue index 2e9cc9d64c89a85ea6897286b05225b44d926502..4ea8b410fad28f2ab7d7c007560ab60fda83e213 100644 --- a/src/components/map-layers/SidebarLayers.vue +++ b/src/components/map-layers/SidebarLayers.vue @@ -1,5 +1,8 @@ <template> - <div :class="['sidebar-container', { expanded }]"> + <div + v-if="isOnline" + :class="['sidebar-container', { expanded }]" + > <!-- <div class="sidebar-layers"></div> --> <div class="layers-icon" @@ -132,7 +135,12 @@ export default { }, computed: { - ...mapState('map', ['availableLayers']), + ...mapState([ + 'isOnline', + ]), + ...mapState('map', [ + 'availableLayers' + ]), }, mounted() { diff --git a/src/main.js b/src/main.js index 2aaa4ad7377d468b804c8deb1513ee05de3a0e6a..c8dd8f15eafac74b7b953c484289b8d7f401381b 100644 --- a/src/main.js +++ b/src/main.js @@ -38,6 +38,9 @@ if(navigator.serviceWorker){ let onConfigLoaded = function(config){ store.commit('SET_CONFIG', config); + setInterval(() => { //* check if navigator is online + store.commit('SET_IS_ONLINE', navigator.onLine); + }, 1000); // set title and favico document.title= config.VUE_APP_APPLICATION_NAME+' '+config.VUE_APP_APPLICATION_ABSTRACT; diff --git a/src/services/feature-api.js b/src/services/feature-api.js index a2278387d8e0f5c1f1bd980a3e133d899253d710..690df05e61decf02e2d0749458d73350a13f5d84 100644 --- a/src/services/feature-api.js +++ b/src/services/feature-api.js @@ -62,6 +62,55 @@ const featureAPI = { } }, + async getFeatureLinks(featureId) { + const response = await axios.get( + `${baseUrl}features/${featureId}/feature-links/` + ); + if ( + response.status === 200 && + response.data + ) { + return response.data; + } else { + return null; + } + }, + + async getFeaturesBlob(url) { + const response = await axios + .get(url, { responseType: 'blob' }); + if ( + response.status === 200 && + response.data + ) { + return response.data; + } else { + return null; + } + }, + // todo : fonction pour faire un post ou un put du signalement + + + async postOrPutFeature({ method, feature_id, feature_type__slug, project__slug, data }) { + let url = `${baseUrl}features/`; + if (method === 'PUT') { + url += `${feature_id}/? + feature_type__slug=${feature_type__slug} + &project__slug=${project__slug}`; + } + + const response = await axios({ + url, + method, + data, + }); + if ((response.status === 200 || response.status === 201) && response.data) { + return response; + } else { + return null; + } + }, + async updateFeature({ feature_id, feature_type__slug, project__slug, newStatus }) { let url = `${baseUrl}features/${feature_id}/?feature_type__slug=${feature_type__slug}&project__slug=${project__slug}`; @@ -108,33 +157,6 @@ const featureAPI = { return null; } }, - - async getFeatureLinks(featureId) { - const response = await axios.get( - `${baseUrl}features/${featureId}/feature-links/` - ); - if ( - response.status === 200 && - response.data - ) { - return response.data; - } else { - return null; - } - }, - - async getFeaturesBlob(url) { - const response = await axios - .get(url, { responseType: 'blob' }); - if ( - response.status === 200 && - response.data - ) { - return response.data; - } else { - return null; - } - }, }; export default featureAPI; diff --git a/src/services/project-api.js b/src/services/project-api.js index f150a81ed390fc9d128cb131201e1cc2fae1a135..5b2e79ee8b58fcec74b863f781cb1e83da6b30a4 100644 --- a/src/services/project-api.js +++ b/src/services/project-api.js @@ -45,21 +45,22 @@ const projectAPI = { } }, - async getProjects(baseUrl, filters, page) { + async getProjects({ baseUrl, filters, page, projectSlug, ismyaccount }) { + let url = `${baseUrl}projects/`; + if (projectSlug) url += `${projectSlug}/`; + url += `?page=${page}`; + if (ismyaccount) { + url += '&ismyaccount'; + } try { - const url = `${baseUrl}projects/?page=${page}`; - - let filteredUrl; if (Object.values(filters).some(el => el && el.length > 0)) { - filteredUrl = url; for (const filter in filters) { if (filters[filter]) { - filteredUrl = filteredUrl.concat('', `&${filter}=${filters[filter]}`); + url = url.concat('', `&${filter}=${filters[filter]}`); } } } - - const response = await axios.get(filteredUrl ? filteredUrl : url); + const response = await axios.get(url); if (response.status === 200 && response.data) { return response.data; } diff --git a/src/store/index.js b/src/store/index.js index 3aa9c61d5fa0359782e1ac3544647c40ab22ee7b..805c0fe99a5e12cc883303ce88bbe294b4c7fa25 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -23,25 +23,29 @@ const noPermissions = { export default new Vuex.Store({ modules, - + state: { - logged: false, - user: false, + cancellableSearchRequest: [], configuration: null, - staticPages: null, - USER_LEVEL_PROJECTS: null, - user_permissions: null, + isOnline: true, levelsPermissions: [], - messages: [], loader: { isLoading: false, message: 'En cours de chargement' }, - cancellableSearchRequest: [], - reloadIntervalId: null + logged: false, + messages: [], + reloadIntervalId: null, + staticPages: null, + user: false, + USER_LEVEL_PROJECTS: null, + user_permissions: null, }, mutations: { + SET_IS_ONLINE(state, payload) { + state.isOnline = payload; + }, SET_USER(state, payload) { state.user = payload; }, @@ -106,7 +110,7 @@ export default new Vuex.Store({ CLEAR_RELOAD_INTERVAL_ID(state) { clearInterval(state.reloadIntervalId); state.reloadIntervalId = null; - } + }, }, getters: { diff --git a/src/store/modules/feature.store.js b/src/store/modules/feature.store.js index d7f5efe7df626de0e77e02ff4370af2b4338aa59..d57046265453d56dbb28da4e8dc4dca686a461a0 100644 --- a/src/store/modules/feature.store.js +++ b/src/store/modules/feature.store.js @@ -1,13 +1,13 @@ import axios from '@/axios-client.js'; import router from '../../router'; - const feature = { namespaced: true, state: { attachmentFormset: [], attachmentsToDelete: [], checkedFeatures: [], + clickedFeatures: [], extra_form: [], features: [], features_count: 0, @@ -15,6 +15,7 @@ const feature = { form: null, linkedFormset: [], linked_features: [], + massMode: 'modify', statusChoices: [ { name: 'Brouillon', @@ -100,7 +101,13 @@ const feature = { }, UPDATE_CHECKED_FEATURES(state, checkedFeatures) { state.checkedFeatures = checkedFeatures; - } + }, + UPDATE_CLICKED_FEATURES(state, clickedFeatures) { + state.clickedFeatures = clickedFeatures; + }, + TOGGLE_MASS_MODE(state, payload) { + state.massMode = payload; + }, }, getters: { }, @@ -235,6 +242,9 @@ const feature = { &project__slug=${rootState.projects.project.slug}`; } + //* postOrPutFeature function from service featureAPI could be used here, but because configuration is in store, + //* projectBase would need to be sent with each function which imply to modify all function from this service, + //* which could create regression return axios({ url, method: routeName === 'editer-signalement' ? 'PUT' : 'POST', @@ -252,7 +262,7 @@ const feature = { }) .catch((error) => { commit('DISCARD_LOADER', null, { root: true }); - if (error.message === 'Network Error' || window.navigator.onLine === false) { + if (error.message === 'Network Error' || !rootState.isOnline) { let arraysOffline = []; let localStorageArray = localStorage.getItem('geocontrib_offline'); if (localStorageArray) { diff --git a/src/store/modules/projects.store.js b/src/store/modules/projects.store.js index e6229cff4e35e2dcbae23a160ed0b9b517596226..ba6754768d2c7e36922acc13d940856da4d46732 100644 --- a/src/store/modules/projects.store.js +++ b/src/store/modules/projects.store.js @@ -1,6 +1,13 @@ import axios from '@/axios-client.js'; import projectAPI from '@/services/project-api'; +const initialFilters = { + moderation: null, + access_level: null, + user_access_level: null, + accessible: null +}; + const projects = { namespaced: true, @@ -8,12 +15,7 @@ const projects = { state: { count: 0, currentPage: 1, - filters: { - moderation: null, - access_level: null, - user_access_level: null, - accessible: null - }, + filters: { ...initialFilters }, isProjectsListSearched: null, last_comments: [], projects: [], @@ -22,11 +24,6 @@ const projects = { searchProjectsFilter: null, }, - getters: { - project_types: state => state.projects.filter(projet => projet.is_project_type), - project_user: state => state.projects.filter(projet => projet.creator === state.user.id), - }, - mutations: { SET_CURRENT_PAGE (state, payload) { state.currentPage = payload; @@ -54,6 +51,10 @@ const projects = { state.filters[payload.filter] = payload.value; }, + RESET_PROJECTS_FILTER(state) { + state.filters = { ...initialFilters }; + }, + SET_PROJECTS_SEARCH_STATE(state, payload) { state.isProjectsListSearched = payload.isSearched; state.searchProjectsFilter = payload.text; @@ -78,13 +79,21 @@ const projects = { } }, - async GET_PROJECTS({ state, rootState, commit }, page) { + async GET_PROJECTS({ state, rootState, commit }, payload) { + let { page, ismyaccount, projectSlug } = payload || {}; if (!page) { page = state.currentPage; } const baseUrl = rootState.configuration.VUE_APP_DJANGO_API_BASE; - const projects = await projectAPI.getProjects(baseUrl, state.filters, page); + const projects = await projectAPI.getProjects({ + baseUrl, + filters : state.filters, + page, + projectSlug, + ismyaccount, + }); commit('SET_PROJECTS', projects); + return; }, async SEARCH_PROJECTS({ commit, dispatch }, text) { @@ -99,7 +108,7 @@ const projects = { } }, - async GET_PROJECT({ rootState, commit }, slug) { + async GET_PROJECT({ rootState, commit }, slug) { // todo : use GET_PROJECTS instead, with slug const baseUrl = rootState.configuration.VUE_APP_DJANGO_API_BASE; const project = await projectAPI.getProject(baseUrl, slug); commit('SET_PROJECT', project); diff --git a/src/views/My_account.vue b/src/views/My_account.vue index 1959ce4516be28b94ffd585199fddc44879a4685..bf6e2886182a555a126e9b0da8c8a7752b6a3058 100644 --- a/src/views/My_account.vue +++ b/src/views/My_account.vue @@ -58,9 +58,9 @@ MES PROJETS </h4> - <div class="ui divided items"> + <div class="ui divided items dimmer-anchor"> <div - v-for="project in availableProjects" + v-for="project in projectsArray" :key="project.slug" class="item" > @@ -133,6 +133,21 @@ </div> </div> </div> + + <div + :class="['ui inverted dimmer', { active: projectsLoading }]" + > + <div class="ui text loader"> + Récupération des projets en cours... + </div> + </div> + <!-- PAGINATION --> + <Pagination + v-if="count" + :nb-pages="Math.ceil(count/10)" + :on-page-change="SET_CURRENT_PAGE" + @change-page="changePage" + /> </div> </div> </div> @@ -308,8 +323,10 @@ <script> import frag from 'vue-frag'; -import { mapState } from 'vuex'; +import { mapActions, mapMutations, mapState } from 'vuex'; import miscAPI from '@/services/misc-api'; +import Pagination from '@/components/Pagination.vue'; + export default { name: 'MyAccount', @@ -318,11 +335,16 @@ export default { frag, }, + components: { + Pagination, + }, + data() { return { events: [], features: [], comments: [], + projectsLoading: true, }; }, @@ -333,9 +355,9 @@ export default { 'user_permissions', ]), - // todo : filter projects to user ...mapState('projects', [ - 'projects' + 'projects', + 'count', ]), DJANGO_BASE_URL() { @@ -352,19 +374,30 @@ export default { return this.$route.path.includes('projet-partage'); }, - availableProjects() { - if (this.isSharedProject) { - return this.projects.filter((el) => el.slug === this.$route.params.slug); - } - return this.projects; + projectsArray() { //* if only one project, only project object is returned + return Array.isArray(this.projects) ? this.projects : [this.projects]; } }, created(){ + this.RESET_PROJECTS_FILTER(); //* empty remaining filters in store + this.SET_PROJECTS([]); //* empty previous project to avoid undefined user_permissions[project.slug] + this.getData(); this.getEvents(); }, methods: { + ...mapActions('projects', [ + 'GET_PROJECTS', + ]), + ...mapMutations('projects', [ + 'SET_PROJECTS', + 'RESET_PROJECTS_FILTER', + ]), + ...mapMutations('projects', [ + 'SET_CURRENT_PAGE', + ]), + refreshId() { return '?ver=' + Math.random(); }, @@ -383,7 +416,18 @@ export default { return url.replace('projet', 'projet-partage'); } return url; - } + }, + + getData(page) { + this.projectsLoading = true; + this.GET_PROJECTS({ ismyaccount: true, projectSlug: this.$route.params.slug, page }) + .then(() => this.projectsLoading = false) + .catch(() => this.projectsLoading = false); + }, + + changePage(e) { + this.getData(e); + }, } }; </script> \ No newline at end of file diff --git a/src/views/Projects.vue b/src/views/Projects.vue index 42640515ef9d34aa72360921eabb32370d568721..5945f646d6fdbaf7fa10839e426ee6ce795bd33b 100644 --- a/src/views/Projects.vue +++ b/src/views/Projects.vue @@ -6,14 +6,14 @@ <div class="flex"> <router-link - v-if="user && user.can_create_project && isOffline() != true" + v-if="user && user.can_create_project && isOnline" :to="{ name: 'project_create', params: { action: 'create' } }" class="ui green basic button" > <i class="plus icon" /> Créer un nouveau projet </router-link> <router-link - v-if="user && user.can_create_project && isOffline() != true" + v-if="user && user.can_create_project && isOnline" :to="{ name: 'project_type_list', }" @@ -48,12 +48,6 @@ v-if="projects" class="ui divided items dimmable dimmed" > - <div - :class="{ active: loading }" - class="ui inverted dimmer" - > - <div class="ui loader" /> - </div> <div v-for="project in projects" :key="project.slug" @@ -124,16 +118,21 @@ v-if="!projects || projects.length === 0" >Vous n'avez accès à aucun projet.</span> - <div class="item" /> - </div> + <div + :class="{ active: loading }" + class="ui inverted dimmer" + > + <div class="ui loader" /> + </div> - <!-- PAGINATION --> - <pagination - v-if="count" - :nb-pages="Math.ceil(count/10)" - :on-page-change="SET_CURRENT_PAGE" - @change-page="changePage" - /> + <!-- PAGINATION --> + <Pagination + v-if="count" + :nb-pages="Math.ceil(count/10)" + :on-page-change="SET_CURRENT_PAGE" + @change-page="changePage" + /> + </div> </div> </template> @@ -162,12 +161,13 @@ export default { ...mapState([ 'configuration', 'user', - 'USER_LEVEL_PROJECTS' + 'USER_LEVEL_PROJECTS', + 'isOnline', ]), ...mapState('projects', [ 'projects', 'count', - 'filters' + 'filters', ]), APPLICATION_NAME() { return this.$store.state.configuration.VUE_APP_APPLICATION_NAME; @@ -226,9 +226,6 @@ export default { 'GET_PROJECTS' ]), - isOffline() { - return navigator.onLine == false; - }, refreshId() { //* change path of thumbnail to update image return '?ver=' + Math.random(); @@ -236,7 +233,7 @@ export default { getData(page) { this.loading = true; - this.GET_PROJECTS(page) + this.GET_PROJECTS({ page }) .then(() => { this.loading = false; }) diff --git a/src/views/feature/Feature_detail.vue b/src/views/feature/Feature_detail.vue index a6be3e6694bfdd43413ff53ebbbb77fa3dda8276..98bd9fe25887eb6f739aa91a14424f51a99c5d3a 100644 --- a/src/views/feature/Feature_detail.vue +++ b/src/views/feature/Feature_detail.vue @@ -42,7 +42,7 @@ <i class="inverted grey pencil alternate icon" /> </router-link> <a - v-if="isFeatureCreator" + v-if="isFeatureCreator && isOnline" id="feature-delete" class="ui button button-hover-red" @click="isCanceling = true" @@ -268,7 +268,7 @@ </div> <div - v-if="permissions && permissions.can_create_feature && isOffline() !== true" + v-if="permissions && permissions.can_create_feature && isOnline" class="ui segment" > <form @@ -433,7 +433,8 @@ export default { computed: { ...mapState([ 'user', - 'USER_LEVEL_PROJECTS' + 'USER_LEVEL_PROJECTS', + 'isOnline', ]), ...mapState('projects', [ 'project' @@ -541,9 +542,7 @@ export default { ...mapActions('feature', [ 'GET_PROJECT_FEATURES' ]), - isOffline() { - return navigator.onLine == false; - }, + pushNgo(link) { this.$router.push({ name: 'details-signalement', diff --git a/src/views/feature/Feature_edit.vue b/src/views/feature/Feature_edit.vue index c54f912ee7a7d0ac66356e40e07ce5b30fd3e864..c3ec4d6a233eadd2fab5d3b6e8fa14f381683151 100644 --- a/src/views/feature/Feature_edit.vue +++ b/src/views/feature/Feature_edit.vue @@ -74,7 +74,7 @@ v-if="feature_type && feature_type.geom_type === 'point'" v-frag > - <p v-if="isOffline() !== true"> + <p v-if="isOnline"> <button id="add-geo-image" type="button" @@ -229,12 +229,12 @@ </div> <!-- Pièces jointes --> - <div v-if="isOffline() !== true"> + <div v-if="isOnline"> <div class="ui horizontal divider"> PIÈCES JOINTES </div> <div - v-if="isOffline() !== true" + v-if="isOnline" id="formsets-attachment" > <FeatureAttachmentForm @@ -256,7 +256,7 @@ </div> <!-- Signalements liés --> - <div v-if="isOffline() !== true"> + <div v-if="isOnline"> <div class="ui horizontal divider"> SIGNALEMENTS LIÉS </div> @@ -373,7 +373,7 @@ export default { computed: { ...mapGetters(['permissions']), ...mapGetters('feature_type', ['feature_type']), - ...mapState(['user', 'USER_LEVEL_PROJECTS']), + ...mapState(['user', 'USER_LEVEL_PROJECTS', 'isOnline']), ...mapState('projects', ['project']), ...mapState('map', ['basemaps']), ...mapState('feature', [ @@ -486,9 +486,6 @@ export default { }, methods: { - isOffline() { - return navigator.onLine == false; - }, initForm() { if (this.currentRouteName === 'editer-signalement') { for (let key in this.feature) { diff --git a/src/views/feature/Feature_list.vue b/src/views/feature/Feature_list.vue index d2b121700d445b9f1abbd26e2fbed121953de6f0..19628e334dbf29093bf40e53e1fde18e0d5dcb6a 100644 --- a/src/views/feature/Feature_list.vue +++ b/src/views/feature/Feature_list.vue @@ -71,7 +71,7 @@ </div> <div - v-if="checkedFeatures.length > 0 && mode === 'modify'" + v-if="checkedFeatures.length > 0 && massMode === 'modify'" class="ui dropdown button compact button-hover-green margin-left-25" data-tooltip="Modifier le statut des Signalements" data-position="bottom right" @@ -100,9 +100,9 @@ </div> <div - v-if="checkedFeatures.length > 0 && mode === 'delete'" + v-if="checkedFeatures.length > 0 && massMode === 'delete'" class="ui button compact button-hover-red margin-left-25" - data-tooltip="Effacer tous les types de signalements sélectionnés" + data-tooltip="Supprimer tous les signalements sélectionnés" data-position="bottom right" @click="toggleDeleteModal" > @@ -193,8 +193,6 @@ :page-numbers="pageNumbers" :checked-features.sync="checkedFeatures" :features-count="featuresCount" - :clicked-features.sync="clickedFeatures" - :mode.sync="mode" :pagination="pagination" :sort="sort" @update:page="handlePageChange" @@ -238,7 +236,7 @@ </template> <script> -import { mapGetters, mapState, mapActions } from 'vuex'; +import { mapGetters, mapState, mapActions, mapMutations } from 'vuex'; import { mapUtil } from '@/assets/js/map-util.js'; import featureAPI from '@/services/feature-api'; import SidebarLayers from '@/components/map-layers/SidebarLayers'; @@ -264,7 +262,6 @@ export default { data() { return { - clickedFeatures: [], currentLayer: null, featuresCount: 0, form: { @@ -280,7 +277,6 @@ export default { lat: null, lng: null, map: null, - mode: 'modify', paginatedFeatures: [], pagination: { ...initialPagination }, projectSlug: this.$route.params.slug, @@ -305,7 +301,9 @@ export default { ]), ...mapState('feature', [ 'checkedFeatures', + 'clickedFeatures', 'statusChoices', + 'massMode', ]), ...mapState('feature_type', [ 'feature_types', @@ -414,6 +412,10 @@ export default { 'DELETE_FEATURE', ]), + ...mapMutations('feature', [ + 'UPDATE_CHECKED_FEATURES' + ]), + toggleAddFeature() { this.showAddFeature = !this.showAddFeature; this.showModifyStatus = false; @@ -449,7 +451,9 @@ export default { newStatus }).then((response) => { if (response && response.data && response.status === 200) { - this.checkedFeatures.splice(this.checkedFeatures.indexOf(response.data.id), 1); + let newCheckedFeatures = [...this.checkedFeatures]; + newCheckedFeatures.splice(this.checkedFeatures.indexOf(response.data.id), 1); + this.UPDATE_CHECKED_FEATURES(newCheckedFeatures); this.modifyStatus(newStatus); } else { this.$store.commit('DISPLAY_MESSAGE', { @@ -728,6 +732,10 @@ export default { margin-right: 0 !important; } +#button-dropdown { + z-index: 1; +} + @media screen and (min-width: 767px) { .twelve-wide { width: 75% !important; diff --git a/src/views/feature_type/Feature_type_detail.vue b/src/views/feature_type/Feature_type_detail.vue index 1beab1d4ae462542b6390695b535570590d84a6e..d066c264e885239da26e3d8b1ec9483bbe7c9a72 100644 --- a/src/views/feature_type/Feature_type_detail.vue +++ b/src/views/feature_type/Feature_type_detail.vue @@ -71,13 +71,13 @@ class="ui styled accordion" > <div - :class="['title', { active: showImport }]" + :class="['title', { active: showImport && isOnline, nohover: !isOnline }]" @click="toggleShowImport" > <i class="dropdown icon" /> Importer des signalements </div> - <div :class="['content', { active: showImport }]"> + <div :class="['content', { active: showImport && isOnline }]"> <div id="form-import-features" class="ui form" @@ -120,7 +120,7 @@ </router-link> <div v-if="$route.params.geojson" - class="ui button import-catalog basic active teal no-hover" + class="ui button import-catalog basic active teal nohover" > Ressource {{ $route.params.geojson.name }} </div> @@ -149,13 +149,13 @@ </div> <div class="ui styled accordion"> <div - :class="['title', { active: !showImport }]" + :class="['title', { active: !showImport && isOnline, nohover: !isOnline }]" @click="toggleShowImport" > <i class="dropdown icon" /> Exporter les signalements </div> - <div :class="['content', { active: !showImport }]"> + <div :class="['content', { active: !showImport && isOnline}]"> <p> Vous pouvez télécharger tous les signalements qui vous sont accessibles. @@ -332,7 +332,7 @@ export default { computed: { ...mapGetters([ - 'permissions' + 'permissions', ]), ...mapGetters('projects', [ 'project' @@ -340,6 +340,7 @@ export default { ...mapState([ 'reloadIntervalId', 'configuration', + 'isOnline', ]), ...mapState('projects', [ 'project' @@ -591,7 +592,11 @@ export default { margin-bottom: 1em; } -.no-hover { +.nohover, .nohover:hover { cursor: default; } + +.ui.styled.accordion .nohover.title:hover { + color: rgba(0, 0, 0, .4); +} </style> \ No newline at end of file diff --git a/src/views/project/Project_detail.vue b/src/views/project/Project_detail.vue index c2abc54a4dd46760520cab3280b28e15948f7ead..a5ad5c145d0eb04f64e4d32fb8cb6f4d03b3d1f4 100644 --- a/src/views/project/Project_detail.vue +++ b/src/views/project/Project_detail.vue @@ -87,7 +87,7 @@ user && permissions && permissions.can_view_project && - isOffline() !== true + isOnline " id="subscribe-button" class="ui button button-hover-green" @@ -102,7 +102,7 @@ v-if=" permissions && permissions.can_update_project && - isOffline() !== true + isOnline " :to="{ name: 'project_edit', params: { slug } }" class="ui button button-hover-orange" @@ -113,7 +113,7 @@ <i class="inverted grey pencil alternate icon" /> </router-link> <a - v-if="isProjectAdmin && isOffline() !== true" + v-if="isProjectAdmin && isOnline" id="delete-button" class="ui button button-hover-red" data-tooltip="Supprimer le projet" @@ -152,9 +152,9 @@ <div v-if="arraysOffline.length > 0"> {{ arraysOffline.length }} modification<span v-if="arraysOffline.length>1">s</span> en attente <button - :disabled="isOffline()" + :disabled="!isOnline" class="ui fluid labeled teal icon button" - @click="sendOfflineFeatures()" + @click="sendOfflineFeatures" > <i class="upload icon" /> Envoyer au serveur @@ -244,7 +244,7 @@ project && permissions && permissions.can_create_feature_type && - isOffline() !== true + isOnline " :to="{ name: 'dupliquer-type-signalement', @@ -277,7 +277,7 @@ v-frag > <a - v-if="isProjectAdmin && isOffline() !== true" + v-if="isProjectAdmin && isOnline" class=" ui compact @@ -299,7 +299,7 @@ project && permissions && permissions.can_create_feature_type && - isOffline() !== true + isOnline " :to="{ name: 'editer-symbologie-signalement', @@ -326,7 +326,7 @@ type.is_editable && permissions && permissions.can_create_feature_type && - isOffline() !== true + isOnline " :to="{ name: 'editer-type-signalement', @@ -361,7 +361,7 @@ v-if=" permissions && permissions.can_update_project && - isOffline() !== true + isOnline " :to="{ name: 'ajouter-type-signalement', @@ -377,7 +377,7 @@ v-if=" permissions && permissions.can_update_project && - isOffline() !== true + isOnline " class=" ui @@ -414,7 +414,7 @@ IDGO && permissions && permissions.can_update_project && - isOffline() !== true + isOnline " :to="{ name: 'catalog-import', @@ -444,7 +444,10 @@ </button> </div> </div> - <div class="seven wide column"> + <div + id="map-column" + class="seven wide column" + > <div :class="{ active: mapLoading }" class="ui inverted dimmer" @@ -457,6 +460,16 @@ id="map" ref="map" /> + <div + class="ui button teal" + @click="$router.push({ + name: 'liste-signalements', + params: { slug: slug }, + })" + > + <i class="ui icon arrow right" /> + Voir tous les signalements + </div> </div> </div> @@ -752,8 +765,6 @@ import projectAPI from '@/services/project-api'; import featureTypeAPI from '@/services/featureType-api'; import featureAPI from '@/services/feature-api'; -import axios from '@/axios-client.js'; - import { fileConvertSizeToMo } from '@/assets/js/utils'; export default { @@ -813,6 +824,7 @@ export default { ]), ...mapState([ 'configuration', + 'isOnline', ]), ...mapState('feature_type', [ 'feature_types', @@ -927,6 +939,8 @@ export default { 'SET_RELOAD_INTERVAL_ID', 'CLEAR_RELOAD_INTERVAL_ID', 'DISPLAY_MESSAGE', + 'DISPLAY_LOADER', + 'DISCARD_LOADER', ]), ...mapActions('projects', [ 'GET_PROJECT_INFO', @@ -953,9 +967,6 @@ export default { } return url.replace(this.$store.state.configuration.BASE_URL, ''); //* remove duplicate /geocontrib }, - isOffline() { - return navigator.onLine === false; - }, isImporting(type) { if (this.importFeatureTypeData) { const singleImportData = this.importFeatureTypeData.find( @@ -978,13 +989,13 @@ export default { }, retrieveProjectInfo() { - this.$store.commit('DISPLAY_LOADER', 'Projet en cours de chargement.'); + this.DISPLAY_LOADER('Projet en cours de chargement.'); Promise.all([ this.GET_PROJECT(this.slug), this.GET_PROJECT_INFO(this.slug) ]) .then(() => { - this.$store.commit('DISCARD_LOADER'); + this.DISCARD_LOADER(); this.projectInfoLoading = false; setTimeout(() => { let map = mapUtil.getMap(); @@ -994,14 +1005,14 @@ export default { }) .catch((err) => { console.error(err); - this.$store.commit('DISCARD_LOADER'); + this.DISCARD_LOADER(); this.projectInfoLoading = false; }); }, checkForOfflineFeature() { let arraysOffline = []; - let localStorageArray = localStorage.getItem('geocontrib_offline'); + const localStorageArray = localStorage.getItem('geocontrib_offline'); if (localStorageArray) { arraysOffline = JSON.parse(localStorageArray); this.arraysOffline = arraysOffline.filter( @@ -1011,60 +1022,36 @@ export default { }, sendOfflineFeatures() { - var promises = []; - let self = this; this.arraysOfflineErrors = []; - this.arraysOffline.forEach((feature) => { - if (feature.type === 'post') { - promises.push( - axios - .post(`${this.API_BASE_URL}features/`, feature.geojson) - .then((response) => { - if (response.status === 201 && response.data) { - return 'OK'; - } else { - self.arraysOfflineErrors.push(feature); - } - }) - .catch((error) => { - console.error(error); - self.arraysOfflineErrors.push(feature); - }) - ); - } else if (feature.type === 'put') { - promises.push( - axios - .put( - `${this.API_BASE_URL}features/${feature.featureId}`, - feature.geojson - ) - .then((response) => { - if (response.status === 200 && response.data) { - return 'OK'; - } else { - self.arraysOfflineErrors.push(feature); - } - }) - .catch((error) => { - console.error(error); - self.arraysOfflineErrors.push(feature); - }) - ); - } - }); + 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.DISPLAY_LOADER('Envoi des signalements en cours.'); Promise.all(promises).then(() => { this.updateLocalStorage(); - window.location.reload(); + this.retrieveProjectInfo(); }); }, updateLocalStorage() { let arraysOffline = []; - let localStorageArray = localStorage.getItem('geocontrib_offline'); + const localStorageArray = localStorage.getItem('geocontrib_offline'); if (localStorageArray) { arraysOffline = JSON.parse(localStorageArray); } - let arraysOfflineOtherProject = arraysOffline.filter( + const arraysOfflineOtherProject = arraysOffline.filter( (x) => x.project !== this.slug ); this.arraysOffline = []; @@ -1164,7 +1151,6 @@ export default { .then((response) => { this.modalType = false; if (response === 'success') { - this.GET_PROJECT(); this.retrieveProjectInfo(); this.DISPLAY_MESSAGE({ comment: `Le type de signalement ${this.featureTypeToDelete.title} a bien été supprimé.`, @@ -1247,10 +1233,15 @@ export default { <style> +#map-column { + display: flex; + flex-direction: column; +} #map { width: 100%; height: 100%; min-height: 250px; + margin-bottom: 1em; } /* // ! missing style in semantic.min.css, je ne comprends pas comment... */ .ui.right.floated.button {