forked from Techsystech/web
[IMP] web_pwa_oca: Drop cache and refactor
parent
7473a485e9
commit
e5c219f9f3
|
@ -31,6 +31,17 @@ Progressive Web Apps provide an installable, app-like experience on desktop and
|
|||
They're web apps that are fast and reliable. And most importantly, they're web apps that work in any browser.
|
||||
If you're building a web app today, you're already on the path towards building a Progressive Web App.
|
||||
|
||||
|
||||
+ Developers Info.
|
||||
|
||||
The service worker is contructed using 'Odoo Class' to have the same class inheritance behaviour that in the 'user pages'. Be noticed
|
||||
that 'Odoo Bootstrap' is not supported so, you can't use 'require' here.
|
||||
|
||||
All service worker content can be found in 'static/src/js/worker'. The management between 'user pages' and service worker is done in
|
||||
'pwa_manager.js'.
|
||||
|
||||
The purpose of this module is give a base to make PWA applications.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
.. contents::
|
||||
|
@ -86,33 +97,43 @@ To use your PWA:
|
|||
Known issues / Roadmap
|
||||
======================
|
||||
|
||||
* Evaluate to extend ``FILES_TO_CACHE``
|
||||
* Integrate `Notification API <https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification>`_
|
||||
* Integrate `Web Share API <https://web.dev/web-share/>`_
|
||||
* Create ``portal_pwa`` module, intended to be used by front-end users (customers, suppliers...)
|
||||
* Current ``John Resig's inheritance`` implementation doesn't support ``async`` functions because ``this._super`` can't be called inside a promise. So we need use the following workaround:
|
||||
- Natural 'async/await' example (This breaks "_super" call)
|
||||
.. code-block:: javascript
|
||||
* Current *John Resig's inheritance* implementation doesn't support ``async``
|
||||
functions because ``this._super`` can't be called inside a promise. So we
|
||||
need to use the following workaround:
|
||||
|
||||
var MyClass = OdooClass.extend({
|
||||
myFunc: async function() {
|
||||
- Natural 'async/await' example (This breaks "_super" call):
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var MyClass = OdooClass.extend({
|
||||
myFunc: async function() {
|
||||
const mydata = await ...do await stuff...
|
||||
return mydata;
|
||||
}
|
||||
});
|
||||
|
||||
- Same code with the workaround:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var MyClass = OdooClass.extend({
|
||||
myFunc: function() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const mydata = await ...do await stuff...
|
||||
return mydata;
|
||||
}
|
||||
});
|
||||
return resolve(mydata);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
- Same code with the workaround:
|
||||
.. code-block:: javascript
|
||||
|
||||
var MyClass = OdooClass.extend({
|
||||
myFunc: function() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const mydata = await ...do await stuff...
|
||||
resolve(mydata);
|
||||
});
|
||||
}
|
||||
});
|
||||
* Fix issue when trying to run in localhost with several databases. The browser doesn't send the cookie and web manifest returns 404.
|
||||
* Fix issue when trying to run in localhost with several databases. The browser
|
||||
doesn't send the cookie and web manifest returns 404.
|
||||
* Evaluate to support 'require' system.
|
||||
* 'Install PWA' menu option disappears even if not installed. This is due to the
|
||||
very nature of service workers, so they are running including when you close
|
||||
the page tabs.
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
|
|
@ -9,38 +9,13 @@ from odoo.http import request, Controller, route
|
|||
|
||||
class PWA(Controller):
|
||||
def _get_pwa_scripts(self):
|
||||
""" Scripts to be imported in the service worker (Order is important) """
|
||||
"""Scripts to be imported in the service worker (Order is important)"""
|
||||
return [
|
||||
"/web/static/lib/underscore/underscore.js",
|
||||
"/web_pwa_oca/static/src/js/worker/libs/class.js",
|
||||
"/web_pwa_oca/static/src/js/worker/base/tools.js",
|
||||
"/web_pwa_oca/static/src/js/worker/base/cache_manager.js",
|
||||
"/web_pwa_oca/static/src/js/worker/pwa.js",
|
||||
]
|
||||
|
||||
def _get_asset_urls(self, asset_xml_id):
|
||||
"""Get all urls that have 'asset_xml_id'"""
|
||||
qweb_sudo = request.env["ir.qweb"].sudo()
|
||||
assets = qweb_sudo._get_asset_nodes(asset_xml_id, {}, True, True)
|
||||
urls = []
|
||||
for asset in assets:
|
||||
if asset[0] == "link":
|
||||
urls.append(asset[1]["href"])
|
||||
if asset[0] == "script":
|
||||
urls.append(asset[1]["src"])
|
||||
return urls
|
||||
|
||||
def _get_pwa_params(self):
|
||||
"""Get javascript PWA class initialzation params"""
|
||||
urls = []
|
||||
urls.extend(self._get_asset_urls("web.assets_common"))
|
||||
urls.extend(self._get_asset_urls("web.assets_backend"))
|
||||
version_list = []
|
||||
for url in urls:
|
||||
version_list.append(url.split("/")[3])
|
||||
cache_version = "-".join(version_list)
|
||||
return [cache_version, urls]
|
||||
|
||||
@route("/service-worker.js", type="http", auth="public")
|
||||
def render_service_worker(self):
|
||||
"""Route to register the service worker in the 'main' scope ('/')"""
|
||||
|
@ -53,6 +28,10 @@ class PWA(Controller):
|
|||
headers=[("Content-Type", "text/javascript;charset=utf-8")],
|
||||
)
|
||||
|
||||
def _get_pwa_params(self):
|
||||
"""Get javascript PWA class initialzation params"""
|
||||
return {}
|
||||
|
||||
def _get_pwa_manifest_icons(self, pwa_icon):
|
||||
icons = []
|
||||
if not pwa_icon:
|
||||
|
@ -126,7 +105,7 @@ class PWA(Controller):
|
|||
"name": pwa_name,
|
||||
"short_name": pwa_short_name,
|
||||
"icons": self._get_pwa_manifest_icons(pwa_icon),
|
||||
"start_url": "/web",
|
||||
"start_url": '/web',
|
||||
"display": "standalone",
|
||||
"background_color": background_color,
|
||||
"theme_color": theme_color,
|
||||
|
@ -137,5 +116,5 @@ class PWA(Controller):
|
|||
"""Returns the manifest used to install the page as app"""
|
||||
return request.make_response(
|
||||
json.dumps(self._get_pwa_manifest()),
|
||||
headers=[("Content-Type", "text/javascript;charset=utf-8")],
|
||||
headers=[("Content-Type", "application/json;charset=utf-8")],
|
||||
)
|
||||
|
|
|
@ -12,6 +12,7 @@ from odoo.tools.mimetypes import guess_mimetype
|
|||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
_pwa_icon_url_base = "/web_pwa_oca/icon"
|
||||
|
||||
pwa_name = fields.Char(
|
||||
"Progressive Web App Name", help="Name of the Progressive Web Application"
|
||||
|
@ -21,20 +22,20 @@ class ResConfigSettings(models.TransientModel):
|
|||
help="Short Name of the Progressive Web Application",
|
||||
)
|
||||
pwa_icon = fields.Binary("Icon", readonly=False)
|
||||
_pwa_icon_url_base = "/web_pwa_oca/icon"
|
||||
pwa_background_color = fields.Char("Background Color")
|
||||
pwa_theme_color = fields.Char("Theme Color")
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
config_parameter_obj_sudo = self.env["ir.config_parameter"].sudo()
|
||||
res = super(ResConfigSettings, self).get_values()
|
||||
res["pwa_name"] = (
|
||||
self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("pwa.manifest.name", default="")
|
||||
config_parameter_obj_sudo.get_param(
|
||||
"pwa.manifest.name", default="Odoo PWA")
|
||||
)
|
||||
res["pwa_short_name"] = (
|
||||
self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("pwa.manifest.short_name", default="")
|
||||
config_parameter_obj_sudo.get_param(
|
||||
"pwa.manifest.short_name", default="Odoo")
|
||||
)
|
||||
pwa_icon_ir_attachment = (
|
||||
self.env["ir.attachment"]
|
||||
|
@ -44,6 +45,14 @@ class ResConfigSettings(models.TransientModel):
|
|||
res["pwa_icon"] = (
|
||||
pwa_icon_ir_attachment.datas if pwa_icon_ir_attachment else False
|
||||
)
|
||||
res["pwa_background_color"] = (
|
||||
config_parameter_obj_sudo.get_param(
|
||||
"pwa.manifest.background_color", default="#2E69B5")
|
||||
)
|
||||
res["pwa_theme_color"] = (
|
||||
config_parameter_obj_sudo.get_param(
|
||||
"pwa.manifest.theme_color", default="#2E69B5")
|
||||
)
|
||||
return res
|
||||
|
||||
def _unpack_icon(self, icon):
|
||||
|
@ -89,13 +98,20 @@ class ResConfigSettings(models.TransientModel):
|
|||
|
||||
@api.model
|
||||
def set_values(self):
|
||||
config_parameter_obj_sudo = self.env["ir.config_parameter"].sudo()
|
||||
res = super(ResConfigSettings, self).set_values()
|
||||
self.env["ir.config_parameter"].sudo().set_param(
|
||||
config_parameter_obj_sudo.set_param(
|
||||
"pwa.manifest.name", self.pwa_name
|
||||
)
|
||||
self.env["ir.config_parameter"].sudo().set_param(
|
||||
config_parameter_obj_sudo.set_param(
|
||||
"pwa.manifest.short_name", self.pwa_short_name
|
||||
)
|
||||
config_parameter_obj_sudo.set_param(
|
||||
"pwa.manifest.background_color", self.pwa_background_color
|
||||
)
|
||||
config_parameter_obj_sudo.set_param(
|
||||
"pwa.manifest.theme_color", self.pwa_theme_color
|
||||
)
|
||||
# Retrieve previous value for pwa_icon from ir_attachment
|
||||
pwa_icon_ir_attachments = (
|
||||
self.env["ir.attachment"]
|
||||
|
|
|
@ -3,3 +3,14 @@ Make Odoo an installable Progressive Web Application.
|
|||
Progressive Web Apps provide an installable, app-like experience on desktop and mobile that are built and delivered directly via the web.
|
||||
They're web apps that are fast and reliable. And most importantly, they're web apps that work in any browser.
|
||||
If you're building a web app today, you're already on the path towards building a Progressive Web App.
|
||||
|
||||
|
||||
+ Developers Info.
|
||||
|
||||
The service worker is contructed using 'Odoo Class' to have the same class inheritance behaviour that in the 'user pages'. Be noticed
|
||||
that 'Odoo Bootstrap' is not supported so, you can't use 'require' here.
|
||||
|
||||
All service worker content can be found in 'static/src/js/worker'. The management between 'user pages' and service worker is done in
|
||||
'pwa_manager.js'.
|
||||
|
||||
The purpose of this module is give a base to make PWA applications.
|
||||
|
|
|
@ -1,27 +1,37 @@
|
|||
* Evaluate to extend ``FILES_TO_CACHE``
|
||||
* Integrate `Notification API <https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification>`_
|
||||
* Integrate `Web Share API <https://web.dev/web-share/>`_
|
||||
* Create ``portal_pwa`` module, intended to be used by front-end users (customers, suppliers...)
|
||||
* Current ``John Resig's inheritance`` implementation doesn't support ``async`` functions because ``this._super`` can't be called inside a promise. So we need use the following workaround:
|
||||
- Natural 'async/await' example (This breaks "_super" call)
|
||||
.. code-block:: javascript
|
||||
* Current *John Resig's inheritance* implementation doesn't support ``async``
|
||||
functions because ``this._super`` can't be called inside a promise. So we
|
||||
need to use the following workaround:
|
||||
|
||||
var MyClass = OdooClass.extend({
|
||||
myFunc: async function() {
|
||||
- Natural 'async/await' example (This breaks "_super" call):
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var MyClass = OdooClass.extend({
|
||||
myFunc: async function() {
|
||||
const mydata = await ...do await stuff...
|
||||
return mydata;
|
||||
}
|
||||
});
|
||||
|
||||
- Same code with the workaround:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var MyClass = OdooClass.extend({
|
||||
myFunc: function() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const mydata = await ...do await stuff...
|
||||
return mydata;
|
||||
}
|
||||
});
|
||||
return resolve(mydata);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
- Same code with the workaround:
|
||||
.. code-block:: javascript
|
||||
|
||||
var MyClass = OdooClass.extend({
|
||||
myFunc: function() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const mydata = await ...do await stuff...
|
||||
resolve(mydata);
|
||||
});
|
||||
}
|
||||
});
|
||||
* Fix issue when trying to run in localhost with several databases. The browser doesn't send the cookie and web manifest returns 404.
|
||||
* Fix issue when trying to run in localhost with several databases. The browser
|
||||
doesn't send the cookie and web manifest returns 404.
|
||||
* Evaluate to support 'require' system.
|
||||
* 'Install PWA' menu option disappears even if not installed. This is due to the
|
||||
very nature of service workers, so they are running including when you close
|
||||
the page tabs.
|
||||
|
|
|
@ -372,6 +372,14 @@ ul.auto-toc {
|
|||
<p>Progressive Web Apps provide an installable, app-like experience on desktop and mobile that are built and delivered directly via the web.
|
||||
They’re web apps that are fast and reliable. And most importantly, they’re web apps that work in any browser.
|
||||
If you’re building a web app today, you’re already on the path towards building a Progressive Web App.</p>
|
||||
<ul class="simple">
|
||||
<li>Developers Info.</li>
|
||||
</ul>
|
||||
<p>The service worker is contructed using ‘Odoo Class’ to have the same class inheritance behaviour that in the ‘user pages’. Be noticed
|
||||
that ‘Odoo Bootstrap’ is not supported so, you can’t use ‘require’ here.</p>
|
||||
<p>All service worker content can be found in ‘static/src/js/worker’. The management between ‘user pages’ and service worker is done in
|
||||
‘pwa_manager.js’.</p>
|
||||
<p>The purpose of this module is give a base to make PWA applications.</p>
|
||||
<p><strong>Table of contents</strong></p>
|
||||
<div class="contents local topic" id="contents">
|
||||
<ul class="simple">
|
||||
|
@ -431,20 +439,18 @@ And like all other installed apps, it’s a top level app in the task switcher.<
|
|||
<div class="section" id="known-issues-roadmap">
|
||||
<h1><a class="toc-backref" href="#id4">Known issues / Roadmap</a></h1>
|
||||
<ul>
|
||||
<li><p class="first">Evaluate to extend <tt class="docutils literal">FILES_TO_CACHE</tt></p>
|
||||
</li>
|
||||
<li><p class="first">Integrate <a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification">Notification API</a></p>
|
||||
</li>
|
||||
<li><p class="first">Integrate <a class="reference external" href="https://web.dev/web-share/">Web Share API</a></p>
|
||||
</li>
|
||||
<li><p class="first">Create <tt class="docutils literal">portal_pwa</tt> module, intended to be used by front-end users (customers, suppliers…)</p>
|
||||
</li>
|
||||
<li><dl class="first docutils">
|
||||
<dt>Current <tt class="docutils literal">John Resig's inheritance</tt> implementation doesn’t support <tt class="docutils literal">async</tt> functions because <tt class="docutils literal">this._super</tt> can’t be called inside a promise. So we need use the following workaround:</dt>
|
||||
<dd><ul class="first last">
|
||||
<li><dl class="first docutils">
|
||||
<dt>Natural ‘async/await’ example (This breaks “_super” call)</dt>
|
||||
<dd><pre class="code javascript first last literal-block">
|
||||
<li><p class="first">Current <em>John Resig’s inheritance</em> implementation doesn’t support <tt class="docutils literal">async</tt>
|
||||
functions because <tt class="docutils literal">this._super</tt> can’t be called inside a promise. So we
|
||||
need to use the following workaround:</p>
|
||||
<ul>
|
||||
<li><p class="first">Natural ‘async/await’ example (This breaks “_super” call):</p>
|
||||
<pre class="code javascript literal-block">
|
||||
<span class="kd">var</span> <span class="nx">MyClass</span> <span class="o">=</span> <span class="nx">OdooClass</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
|
||||
<span class="nx">myFunc</span><span class="o">:</span> <span class="nx">async</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
|
||||
<span class="kr">const</span> <span class="nx">mydata</span> <span class="o">=</span> <span class="nx">await</span> <span class="p">...</span><span class="k">do</span> <span class="nx">await</span> <span class="nx">stuff</span><span class="p">...</span>
|
||||
|
@ -452,29 +458,29 @@ And like all other installed apps, it’s a top level app in the task switcher.<
|
|||
<span class="p">}</span>
|
||||
<span class="p">});</span>
|
||||
</pre>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li><dl class="first docutils">
|
||||
<dt>Same code with the workaround:</dt>
|
||||
<dd><pre class="code javascript first last literal-block">
|
||||
<li><p class="first">Same code with the workaround:</p>
|
||||
<pre class="code javascript literal-block">
|
||||
<span class="kd">var</span> <span class="nx">MyClass</span> <span class="o">=</span> <span class="nx">OdooClass</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
|
||||
<span class="nx">myFunc</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
|
||||
<span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="nx">async</span> <span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span>
|
||||
<span class="kr">const</span> <span class="nx">mydata</span> <span class="o">=</span> <span class="nx">await</span> <span class="p">...</span><span class="k">do</span> <span class="nx">await</span> <span class="nx">stuff</span><span class="p">...</span>
|
||||
<span class="nx">resolve</span><span class="p">(</span><span class="nx">mydata</span><span class="p">);</span>
|
||||
<span class="k">return</span> <span class="nx">resolve</span><span class="p">(</span><span class="nx">mydata</span><span class="p">);</span>
|
||||
<span class="p">});</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">});</span>
|
||||
</pre>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li><p class="first">Fix issue when trying to run in localhost with several databases. The browser doesn’t send the cookie and web manifest returns 404.</p>
|
||||
<li><p class="first">Fix issue when trying to run in localhost with several databases. The browser
|
||||
doesn’t send the cookie and web manifest returns 404.</p>
|
||||
</li>
|
||||
<li><p class="first">Evaluate to support ‘require’ system.</p>
|
||||
</li>
|
||||
<li><p class="first">‘Install PWA’ menu option disappears even if not installed. This is due to the
|
||||
very nature of service workers, so they are running including when you close
|
||||
the page tabs.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,37 +1,39 @@
|
|||
/* Copyright 2020 Lorenzo Battistini @ TAKOBI
|
||||
Copyright 2020 Tecnativa - Alexandre D. Díaz
|
||||
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
|
||||
|
||||
odoo.define('web_pwa_oca.systray.install', function(require) {
|
||||
odoo.define('web_pwa_oca.systray.install', function (require) {
|
||||
"use strict";
|
||||
|
||||
var UserMenu = require('web.UserMenu');
|
||||
|
||||
var deferredInstallPrompt = null;
|
||||
var WebClientObj = require("web.web_client");
|
||||
|
||||
|
||||
UserMenu.include({
|
||||
start: function() {
|
||||
window.addEventListener('beforeinstallprompt', this.saveBeforeInstallPromptEvent);
|
||||
return this._super.apply(this, arguments);
|
||||
|
||||
/**
|
||||
* We can't control if the UserMenu is loaded berfore PWA manager...
|
||||
* So check if need unhide the user menu options to install the PWA.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).then(function () {
|
||||
if (WebClientObj.pwa_manager.canBeInstalled()) {
|
||||
self.$el.find('#pwa_install_button')[0]
|
||||
.removeAttribute('hidden');
|
||||
}
|
||||
});
|
||||
},
|
||||
saveBeforeInstallPromptEvent: function(evt) {
|
||||
deferredInstallPrompt = evt;
|
||||
this.$.find('#pwa_install_button')[0].removeAttribute('hidden');
|
||||
},
|
||||
_onMenuInstallpwa: function() {
|
||||
deferredInstallPrompt.prompt();
|
||||
// Hide the install button, it can't be called twice.
|
||||
this.el.setAttribute('hidden', true);
|
||||
// Log user response to prompt.
|
||||
deferredInstallPrompt.userChoice
|
||||
.then(function(choice) {
|
||||
if (choice.outcome === 'accepted') {
|
||||
console.log('User accepted the A2HS prompt', choice);
|
||||
} else {
|
||||
console.log('User dismissed the A2HS prompt', choice);
|
||||
}
|
||||
deferredInstallPrompt = null;
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle 'Install PWA' user menu option click
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onMenuInstallpwa: function () {
|
||||
WebClientObj.pwa_manager.install();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,34 +1,94 @@
|
|||
/* Copyright 2020 Tecnativa - Alexandre D. Díaz
|
||||
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
|
||||
|
||||
odoo.define("web_pwa_oca.PWAManager", function(require) {
|
||||
odoo.define("web_pwa_oca.PWAManager", function (require) {
|
||||
"use strict";
|
||||
|
||||
var Class = require("web.Class");
|
||||
var mixins = require("web.mixins");
|
||||
var Widget = require("web.Widget");
|
||||
|
||||
|
||||
var PWAManager = Class.extend(mixins.ParentedMixin, {
|
||||
init: function() {
|
||||
var PWAManager = Widget.extend({
|
||||
_deferredInstallPrompt: null,
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
throw new Error(
|
||||
"This browser is not compatible with service workers");
|
||||
}
|
||||
this._service_worker = navigator.serviceWorker;
|
||||
this.registerServiceWorker('/service-worker.js');
|
||||
window.addEventListener(
|
||||
'beforeinstallprompt', this._onBeforeInstallPrompt.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} sw_script
|
||||
* @param {Function} success_callback
|
||||
* @returns {Promise}
|
||||
*/
|
||||
registerServiceWorker: function(sw_script, success_callback) {
|
||||
registerServiceWorker: function (sw_script) {
|
||||
return this._service_worker.register(sw_script)
|
||||
.then(success_callback)
|
||||
.catch(function(error) {
|
||||
.then(this._onRegisterServiceWorker)
|
||||
.catch(function (error) {
|
||||
console.log('[ServiceWorker] Registration failed: ', error);
|
||||
});
|
||||
},
|
||||
|
||||
install: function () {
|
||||
if (!this._deferredInstallPrompt) {
|
||||
return;
|
||||
}
|
||||
var self = this;
|
||||
var systray_menu = this.getParent().menu.systray_menu;
|
||||
this._deferredInstallPrompt.prompt();
|
||||
// Log user response to prompt.
|
||||
this._deferredInstallPrompt.userChoice
|
||||
.then(function (choice) {
|
||||
if (choice.outcome === 'accepted') {
|
||||
// Hide the install button, it can't be called twice.
|
||||
systray_menu.$el.find('#pwa_install_button')
|
||||
.attr('hidden', true);
|
||||
self._deferredInstallPrompt = null;
|
||||
console.log('User accepted the A2HS prompt', choice);
|
||||
} else {
|
||||
console.log('User dismissed the A2HS prompt', choice);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
canBeInstalled: function () {
|
||||
return !_.isNull(this._deferredInstallPrompt);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle PWA installation flow
|
||||
*
|
||||
* @private
|
||||
* @param {BeforeInstallPromptEvent} evt
|
||||
*/
|
||||
_onBeforeInstallPrompt: function (evt) {
|
||||
evt.preventDefault();
|
||||
this._deferredInstallPrompt = evt;
|
||||
// UserMenu can be loaded after this module
|
||||
var menu = this.getParent().menu;
|
||||
if (menu && menu.systray_menu) {
|
||||
menu.systray_menu.$el.find('#pwa_install_button')[0]
|
||||
.removeAttribute('hidden');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Need register some extra API? override this!
|
||||
*
|
||||
* @private
|
||||
* @param {ServiceWorkerRegistration} registration
|
||||
*/
|
||||
_onRegisterServiceWorker: function (registration) {
|
||||
console.log('[ServiceWorker] Registered:', registration);
|
||||
},
|
||||
});
|
||||
|
||||
return PWAManager;
|
||||
|
|
|
@ -1,30 +1,20 @@
|
|||
/* Copyright 2020 Tecnativa - Alexandre D. Díaz
|
||||
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
|
||||
|
||||
odoo.define("web_pwa_oca.webclient", function(require) {
|
||||
odoo.define("web_pwa_oca.webclient", function (require) {
|
||||
"use strict";
|
||||
|
||||
var WebClient = require("web.WebClient");
|
||||
var PWAManager = require("web_pwa_oca.PWAManager");
|
||||
|
||||
WebClient.include({
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
show_application: function() {
|
||||
this.pwa_manager = new PWAManager();
|
||||
this.pwa_manager.setParent(this);
|
||||
this.pwa_manager.registerServiceWorker('/service-worker.js', this._onRegisterServiceWorker);
|
||||
show_application: function () {
|
||||
this.pwa_manager = new PWAManager(this);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Need register some extra API? override this!
|
||||
*
|
||||
* @param {ServiceWorkerRegistration} registration
|
||||
*/
|
||||
_onRegisterServiceWorker: function(registration) {
|
||||
console.log('[ServiceWorker] Registered:', registration);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
/* Copyright 2020 Tecnativa - Alexandre D. Díaz
|
||||
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
|
||||
"use strict";
|
||||
|
||||
const CacheManager = OdooClass.extend({
|
||||
init: function() {
|
||||
this._cache = caches;
|
||||
this._caches = {};
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise[CacheStorage]}
|
||||
*/
|
||||
initCache: function(cache_name) {
|
||||
if (_.has(this._caches, cache_name)) {
|
||||
return Promise.resolve(this._caches[cache_name]);
|
||||
}
|
||||
return new Promise(async resolve => {
|
||||
this._caches[cache_name] = await this._cache.open(cache_name);
|
||||
resolve(this._caches[cache_name]);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {CacheStorage}
|
||||
*/
|
||||
get: function(cache_name) {
|
||||
return this._caches[cache_name];
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise}
|
||||
*/
|
||||
clean: function() {
|
||||
return this._cache.keys().then(keyList => {
|
||||
return Promise.all(keyList.map(key => {
|
||||
if (!_.has(this._caches, key)) {
|
||||
console.log('[ServiceWorker] Removing old cache', key);
|
||||
return this._cache.delete(key);
|
||||
}
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
});
|
|
@ -1,17 +0,0 @@
|
|||
/* Copyright 2020 Tecnativa - Alexandre D. Díaz
|
||||
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
|
||||
"use strict";
|
||||
|
||||
|
||||
/**
|
||||
* Execute various promises in sequential order and return a promise with all results
|
||||
* Code from: https://decembersoft.com/posts/promises-in-serial-with-array-reduce/
|
||||
*
|
||||
* @param {Array[Promise]} tasks
|
||||
* @returns {Promise[Array]}
|
||||
*/
|
||||
function execSequentialPromises(tasks) {
|
||||
return tasks.reduce((promiseChain, currentTask) => {
|
||||
return promiseChain.then(chainResults => currentTask.then(currentResult => [...chainResults, currentResult]));
|
||||
}, Promise.resolve([]));
|
||||
}
|
|
@ -1,62 +1,35 @@
|
|||
"use strict";
|
||||
/* eslint strict: ["error", "global"] */
|
||||
/* eslint-disable no-undef, no-empty-function, no-implicit-globals,
|
||||
no-unused-vars */
|
||||
/* Copyright 2020 Tecnativa - Alexandre D. Díaz
|
||||
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Services workers are a piece of software separated from the user page.
|
||||
* Here can't use 'Odoo Bootstrap', so we can't work with 'require' system.
|
||||
* When the service worker is called to be installed from the "pwa_manager"
|
||||
* this class is instantiated.
|
||||
*/
|
||||
var PWA = OdooClass.extend({
|
||||
|
||||
/**
|
||||
* @param {String} cache_name
|
||||
* @param {Array[String]} prefetched_urls
|
||||
*/
|
||||
init: function (cache_name, prefetched_urls) {
|
||||
this._cache_name = cache_name;
|
||||
this._prefetched_urls = prefetched_urls;
|
||||
this._cache = new CacheManager();
|
||||
this._cacheLoadPromise = this._cache.initCache(this._cache_name);
|
||||
// eslint-disable-next-line
|
||||
init: function (params) {
|
||||
// To be overridden
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise}
|
||||
*/
|
||||
installWorker: function () {
|
||||
return this._prefetchData();
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise}
|
||||
*/
|
||||
activateWorker: function () {
|
||||
return new Promise(async resolve => {
|
||||
await this._cacheLoadPromise;
|
||||
await this._cache.clean();
|
||||
return resolve();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise[Response]}
|
||||
*/
|
||||
processRequest: function (request) {
|
||||
if (request.method === 'GET' && (request.cache !== 'only-if-cached' || request.mode === 'same-origin')) {
|
||||
// Strategy: Cache First
|
||||
return this._cache.get(this._cache_name).match(request).then(function(response){
|
||||
return response || fetch(request);
|
||||
});
|
||||
}
|
||||
// Default browser behaviour
|
||||
return fetch(request);
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_prefetchData: function() {
|
||||
// Prefetch URL's
|
||||
return new Promise(resolve => {
|
||||
this._cacheLoadPromise.then(async () => {
|
||||
return resolve(await this._cache.get(this._cache_name).addAll(this._prefetched_urls));
|
||||
});
|
||||
});
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
});
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
<template id="web_layout_pwa" name="Web layout PWA" inherit_id="web.layout">
|
||||
<xpath expr="//meta[@name='viewport']" position="after">
|
||||
<!-- Add link rel manifest -->
|
||||
<link rel="manifest" href="/web_pwa_oca/manifest.webmanifest"/>
|
||||
<link rel="manifest" t-attf-href="/web_pwa_oca/manifest.webmanifest"/>
|
||||
<!-- Add iOS meta tags and icons -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes"/>
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black"/>
|
||||
<meta name="apple-mobile-web-app-title" content="Odoo PWA"/>
|
||||
<t t-set="pwa_name" t-value="request.env['ir.config_parameter'].sudo().get_param('pwa.manifest.name')"/>
|
||||
<meta name="apple-mobile-web-app-title" t-att-content="pwa_name"/>
|
||||
<link rel="apple-touch-icon" href="/web_pwa_oca/static/img/icons/icon-152x152.png"/>
|
||||
<!-- Add meta theme-color -->
|
||||
<meta name="theme-color" content="#2E69B5" />
|
||||
<t t-set="pwa_theme_color" t-value="request.env['ir.config_parameter'].sudo().get_param('pwa.manifest.theme_color')"/>
|
||||
<meta name="theme-color" t-att-content="pwa_theme_color" />
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="assets_backend" name="web service worker assets" inherit_id="web.assets_backend">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<odoo>
|
||||
|
||||
<template id="pwa_init" name="PWA Initialization">
|
||||
const oca_pwa = new PWA(...<t t-esc="str(pwa_params)"/>);
|
||||
const oca_pwa = new PWA(<t t-esc="str(pwa_params)"/>);
|
||||
</template>
|
||||
<template id="pwa_core_events" name="PWA Core Events">
|
||||
<t t-set="evt_install">
|
||||
|
@ -14,11 +14,10 @@
|
|||
<t t-raw="evt_install" />
|
||||
});
|
||||
|
||||
<t t-set="evt_fetch">
|
||||
evt.respondWith(oca_pwa.processRequest(evt.request));
|
||||
</t>
|
||||
<!-- This is necessary to get PWA installable.
|
||||
Other modules can add logic using 'evt_fetch' -->
|
||||
<t t-set="evt_fetch" />
|
||||
self.addEventListener('fetch', evt => {
|
||||
console.log('[ServiceWorker] Fetching...');
|
||||
<t t-raw="evt_fetch" />
|
||||
});
|
||||
|
||||
|
|
|
@ -66,7 +66,9 @@ class TestUi(odoo.tests.HttpCase):
|
|||
|
||||
self.assertEquals(manifest_content["icons"][0]["src"], "/web_pwa_oca/icon.svg")
|
||||
self.assertTrue(manifest_content["icons"][0]["type"].startswith("image/svg"))
|
||||
self.assertEquals(manifest_content["icons"][0]["sizes"], "128x128 144x144 152x152 192x192 256x256 512x512")
|
||||
self.assertEquals(
|
||||
manifest_content["icons"][0]["sizes"],
|
||||
"128x128 144x144 152x152 192x192 256x256 512x512")
|
||||
|
||||
# Get the icon and compare it
|
||||
icon_data = self.url_open("/web_pwa_oca/icon.svg")
|
||||
|
|
|
@ -9,29 +9,37 @@
|
|||
<div id="emails" position='after'>
|
||||
<h2>Progressive Web App</h2>
|
||||
<div class="row mt16 o_settings_container" id="pwa_settings">
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="domain_setting">
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="pwa_name" string="PWA Title"/>
|
||||
<span class="fa fa-lg fa-globe"/>
|
||||
<div class="text-muted">
|
||||
Name and icon of your PWA
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="domain_setting">
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="pwa_name" string="PWA Title"/>
|
||||
<span class="fa fa-lg fa-globe"/>
|
||||
<div class="text-muted">
|
||||
Name and icon of your PWA
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label class="col-lg-3 o_light_label" string="Name" for="pwa_name"/>
|
||||
<field name="pwa_name"/>
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label class="col-lg-3 o_light_label" string="Name" for="pwa_name"/>
|
||||
<field name="pwa_name"/>
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<label class="col-lg-3 o_light_label" string="Short Name" for="pwa_short_name"/>
|
||||
<field name="pwa_short_name"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-lg-3 o_light_label" for="pwa_icon" />
|
||||
<field name="pwa_icon" widget="image" class="float-left oe_avatar"/>
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<label class="col-lg-3 o_light_label" string="Short Name" for="pwa_short_name"/>
|
||||
<field name="pwa_short_name"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-lg-3 o_light_label" for="pwa_background_color"/>
|
||||
<field name="pwa_background_color"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-lg-3 o_light_label" for="pwa_theme_color"/>
|
||||
<field name="pwa_theme_color"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-lg-3 o_light_label" for="pwa_icon" />
|
||||
<field name="pwa_icon" widget="image" class="float-left oe_avatar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
|
|
Loading…
Reference in New Issue