diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4f114a3f40d589bb08e0dae260833e2c1ac3b4da..8be5817d9c4da0cf5bcf7e958c46b03728f2f0e1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,11 +15,19 @@ Added * GraphQL schema for Rooms * [Dev] UpdateIndicator Vue Component to display the status of interactive pages +Changed +~~~~~~~ + +* Show message on successful logout to inform users properly. Fixed ~~~~~ * GraphQL endpoints for groups, persons, and notifications didn't expose all necessary fields. +* Loading indicator in toolbar was not shown at the complete loading progress. +* 404 page was sometimes shown while the page was still loading. +* Setting of page height in the iframe was not working correctly. +* App switched to offline state when the user was logged out/in. `3.0b3`_ - 2023-03-19 --------------------- diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py index 9b0518472c6309d33305188659de22700497aee5..0287a0fd0736a71479ca0c945d1f89a5c697b74b 100644 --- a/aleksis/core/apps.py +++ b/aleksis/core/apps.py @@ -3,6 +3,7 @@ from typing import Any, Optional import django.apps from django.apps import apps from django.conf import settings +from django.contrib import messages from django.http import HttpRequest from django.utils.module_loading import autodiscover_modules from django.utils.translation import gettext as _ @@ -144,6 +145,11 @@ class CoreConfig(AppConfig): # Save the associated person to pick up defaults user.person.save() + def user_logged_out( + self, sender: type, request: Optional[HttpRequest], user: "User", **kwargs + ) -> None: + messages.success(request, _("You have been logged out successfully.")) + @classmethod def get_all_scopes(cls) -> dict[str, str]: scopes = { diff --git a/aleksis/core/frontend/app/apollo.js b/aleksis/core/frontend/app/apollo.js index 1b23d43fb32d402c7e802a7e892e16f70545aa0e..267997b9f93ba8d992a90093a93e650f32995811 100644 --- a/aleksis/core/frontend/app/apollo.js +++ b/aleksis/core/frontend/app/apollo.js @@ -70,7 +70,7 @@ const apolloOpts = { } // Add a snackbar on all errors returned by the GraphQL endpoint // If App is offline, don't add snackbar since only the ping query is active - if (!vm.$root.offline) { + if (!vm.$root.offline && !vm.$root.invalidation) { vm.$root.snackbarItems.push({ id: crypto.randomUUID(), timeout: 5000, @@ -79,7 +79,7 @@ const apolloOpts = { }); } } - if (networkError) { + if (networkError && !vm.$root.invalidation) { // Set app offline globally on network errors // This will cause the offline logic to kick in, starting a ping check or // similar recovery strategies depending on the app/navigator state diff --git a/aleksis/core/frontend/components/LegacyBaseTemplate.vue b/aleksis/core/frontend/components/LegacyBaseTemplate.vue index d28621a146f74a980f44e6cec52c0deccc024961..30ef189b7daa037bc47fe711515e112b8391b377 100644 --- a/aleksis/core/frontend/components/LegacyBaseTemplate.vue +++ b/aleksis/core/frontend/components/LegacyBaseTemplate.vue @@ -78,12 +78,23 @@ export default { this.$root.$setPageTitle(title); // Adapt height of IFrame according to the height of its contents once and observe height changes - this.iFrameHeight = - this.$refs.contentIFrame.contentDocument.body.scrollHeight; - new ResizeObserver(() => { + if ( + this.$refs.contentIFrame.contentDocument && + this.$refs.contentIFrame.contentDocument.body + ) { this.iFrameHeight = this.$refs.contentIFrame.contentDocument.body.scrollHeight; - }).observe(this.$refs.contentIFrame.contentDocument.body); + new ResizeObserver(() => { + if ( + this.$refs.contentIFrame && + this.$refs.contentIFrame.contentDocument && + this.$refs.contentIFrame.contentDocument.body + ) { + this.iFrameHeight = + this.$refs.contentIFrame.contentDocument.body.scrollHeight; + } + }).observe(this.$refs.contentIFrame.contentDocument.body); + } this.$root.contentLoading = false; }, diff --git a/aleksis/core/frontend/components/app/App.vue b/aleksis/core/frontend/components/app/App.vue index e11725a36e9dc33310750397a68305b3af05c566..6db2abd0a882802156a269ec6ae24462615bd918 100644 --- a/aleksis/core/frontend/components/app/App.vue +++ b/aleksis/core/frontend/components/app/App.vue @@ -45,7 +45,10 @@ > <v-icon>mdi-update</v-icon> </v-btn> - <div v-if="whoAmI && whoAmI.isAuthenticated" class="d-flex"> + <div + v-if="whoAmI && whoAmI.isAuthenticated && whoAmI.person" + class="d-flex" + > <notification-list v-if="!whoAmI.person.isDummy" /> <account-menu :account-menu="accountMenu" @@ -83,7 +86,7 @@ </div> <error-page - v-if="error404" + v-if="error404 && !$root.contentLoading" short-error-message-key="network_errors.error_404" long-error-message-key="network_errors.page_not_found" redirect-button-text-key="network_errors.back_to_start" @@ -97,6 +100,7 @@ checkPermission($route.meta.permission) || $route.name === 'dashboard' " + @mounted="routeComponentMounted" /> <error-page v-else-if=" @@ -253,6 +257,13 @@ export default { pollInterval: 1000, }, }, + methods: { + routeComponentMounted() { + if (!this.$root.isLegacyBaseTemplate) { + this.$root.contentLoading = false; + } + }, + }, watch: { systemProperties: function (newProperties) { this.$vuetify.theme.themes.light.primary = @@ -272,7 +283,7 @@ export default { }, $route: { handler(newRoute) { - if (newRoute.matched.length == 0) { + if (newRoute.matched.length === 0) { this.error404 = true; } else { this.error404 = false; diff --git a/aleksis/core/frontend/index.js b/aleksis/core/frontend/index.js index 25df11b343a0708bbb6a1a378cfa1b17ab154749..f59aa8578b59addcb420d6165c6ef1672c669c30 100644 --- a/aleksis/core/frontend/index.js +++ b/aleksis/core/frontend/index.js @@ -65,11 +65,25 @@ const app = new Vue({ render: (h) => h(App), data: () => ({ showCacheAlert: false, - contentLoading: false, + contentLoading: true, offline: false, backgroundActive: true, + invalidation: false, snackbarItems: [], }), + computed: { + matchedComponents() { + if (this.$route.matched.length > 0) { + return this.$route.matched.map( + (route) => route.components.default.name + ); + } + return []; + }, + isLegacyBaseTemplate() { + return this.matchedComponents.includes("LegacyBaseTemplate"); + }, + }, router, i18n, }); diff --git a/aleksis/core/frontend/mixins/aleksis.js b/aleksis/core/frontend/mixins/aleksis.js index 036e2eac71db6304bba88e2a01c3cfbe33edca51..59204a7f689d6280941ea5f72f2e8e8d591c59b9 100644 --- a/aleksis/core/frontend/mixins/aleksis.js +++ b/aleksis/core/frontend/mixins/aleksis.js @@ -20,7 +20,7 @@ const aleksisMixin = { }, }, mounted() { - this.$root.contentLoading = false; + this.$emit("mounted"); }, beforeDestroy() { // Unregister all safely added event listeners as to not leak them diff --git a/aleksis/core/frontend/plugins/aleksis.js b/aleksis/core/frontend/plugins/aleksis.js index 8c221c6d62953206b501aa7c7d949e808d2e63cd..c35b9127f78f6c26c3e5c052cc5732322f509090 100644 --- a/aleksis/core/frontend/plugins/aleksis.js +++ b/aleksis/core/frontend/plugins/aleksis.js @@ -123,15 +123,19 @@ AleksisVue.install = function (Vue) { Vue.prototype.$invalidateState = function () { console.info("Invalidating application state"); + this.invalidation = true; + this.$apollo .getClient() .resetStore() .then( - function () { + () => { console.info("GraphQL cache cleared"); + this.invalidation = false; }, - function (error) { + (error) => { console.error("Could not clear GraphQL cache:", error); + this.invalidation = false; } ); }; @@ -156,6 +160,12 @@ AleksisVue.install = function (Vue) { // eslint-disable-next-line no-unused-vars this.$router.afterEach((to, from) => { + if (vm.isLegacyBaseTemplate) { + // Skip resetting loading state for legacy pages + // as they are probably not finished with loading yet + // LegacyBaseTemplate will reset the loading state later + return; + } vm.contentLoading = false; });