diff --git a/addons/barcodes/static/tests/barcode_tests.js b/addons/barcodes/static/tests/barcode_tests.js index 47a53402b14..92b5604f622 100644 --- a/addons/barcodes/static/tests/barcode_tests.js +++ b/addons/barcodes/static/tests/barcode_tests.js @@ -562,7 +562,7 @@ QUnit.test('barcode_scanned only trigger error for active view', async function } _.each(['O','-','B','T','N','.','c','a','n','c','e','l','Enter'], modalTriggerKeypressEvent); await testUtils.nextTick(); - assert.verifySteps(['warning'], "only one event should be triggered"); + assert.verifySteps(['danger'], "only one event should be triggered"); form.destroy(); }); }); diff --git a/addons/calendar/__manifest__.py b/addons/calendar/__manifest__.py index a2ea47ad13c..dd92675090a 100644 --- a/addons/calendar/__manifest__.py +++ b/addons/calendar/__manifest__.py @@ -33,7 +33,7 @@ If you need to manage your meetings, you should install the CRM module. 'views/calendar_views.xml', 'data/mail_activity_data.xml', ], - 'qweb': ['static/src/xml/*.xml'], + 'qweb': ['static/src/xml/base_calendar.xml'], 'installable': True, 'application': True, 'auto_install': False, diff --git a/addons/calendar/static/src/js/base_calendar.js b/addons/calendar/static/src/js/base_calendar.js index 9487b8fcb3d..036f691890c 100644 --- a/addons/calendar/static/src/js/base_calendar.js +++ b/addons/calendar/static/src/js/base_calendar.js @@ -2,17 +2,19 @@ odoo.define('base_calendar.base_calendar', function (require) { "use strict"; var BasicModel = require('web.BasicModel'); -var field_registry = require('web.field_registry'); +var fieldRegistry = require('web.field_registry'); var Notification = require('web.Notification'); -var relational_fields = require('web.relational_fields'); +var relationalFields = require('web.relational_fields'); var session = require('web.session'); var WebClient = require('web.WebClient'); -var FieldMany2ManyTags = relational_fields.FieldMany2ManyTags; +var FieldMany2ManyTags = relationalFields.FieldMany2ManyTags; var CalendarNotification = Notification.extend({ template: "CalendarNotification", + xmlDependencies: (Notification.prototype.xmlDependencies || []) + .concat(['/calendar/static/src/xml/notification_calendar.xml']), init: function(parent, params) { this._super(parent, params); @@ -156,6 +158,6 @@ var Many2ManyAttendee = FieldMany2ManyTags.extend({ }, }); -field_registry.add('many2manyattendee', Many2ManyAttendee); +fieldRegistry.add('many2manyattendee', Many2ManyAttendee); }); diff --git a/addons/calendar/static/src/xml/base_calendar.xml b/addons/calendar/static/src/xml/base_calendar.xml index 99de4974050..ebe1c06e621 100644 --- a/addons/calendar/static/src/xml/base_calendar.xml +++ b/addons/calendar/static/src/xml/base_calendar.xml @@ -6,20 +6,6 @@ - - - - - - - -

- - - -
-
-
@@ -44,5 +30,4 @@
- diff --git a/addons/calendar/static/src/xml/notification_calendar.xml b/addons/calendar/static/src/xml/notification_calendar.xml new file mode 100644 index 00000000000..3c51293f36c --- /dev/null +++ b/addons/calendar/static/src/xml/notification_calendar.xml @@ -0,0 +1,19 @@ + + diff --git a/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js b/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js index 0f5a7fb7d1c..ca1144b8822 100644 --- a/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js +++ b/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js @@ -81,12 +81,7 @@ odoo.define('partner_autocomplete.tests', function (require) { return this._getOdooSuggestions(value); }, do_notify: function (title, message, sticky, className) { - return this.call('notification', 'notify', { - title: title, - message: message, - sticky: true, - className: 'o_partner_autocomplete_test_notify' - }); + return this.displayNotification(title, message, 'warning', true, 'o_partner_autocomplete_test_notify'); }, }); }); diff --git a/addons/web/static/src/js/core/service_mixins.js b/addons/web/static/src/js/core/service_mixins.js index 4f296b6bb90..209f6ccabb3 100644 --- a/addons/web/static/src/js/core/service_mixins.js +++ b/addons/web/static/src/js/core/service_mixins.js @@ -222,11 +222,37 @@ var ServicesMixin = { }); }); }, - do_notify: function (title, message, sticky, className) { - return this.call('notification', 'notify', {title: title, message: message, sticky: sticky, className: className}); + /** + * Displays a notification. + * + * @param {string} title + * @param {string} [message] + * @param {string} [type='warning'] 'info', 'success', 'warning', 'danger' or '' + * @param {boolean} [sticky=false] + * @param {string} [className] + */ + displayNotification: function (title, message, type, sticky, className) { + return this.call('notification', 'notify', { + type: type, + title: title, + message: message, + sticky: sticky, + className: className, + }); }, + /** + * @deprecated will be removed as soon as the notification system is reviewed + * @see displayNotification + */ + do_notify: function (title, message, sticky, className) { + return this.displayNotification(title, message, 'warning', sticky, className); + }, + /** + * @deprecated will be removed as soon as the notification system is reviewed + * @see displayNotification + */ do_warn: function (title, message, sticky, className) { - return this.call('notification', 'notify', {type: 'warning', title: title, message: message, sticky: sticky, className: className}); + return this.displayNotification(title, message, 'danger', sticky, className); }, }; diff --git a/addons/web/static/src/js/services/notification_service.js b/addons/web/static/src/js/services/notification_service.js index a0d2720604b..ea16d9ef66f 100644 --- a/addons/web/static/src/js/services/notification_service.js +++ b/addons/web/static/src/js/services/notification_service.js @@ -1,9 +1,10 @@ odoo.define('web.NotificationService', function (require) { -"use strict"; +'use strict'; var AbstractService = require('web.AbstractService'); var Notification = require('web.Notification'); var core = require('web.core'); + var id = 0; /** @@ -16,9 +17,7 @@ var id = 0; * by using this file. The proper way is to use the do_warn or do_notify * methods on the Widget class. */ - var NotificationService = AbstractService.extend({ - custom_events: { close: '_onCloseNotification', }, @@ -37,8 +36,8 @@ var NotificationService = AbstractService.extend({ /** * It may sometimes be useful to close programmatically a notification. For - * example, when there is a sticky notification that warns the user about - * some condition (connection lost), but the condition do not apply anymore. + * example, when there is a sticky notification warning the user about some + * condition (connection lost), but the condition does not apply anymore. * * @param {number} notificationId * @param {boolean} [silent=false] if true, the notification does not call @@ -105,9 +104,7 @@ var NotificationService = AbstractService.extend({ }, }); - core.serviceRegistry.add('notification', NotificationService); - return NotificationService; }); diff --git a/addons/web/static/src/js/widgets/notification.js b/addons/web/static/src/js/widgets/notification.js index e9425144085..31caf5b3e91 100644 --- a/addons/web/static/src/js/widgets/notification.js +++ b/addons/web/static/src/js/widgets/notification.js @@ -1,39 +1,38 @@ odoo.define('web.Notification', function (require) { -"use strict"; +'use strict'; + +var Widget = require('web.Widget'); /** - * Notification - * - * This file contains the widget which is used to display a warning/information - * message in the top right of the screen. + * Widget which is used to display a warning/information message on the top + * right of the screen. * * If you want to display such a notification, you probably do not want to do it * by importing this file. The proper way is to use the do_warn or do_notify * methods on the Widget class. */ - -var Widget = require('web.Widget'); - var Notification = Widget.extend({ template: 'Notification', + xmlDependencies: ['/web/static/src/xml/notification.xml'], events: { - 'click > .o_close': '_onClose', - 'click .o_buttons button': '_onClickButton' + 'hidden.bs.toast': '_onClose', + 'click .o_notification_buttons button': '_onClickButton' }, _autoCloseDelay: 2500, - _animationDelay: 400, + _animation: true, + /** * @override * @param {Widget} parent * @param {Object} params - * @param {string} params.title notification title - * @param {string} params.message notification main message - * @param {string} params.type 'notification' or 'warning' - * @param {boolean} [params.sticky=false] if true, the notification will stay - * visible until the user clicks on it. - * @param {string} [params.className] className to add on the dom + * @param {string} params.title + * @param {string} [params.message] + * @param {string} [params.type='warning'] 'info', 'success', 'warning', 'danger' or '' + * @param {boolean} [params.sticky=false] if true, the notification will + * stay visible until the user clicks on it. + * @param {string} [params.className] * @param {function} [params.onClose] callback when the user click on the x - * or when the notification is auto close (no sticky) + * or when the notification is auto close (no sticky) * @param {Object[]} params.buttons * @param {function} params.buttons[0].click callback on click * @param {boolean} [params.buttons[0].primary] display the button as primary @@ -46,32 +45,47 @@ var Notification = Widget.extend({ this.message = params.message; this.buttons = params.buttons || []; this.sticky = !!this.buttons.length || !!params.sticky; - this.type = params.type || 'notification'; + this.type = params.type === undefined ? 'warning' : params.type; this.className = params.className || ''; this._closeCallback = params.onClose; - this.icon = 'fa-lightbulb-o'; + + if (this.type === 'danger') { + this.icon = 'fa-exclamation'; + this.className += ' bg-danger'; + } else if (this.type === 'warning') { + this.icon = 'fa-lightbulb-o'; + this.className += ' bg-warning'; + } else if (this.type === 'success') { + this.icon = 'fa-check'; + this.className += ' bg-success'; + } else if (this.type === 'info') { + this.icon = 'fa-info'; + this.className += ' bg-info'; + } + if (this.buttons && this.buttons.length) { this.icon = 'fa-question-circle-o'; } - if (this.type === 'warning') { - this.icon = 'fa-exclamation'; - this.className += ' o_error'; - } }, /** * @override */ start: function () { - var self = this; - return this._super.apply(this, arguments).then(function () { - self.$el.animate({opacity: 1.0}, self._animationDelay, "swing", function () { - if(!self.sticky) { - setTimeout(function () { - self.close(); - }, self._autoCloseDelay); - } - }); + this.$el.toast({ + animation: this._animation, + autohide: !this.sticky, + delay: this._autoCloseDelay, }); + void this.$el[0].offsetWidth; // Force a paint refresh before showing the toast + this.$el.toast('show'); + return this._super.apply(this, arguments); + }, + /** + * @override + */ + destroy: function () { + this.$el.toast('dispose'); + this._super.apply(this, arguments); }, //-------------------------------------------------------------------------- @@ -79,22 +93,23 @@ var Notification = Widget.extend({ //-------------------------------------------------------------------------- /** - * This method is used to destroy the widget with a nice animation. We - * first perform an animation, then call the real destroy method. + * Destroys the widget with a nice animation. * * @private * @param {boolean} [silent=false] if true, the notification does not call * _closeCallback method */ close: function (silent) { + this.silent = silent; + this.$el.toast('hide'); + + // Make 'close' work if the notification is not shown yet but will be. + // Should not be needed but the calendar notification system is an + // example of feature that does not work without this yet. var self = this; - this.trigger_up('close'); - if (!silent && !this._buttonClicked) { - if (this._closeCallback) { - this._closeCallback(); - } - } - this.$el.animate({opacity: 0.0, height: 0}, this._animationDelay, "swing", self.destroy.bind(self)); + this.$el.one('shown.bs.toast', function () { + self.$el.toast('hide'); + }); }, //-------------------------------------------------------------------------- @@ -120,14 +135,18 @@ var Notification = Widget.extend({ }, /** * @private - * @param {MouseEvent} ev + * @param {Event} ev */ _onClose: function (ev) { - ev.preventDefault(); - this.close(); + this.trigger_up('close'); + if (!this.silent && !this._buttonClicked) { + if (this._closeCallback) { + this._closeCallback(); + } + } + this.destroy(); }, }); return Notification; - }); diff --git a/addons/web/static/src/scss/bootstrap_overridden.scss b/addons/web/static/src/scss/bootstrap_overridden.scss index c1f8cbb44d3..8cfd6dbd48b 100644 --- a/addons/web/static/src/scss/bootstrap_overridden.scss +++ b/addons/web/static/src/scss/bootstrap_overridden.scss @@ -117,6 +117,15 @@ $nav-pills-border-radius: 0 !default; $nav-pills-link-active-color: $white !default; $nav-pills-link-active-bg: $o-brand-primary !default; +// Toasts + +$toast-max-width: 300px !default; +$toast-padding-x: 1.5rem !default; +$toast-padding-y: 0.5rem !default; +$toast-font-size: $font-size-base !default; +$toast-background-color: rgba($white, .7) !default; +$toast-header-background-color: $toast-background-color !default; + // Badges $badge-font-weight: normal !default; diff --git a/addons/web/static/src/scss/bootstrap_review.scss b/addons/web/static/src/scss/bootstrap_review.scss index b55e454d723..b9e889ce71a 100644 --- a/addons/web/static/src/scss/bootstrap_review.scss +++ b/addons/web/static/src/scss/bootstrap_review.scss @@ -41,6 +41,14 @@ } } +.toast-header { + background-clip: border-box; +} +.toast-body { + // Same as card-body, see explanation above + @include o-bg-color(opacify($toast-background-color, 0.08)); +} + // Modify modals so that their scrollable element is the modal-body (except in // mobile). @include media-breakpoint-up(sm) { diff --git a/addons/web/static/src/scss/name_and_signature.scss b/addons/web/static/src/scss/name_and_signature.scss index 9dc32d5396b..439e9e0bcb1 100644 --- a/addons/web/static/src/scss/name_and_signature.scss +++ b/addons/web/static/src/scss/name_and_signature.scss @@ -37,7 +37,7 @@ .o_field_invalid { .o_signature { - outline: 3px solid $o-notification-error-bg-color; + outline: 3px solid theme-color('danger'); cursor: pointer; } } diff --git a/addons/web/static/src/scss/notification.scss b/addons/web/static/src/scss/notification.scss new file mode 100644 index 00000000000..7bf292ad94c --- /dev/null +++ b/addons/web/static/src/scss/notification.scss @@ -0,0 +1,12 @@ + +.o_notification_manager { + @include o-position-absolute($nav-link-height, 0); + position: fixed; + z-index: ($zindex-modal + $zindex-popover) / 2; + width: $toast-max-width; + max-width: 100%; + + .o_notification { + width: 100%; + } +} diff --git a/addons/web/static/src/scss/primary_variables.scss b/addons/web/static/src/scss/primary_variables.scss index 5196805625d..2370ffb7be2 100644 --- a/addons/web/static/src/scss/primary_variables.scss +++ b/addons/web/static/src/scss/primary_variables.scss @@ -62,9 +62,6 @@ $o-dropdown-hpadding: 20px; $o-sheet-vpadding: 24px; -$o-notification-error-bg-color: #F16567; -$o-notification-info-bg-color: #FCFBEA; - $o-modal-lg: 980px; $o-modal-md: 650px; diff --git a/addons/web/static/src/scss/webclient.scss b/addons/web/static/src/scss/webclient.scss index 436c8074302..fca898f262a 100644 --- a/addons/web/static/src/scss/webclient.scss +++ b/addons/web/static/src/scss/webclient.scss @@ -14,55 +14,6 @@ // ------------------------------------------------------------------ // Misc. widgets // ------------------------------------------------------------------ -.o_notification_manager { - width: 300px; - max-width: 100%; - - @include o-position-absolute($nav-link-height, 0); - z-index: 1100; // Bootstrap modal z-index is 1050 - - .o_notification { - padding: 0; - margin: 5px 0 0 0; - - opacity: 0; - - background-color: $o-notification-info-bg-color; - box-shadow: 0px 0px 5px 1px $o-main-text-color; - - position: relative; - .o_close { - @include o-position-absolute(5px, 5px); - color: rgba(0, 0, 0, 0.3); - text-decoration: none; - } - - .o_notification_title { - display: flex; - align-items: center; - - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - padding: 10px 10px 10px 20px; - - font-weight: bold; - - .o_icon { - display: inline-block; - margin-right: 20px; - color: rgba(0, 0, 0, 0.3); - } - } - - .o_notification_content { - padding: 10px; - } - - &.o_error { - color: white; - background-color: $o-notification-error-bg-color; - } - } -} // Buttons .o_icon_button { diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 4ee2205b3a6..84af548222b 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -120,26 +120,6 @@ - -
- -
- - -
-
-
- -
-
-
-