Skip to content
Snippets Groups Projects
Commit 27ea0ad1 authored by Jan-Hendrik Willms's avatar Jan-Hendrik Willms
Browse files

fix notification timer display and removal, fixes #4327

Closes #4327

Merge request studip/studip!3130
parent 6b15e09a
No related branches found
No related tags found
No related merge requests found
......@@ -39,6 +39,9 @@ Vue.mixin({
globalOn(...args) {
eventBus.on(...args);
},
globalOff(...args) {
eventBus.off(...args);
},
getStudipConfig: store.getters['studip/getConfig']
},
});
......
......@@ -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;
......
<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>
<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);
}
});
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment