Changed notification message position ( #86696p66d )

This commit is contained in:
Cüneyt Şentürk 2023-05-23 17:44:28 +03:00
parent 0863da593b
commit 4d7dfa181a
10 changed files with 399 additions and 299 deletions

8
public/css/app.css vendored
View File

@ -65184,6 +65184,10 @@ body{
right: 1rem; right: 1rem;
} }
:is([dir="ltr"] .sm\:ltr\:left-4){
left: 1rem;
}
:is([dir="ltr"] .sm\:ltr\:pl-10){ :is([dir="ltr"] .sm\:ltr\:pl-10){
padding-left: 2.5rem; padding-left: 2.5rem;
} }
@ -65192,6 +65196,10 @@ body{
left: 1rem; left: 1rem;
} }
:is([dir="rtl"] .sm\:rtl\:right-4){
right: 1rem;
}
:is([dir="rtl"] .sm\:rtl\:space-x-reverse) > :not([hidden]) ~ :not([hidden]){ :is([dir="rtl"] .sm\:rtl\:space-x-reverse) > :not([hidden]) ~ :not([hidden]){
--tw-space-x-reverse: 1; --tw-space-x-reverse: 1;
} }

View File

@ -2,9 +2,24 @@
<div <div
@click="tryClose" @click="tryClose"
data-notify="container" data-notify="container"
class="alert alert-notify fixed w-full sm:w-500 flex items-center justify-between ltr:right-0 rtl:left-0 sm:ltr:right-4 sm:rtl:left-4 p-4 text-black font-bold rounded-lg z-30"
:class="[ :class="[
{ 'alert-with-icon': icon }, 'alert alert-notify',
'fixed w-full sm:w-500 flex items-center justify-between',
{
'rtl:right-0 ltr:left-0' : horizontalAlign == 'left',
'sm:rtl:right-4 sm:ltr:left-4' : horizontalAlign == 'left',
},
{
'ltr:right-0 rtl:left-0' : horizontalAlign == 'right',
'sm:ltr:right-4 sm:rtl:left-4' : horizontalAlign == 'right',
},
'p-4',
'text-black font-bold',
'rounded-lg',
'z-30',
{
'alert-with-icon': icon
},
verticalAlign, verticalAlign,
horizontalAlign, horizontalAlign,
alertType alertType
@ -26,11 +41,10 @@
<span v-if="title" class="title"> <span v-if="title" class="title">
<b>{{ title }}<br/></b> <b>{{ title }}<br/></b>
</span> </span>
<span v-if="message" v-html="message"></span> <span v-if="message" v-html="message"></span>
<content-render
v-if="!message && component" <content-render v-if="!message && component" :component="component"></content-render>
:component="component"
></content-render>
</span> </span>
</div> </div>
@ -39,49 +53,60 @@
class="close text-2xl" class="close text-2xl"
data-dismiss="alert" data-dismiss="alert"
aria-label="Close" aria-label="Close"
@click="close"> @click="close"
>
<span aria-hidden="true">×</span> <span aria-hidden="true">×</span>
</button> </button>
</slot> </slot>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'notification', name: 'notification',
components: { components: {
contentRender: { contentRender: {
props: ['component'], props: ['component'],
render: h => h(this.component) render: h => h(this.component)
} }
}, },
props: { props: {
message: String, message: String,
title: { title: {
type: String, type: String,
description: 'Notification title' description: 'Notification title'
}, },
icon: { icon: {
type: String, type: String,
description: 'Notification icon' description: 'Notification icon'
}, },
verticalAlign: { verticalAlign: {
type: String, type: String,
default: 'top', default: 'top',
validator: value => { validator: value => {
let acceptedValues = ['top', 'bottom']; let acceptedValues = ['top', 'bottom'];
return acceptedValues.indexOf(value) !== -1; return acceptedValues.indexOf(value) !== -1;
}, },
description: 'Vertical alignment of notification (top|bottom)' description: 'Vertical alignment of notification (top|bottom)'
}, },
horizontalAlign: { horizontalAlign: {
type: String, type: String,
default: 'right', default: 'right',
validator: value => { validator: value => {
let acceptedValues = ['left', 'center', 'right']; let acceptedValues = ['left', 'center', 'right'];
return acceptedValues.indexOf(value) !== -1; return acceptedValues.indexOf(value) !== -1;
}, },
description: 'Horizontal alignment of notification (left|center|right)' description: 'Horizontal alignment of notification (left|center|right)'
}, },
type: { type: {
type: String, type: String,
default: 'info', default: 'info',
@ -94,10 +119,12 @@
'warning', 'warning',
'success' 'success'
]; ];
return acceptedValues.indexOf(value) !== -1; return acceptedValues.indexOf(value) !== -1;
}, },
description: 'Notification type of notification (gray-300|blue-300|gray-300|red-300|orange-300|green-300)' description: 'Notification type of notification (gray-300|blue-300|gray-300|red-300|orange-300|green-300)'
}, },
timeout: { timeout: {
type: Number, type: Number,
default: 5000, default: 5000,
@ -106,33 +133,40 @@
}, },
description: 'Notification timeout (closes after X milliseconds). Default is 5000 (5s)' description: 'Notification timeout (closes after X milliseconds). Default is 5000 (5s)'
}, },
timestamp: { timestamp: {
type: Date, type: Date,
default: () => new Date(), default: () => new Date(),
description: 'Notification timestamp (used internally to handle notification removal correctly)' description: 'Notification timestamp (used internally to handle notification removal correctly)'
}, },
component: { component: {
type: [Object, Function], type: [Object, Function],
description: 'Custom content component. Cane be a `.vue` component or render function' description: 'Custom content component. Cane be a `.vue` component or render function'
}, },
showClose: { showClose: {
type: Boolean, type: Boolean,
default: true, default: true,
description: 'Whether to show close button' description: 'Whether to show close button'
}, },
closeOnClick: { closeOnClick: {
type: Boolean, type: Boolean,
default: true, default: true,
description: 'Whether to close notification when clicking it\' body' description: 'Whether to close notification when clicking it\' body'
}, },
clickHandler: { clickHandler: {
type: Function, type: Function,
description: 'Custom notification click handler' description: 'Custom notification click handler'
} }
}, },
data() { data() {
return { return {
elmHeight: 0, elmHeight: 0,
typeByClass: { typeByClass: {
'default': 'black-100', 'default': 'black-100',
'info': 'blue-100', 'info': 'blue-100',
@ -141,6 +175,7 @@
'warning': 'orange-100', 'warning': 'orange-100',
'success': 'green-100', 'success': 'green-100',
}, },
textByClass: { textByClass: {
'default': 'black-600', 'default': 'black-600',
'info': 'blue-600', 'info': 'blue-600',
@ -151,16 +186,20 @@
} }
}; };
}, },
computed: { computed: {
hasIcon() { hasIcon() {
return this.icon && this.icon.length > 0; return this.icon && this.icon.length > 0;
}, },
alertType() { alertType() {
return `bg-${this.typeByClass[this.type]} text-${this.textByClass[this.type]}`; return `bg-${this.typeByClass[this.type]} text-${this.textByClass[this.type]}`;
}, },
customPosition() { customPosition() {
let initialMargin = 20; let initialMargin = 20;
let alertHeight = this.elmHeight + 10; let alertHeight = this.elmHeight + 10;
let sameAlertsCount = this.$notifications.state.filter(alert => { let sameAlertsCount = this.$notifications.state.filter(alert => {
return ( return (
alert.horizontalAlign === this.horizontalAlign && alert.horizontalAlign === this.horizontalAlign &&
@ -168,37 +207,51 @@
alert.timestamp <= this.timestamp alert.timestamp <= this.timestamp
); );
}).length; }).length;
if (this.$notifications.settings.overlap) { if (this.$notifications.settings.overlap) {
sameAlertsCount = 1; sameAlertsCount = 1;
} }
let pixels = (sameAlertsCount - 1) * alertHeight + initialMargin; let pixels = (sameAlertsCount - 1) * alertHeight + initialMargin;
if (sameAlertsCount > 1) {
pixels = 30 + this.$parent.children[sameAlertsCount - 2].elm.offsetHeight;
}
let styles = {}; let styles = {};
if (this.verticalAlign === 'top') { if (this.verticalAlign === 'top') {
styles.top = `${pixels}px`; styles.top = `${pixels}px`;
} else { } else {
styles.bottom = `${pixels}px`; styles.bottom = `${pixels}px`;
} }
return styles; return styles;
} }
}, },
methods: { methods: {
close() { close() {
this.$emit('close', this.timestamp); this.$emit('close', this.timestamp);
}, },
tryClose(evt) { tryClose(evt) {
if (this.clickHandler) { if (this.clickHandler) {
this.clickHandler(evt, this); this.clickHandler(evt, this);
} }
if (this.closeOnClick) { if (this.closeOnClick) {
this.close(); this.close();
} }
} }
}, },
mounted() { mounted() {
this.elmHeight = this.$el.clientHeight; this.elmHeight = this.$el.clientHeight;
if (this.timeout) { if (this.timeout) {
setTimeout(this.close, this.timeout); //setTimeout(this.close, this.timeout);
}
} }
},
}; };
</script> </script>

View File

@ -2,7 +2,8 @@
<div class="notifications"> <div class="notifications">
<slide-y-up-transition :duration="transitionDuration" <slide-y-up-transition :duration="transitionDuration"
group group
mode="out-in"> mode="out-in"
>
<notification <notification
v-for="notification in notifications" v-for="notification in notifications"
v-bind="notification" v-bind="notification"
@ -14,6 +15,7 @@
</slide-y-up-transition> </slide-y-up-transition>
</div> </div>
</template> </template>
<script> <script>
import Notification from './Notification.vue'; import Notification from './Notification.vue';
import { SlideYUpTransition } from 'vue2-transitions'; import { SlideYUpTransition } from 'vue2-transitions';
@ -23,29 +25,35 @@
SlideYUpTransition, SlideYUpTransition,
Notification Notification
}, },
props: { props: {
transitionDuration: { transitionDuration: {
type: Number, type: Number,
default: 200 default: 200
}, },
overlap: { overlap: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
data() { data() {
return { return {
notifications: this.$notifications.state notifications: this.$notifications.state
}; };
}, },
methods: { methods: {
removeNotification(timestamp) { removeNotification(timestamp) {
this.$notifications.removeNotification(timestamp); this.$notifications.removeNotification(timestamp);
} }
}, },
created() { created() {
this.$notifications.settings.overlap = this.overlap; this.$notifications.settings.overlap = this.overlap;
}, },
watch: { watch: {
overlap: function (newVal) { overlap: function (newVal) {
this.$notifications.settings.overlap = newVal; this.$notifications.settings.overlap = newVal;

View File

@ -2,6 +2,7 @@ import Notifications from './Notifications.vue';
const NotificationStore = { const NotificationStore = {
state: [], // here the notifications will be added state: [], // here the notifications will be added
settings: { settings: {
overlap: false, overlap: false,
verticalAlign: 'top', verticalAlign: 'top',
@ -11,26 +12,37 @@ const NotificationStore = {
closeOnClick: true, closeOnClick: true,
showClose: true showClose: true
}, },
setOptions(options) { setOptions(options) {
this.settings = Object.assign(this.settings, options); this.settings = Object.assign(this.settings, options);
}, },
removeNotification(timestamp) { removeNotification(timestamp) {
const indexToDelete = this.state.findIndex(n => n.timestamp === timestamp); const indexToDelete = this.state.findIndex(n => n.timestamp === timestamp);
if (indexToDelete !== -1) { if (indexToDelete !== -1) {
this.state.splice(indexToDelete, 1); this.state.splice(indexToDelete, 1);
} }
}, },
addNotification(notification) { addNotification(notification) {
if (typeof notification === 'string' || notification instanceof String) { if (typeof notification === 'string' || notification instanceof String) {
notification = { message: notification }; notification = {
message: notification
};
} }
notification.timestamp = new Date(); notification.timestamp = new Date();
notification.timestamp.setMilliseconds( notification.timestamp.setMilliseconds(
notification.timestamp.getMilliseconds() + this.state.length notification.timestamp.getMilliseconds() + this.state.length
); );
notification = Object.assign({}, this.settings, notification); notification = Object.assign({}, this.settings, notification);
this.state.push(notification); this.state.push(notification);
}, },
notify(notification) { notify(notification) {
if (Array.isArray(notification)) { if (Array.isArray(notification)) {
notification.forEach(notificationInstance => { notification.forEach(notificationInstance => {
@ -48,15 +60,18 @@ const NotificationsPlugin = {
data: { data: {
notificationStore: NotificationStore notificationStore: NotificationStore
}, },
methods: { methods: {
notify(notification) { notify(notification) {
this.notificationStore.notify(notification); this.notificationStore.notify(notification);
} }
} }
}); });
Vue.prototype.$notify = app.notify; Vue.prototype.$notify = app.notify;
Vue.prototype.$notifications = app.notificationStore; Vue.prototype.$notifications = app.notificationStore;
Vue.component('Notifications', Notifications); Vue.component('Notifications', Notifications);
if (options) { if (options) {
NotificationStore.setOptions(options); NotificationStore.setOptions(options);
} }

View File

@ -271,6 +271,8 @@ export default {
} }
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: notify.message, message: notify.message,
timeout: timeout, timeout: timeout,
icon: 'error_outline', icon: 'error_outline',
@ -1120,6 +1122,8 @@ export default {
document.execCommand('copy'); document.execCommand('copy');
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: this.share.success_message, message: this.share.success_message,
timeout: 5000, timeout: 5000,
icon: 'error_outline', icon: 'error_outline',

View File

@ -72,6 +72,8 @@ export default {
} }
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: response.data.message, message: response.data.message,
timeout: timeout, timeout: timeout,
icon: "error_outline", icon: "error_outline",
@ -92,6 +94,8 @@ export default {
} }
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: event.message, message: event.message,
timeout: timeout, timeout: timeout,
icon: "error_outline", icon: "error_outline",

View File

@ -54,6 +54,8 @@ const login = new Vue({
let type = notify.level; let type = notify.level;
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: notify.message, message: notify.message,
timeout: 5000, timeout: 5000,
icon: '', icon: '',

View File

@ -69,6 +69,8 @@ const app = new Vue({
if (response.data.error) { if (response.data.error) {
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: response.data.message, message: response.data.message,
timeout: 0, timeout: 0,
icon: 'fas fa-bell', icon: 'fas fa-bell',

View File

@ -106,6 +106,8 @@ const app = new Vue({
add_to_cart_promise.then(response => { add_to_cart_promise.then(response => {
if (response.data.success) { if (response.data.success) {
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: response.data.message, message: response.data.message,
timeout: 0, timeout: 0,
icon: "shopping_cart_checkout", icon: "shopping_cart_checkout",

View File

@ -112,6 +112,8 @@ export default {
}) })
.catch((error) => { .catch((error) => {
this.$notify({ this.$notify({
verticalAlign: 'bottom',
horizontalAlign: 'left',
message: this.translations.finish.error_message, message: this.translations.finish.error_message,
timeout: 1000, timeout: 1000,
icon: "", icon: "",