diff --git a/web/static/src/scss/app.scss b/web/static/src/scss/app.scss index 5dcd58b0..0ed8fbd4 100644 --- a/web/static/src/scss/app.scss +++ b/web/static/src/scss/app.scss @@ -68,6 +68,15 @@ body { margin: 5px 0 0 0; background-color: $o-notification-info-bg-color; box-shadow: 0px 0px 5px 1px $o-main-text-color; + position: relative; + + .o_close { + position: absolute; + top: 5px; + right: 5px; + color: rgba(0, 0, 0, 0.3); + text-decoration: none; + } .o_notification_title { display: flex; diff --git a/web/static/src/ts/root.ts b/web/static/src/ts/root.ts index d5154057..afbff914 100644 --- a/web/static/src/ts/root.ts +++ b/web/static/src/ts/root.ts @@ -8,7 +8,7 @@ import { Env } from "./env"; const template = `
-
+
@@ -27,7 +27,9 @@ export class Root extends Widget { mounted() { this.env.actionManager.on("action_ready", this, this.setContentWidget); - this.env.notifications.on("notification_added", this, this.addNotification); + this.env.notifications.on("notification_added", this, this.addNotif); + this.env.notifications.on("notification_removed", this, this.removeNotif); + const actionWidget = this.env.actionManager.getCurrentAction(); if (actionWidget) { this.setContentWidget(actionWidget); @@ -44,8 +46,12 @@ export class Root extends Widget { this.content = newWidget; } - addNotification(notif: INotification) { + addNotif(notif: INotification) { const notifications = this.state.notifications.concat(notif); this.updateState({ notifications }); } + removeNotif(notif: INotification) { + const notifs = this.state.notifications.filter(f => f.id !== notif.id); + this.updateState({ notifications: notifs }); + } } diff --git a/web/static/src/ts/services/notifications.ts b/web/static/src/ts/services/notifications.ts index 44a52c53..24da7d91 100644 --- a/web/static/src/ts/services/notifications.ts +++ b/web/static/src/ts/services/notifications.ts @@ -12,7 +12,7 @@ export interface INotification { sticky: boolean; } -export type NotificationEvent = "notification_added" | "notification_closed"; +export type NotificationEvent = "notification_added" | "notification_removed"; export type Callback = (notif: INotification) => void; @@ -40,13 +40,16 @@ export class NotificationManager extends Bus implements INotificationManager { const notification = Object.assign(defaultVals, notif, { id }); this.notifications[id] = notification; this.trigger("notification_added", notification); + if (!notification.sticky) { + setTimeout(() => this.close(id), 2500); + } return id; } close(id: number) { let notification = this.notifications[id]; if (notification) { delete this.notifications[id]; - this.trigger("notification_closed", notification); + this.trigger("notification_removed", notification); } } } diff --git a/web/static/src/ts/widgets/Discuss.ts b/web/static/src/ts/widgets/Discuss.ts index c435af51..fc82bf81 100644 --- a/web/static/src/ts/widgets/Discuss.ts +++ b/web/static/src/ts/widgets/Discuss.ts @@ -20,7 +20,8 @@ const template = ` - + +
`; @@ -54,10 +55,10 @@ export class Discuss extends Widget { this.updateState({ color: newColor }); } - addNotif() { + addNotif(sticky: boolean) { const text = (this.refs.textinput).value; const message = `It is now ${new Date().toLocaleTimeString()}.
Msg: ${text}`; - this.env.notifications.add({ title: "hey", message: message }); + this.env.notifications.add({ title: "hey", message: message, sticky }); } } diff --git a/web/static/tests/helpers.ts b/web/static/tests/helpers.ts index 8582597d..204520e7 100644 --- a/web/static/tests/helpers.ts +++ b/web/static/tests/helpers.ts @@ -2,7 +2,9 @@ import { QWeb } from "../src/ts/core/qweb_vdom"; import { idGenerator } from "../src/ts/core/utils"; import { WEnv } from "../src/ts/core/widget"; import { Env } from "../src/ts/env"; -import { IAjax, RPC } from "../src/ts/services/ajax"; +import { IAjax, RPCQuery } from "../src/ts/services/ajax"; +import { NotificationManager } from "../src/ts/services/notifications"; + import { IActionManager, ActionEvent, @@ -28,12 +30,14 @@ export function makeTestEnv(): Env { const ajax = new MockAjax(); const actionManager = new MockActionManager(); const router = new MockRouter(); + const notifications = new NotificationManager(); let { qweb, getID } = makeTestWEnv(); return { qweb, getID, ajax, actionManager, + notifications, router, rpc: ajax.rpc, debug: false, @@ -42,7 +46,7 @@ export function makeTestEnv(): Env { } class MockAjax implements IAjax { - async rpc(rpc: RPC) { + async rpc(rpc: RPCQuery) { return true; } } diff --git a/web/static/tests/services/notifications.test.ts b/web/static/tests/services/notifications.test.ts index f53d13bd..bab35b60 100644 --- a/web/static/tests/services/notifications.test.ts +++ b/web/static/tests/services/notifications.test.ts @@ -36,7 +36,7 @@ test("can subscribe and add notification", () => { test("can close a notification", () => { const notifications: INotificationManager = new NotificationManager(); let notified = false; - notifications.on("notification_closed", {}, () => (notified = true)); + notifications.on("notification_removed", {}, () => (notified = true)); const id = notifications.add(makeNotification()); expect(notified).toBe(false); @@ -44,3 +44,31 @@ test("can close a notification", () => { notifications.close(id); expect(notified).toBe(true); }); + +test("notifications closes themselves after a while", () => { + jest.useFakeTimers(); + const notifications: INotificationManager = new NotificationManager(); + let removed = false; + notifications.on("notification_removed", {}, () => (removed = true)); + + notifications.add(makeNotification()); + + expect(setTimeout).toHaveBeenCalledTimes(1); + expect(removed).toBe(false); + jest.runAllTimers(); + expect(removed).toBe(true); +}); + +test("sticky notifications do not close themselves after a while", () => { + jest.useFakeTimers(); + const notifications: INotificationManager = new NotificationManager(); + let removed = false; + notifications.on("notification_removed", {}, () => (removed = true)); + + notifications.add(makeNotification({ sticky: true })); + + expect(setTimeout).toHaveBeenCalledTimes(0); + expect(removed).toBe(false); + jest.runAllTimers(); + expect(removed).toBe(false); +});