diff --git a/app/controllers/course/basicdata.php b/app/controllers/course/basicdata.php index f4f50feef18298ce7ea99f803a15547cbaedde0c..97ec0531cbbabff27a16c40aa9ff6ec4d212dfd9 100644 --- a/app/controllers/course/basicdata.php +++ b/app/controllers/course/basicdata.php @@ -445,6 +445,14 @@ class Course_BasicdataController extends AuthenticatedController $widget = new CourseManagementSelectWidget(); $sidebar->addWidget($widget); } + + foreach ($this->flash['msg'] ?? [] as $msg) { + match ($msg[0]) { + 'msg' => PageLayout::postSuccess($msg[1]), + 'error' => PageLayout::postError($msg[1]), + 'info' => PageLayout::postInfo($msg[1]), + }; + } } /** diff --git a/app/controllers/settings/general.php b/app/controllers/settings/general.php index 734cca1a796cf3661db783cb2aa4cb0022957ba5..0e8ec70a19db88a0d43b587a8b695a6810489455 100644 --- a/app/controllers/settings/general.php +++ b/app/controllers/settings/general.php @@ -44,6 +44,7 @@ class Settings_GeneralController extends Settings_SettingsController public function index_action() { $this->user_language = getUserLanguage($this->user->id); + $this->notifications_placement = User::findCurrent()->getConfiguration()->SYSTEM_NOTIFICATIONS_PLACEMENT; } /** @@ -80,6 +81,7 @@ class Settings_GeneralController extends Settings_SettingsController } else { PersonalNotifications::deactivateAudioFeedback($this->user->id); } + $this->config->store('SYSTEM_NOTIFICATIONS_PLACEMENT', Request::get('system_notifications_placement')); PageLayout::postSuccess(_('Die Einstellungen wurden gespeichert.')); $this->redirect('settings/general'); diff --git a/app/views/course/basicdata/view.php b/app/views/course/basicdata/view.php index 380fdb48ecfdae75b7f0c1ffa888b5d998b55653..9438aeb7ca37d1f0178dcd6f8ed7d617d0854989 100644 --- a/app/views/course/basicdata/view.php +++ b/app/views/course/basicdata/view.php @@ -12,14 +12,8 @@ use Studip\Button, Studip\LinkButton; */ $dialog_attr = Request::isXhr() ? ' data-dialog="size=50%"' : ''; - -$message_types = ['msg' => "success", 'error' => "error", 'info' => "info"]; ?> -<? if (is_array($flash['msg'])) foreach ($flash['msg'] as $msg) : ?> - <?= MessageBox::{$message_types[$msg[0]]}($msg[1]) ?> -<? endforeach ?> - <form name="course-details" name="details" method="post" action="<?= $controller->link_for('course/basicdata/set', $course_id) ?>" <?= $dialog_attr ?> class="default collapsable"> <?= CSRFProtection::tokenTag() ?> <input id="open_variable" type="hidden" name="open" value="<?= $flash['open'] ?>"> diff --git a/app/views/settings/general.php b/app/views/settings/general.php index 4bd543388973171c5cbced5bd4df62508a5fa35b..32b1a6f98b851ba05eee7ecf9ef4ae2fa5a0c9c7 100644 --- a/app/views/settings/general.php +++ b/app/views/settings/general.php @@ -93,6 +93,21 @@ $start_pages = [ '- auch wenn Sie gerade einen anderen Browsertab anschauen. Der Plopp ist ' . 'nur zu hören, wenn Sie die Benachrichtigungen über Javascript aktiviert haben.')) ?> </label> + + <label> + <?= _('Platzierung von Systembenachrichtigungen im Browserfenster') ?> + <?= tooltipIcon(_('Sie können entscheiden, an welcher Stelle Ihres Browserfensters ' . + 'Systembenachrichtigungen erscheinen sollen: mittig am oberen Rand oder rechts unten.')) ?> + <select name="system_notifications_placement" + aria-describedby="system_notifications_notifications_placement_description"> + <option value="topcenter"<?= $notifications_placement === 'topcenter' ? ' selected' : '' ?>> + <?= _('zentriert am oberen Rand') ?> + </option> + <option value="bottomright"<?= $notifications_placement === 'bottomright' ? ' selected' : '' ?>> + <?= _('am rechten unteren Rand') ?> + </option> + </select> + </label> </fieldset> <fieldset> diff --git a/db/migrations/6.0.9_notification_positioning.php b/db/migrations/6.0.9_notification_positioning.php new file mode 100644 index 0000000000000000000000000000000000000000..d6693f3e56be1e9de6e1528a4c5949a62546cbbf --- /dev/null +++ b/db/migrations/6.0.9_notification_positioning.php @@ -0,0 +1,35 @@ +<?php +class NotificationPositioning extends Migration +{ + public function description() + { + return 'Adds a personal config option to select the on-screen position of system notifications'; + } + + protected function up() + { + DBManager::get()->execute( + "INSERT IGNORE INTO `config` VALUES (:field, :value, :type, :range, :section, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :desc)", + [ + 'field' => 'SYSTEM_NOTIFICATIONS_PLACEMENT', + 'value' => 'topcenter', + 'type' => 'string', + 'range' => 'user', + 'section' => '', + 'desc' => 'Wo sollen Systembenachrichtigungen im Fenster angezeigt werden? Gültige Werte sind "topcenter" und "bottomright"' + ] + ); + } + + protected function down() + { + DBManager::get()->execute( + "DELETE FROM `config_values` WHERE `field` = :field", + ['field' => 'SYSTEM_NOTIFICATIONS_PLACEMENT'] + ); + DBManager::get()->execute( + "DELETE FROM `config` WHERE `field` = :field", + ['field' => 'SYSTEM_NOTIFICATIONS_PLACEMENT'] + ); + } +}; diff --git a/lib/classes/MessageBox.class.php b/lib/classes/MessageBox.class.php index c0d94b297edd31ad68150f273b9707f88f1254ff..8936ebec08790e152a1ce836c378a203e0386134 100644 --- a/lib/classes/MessageBox.class.php +++ b/lib/classes/MessageBox.class.php @@ -141,6 +141,15 @@ class MessageBox implements LayoutMessage return $this; } + /** + * Return whether this messagebox can be closed or not. + * @return bool + */ + public function isCloseable(): bool + { + return $this->hide_close; + } + /** * This method renders a MessageBox object to a string. * diff --git a/lib/classes/PageLayout.php b/lib/classes/PageLayout.php index d6b28ec0a0782acb838a8dd7972db274391d21b3..7f95b7aaa1d028d78d6f6dbd06638820f9ad3298 100644 --- a/lib/classes/PageLayout.php +++ b/lib/classes/PageLayout.php @@ -599,10 +599,18 @@ class PageLayout if (!isset($_SESSION['messages'])) { $_SESSION['messages'] = []; } + + $structure = [ + 'type' => $message->class, + 'message' => $message->message, + 'details' => $message->details, + 'closeable' => $message->isCloseable() + ]; + if ($id === null ) { - $_SESSION['messages'][] = $message; + $_SESSION['messages'][] = $structure; } else { - $_SESSION['messages'][$id] = $message; + $_SESSION['messages'][$id] = $structure; } } diff --git a/lib/phplib/Seminar_Auth.class.php b/lib/phplib/Seminar_Auth.class.php index 4849ece82f6442ffcbe3bf05bae223bfe4d34096..2c1c60c3b92df68c1fa03519fcdd8bb8b5877d77 100644 --- a/lib/phplib/Seminar_Auth.class.php +++ b/lib/phplib/Seminar_Auth.class.php @@ -336,7 +336,9 @@ class Seminar_Auth } else { unset($_SESSION['semi_logged_in']); // used by email activation $login_template = $GLOBALS['template_factory']->open('loginform'); - $login_template->set_attribute('loginerror', (isset($this->auth["uname"]) && $this->error_msg)); + if (isset($this->auth['uname']) && $this->error_msg) { + PageLayout::postException(_('Bei der Anmeldung trat ein Fehler auf!'), $this->error_msg); + } $login_template->set_attribute('error_msg', $this->error_msg); $login_template->set_attribute('uname', (isset($this->auth["uname"]) ? $this->auth["uname"] : Request::username('loginname'))); $login_template->set_attribute('self_registration_activated', Config::get()->ENABLE_SELF_REGISTRATION); diff --git a/resources/assets/javascripts/bootstrap/system-notifications.js b/resources/assets/javascripts/bootstrap/system-notifications.js new file mode 100644 index 0000000000000000000000000000000000000000..7a85fcdbfbce669b45a63051e5016ca3464edfab --- /dev/null +++ b/resources/assets/javascripts/bootstrap/system-notifications.js @@ -0,0 +1,11 @@ +import SystemNotificationManager from '../../../vue/components/SystemNotificationManager.vue'; + +STUDIP.domReady(() => { + document.getElementById('system-notifications')?.classList.add('vueified'); + STUDIP.Vue.load().then(({ createApp }) => { + createApp({ + el: '#system-notifications', + components: { SystemNotificationManager } + }); + }); +}); diff --git a/resources/assets/javascripts/entry-base.js b/resources/assets/javascripts/entry-base.js index 26834395e6d69b63c80b801f7f8346b20e59a847..5f88c3069ba8f098cad208ad8221efd838bd01ce 100644 --- a/resources/assets/javascripts/entry-base.js +++ b/resources/assets/javascripts/entry-base.js @@ -16,6 +16,8 @@ import "./init.js" import "./bootstrap/responsive.js" import "./bootstrap/vue.js" +import "./bootstrap/system-notifications.js" + import "./bootstrap/my-courses.js"; import "./studip-ui.js" diff --git a/resources/assets/stylesheets/scss/responsive.scss b/resources/assets/stylesheets/scss/responsive.scss index c4317e933e7ee67c0716e1475d2c16f8cbf5b1d2..e84a8a800521436c64fd8f77efafc4f4e1069802 100644 --- a/resources/assets/stylesheets/scss/responsive.scss +++ b/resources/assets/stylesheets/scss/responsive.scss @@ -537,6 +537,26 @@ $sidebarOut: -330px; } } } + + #system-notifications { + top: 0; + position: fixed; + left: 0; + width: 100%; + z-index: 1001; + } + + .system-notification { + &.system-notification-slide-enter, + &.system-notification-slide-leave-to { + transform: translateY(-100%); + } + + &.system-notification-slide-leave, + &.system-notification-slide-enter-to { + transform: translateY(0); + } + } } /* Settings especially for fullscreen mode */ diff --git a/resources/assets/stylesheets/scss/system-notifications.scss b/resources/assets/stylesheets/scss/system-notifications.scss new file mode 100644 index 0000000000000000000000000000000000000000..1da1ec249037bcd129646d24d69cb9071b2ca963 --- /dev/null +++ b/resources/assets/stylesheets/scss/system-notifications.scss @@ -0,0 +1,174 @@ +#system-notifications { + &.bottom-right { + bottom: 50px; + right: 15px; + + .system-notification { + box-shadow: 5px 5px var(--dark-gray-color-10); + + &.system-notification-slide-enter, + &.system-notification-slide-leave-to { + opacity: 0; + transform: translateX(100%); + } + + &.system-notification-slide-leave, + &.system-notification-slide-enter-to { + opacity: 1; + transform: translateX(0); + } + } + } + + &.top-center { + left: calc(50% - 300px); + top: 0; + + .system-notification { + box-shadow: 0 0 0 3px var(--dark-gray-color-10); + + &.system-notification-slide-enter, + &.system-notification-slide-leave-to { + opacity: 0; + transform: translateY(-100%); + } + + &.system-notification-slide-leave, + &.system-notification-slide-enter-to { + opacity: 1; + transform: translateY(0); + } + } + } + + &:not(.system-notifications-login) { + position: fixed; + width: 600px; + } + + &.system-notifications-login { + margin-bottom: 15px; + } + + overflow: hidden; + z-index: 1001; +} + +.system-notification { + background-color: var(--content-color-20); + border: thin solid var(--content-color-40); + color: var(--black); + cursor: pointer; + display: flex; + padding: 10px; + position: relative; + + &.system-notification-slide-enter-active, + &.system-notification-slide-leave-active { + transition: all var(--transition-duration-slow) ease-in-out; + } + + .system-notification-icon { + flex: 0; + padding: 10px; + } + + .system-notification-content { + flex: 1; + height: auto; + margin: auto 25px auto 10px; + word-wrap: break-word; + + .system-notification-details { + .system-notification-details-content { + padding-left: 10px; + } + } + } + + .system-notification-close { + align-self: normal; + flex: 0; + height: 20px; + width: 20px; + + img, svg { + cursor: pointer; + position: absolute; + right: 10px; + top: 10px; + } + } + + .system-notification-timeout { + &.system-notification-timeout-enter-active, + &.system-notification-timeout-leave-active { + transition: width 5s linear; + } + + &.system-notification-timeout-enter { + width: 100%; + } + + &.system-notification-timeout-leave { + width: 0; + } + + background-color: var(--base-color-40); + bottom: 0; + height: 5px; + left: 0; + position: absolute; + width: 0; + } + + a:not(.system-notification-message) { + color: var(--black); + text-decoration-line: underline; + } + + a.system-notification-message { + color: var(--text-color); + text-decoration: unset; + } +} + +.system-notification-exception { + background-color: var(--red-40); + + .system-notification-timeout { + background-color: var(--red); + } +} + +.system-notification-error { + background-color: var(--red-20); + + .system-notification-timeout { + background-color: var(--red-80); + } +} + +.system-notification-warning { + background-color: var(--yellow-20); + + .system-notification-timeout { + background-color: var(--yellow-80); + } +} + +.system-notification-success { + background-color: var(--green-20); + + .system-notification-timeout { + background-color: var(--green-80); + } +} + +.system-notification-info { + background-color: var(--dark-violet-20); + + .system-notification-timeout { + background-color: var(--dark-violet-60); + } +} diff --git a/resources/assets/stylesheets/studip.scss b/resources/assets/stylesheets/studip.scss index 0f329a497e3899a276910387be1d7d6537ee5800..a322400ecb67c4317a39b6096a2c733f9bde38e0 100644 --- a/resources/assets/stylesheets/studip.scss +++ b/resources/assets/stylesheets/studip.scss @@ -96,6 +96,7 @@ @import "scss/studygroup"; @import "scss/studip-overlay"; @import "scss/studip-selection"; +@import "scss/system-notifications"; @import "scss/table_of_contents"; @import "scss/tables"; @import "scss/tabs"; diff --git a/resources/vue/components/SystemNotification.vue b/resources/vue/components/SystemNotification.vue new file mode 100644 index 0000000000000000000000000000000000000000..bf804ae3f214b8203764bc849dd7fad28923cfec --- /dev/null +++ b/resources/vue/components/SystemNotification.vue @@ -0,0 +1,156 @@ +<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> + </transition> +</template> + +<script> +export default { + name: 'SystemNotification', + props: { + notification: { + type: Object, + required: true + }, + visibleFor: { + type: Number, + default: 5000 + }, + appendTo: { + type: String, + default: null + }, + allowClosing: { + type: Boolean, + default: true + } + }, + data() { + return { + showMe: false, + stopTimeout: false + } + }, + computed: { + icon() { + let iconShape = 'info-circle'; + let iconColor = 'info'; + switch (this.type) { + case 'exception': + iconShape = 'exclaim-circle'; + iconColor = 'info_alt'; + break; + case 'error': + iconShape = 'exclaim-circle'; + iconColor = 'status-red'; + break; + case 'warning': + iconShape = 'exclaim-circle'; + iconColor = 'status-yellow'; + break; + case 'success': + iconShape = 'check-circle'; + iconColor = 'status-green'; + break; + } + return { shape: iconShape, color: iconColor }; + }, + hasTimeout() { + return !['exception', 'error'].includes(this.notification.type); + } + }, + methods: { + initTimeout() { + if (this.hasTimeout && this.visibleFor > 0) { + this.stopTimeout = false; + setTimeout(() => { + if (!this.stopTimeout) { + this.destroyMe(); + } + }, this.visibleFor); + } + }, + disruptTimeout() { + this.stopTimeout = true; + }, + destroyMe() { + this.showMe = false; + this.$emit('destroyMe'); + } + }, + mounted() { + if (this.appendTo !== null) { + const target = document.querySelector(this.appendTo); + + // Create a live area for screen reader compatibility. + const div = document.createElement('div'); + div.setAttribute('role', 'alert'); + div.appendChild(this.$el); + if (target) { + target.prepend(div); + } + } + + 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(); + } + }) + } +} +</script> diff --git a/resources/vue/components/SystemNotificationManager.vue b/resources/vue/components/SystemNotificationManager.vue new file mode 100644 index 0000000000000000000000000000000000000000..72c4dbfdaae400e241e85d5ac7e3b5fc104cf1a8 --- /dev/null +++ b/resources/vue/components/SystemNotificationManager.vue @@ -0,0 +1,52 @@ +<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> +</template> + +<script> +import SystemNotification from './SystemNotification.vue'; + +export default { + name: 'SystemNotificationManager', + components: { SystemNotification }, + props: { + notifications: { + type: Array, + default: () => [] + }, + placement: { + type: String, + default: 'topcenter', + validator: value => { + return ['topcenter', 'bottomright'].includes(value); + } + }, + appendAllTo: { + type: String, + default: null + } + }, + data() { + return { + allNotifications: this.notifications + } + }, + methods: { + getNotifications(type) { + return this.allNotifications.filter((n) => n.type === type); + }, + destroyNotification(type, index) { + + } + }, + mounted() { + this.globalOn('push-system-notification', notification => { + this.allNotifications.push(notification); + }); + } +} +</script> diff --git a/resources/vue/components/courseware/layouts/CoursewareCompanionBox.vue b/resources/vue/components/courseware/layouts/CoursewareCompanionBox.vue index f617e5ad2575b5945503bf0b0921b09ff753f5b0..c40edd1e678def7ba43336ec435b28aba62a64c4 100644 --- a/resources/vue/components/courseware/layouts/CoursewareCompanionBox.vue +++ b/resources/vue/components/courseware/layouts/CoursewareCompanionBox.vue @@ -1,12 +1,3 @@ -<template> - <div class="cw-companion-box" :class="[mood]"> - <div> - <p v-html="msgCompanion"></p> - <slot name="companionActions"></slot> - </div> - </div> -</template> - <script> export default { name: 'courseware-companion-box', @@ -20,5 +11,40 @@ export default { } } }, + computed: { + msgType() { + let type = 'info'; + switch (this.mood) { + case 'special': + case 'unsure': + type = 'warning'; + break; + case 'sad': + type = 'error'; + break; + case 'happy': + type = 'success'; + break + case 'pointing': + case 'curious': + } + return type; + } + }, + watch: { + msgCompanion: { + handler(current) { + if (current.trim().length === 0) { + return; + } + const notification = { + type: this.msgType, + message: current + }; + this.globalEmit('push-system-notification', notification); + }, + immediate: true + } + } }; -</script> \ No newline at end of file +</script> diff --git a/resources/vue/components/courseware/layouts/CoursewareCompanionOverlay.vue b/resources/vue/components/courseware/layouts/CoursewareCompanionOverlay.vue index ff177f7e177d2c8961eca3113a28d6066864ffd9..a45da116d836ad87fb2e949a6efc5e056e7d05d9 100644 --- a/resources/vue/components/courseware/layouts/CoursewareCompanionOverlay.vue +++ b/resources/vue/components/courseware/layouts/CoursewareCompanionOverlay.vue @@ -1,23 +1,3 @@ -<template> - <div class="cw-companion-overlay-wrapper"> - <div - class="cw-companion-overlay" - :class="[showCompanion ? 'cw-companion-overlay-in' : '', showCompanion ? '' : 'cw-companion-overlay-out', styleCompanion]" - aria-hidden="true" - > - <div class="cw-companion-overlay-content" v-html="msgCompanion"></div> - <button class="cw-compantion-overlay-close" @click="hideCompanion"></button> - </div> - <div - class="sr-only" - aria-live="polite" - role="log" - > - <p>{{ msgCompanion }}</p> - </div> - </div> -</template> - <script> import { mapActions, mapGetters } from 'vuex'; @@ -30,6 +10,24 @@ export default { styleCompanion: 'styleCompanionOverlay', showToolbar: 'showToolbar', }), + msgType() { + let type = 'info'; + switch (this.styleCompanion) { + case 'special': + case 'unsure': + type = 'warning'; + break; + case 'sad': + type = 'error'; + break; + case 'happy': + type = 'success'; + break + case 'pointing': + case 'curious': + } + return type; + } }, methods: { ...mapActions({ @@ -49,11 +47,24 @@ export default { } }, showToolbar(newValue, oldValue) { - // hide companion when toolbar is closed + // hide companion when toolbar is closed if (oldValue === true && newValue === false) { this.hideCompanion(); } + }, + msgCompanion: { + handler(current) { + if (current.trim().length === 0) { + return; + } + const notification = { + type: this.msgType, + message: current + }; + this.globalEmit('push-system-notification', notification); + }, + immediate: true } - }, + } }; </script> diff --git a/resources/vue/store/courseware/courseware-shelf.module.js b/resources/vue/store/courseware/courseware-shelf.module.js index dc92a23ae4601a15a48452ec86aec2fd2ee3af89..d907ee421c4215a5032a51dc1415ac1e75283a85 100644 --- a/resources/vue/store/courseware/courseware-shelf.module.js +++ b/resources/vue/store/courseware/courseware-shelf.module.js @@ -249,33 +249,23 @@ export const actions = { } }, async companionInfo({ dispatch }, { info }) { - await dispatch('setStyleCompanionOverlay', 'default'); - await dispatch('setMsgCompanionOverlay', info); - return dispatch('setShowCompanionOverlay', true); + STUDIP.eventBus.emit('push-system-notification', { type: 'info', message: info}); }, async companionSuccess({ dispatch }, { info }) { - await dispatch('setStyleCompanionOverlay', 'happy'); - await dispatch('setMsgCompanionOverlay', info); - return dispatch('setShowCompanionOverlay', true); + STUDIP.eventBus.emit('push-system-notification', { type: 'success', message: info}); }, async companionError({ dispatch }, { info }) { - await dispatch('setStyleCompanionOverlay', 'sad'); - await dispatch('setMsgCompanionOverlay', info); - return dispatch('setShowCompanionOverlay', true); + STUDIP.eventBus.emit('push-system-notification', { type: 'error', message: info}); }, async companionWarning({ dispatch }, { info }) { - await dispatch('setStyleCompanionOverlay', 'alert'); - await dispatch('setMsgCompanionOverlay', info); - return dispatch('setShowCompanionOverlay', true); + STUDIP.eventBus.emit('push-system-notification', { type: 'warning', message: info}); }, async companionSpecial({ dispatch }, { info }) { - await dispatch('setStyleCompanionOverlay', 'special'); - await dispatch('setMsgCompanionOverlay', info); - return dispatch('setShowCompanionOverlay', true); + STUDIP.eventBus.emit('push-system-notification', { type: 'info', message: info}); }, coursewareShowCompanionOverlay({dispatch}, { data }) { return dispatch('setShowCompanionOverlay', data); @@ -310,7 +300,7 @@ export const actions = { return dispatch(loadUnits, state.context.id); }, - + async sortUnits({ dispatch, state }, data) { let loadUnits = null; if (state.context.type === 'courses') { @@ -321,7 +311,7 @@ export const actions = { } await state.httpClient.post(`${state.context.type}/${state.context.id}/courseware-units/sort`, {data: data}); - + return dispatch(loadUnits, state.context.id); }, diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js index c44bba17ce5f467232a169a790bcb885cccadee4..784b75090dff4b5be7bcfd5610c590b3dd4fb1a7 100644 --- a/resources/vue/store/courseware/courseware.module.js +++ b/resources/vue/store/courseware/courseware.module.js @@ -874,33 +874,23 @@ export const actions = { }, async companionInfo({ dispatch }, { info }) { - await dispatch('coursewareStyleCompanionOverlay', 'default'); - await dispatch('coursewareMsgCompanionOverlay', info); - return dispatch('coursewareShowCompanionOverlay', true); + STUDIP.eventBus.emit('push-system-notification', { type: 'info', message: info}); }, async companionSuccess({ dispatch }, { info }) { - await dispatch('coursewareStyleCompanionOverlay', 'happy'); - await dispatch('coursewareMsgCompanionOverlay', info); - return dispatch('coursewareShowCompanionOverlay', true); + STUDIP.eventBus.emit('push-system-notification', { type: 'success', message: info}); }, async companionError({ dispatch }, { info }) { - await dispatch('coursewareStyleCompanionOverlay', 'sad'); - await dispatch('coursewareMsgCompanionOverlay', info); - return dispatch('coursewareShowCompanionOverlay', true); + STUDIP.eventBus.emit('push-system-notification', { type: 'error', message: info}); }, async companionWarning({ dispatch }, { info }) { - await dispatch('coursewareStyleCompanionOverlay', 'alert'); - await dispatch('coursewareMsgCompanionOverlay', info); - return dispatch('coursewareShowCompanionOverlay', true); + STUDIP.eventBus.emit('push-system-notification', { type: 'exception', message: info}); }, async companionSpecial({ dispatch }, { info }) { - await dispatch('coursewareStyleCompanionOverlay', 'special'); - await dispatch('coursewareMsgCompanionOverlay', info); - return dispatch('coursewareShowCompanionOverlay', true); + STUDIP.eventBus.emit('push-system-notification', { type: 'warning', message: info}); }, // adds a favorite block type using the `type` of the BlockType diff --git a/templates/layouts/base.php b/templates/layouts/base.php index 6597fd8e124d6cc89bc0b5075c1330f2ae3a9870..d7fc1f10d2bd7644c86e496e8462de0979bf0dc6 100644 --- a/templates/layouts/base.php +++ b/templates/layouts/base.php @@ -53,7 +53,9 @@ $lang_attr = str_replace('_', '-', $_SESSION['_language']); 'ACTIONMENU_THRESHOLD' => Config::get()->ACTION_MENU_THRESHOLD, 'ENTRIES_PER_PAGE' => Config::get()->ENTRIES_PER_PAGE, 'OPENGRAPH_ENABLE' => Config::get()->OPENGRAPH_ENABLE, - 'COURSEWARE_CERTIFICATES_ENABLE' => Config::get()->COURSEWARE_CERTIFICATES_ENABLE + 'COURSEWARE_CERTIFICATES_ENABLE' => Config::get()->COURSEWARE_CERTIFICATES_ENABLE, + 'PERSONAL_NOTIFICATIONS_AUDIO_DEACTIVATED' => + (bool) User::findCurrent()->getConfiguration()->PERSONAL_NOTIFICATIONS_AUDIO_DEACTIVATED, ]) ?>, } </script> @@ -90,9 +92,12 @@ $lang_attr = str_replace('_', '-', $_SESSION['_language']); <?= Icon::create('zoom-out2')->asImg(24) ?> </button> <? endif; ?> - <?= implode(PageLayout::getMessages()) ?> <?= $content_for_layout ?> </div> + <system-notification-manager + id="system-notifications" + :notifications='<?= htmlReady(json_encode(PageLayout::getMessages())) ?>' + placement="<?= User::findCurrent()->getConfiguration()->SYSTEM_NOTIFICATIONS_PLACEMENT ?>"></system-notification-manager> </main> <!-- End main content --> diff --git a/templates/loginform.php b/templates/loginform.php index 69fc1f06ab65df91b7740e656c7785e7ac5378ee..da717eb8e5ed595ca62d8ebe3a9ba485995978ea 100644 --- a/templates/loginform.php +++ b/templates/loginform.php @@ -32,20 +32,14 @@ $show_hidden_login = !$show_login && StudipAuthAbstract::isLoginEnabled(); <div id="login_flex"> <div> - <? if ($loginerror): ?> - <!-- failed login code --> - <?= MessageBox::error(_('Bei der Anmeldung trat ein Fehler auf!'), [ - $error_msg, - sprintf( - _('Bitte wenden Sie sich bei Problemen an: <a href="mailto:%1$s">%1$s</a>'), - $GLOBALS['UNI_CONTACT'] - ) - ]) ?> - <? endif ?> - - <?= implode('', PageLayout::getMessages()); ?> <div id="loginbox"> <header> + <system-notification-manager + id="system-notifications" + class="system-notifications-login" + :notifications='<?= htmlReady(json_encode(PageLayout::getMessages())) ?>' + append-all-to="#loginbox"></system-notification-manager> + <h1><?= htmlReady(Config::get()->UNI_NAME_CLEAN) ?></h1> </header> @@ -139,7 +133,6 @@ $show_hidden_login = !$show_login && StudipAuthAbstract::isLoginEnabled(); </div> - </main> <script type="text/javascript" language="javascript">