From 27ea0ad12b9c47675fe3b763b79dd7465e8657c3 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Willms <tleilax+studip@gmail.com> Date: Thu, 20 Jun 2024 07:05:33 +0000 Subject: [PATCH] fix notification timer display and removal, fixes #4327 Closes #4327 Merge request studip/studip!3130 --- resources/assets/javascripts/chunks/vue.js | 3 + .../scss/system-notifications.scss | 4 + .../vue/components/SystemNotification.vue | 183 ++++++++++-------- .../components/SystemNotificationManager.vue | 57 ++++-- 4 files changed, 146 insertions(+), 101 deletions(-) diff --git a/resources/assets/javascripts/chunks/vue.js b/resources/assets/javascripts/chunks/vue.js index b98cc2707b5..8d506a1dfc8 100644 --- a/resources/assets/javascripts/chunks/vue.js +++ b/resources/assets/javascripts/chunks/vue.js @@ -39,6 +39,9 @@ Vue.mixin({ globalOn(...args) { eventBus.on(...args); }, + globalOff(...args) { + eventBus.off(...args); + }, getStudipConfig: store.getters['studip/getConfig'] }, }); diff --git a/resources/assets/stylesheets/scss/system-notifications.scss b/resources/assets/stylesheets/scss/system-notifications.scss index 1da1ec24903..34c341be027 100644 --- a/resources/assets/stylesheets/scss/system-notifications.scss +++ b/resources/assets/stylesheets/scss/system-notifications.scss @@ -122,6 +122,10 @@ width: 0; } + &.system-notification-disrupted .system-notification-timeout { + display: none; + } + a:not(.system-notification-message) { color: var(--black); text-decoration-line: underline; diff --git a/resources/vue/components/SystemNotification.vue b/resources/vue/components/SystemNotification.vue index bf804ae3f21..60cefd729c0 100644 --- a/resources/vue/components/SystemNotification.vue +++ b/resources/vue/components/SystemNotification.vue @@ -1,61 +1,70 @@ <template> - <transition name="system-notification-slide" appear> - <div v-if="showMe" - :class="'system-notification system-notification-' + notification.type" - @mouseover="disruptTimeout" - @mouseout="initTimeout" - @focus="disruptTimeout" - @blur="initTimeout"> - <div class="system-notification-icon"> - <studip-icon :shape="icon.shape" - :size="48" - :role="icon.color" - alt="" - title=""></studip-icon> - </div> - <div class="system-notification-content"> - <p v-html="notification.message"></p> - <p class="sr-only" v-if="hasTimeout"> - {{ $gettext('Strg+Alt+T hält das automatische Ausblenden der Meldung an bzw. setzt es wieder fort.') }} - </p> - <details v-if="notification.details?.length > 0" - class="system-notification-details"> - <summary> - {{ $gettext('Details') }} - </summary> - <template v-if="Array.isArray(notification.details)"> - <p v-for="(detail, index) in notification.details" - :key="index" - v-html="detail"></p> - </template> - <p v-else v-html="notification.details"></p> - </details> - </div> - <button v-if="allowClosing" - class="system-notification-close undecorated" - :title="$gettext('Diese Meldung schließen')" - @click.prevent="destroyMe" - @keydown.space="destroyMe" - tabindex="0"> - <studip-icon shape="decline" - :size="20" - class="close-system-notification"/> - </button> - <transition v-if="hasTimeout" - name="system-notification-timeout" - appear> - <div v-if="!stopTimeout" - class="system-notification-timeout" - ref="timeout-counter"></div> - </transition> + <div v-cloak + class="system-notification" + :class="cssClasses" + @mouseover="disruptTimeout" + @mouseout="initTimeout" + @focus="disruptTimeout" + @blur="initTimeout" + > + <div class="system-notification-icon"> + <studip-icon :shape="icon.shape" + :size="48" + :role="icon.color" + alt="" + title=""></studip-icon> </div> - </transition> + <div class="system-notification-content"> + <p v-html="notification.message"></p> + <p class="sr-only" v-if="hasTimeout"> + {{ $gettext('Strg+Alt+T hält das automatische Ausblenden der Meldung an bzw. setzt es wieder fort.') }} + </p> + <details v-if="notification.details?.length > 0" + class="system-notification-details"> + <summary> + {{ $gettext('Details') }} + </summary> + <template v-if="Array.isArray(notification.details)"> + <p v-for="(detail, index) in notification.details" + :key="index" + v-html="detail"></p> + </template> + <p v-else v-html="notification.details"></p> + </details> + </div> + <button v-if="allowClosing" + class="system-notification-close undecorated" + :title="$gettext('Diese Meldung schließen')" + @click.prevent="destroyMe" + @keydown.space="destroyMe" + tabindex="0"> + <studip-icon shape="decline" + :size="20" + class="close-system-notification"/> + </button> + <transition v-if="hasTimeout" + name="system-notification-timeout" + appear + > + <div v-if="!stopTimeout" + class="system-notification-timeout" + ref="timeout-counter"></div> + </transition> + </div> </template> <script> export default { name: 'SystemNotification', props: { + allowClosing: { + type: Boolean, + default: true + }, + appendTo: { + type: String, + default: null + }, notification: { type: Object, required: true @@ -63,23 +72,26 @@ export default { visibleFor: { type: Number, default: 5000 - }, - appendTo: { - type: String, - default: null - }, - allowClosing: { - type: Boolean, - default: true } }, data() { return { - showMe: false, - stopTimeout: false + stopTimeout: false, + timeout: null, + windowIsBlurred: false, } }, computed: { + cssClasses() { + const classes = [`system-notification-${this.notification.type}`]; + if (this.isDisrupted) { + classes.push('system-notification-disrupted'); + } + return classes; + }, + hasTimeout() { + return !['exception', 'error'].includes(this.notification.type); + }, icon() { let iconShape = 'info-circle'; let iconColor = 'info'; @@ -101,29 +113,28 @@ export default { iconColor = 'status-green'; break; } - return { shape: iconShape, color: iconColor }; + return {shape: iconShape, color: iconColor}; }, - hasTimeout() { - return !['exception', 'error'].includes(this.notification.type); + isDisrupted() { + return this.timeout !== null && this.stopTimeout; } }, methods: { - initTimeout() { - if (this.hasTimeout && this.visibleFor > 0) { - this.stopTimeout = false; - setTimeout(() => { - if (!this.stopTimeout) { - this.destroyMe(); - } - }, this.visibleFor); - } + destroyMe() { + this.$emit('destroyMe'); }, disruptTimeout() { this.stopTimeout = true; + clearTimeout(this.timeout); }, - destroyMe() { - this.showMe = false; - this.$emit('destroyMe'); + initTimeout() { + if (this.hasTimeout && this.visibleFor > 0) { + this.stopTimeout = false; + this.timeout = setTimeout( + () => this.destroyMe(), + this.visibleFor + ); + } } }, mounted() { @@ -139,18 +150,24 @@ export default { } } + this.initTimeout(); + + this.globalOn('disrupt-system-notifications', this.disruptTimeout); + this.globalOn('resume-system-notifications', this.initTimeout); + if (!STUDIP.config?.PERSONAL_NOTIFICATIONS_AUDIO_DEACTIVATED) { const audio = new Audio(STUDIP.ASSETS_URL + '/sounds/blubb.mp3'); audio.play(); } - this.showMe = true; - - this.initTimeout(); - window.addEventListener('keydown', evt => { - if (evt.altKey && evt.ctrlKey && evt.code === 'KeyT') { - this.stopTimeout ? this.initTimeout() : this.disruptTimeout(); - } - }) + }, + destroyed() { + this.globalOff('disrupt-system-notifications', this.disruptTimeout); + this.globalOff('resume-system-notifications', this.initTimeout); } } </script> +<style scoped> +[v-cloak] { + display: none; +} +</style> diff --git a/resources/vue/components/SystemNotificationManager.vue b/resources/vue/components/SystemNotificationManager.vue index 72c4dbfdaae..0acef248002 100644 --- a/resources/vue/components/SystemNotificationManager.vue +++ b/resources/vue/components/SystemNotificationManager.vue @@ -1,10 +1,16 @@ <template> - <div role="alert" - :class="'system-notifications ' + (placement === 'topcenter' ? 'top-center' : 'bottom-right')"> - <system-notification v-for="(notification, index) in allNotifications" - :key="'message-' + index" - :notification="notification"></system-notification> - </div> + <transition-group name="system-notification-slide" + :class="'system-notifications ' + (placement === 'topcenter' ? 'top-center' : 'bottom-right')" + tag="div" + role="alert" + appear + > + <system-notification v-for="notification in allNotifications" + :key="`message-${notification.key}`" + :notification="notification" + @destroyMe="destroyNotification(notification)" + ></system-notification> + </transition-group> </template> <script> @@ -14,8 +20,9 @@ export default { name: 'SystemNotificationManager', components: { SystemNotification }, props: { + appendAllTo: String, notifications: { - type: Array, + type: [Array, Object], default: () => [] }, placement: { @@ -24,28 +31,42 @@ export default { validator: value => { return ['topcenter', 'bottomright'].includes(value); } - }, - appendAllTo: { - type: String, - default: null } }, data() { return { - allNotifications: this.notifications + allNotifications: [], + counter: 0, + stoppedNotifications: false } }, methods: { - getNotifications(type) { - return this.allNotifications.filter((n) => n.type === type); - }, - destroyNotification(type, index) { - + destroyNotification(notification) { + this.allNotifications = this.allNotifications.filter(n => n !== notification); + } + }, + created() { + if (Array.isArray(this.notifications)) { + this.allNotifications = [...this.notifications]; + } else { + this.allNotifications = Object.values(this.notifications); } }, mounted() { this.globalOn('push-system-notification', notification => { - this.allNotifications.push(notification); + this.allNotifications.push({ + key: this.counter++, + ...notification + }); + }); + + window.addEventListener('keydown', evt => { + if (evt.altKey && evt.ctrlKey && evt.code === 'KeyT') { + this.stoppedNotifications = !this.stoppedNotifications; + + const event = this.stoppedNotifications ? 'disrupt-system-notifications' : 'resume-system-notifications'; + this.globalEmit(event); + } }); } } -- GitLab