Initial commit: OCA Technical packages (595 packages)

This commit is contained in:
Ernad Husremovic 2025-08-29 15:43:03 +02:00
commit 2cc02aac6e
24950 changed files with 2318079 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View file

@ -0,0 +1,514 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>README.rst</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document">
<a class="reference external image-reference" href="https://odoo-community.org/get-involved?utm_source=readme">
<img alt="Odoo Community Association" src="https://odoo-community.org/readme-banner-image" />
</a>
<div class="section" id="web-notify">
<h1>Web Notify</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:20617e7f2a08e151432917686de12fb06a9644a8e086498ffb8b3bcf2d0d57b0
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/license-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/web/tree/16.0/web_notify"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_notify"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/web&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>Send instant notification messages to the user in live.</p>
<p>This technical module allows you to send instant notification messages from the server to the user in live.
Two kinds of notification are supported.</p>
<ul class="simple">
<li>Success: Displayed in a <cite>success</cite> theme color flying popup div</li>
<li>Danger: Displayed in a <cite>danger</cite> theme color flying popup div</li>
<li>Warning: Displayed in a <cite>warning</cite> theme color flying popup div</li>
<li>Information: Displayed in a <cite>info</cite> theme color flying popup div</li>
<li>Default: Displayed in a <cite>default</cite> theme color flying popup div</li>
</ul>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#installation" id="toc-entry-1">Installation</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-2">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h2><a class="toc-backref" href="#toc-entry-1">Installation</a></h2>
<p>This module is based on the Instant Messaging Bus. To work properly, the server must be launched in gevent mode.</p>
</div>
<div class="section" id="usage">
<h2><a class="toc-backref" href="#toc-entry-2">Usage</a></h2>
<p>To send a notification to the user you just need to call one of the new methods defined on res.users:</p>
<pre class="code python literal-block">
<span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">notify_success</span><span class="p">(</span><span class="n">message</span><span class="o">=</span><span class="s1">'My success message'</span><span class="p">)</span>
</pre>
<p>or</p>
<pre class="code python literal-block">
<span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">notify_danger</span><span class="p">(</span><span class="n">message</span><span class="o">=</span><span class="s1">'My danger message'</span><span class="p">)</span>
</pre>
<p>or</p>
<pre class="code python literal-block">
<span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">notify_warning</span><span class="p">(</span><span class="n">message</span><span class="o">=</span><span class="s1">'My warning message'</span><span class="p">)</span>
</pre>
<p>or</p>
<pre class="code python literal-block">
<span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">notify_info</span><span class="p">(</span><span class="n">message</span><span class="o">=</span><span class="s1">'My information message'</span><span class="p">)</span>
</pre>
<p>or</p>
<pre class="code python literal-block">
<span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">notify_default</span><span class="p">(</span><span class="n">message</span><span class="o">=</span><span class="s1">'My default message'</span><span class="p">)</span>
</pre>
<p>You can also add sound to your notifications by using the sound parameter. The sound parameter expects a string containing the URL path to the audio file that should be played when the notification is displayed.</p>
<p>Example:</p>
<pre class="code python literal-block">
<span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">notify_success</span><span class="p">(</span><span class="n">message</span><span class="o">=</span><span class="s1">'My success message'</span><span class="p">,</span> <span class="n">sound</span><span class="o">=</span><span class="s1">'/&lt;YOUR_MODULE&gt;/static/audio/success.mp3'</span> <span class="p">)</span>
</pre>
<p>or</p>
<pre class="code python literal-block">
<span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">notify_info</span><span class="p">(</span> <span class="n">message</span><span class="o">=</span><span class="s1">'My information message'</span><span class="p">,</span> <span class="n">sound</span><span class="o">=</span><span class="s1">'/&lt;YOUR_MODULE&gt;/static/audio/info.mp3'</span> <span class="p">)</span>
</pre>
<p>The sound parameter can be used with any notification type (success, danger, warning, info, or default). If the sound parameter is not provided, the notification will be displayed without any sound.</p>
<p>The notifications can bring interactivity with some buttons.</p>
<ul class="simple">
<li>One allowing to refresh the active view</li>
<li>Another allowing to send a window / client action</li>
</ul>
<p>The reload button is activated when sending the notification with:</p>
<p>The action can be used using the <tt class="docutils literal">action</tt> keyword and we can choose which name to
give to our button with the <tt class="docutils literal">button_name</tt> key in the action context <cite>params</cite> key:</p>
<pre class="code python literal-block">
<span class="n">action</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="p">[</span><span class="s2">&quot;ir.actions.act_window&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">_for_xml_id</span><span class="p">(</span><span class="s1">'sale.action_orders'</span><span class="p">)</span><span class="w">
</span><span class="n">action</span><span class="o">.</span><span class="n">update</span><span class="p">({</span><span class="w">
</span> <span class="s1">'res_id'</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">id</span><span class="p">,</span><span class="w">
</span> <span class="s1">'views'</span><span class="p">:</span> <span class="p">[(</span><span class="kc">False</span><span class="p">,</span> <span class="s1">'form'</span><span class="p">)],</span><span class="w">
</span><span class="p">})</span><span class="w">
</span><span class="n">action</span><span class="p">[</span><span class="s2">&quot;context&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s2">&quot;params&quot;</span><span class="p">,</span> <span class="p">{})</span><span class="w">
</span><span class="n">action</span><span class="p">[</span><span class="s2">&quot;context&quot;</span><span class="p">][</span><span class="s2">&quot;params&quot;</span><span class="p">][</span><span class="s2">&quot;button_name&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;Sales&quot;</span><span class="w">
</span><span class="n">action</span><span class="p">[</span><span class="s2">&quot;context&quot;</span><span class="p">][</span><span class="s2">&quot;params&quot;</span><span class="p">][</span><span class="s2">&quot;button_icon&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;fa-eye&quot;</span><span class="w">
</span><span class="bp">self</span><span class="o">.</span><span class="n">env</span><span class="o">.</span><span class="n">user</span><span class="o">.</span><span class="n">notify_info</span><span class="p">(</span><span class="s1">'My information message'</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="n">action</span><span class="p">)</span>
</pre>
<div class="figure">
<img alt="Sample notifications" src="https://raw.githubusercontent.com/OCA/web/16.0/web_notify/static/img/notifications_screenshot.gif" />
</div>
<p>You can test the behaviour of the notifications by installing this module in a demo database.
Access the users form through Settings -&gt; Users &amp; Companies. Youll see a tab called “Test web notify”, here youll find two buttons thatll allow you test the module.</p>
<div class="figure">
<img alt="Sample notifications" src="https://raw.githubusercontent.com/OCA/web/16.0/web_notify/static/img/test_notifications_demo.png" />
</div>
</div>
<div class="section" id="bug-tracker">
<h2><a class="toc-backref" href="#toc-entry-3">Bug Tracker</a></h2>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_notify%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h2><a class="toc-backref" href="#toc-entry-4">Credits</a></h2>
<div class="section" id="authors">
<h3><a class="toc-backref" href="#toc-entry-5">Authors</a></h3>
<ul class="simple">
<li>ACSONE SA/NV</li>
<li>AdaptiveCity</li>
</ul>
</div>
<div class="section" id="contributors">
<h3><a class="toc-backref" href="#toc-entry-6">Contributors</a></h3>
<ul class="simple">
<li>Laurent Mignon &lt;<a class="reference external" href="mailto:laurent.mignon&#64;acsone.eu">laurent.mignon&#64;acsone.eu</a>&gt;</li>
<li>Serpent Consulting Services Pvt. Ltd.&lt;<a class="reference external" href="mailto:jay.vora&#64;serpentcs.com">jay.vora&#64;serpentcs.com</a>&gt;</li>
<li>Aitor Bouzas &lt;<a class="reference external" href="mailto:aitor.bouzas&#64;adaptivecity.com">aitor.bouzas&#64;adaptivecity.com</a>&gt;</li>
<li>Shepilov Vladislav &lt;<a class="reference external" href="mailto:shepilov.v&#64;protonmail.com">shepilov.v&#64;protonmail.com</a>&gt;</li>
<li>Kevin Khao &lt;<a class="reference external" href="mailto:kevin.khao&#64;akretion.com">kevin.khao&#64;akretion.com</a>&gt;</li>
<li><a class="reference external" href="https://www.tecnativa.com">Tecnativa</a>:<ul>
<li>David Vidal</li>
</ul>
</li>
<li>Cetmix OÜ &lt;<a class="reference external" href="https://cetmix.com/">https://cetmix.com/</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
<h3><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h3>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/16.0/web_notify">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View file

@ -0,0 +1,70 @@
/** @odoo-module alias=web_notify.AudioPlayer **/
import {Component, useState} from "@odoo/owl";
/**
* @typedef AudioPlayerProps
* @property {string} src URL of the audio file to be played
* @property {number} [volume=1.0] Volume level of the audio (from 0.0 to 1.0)
* @property {boolean} [loop=false] Whether the audio should loop
* @property {Function} [onEnded] Callback function to be called when the audio ends
*/
/**
* The AudioPlayer component is responsible for playing audio files with
* specified settings like volume and looping. It also provides the ability
* to trigger actions when the audio playback ends.
*/
export class AudioPlayer extends Component {
setup() {
this.state = useState({isPlaying: false});
this.audioElement = new Audio(this.props.src);
// Set audio properties
this.audioElement.volume = this.props.volume || 1.0;
this.audioElement.loop = this.props.loop || false;
// Start playing the audio
this.audioElement
.play()
.then(() => {
this.state.isPlaying = true;
})
.catch((error) => {
console.error("Audio playback failed:", error);
});
// Listen for the end of the audio playback
this.audioElement.addEventListener("ended", this.onAudioEnded.bind(this));
}
/**
* Stops the audio playback and triggers the onEnded callback if provided.
*/
stopAudio() {
this.audioElement.pause();
this.audioElement.currentTime = 0;
this.state.isPlaying = false;
if (this.props.onEnded) {
this.props.onEnded();
}
}
/**
* Handler for when the audio playback ends.
*/
onAudioEnded() {
if (!this.props.loop) {
this.stopAudio();
}
}
willUnmount() {
// Clean up the audio element and listeners
this.audioElement.removeEventListener("ended", this.onAudioEnded);
this.audioElement.pause();
}
}
AudioPlayer.template = "web_notify.AudioPlayer";

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2024 Cetmix OÜ
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<templates xml:space="preserve">
<t t-name="web_notify.AudioPlayer" owl="1">
<!-- No visual elements needed, audio is controlled programmatically -->
</t>
</templates>

View file

@ -0,0 +1,12 @@
/** @odoo-module */
import {Notification} from "@web/core/notifications/notification";
import {patch} from "web.utils";
patch(Notification.props, "webNotifyProps", {
type: {
type: String,
optional: true,
validate: (t) =>
["warning", "danger", "success", "info", "default"].includes(t),
},
});

View file

@ -0,0 +1,70 @@
/** @odoo-module **/
import {Markup} from "web.utils";
import {browser} from "@web/core/browser/browser";
import {registry} from "@web/core/registry";
export const webNotificationService = {
dependencies: ["bus_service", "action", "notification_sound"],
start(env, {bus_service, action, notification_sound}) {
let webNotifTimeouts = {};
/**
* Displays the web notification with sound on user's screen
* @param {*} notifications
*/
function displaywebNotification(notifications) {
Object.values(webNotifTimeouts).forEach((notif) =>
browser.clearTimeout(notif)
);
webNotifTimeouts = {};
notifications.forEach((notif) => {
browser.setTimeout(() => {
var buttons = [];
if (notif.action) {
const params =
(notif.action.context && notif.action.context.params) || {};
buttons = [
{
name: params.button_name || env._t("Open"),
primary: true,
onClick: async () => {
await action.doAction(notif.action);
},
...(params.button_icon && {icon: params.button_icon}),
},
];
}
const notificationRemove = notification_sound.add(
Markup(notif.message),
{
title: notif.title,
type: notif.type,
sticky: notif.sticky,
className: notif.className,
buttons: buttons.map((button) => {
const onClick = button.onClick;
button.onClick = async () => {
await onClick();
notificationRemove();
};
return button;
}),
sound: notif.sound,
}
);
});
});
}
bus_service.addEventListener("notification", ({detail: notifications}) => {
for (const {payload, type} of notifications) {
if (type === "web.notify") {
displaywebNotification(payload);
}
}
});
bus_service.start();
},
};
registry.category("services").add("webNotification", webNotificationService);

View file

@ -0,0 +1,43 @@
/** @odoo-module **/
import {registry} from "@web/core/registry";
import {AudioPlayer} from "../components/audio_player.esm";
const effectRegistry = registry.category("effects");
// -----------------------------------------------------------------------------
// Audio effect
// -----------------------------------------------------------------------------
/**
* Handles effect of type "audio_effect". It returns the AudioPlayer component
* with the given audio source URL and other properties.
*
* @param {Object} env
* @param {Object} [params={}]
* @param {string} params.src
* The URL of the audio file to play.
* @param {number} [params.volume=1.0] Volume level of the audio (from 0.0 to 1.0)
* @param {boolean} [params.loop=false] Whether the audio should loop
* @param {Function} [params.onEnded] Callback function to be called when the audio ends
*/
function audioEffect(env, params = {}) {
if (!params.src) {
console.warn(
"Audio effect requires a 'src' parameter with the URL of the audio file."
);
return;
}
return {
Component: AudioPlayer,
props: {
src: params.src,
volume: params.volume || 1.0,
loop: params.loop || false,
onEnded: params.onEnded,
},
};
}
effectRegistry.add("audio_effect", audioEffect);

View file

@ -0,0 +1,58 @@
/** @odoo-module **/
import {registry} from "@web/core/registry";
/**
* The notificationSoundService is responsible for handling the playback of audio
* notifications when a new notification is added. This service integrates with
* the notification system and the effect service to provide audible feedback
* based on the type of notification.
*
* Dependencies:
* - notification: The service responsible for displaying notifications on the UI.
* - effect: The service that handles visual and auditory effects in the application.
*/
export const notificationSoundService = {
dependencies: ["notification", "effect"],
/**
* Starts the notification sound service, enabling sound playback for notifications.
*
* @param {Object} env The environment object, providing access to various services.
* @param {Object} services An object containing the dependencies (notification, effect).
* @returns {Object} The add function, used to add notifications with sound.
*/
start(env, {notification, effect}) {
/**
* Adds a notification with an associated sound effect.
*
* @param {String} message The message to be displayed in the notification.
* @param {Object} [options={}] Additional options for the notification, such as type, sound and etc
* @returns {Function} A function to close the notification.
*/
function add(message, options = {}) {
const sound = options.sound || false;
delete options.sound; // Remove sound option from the options before passing to notification
const closeFn = notification.add(message, options);
if (sound)
// Trigger the audio effect.
effect.add({
type: "audio_effect",
src: sound,
volume: 0.8,
loop: false,
onEnded: () => {
// Placeholder for any action after sound ends
},
});
return closeFn;
}
return {add};
},
};
// Register the notification sound service in the service registry
registry.category("services").add("notification_sound", notificationSoundService);