From b01cd99a93715dde6f21ebea666d90fbc3143164 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Thu, 19 Oct 2023 14:10:55 +0200 Subject: [PATCH 1/3] Plugin: Fhc Alert --- public/js/plugin/FhcAlert.js | 369 +++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 public/js/plugin/FhcAlert.js diff --git a/public/js/plugin/FhcAlert.js b/public/js/plugin/FhcAlert.js new file mode 100644 index 000000000..5e1111dd6 --- /dev/null +++ b/public/js/plugin/FhcAlert.js @@ -0,0 +1,369 @@ +/** + * Copyright (C) 2022 fhcomplete.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * @usage: + * Preperations: + * Be sure to have PrimeVue loaded with the toast and confirmdialog + * components as primevue variable + * Install: + * Import this Plugin and install it with the app.use() function. + * Use: + * In your component you can call now the global property $fhcAlert + * which has the following functions: + * + * alertSuccess + * ------------ + * Displays a success message + * @param string message + * @return void + * + * alertInfo + * --------- + * Displays an info message + * @param string message + * @return void + * + * alertWarning + * ------------ + * Displays a warning + * @param string message + * @return void + * + * alertError + * ---------- + * Displays an error + * @param string message + * @return void + * + * alertSystemError + * ---------------- + * Displays an alert with the error details and a button to mail + * the error to the Support Team + * @param string message + * @return void + * + * confirmDelete + * ------------- + * Displays a confirmation dialog and returns a Promise which resolves + * with true or false depending und the pressed button. + * @return Promise + * + * alertDefault + * ------------ + * Displays an alert + * @param string severity can be 'success', 'info', 'warning', 'error' + * @param string title + * @param string message + * @param boolean sticky (optional) defaults to false + * @return void + * + * alertMultiple + * ------------- + * Displays multiple alerts + * @param array messageArray + * @param string severity (optional) defaults to 'info' + * @param string title (optional) defaults to 'Info' + * @param boolean sticky (optional) defaults to false + * @return void + * + * handleSystemError + * ----------------- + * Automatiticly determine how to display an system error and display it. + * This would be used in a catch block of an ajax call. + * @param mixed error + * @return void + * + * handleSystemMessage + * ------------------- + * Automatiticly determine how to display a message and display it. + * @param mixed message + * @return void + */ + +let toast_component = null; +const toast_queue = []; + +function _add_to_toast(...args) { + toast_component.add.apply(toast_component, args); +} +let toast_add = function _add_to_toast_queue(...args) { + toast_queue.push(args); +}; + +let alert_component = null; +const alert_queue = []; + +function _add_to_alert(...args) { + alert_component.add.apply(alert_component, args); +} +let alert_add = function _add_to_alert_queue(...args) { + alert_queue.push(args); +}; + +let confirm_component = null; +const confirm_queue = []; + +function _require_confirm(...args) { + confirm_component.confirm.apply(confirm_component, args); +} +let confirm_require = function _add_to_confirm_queue(...args) { + confirm_queue.push(args); +}; + + +const helperAppContainer = document.createElement('div'); + +const helperApp = Vue.createApp({ + setup() { + return self => { + return [ + Vue.h(primevue.toast, { + ref: 'toast', + baseZIndex: 99999 + }), + Vue.h(primevue.toast, { + ref: 'alert', + baseZIndex: 99999, + position: 'center', + }, { + message: slotProps => { + const messageCard = Vue.h('div', { + ref: 'messageCard', + id: 'collapseMessageCard', + class: 'collapse mt-3' + }, [ + Vue.h('div', {class: 'card card-body text-body small', style: 'white-space: pre-wrap'}, slotProps.message.detail) + ]); + return [ + Vue.h('i', {class: 'fa fa-circle-exclamation fa-2xl mt-3'}), + Vue.h('div', {class: 'p-toast-message-text'}, [ + Vue.h('span', {class: 'p-toast-summary'}, slotProps.message.summary), + Vue.h('div', {class: 'p-toast-detail my-3'}, 'Sorry! Ein interner technischer Fehler ist aufgetreten.'), + Vue.h('div', {class: 'd-flex justify-content-between align-items-center'}, [ + Vue.h('a', { + class: 'align-bottom flex-fill me-2', + dataBsToggle: 'collapse', + href: '#collapseMessageCard', + role: 'button', + ariaExpanded: 'false', + ariaControls: 'collapseMessageCard', + onClick: () => { bootstrap.Collapse.getOrCreateInstance(messageCard.el).toggle(); } + }, 'Fehler anzeigen'), + Vue.h('a', { + class: 'btn btn-primary flex-fill', + target: '_blank', + href: self.mailToUrl(slotProps), + }, 'Fehler melden') + ]), + messageCard, + ]), + ]; + } + }), + Vue.h(primevue.confirmdialog, { + ref: 'pvConfirmdialog' + }) + ]; + }; + }, + methods: { + mailToUrl(slotProps) { + let mailTo = 'noreply@technikum-wien.at'; // TODO domain anpassen + let subject = 'Meldung%20Systemfehler'; + let body = ` + Danke, dass Sie uns den Fehler melden. %0D%0A %0D%0A + Bitte beschreiben Sie uns ausführlich das Problem.%0D%0A + Bsp: Ich habe X ausgewählt und Y angelegt. Beim Speichern erhielt ich die Fehlermeldung. [Optional: Ich habe den Browser Z verwendet.]%0D%0A + -----------------------------------------------------------------------------------------------------------------------------------%0D%0A + PROBLEM: ... %0D%0A %0D%0A %0D%0A + + -----------------------------------------------------------------------------------------------------------------------------------%0D%0A + Fehler URL: ` + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method + `%0D%0A + Fehler Meldung: ` + slotProps.message.detail + `%0D%0A + -----------------------------------------------------------------------------------------------------------------------------------%0D%0A %0D%0A + Wir kümmern uns um eine rasche Behebung des Problems!` + + return "mailto:" + mailTo + "?subject=" + subject + "&body=" + body; + } + }, + mounted() { + toast_component = this.$refs.toast; + toast_add = _add_to_toast; + while (toast_queue.length) + _add_to_toast(toast_queue.unshift()); + + alert_component = this.$refs.alert; + alert_add = _add_to_alert; + while (alert_queue.length) + _add_to_alert(alert_queue.unshift()); + + confirm_component = this.$refs.confirm; + confirm_require = _require_confirm; + while (confirm_queue.length) + _require_confirm(confirm_queue.unshift()); + }, + beforeUnmount() { + toast_add = _add_to_toast_queue; + toast_component = null; + + alert_add = _add_to_alert_queue; + alert_component = null; + + confirm_require = _add_to_confirm_queue; + confirm_component = null; + }, + unmounted() { + helperAppContainer.parentElement.removeChild(helperAppContainer); + } +}); + +helperApp + .use(primevue.config.default) + .mount(helperAppContainer); + +document.body.appendChild(helperAppContainer); + +export default { + install: (app, options) => { + const $fhcAlert = { + alertSuccess(message) { + toast_add({ severity: 'success', summary: 'Info', detail: message, life: 1000}); + }, + alertInfo(message) { + toast_add({ severity: 'info', summary: 'Info', detail: message, life: 3000 }); + }, + alertWarning(message) { + toast_add({ severity: 'warn', summary: 'Achtung', detail: message, life: 7000 }); + }, + alertError(message) { + toast_add({ severity: 'error', summary: 'Achtung', detail: message }); + }, + alertSystemError(message) { + alert_add({ severity: 'error', summary: 'Systemfehler', detail: message}); + }, + confirmDelete() { + return new Promise((resolve, reject) => { + confirm_require({ + header: 'Achtung', + message: 'Möchten Sie sicher löschen?', + acceptLabel: 'Löschen', + acceptClass: 'btn btn-danger', + rejectLabel: 'Abbrechen', + rejectClass: 'btn btn-outline-secondary', + accept() { + resolve(true); + }, + reject() { + resolve(false); + }, + }); + }); + }, + alertDefault(severity, title, message, sticky = false) { + let options = { severity: severity, summary: title, detail: message}; + + if (!sticky) + options.life = 3000; + + toast_add(options); + }, + alertMultiple(messageArray, severity = 'info', title = 'Info', sticky = false){ + // Check, if array has only string values + if (messageArray.every(message => typeof message === 'string')) { + messageArray.every(message => this.alertDefault(severity, title, message, sticky)); + return true; + } + return false; + }, + handleSystemError(error) { + // Error is string + if (typeof error === 'string') + return $fhcAlert.alertSystemError(error); + + // Error is array of strings + if (Array.isArray(error) && error.every(err => typeof err === 'string')) + return error.every($fhcAlert.alertSystemError); + + // Error is object + if (typeof error === 'object' && error !== null) { + let errMsg = ''; + + if (error.hasOwnProperty('message')) + errMsg += 'Error Message: ' + error.message.toUpperCase() + '\r\n'; + + if (error.hasOwnProperty('config') && error.config.hasOwnProperty('url')) + errMsg += 'Error ConfigURL: ' + error.config.url + '\r\n'; + + if (error.hasOwnProperty('stack')) + errMsg += 'Error Stack: ' + error.stack + '\r\n'; + + // Fallback object error message + if (errMsg == '') + errMsg = 'Error Message: ' + JSON.stringify(error) + '\r\n'; + + errMsg += 'Error Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method; + + return $fhcAlert.alertSystemError(errMsg); + } + + // Fallback + $fhcAlert.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method); + }, + handleSystemMessage(message) { + // Message is string + if (typeof message === 'string') + return fhcerror.alertWarning(message); + + // Message is array of strings + if (Array.isArray(message)) { + // If Array has only Strings + if (message.every(msg => typeof msg === 'string')) + return message.every(fhcerror.alertWarning); + + // If Array has only Objects + if (message.every(msg => typeof msg === 'object') && msg !== null) { + return message.every(msg => { + if (msg.hasOwnProperty('data')) { + if (msg.data.hasOwnProperty('retval')) + fhcerror.alertWarning(JSON.stringify(msg.data.retval)); + // TODO(chris): there is no else here! join ifs? + } else { + fhcerror.alertSystemError(JSON.stringify(msg)); + } + }); + } + } + + // Message is Object with data property TODO: check object handling + if (typeof message === 'object' && message !== null){ + if (message.hasOwnProperty('data')) { + if (message.data.hasOwnProperty('retval')) + // NOTE(chris): changed: alertSystemError => alertWarning + fhcerror.alertWarning(JSON.stringify(message.data.retval)); + // TODO(chris): there is no else here! join ifs? + } else { + fhcerror.alertSystemError(JSON.stringify(message)); + } + return; + } + + // Fallback + fhcerror.alertSystemError('alertSystemError throws Generic Error\r\nError Controller Path: ' + FHC_JS_DATA_STORAGE_OBJECT.called_path + '/' + FHC_JS_DATA_STORAGE_OBJECT.called_method); + } + }; + app.config.globalProperties.$fhcAlert = $fhcAlert; + } +} \ No newline at end of file From d57fd9eb609de807c823738d2391f5d13e473c0d Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Tue, 24 Oct 2023 12:23:12 +0200 Subject: [PATCH 2/3] fhcAlert: slight improvements --- public/js/plugin/FhcAlert.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/public/js/plugin/FhcAlert.js b/public/js/plugin/FhcAlert.js index 5e1111dd6..8ff45f717 100644 --- a/public/js/plugin/FhcAlert.js +++ b/public/js/plugin/FhcAlert.js @@ -117,7 +117,7 @@ let confirm_component = null; const confirm_queue = []; function _require_confirm(...args) { - confirm_component.confirm.apply(confirm_component, args); + confirm_component.confirmListener.apply(confirm_component, args); } let confirm_require = function _add_to_confirm_queue(...args) { confirm_queue.push(args); @@ -174,7 +174,7 @@ const helperApp = Vue.createApp({ } }), Vue.h(primevue.confirmdialog, { - ref: 'pvConfirmdialog' + ref: 'confirm' }) ]; }; @@ -246,7 +246,7 @@ export default { toast_add({ severity: 'info', summary: 'Info', detail: message, life: 3000 }); }, alertWarning(message) { - toast_add({ severity: 'warn', summary: 'Achtung', detail: message, life: 7000 }); + toast_add({ severity: 'warn', summary: 'Achtung', detail: message}); }, alertError(message) { toast_add({ severity: 'error', summary: 'Achtung', detail: message }); @@ -336,10 +336,8 @@ export default { // If Array has only Objects if (message.every(msg => typeof msg === 'object') && msg !== null) { return message.every(msg => { - if (msg.hasOwnProperty('data')) { - if (msg.data.hasOwnProperty('retval')) - fhcerror.alertWarning(JSON.stringify(msg.data.retval)); - // TODO(chris): there is no else here! join ifs? + if (msg.hasOwnProperty('data') && msg.data.hasOwnProperty('retval')) { + fhcerror.alertWarning(JSON.stringify(msg.data.retval)); } else { fhcerror.alertSystemError(JSON.stringify(msg)); } @@ -347,13 +345,11 @@ export default { } } - // Message is Object with data property TODO: check object handling + // Message is Object with data property if (typeof message === 'object' && message !== null){ - if (message.hasOwnProperty('data')) { - if (message.data.hasOwnProperty('retval')) - // NOTE(chris): changed: alertSystemError => alertWarning - fhcerror.alertWarning(JSON.stringify(message.data.retval)); - // TODO(chris): there is no else here! join ifs? + if (message.hasOwnProperty('data') && message.data.hasOwnProperty('retval')) { + // NOTE(chris): changed: alertSystemError => alertWarning + fhcerror.alertWarning(JSON.stringify(message.data.retval)); } else { fhcerror.alertSystemError(JSON.stringify(message)); } From 22453c71b4aafe690ee386f2a1cf77a6b7e027b4 Mon Sep 17 00:00:00 2001 From: cgfhtw Date: Tue, 24 Oct 2023 13:35:50 +0200 Subject: [PATCH 3/3] fhcAlert: slight improvements 2 --- public/js/plugin/FhcAlert.js | 176 ++++++++++++----------------------- 1 file changed, 57 insertions(+), 119 deletions(-) diff --git a/public/js/plugin/FhcAlert.js b/public/js/plugin/FhcAlert.js index 8ff45f717..6ff54061d 100644 --- a/public/js/plugin/FhcAlert.js +++ b/public/js/plugin/FhcAlert.js @@ -93,91 +93,12 @@ * @return void */ -let toast_component = null; -const toast_queue = []; - -function _add_to_toast(...args) { - toast_component.add.apply(toast_component, args); -} -let toast_add = function _add_to_toast_queue(...args) { - toast_queue.push(args); -}; - -let alert_component = null; -const alert_queue = []; - -function _add_to_alert(...args) { - alert_component.add.apply(alert_component, args); -} -let alert_add = function _add_to_alert_queue(...args) { - alert_queue.push(args); -}; - -let confirm_component = null; -const confirm_queue = []; - -function _require_confirm(...args) { - confirm_component.confirmListener.apply(confirm_component, args); -} -let confirm_require = function _add_to_confirm_queue(...args) { - confirm_queue.push(args); -}; - - const helperAppContainer = document.createElement('div'); const helperApp = Vue.createApp({ - setup() { - return self => { - return [ - Vue.h(primevue.toast, { - ref: 'toast', - baseZIndex: 99999 - }), - Vue.h(primevue.toast, { - ref: 'alert', - baseZIndex: 99999, - position: 'center', - }, { - message: slotProps => { - const messageCard = Vue.h('div', { - ref: 'messageCard', - id: 'collapseMessageCard', - class: 'collapse mt-3' - }, [ - Vue.h('div', {class: 'card card-body text-body small', style: 'white-space: pre-wrap'}, slotProps.message.detail) - ]); - return [ - Vue.h('i', {class: 'fa fa-circle-exclamation fa-2xl mt-3'}), - Vue.h('div', {class: 'p-toast-message-text'}, [ - Vue.h('span', {class: 'p-toast-summary'}, slotProps.message.summary), - Vue.h('div', {class: 'p-toast-detail my-3'}, 'Sorry! Ein interner technischer Fehler ist aufgetreten.'), - Vue.h('div', {class: 'd-flex justify-content-between align-items-center'}, [ - Vue.h('a', { - class: 'align-bottom flex-fill me-2', - dataBsToggle: 'collapse', - href: '#collapseMessageCard', - role: 'button', - ariaExpanded: 'false', - ariaControls: 'collapseMessageCard', - onClick: () => { bootstrap.Collapse.getOrCreateInstance(messageCard.el).toggle(); } - }, 'Fehler anzeigen'), - Vue.h('a', { - class: 'btn btn-primary flex-fill', - target: '_blank', - href: self.mailToUrl(slotProps), - }, 'Fehler melden') - ]), - messageCard, - ]), - ]; - } - }), - Vue.h(primevue.confirmdialog, { - ref: 'confirm' - }) - ]; - }; + components: { + PvToast: primevue.toast, + PvConfirm: primevue.confirmdialog }, methods: { mailToUrl(slotProps) { @@ -197,66 +118,83 @@ const helperApp = Vue.createApp({ Wir kümmern uns um eine rasche Behebung des Problems!` return "mailto:" + mailTo + "?subject=" + subject + "&body=" + body; + }, + openMessagecard() { + bootstrap.Collapse.getOrCreateInstance(this.$refs.messageCard).toggle(); } }, - mounted() { - toast_component = this.$refs.toast; - toast_add = _add_to_toast; - while (toast_queue.length) - _add_to_toast(toast_queue.unshift()); - - alert_component = this.$refs.alert; - alert_add = _add_to_alert; - while (alert_queue.length) - _add_to_alert(alert_queue.unshift()); - - confirm_component = this.$refs.confirm; - confirm_require = _require_confirm; - while (confirm_queue.length) - _require_confirm(confirm_queue.unshift()); - }, - beforeUnmount() { - toast_add = _add_to_toast_queue; - toast_component = null; - - alert_add = _add_to_alert_queue; - alert_component = null; - - confirm_require = _add_to_confirm_queue; - confirm_component = null; - }, unmounted() { helperAppContainer.parentElement.removeChild(helperAppContainer); - } + }, + template: ` + + + + + ` }); -helperApp - .use(primevue.config.default) - .mount(helperAppContainer); +helperApp.use(primevue.config.default) +helperApp.use(primevue.confirmationservice) + +const helperAppInstance = helperApp.mount(helperAppContainer); document.body.appendChild(helperAppContainer); + export default { install: (app, options) => { const $fhcAlert = { alertSuccess(message) { - toast_add({ severity: 'success', summary: 'Info', detail: message, life: 1000}); + helperAppInstance.$refs.toast.add({ severity: 'success', summary: 'Info', detail: message, life: 1000}); }, alertInfo(message) { - toast_add({ severity: 'info', summary: 'Info', detail: message, life: 3000 }); + helperAppInstance.$refs.toast.add({ severity: 'info', summary: 'Info', detail: message, life: 3000 }); }, alertWarning(message) { - toast_add({ severity: 'warn', summary: 'Achtung', detail: message}); + helperAppInstance.$refs.toast.add({ severity: 'warn', summary: 'Achtung', detail: message}); }, alertError(message) { - toast_add({ severity: 'error', summary: 'Achtung', detail: message }); + helperAppInstance.$refs.toast.add({ severity: 'error', summary: 'Achtung', detail: message }); }, alertSystemError(message) { - alert_add({ severity: 'error', summary: 'Systemfehler', detail: message}); + helperAppInstance.$refs.alert.add({ severity: 'error', summary: 'Systemfehler', detail: message}); }, confirmDelete() { return new Promise((resolve, reject) => { - confirm_require({ + helperAppInstance.$confirm.require({ + group: 'fhcAlertConfirm', header: 'Achtung', message: 'Möchten Sie sicher löschen?', acceptLabel: 'Löschen', @@ -278,7 +216,7 @@ export default { if (!sticky) options.life = 3000; - toast_add(options); + helperAppInstance.$refs.toast.add(options); }, alertMultiple(messageArray, severity = 'info', title = 'Info', sticky = false){ // Check, if array has only string values