From 15429d184fdf4b14395717e12461bd30f368add8 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Sat, 8 Apr 2023 15:11:54 +0200 Subject: [PATCH] Improve lifecycle of SPA --- CHANGELOG.rst | 8 ++++++++ aleksis/core/apps.py | 6 ++++++ aleksis/core/frontend/app/apollo.js | 4 ++-- .../components/LegacyBaseTemplate.vue | 19 +++++++++++++++---- aleksis/core/frontend/components/app/App.vue | 17 ++++++++++++++--- aleksis/core/frontend/index.js | 16 +++++++++++++++- aleksis/core/frontend/mixins/aleksis.js | 2 +- aleksis/core/frontend/plugins/aleksis.js | 14 ++++++++++++-- 8 files changed, 73 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4f114a3f4..8be5817d9 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 9b0518472..0287a0fd0 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 1b23d43fb..267997b9f 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 d28621a14..30ef189b7 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 e11725a36..6db2abd0a 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 25df11b34..f59aa8578 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 036e2eac7..59204a7f6 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 8c221c6d6..c35b9127f 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; }); -- GitLab