mirror of https://github.com/OCA/web.git
[IMP] web_pwa_oca: Refactor to use OdooClass
parent
366b35dc10
commit
a74fbb34c0
|
@ -78,10 +78,31 @@ Known issues / Roadmap
|
|||
======================
|
||||
|
||||
* Evaluate to extend ``FILES_TO_CACHE``
|
||||
* Evaluate to use a normal JS file for service worker and download data from a normal JSON controller
|
||||
* 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`` doesn't support ``async`` functions. So we need use the following workaround:
|
||||
- Natural 'async/await' example (This breaks "_super" call)
|
||||
.. code-block:: javascript
|
||||
|
||||
const MyClass = OdooClass.extend({
|
||||
myFunc: async function() {
|
||||
const mydata = await ...do await stuff...
|
||||
return mydata;
|
||||
}
|
||||
});
|
||||
|
||||
- Same code with the workaround:
|
||||
.. code-block:: javascript
|
||||
|
||||
const MyClass = OdooClass.extend({
|
||||
myFunc: function() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const mydata = await ...do await stuff...
|
||||
resolve(mydata);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
@ -100,6 +121,7 @@ Authors
|
|||
~~~~~~~
|
||||
|
||||
* TAKOBI
|
||||
* Tecnativa
|
||||
|
||||
Contributors
|
||||
~~~~~~~~~~~~
|
||||
|
@ -108,6 +130,10 @@ Contributors
|
|||
|
||||
* Lorenzo Battistini
|
||||
|
||||
* `Tecnativa <https://tecnativa.com>`_:
|
||||
|
||||
* Alexandre D. Díaz
|
||||
|
||||
Maintainers
|
||||
~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2020 Lorenzo Battistini @ TAKOBI
|
||||
# Copyright 2020 Tecnativa - Alexandre D. Díaz
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
|
||||
|
||||
{
|
||||
|
@ -8,7 +9,7 @@
|
|||
"development_status": "Beta",
|
||||
"category": "Website",
|
||||
"website": "https://github.com/OCA/web",
|
||||
"author": "TAKOBI, Odoo Community Association (OCA)",
|
||||
"author": "TAKOBI, Tecnativa, Odoo Community Association (OCA)",
|
||||
"maintainers": ["eLBati"],
|
||||
"license": "LGPL-3",
|
||||
"application": True,
|
||||
|
@ -18,7 +19,8 @@
|
|||
'mail',
|
||||
],
|
||||
"data": [
|
||||
"views/webclient_templates.xml",
|
||||
"templates/assets.xml",
|
||||
"templates/service_worker.xml",
|
||||
],
|
||||
'qweb': [
|
||||
'static/src/xml/pwa_install.xml',
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
# Copyright 2020 Lorenzo Battistini @ TAKOBI
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
|
||||
from . import main
|
||||
|
|
|
@ -1,11 +1,26 @@
|
|||
# Copyright 2020 Lorenzo Battistini @ TAKOBI
|
||||
# Copyright 2020 Tecnativa - Alexandre D. Díaz
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
|
||||
import json
|
||||
from odoo.http import request, Controller, route
|
||||
|
||||
|
||||
class PWA(Controller):
|
||||
|
||||
def get_asset_urls(self, asset_xml_id):
|
||||
qweb = request.env['ir.qweb'].sudo()
|
||||
assets = qweb._get_asset_nodes(asset_xml_id, {}, True, True)
|
||||
def _get_pwa_scripts(self):
|
||||
""" 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 url's 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':
|
||||
|
@ -14,62 +29,93 @@ class PWA(Controller):
|
|||
urls.append(asset[1]['src'])
|
||||
return urls
|
||||
|
||||
@route('/service-worker.js', type='http', auth="public")
|
||||
def service_worker(self):
|
||||
qweb = request.env['ir.qweb'].sudo()
|
||||
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"))
|
||||
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)
|
||||
mimetype = 'text/javascript;charset=utf-8'
|
||||
content = qweb.render('web_pwa_oca.service_worker', {
|
||||
'pwa_cache_name': cache_version,
|
||||
'pwa_files_to_cache': urls,
|
||||
})
|
||||
return request.make_response(content, [('Content-Type', mimetype)])
|
||||
return [
|
||||
cache_version,
|
||||
urls
|
||||
]
|
||||
|
||||
@route('/web_pwa_oca/manifest.json', type='http', auth="public")
|
||||
def manifest(self):
|
||||
qweb = request.env['ir.qweb'].sudo()
|
||||
config_param = request.env['ir.config_parameter'].sudo()
|
||||
pwa_name = config_param.get_param("pwa.manifest.name", "Odoo PWA")
|
||||
pwa_short_name = config_param.get_param("pwa.manifest.short_name", "Odoo PWA")
|
||||
icon128x128 = config_param.get_param(
|
||||
@route('/service-worker.js', type='http', auth="public")
|
||||
def render_service_worker(self):
|
||||
"""Route to register the service worker in the 'main' scope ('/')"""
|
||||
return request.render('web_pwa_oca.service_worker', {
|
||||
'pwa_scripts': self._get_pwa_scripts(),
|
||||
'pwa_params': self._get_pwa_params(),
|
||||
}, headers=[('Content-Type', 'text/javascript;charset=utf-8')])
|
||||
|
||||
def _get_pwa_manifest(self):
|
||||
"""Webapp manifest"""
|
||||
config_param_sudo = request.env['ir.config_parameter'].sudo()
|
||||
pwa_name = config_param_sudo.get_param("pwa.manifest.name", "Odoo PWA")
|
||||
pwa_short_name = config_param_sudo.get_param(
|
||||
"pwa.manifest.short_name", "Odoo PWA")
|
||||
icon128x128 = config_param_sudo.get_param(
|
||||
"pwa.manifest.icon128x128",
|
||||
"/web_pwa_oca/static/img/icons/icon-128x128.png")
|
||||
icon144x144 = config_param.get_param(
|
||||
icon144x144 = config_param_sudo.get_param(
|
||||
"pwa.manifest.icon144x144",
|
||||
"/web_pwa_oca/static/img/icons/icon-144x144.png")
|
||||
icon152x152 = config_param.get_param(
|
||||
icon152x152 = config_param_sudo.get_param(
|
||||
"pwa.manifest.icon152x152",
|
||||
"/web_pwa_oca/static/img/icons/icon-152x152.png")
|
||||
icon192x192 = config_param.get_param(
|
||||
icon192x192 = config_param_sudo.get_param(
|
||||
"pwa.manifest.icon192x192",
|
||||
"/web_pwa_oca/static/img/icons/icon-192x192.png")
|
||||
icon256x256 = config_param.get_param(
|
||||
icon256x256 = config_param_sudo.get_param(
|
||||
"pwa.manifest.icon256x256",
|
||||
"/web_pwa_oca/static/img/icons/icon-256x256.png")
|
||||
icon512x512 = config_param.get_param(
|
||||
icon512x512 = config_param_sudo.get_param(
|
||||
"pwa.manifest.icon512x512",
|
||||
"/web_pwa_oca/static/img/icons/icon-512x512.png")
|
||||
background_color = config_param.get_param(
|
||||
background_color = config_param_sudo.get_param(
|
||||
"pwa.manifest.background_color", "#2E69B5")
|
||||
theme_color = config_param.get_param(
|
||||
theme_color = config_param_sudo.get_param(
|
||||
"pwa.manifest.theme_color", "#2E69B5")
|
||||
mimetype = 'application/json;charset=utf-8'
|
||||
content = qweb.render('web_pwa_oca.manifest', {
|
||||
'pwa_name': pwa_name,
|
||||
'pwa_short_name': pwa_short_name,
|
||||
'icon128x128': icon128x128,
|
||||
'icon144x144': icon144x144,
|
||||
'icon152x152': icon152x152,
|
||||
'icon192x192': icon192x192,
|
||||
'icon256x256': icon256x256,
|
||||
'icon512x512': icon512x512,
|
||||
'background_color': background_color,
|
||||
'theme_color': theme_color,
|
||||
})
|
||||
return request.make_response(content, [('Content-Type', mimetype)])
|
||||
return {
|
||||
"name": pwa_name,
|
||||
"short_name": pwa_short_name,
|
||||
"icons": [{
|
||||
"src": icon128x128,
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": icon144x144,
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": icon152x152,
|
||||
"sizes": "152x152",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": icon192x192,
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": icon256x256,
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": icon512x512,
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}],
|
||||
"start_url": "/web",
|
||||
"display": "standalone",
|
||||
"background_color": background_color,
|
||||
"theme_color": theme_color
|
||||
}
|
||||
|
||||
@route('/web_pwa_oca/manifest.webmanifest', type='http', auth="public")
|
||||
def pwa_manifest(self):
|
||||
"""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')])
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
* `TAKOBI <https://takobi.online>`_:
|
||||
|
||||
* Lorenzo Battistini
|
||||
|
||||
* `Tecnativa <https://tecnativa.com>`_:
|
||||
|
||||
* Alexandre D. Díaz
|
||||
|
|
|
@ -1,5 +1,26 @@
|
|||
* Evaluate to extend ``FILES_TO_CACHE``
|
||||
* Evaluate to use a normal JS file for service worker and download data from a normal JSON controller
|
||||
* 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
|
||||
|
||||
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...
|
||||
resolve(mydata);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -420,12 +420,50 @@ And like all other installed apps, it’s a top level app in the task switcher.<
|
|||
</div>
|
||||
<div class="section" id="known-issues-roadmap">
|
||||
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1>
|
||||
<ul class="simple">
|
||||
<li>Evaluate to extend <tt class="docutils literal">FILES_TO_CACHE</tt></li>
|
||||
<li>Evaluate to use a normal JS file for service worker and download data from a normal JSON controller</li>
|
||||
<li>Integrate <a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification">Notification API</a></li>
|
||||
<li>Integrate <a class="reference external" href="https://web.dev/web-share/">Web Share API</a></li>
|
||||
<li>Create <tt class="docutils literal">portal_pwa</tt> module, intended to be used by front-end users (customers, suppliers…)</li>
|
||||
<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> doesn’t support <tt class="docutils literal">async</tt> functions. 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">
|
||||
<span class="kr">const</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>
|
||||
<span class="k">return</span> <span class="nx">mydata</span><span class="p">;</span>
|
||||
<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">
|
||||
<span class="kr">const</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="p">});</span>
|
||||
<span class="p">}</span>
|
||||
<span class="p">});</span>
|
||||
</pre>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
|
@ -442,6 +480,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
|
|||
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>TAKOBI</li>
|
||||
<li>Tecnativa</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
|
@ -451,6 +490,10 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
|
|||
<li>Lorenzo Battistini</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference external" href="https://tecnativa.com">Tecnativa</a>:<ul>
|
||||
<li>Alexandre D. Díaz</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
/* Copyright 2020 Lorenzo Battistini @ TAKOBI
|
||||
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
|
||||
|
||||
odoo.define('web_pwa_oca.systray.install', function(require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var session = require('web.session');
|
||||
var UserMenu = require('web.UserMenu');
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('/service-worker.js')
|
||||
.then(function (reg) {
|
||||
console.log('Service worker registered.', reg);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var deferredInstallPrompt = null;
|
||||
|
||||
|
||||
UserMenu.include({
|
||||
start: function() {
|
||||
window.addEventListener('beforeinstallprompt', this.saveBeforeInstallPromptEvent);
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/* 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) {
|
||||
"use strict";
|
||||
|
||||
var Class = require("web.Class");
|
||||
var mixins = require("web.mixins");
|
||||
|
||||
|
||||
var PWAManager = Class.extend(mixins.ParentedMixin, {
|
||||
init: function() {
|
||||
if (!('serviceWorker' in navigator)) {
|
||||
throw new Error(
|
||||
"This browser is not compatible with service workers");
|
||||
}
|
||||
this._service_worker = navigator.serviceWorker;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} sw_script
|
||||
* @param {Function} success_callback
|
||||
* @returns {Promise}
|
||||
*/
|
||||
registerServiceWorker: function(sw_script, success_callback) {
|
||||
return this._service_worker.register(sw_script)
|
||||
.then(success_callback)
|
||||
.catch(function(error) {
|
||||
console.log('[ServiceWorker] Registration failed: ', error);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return PWAManager;
|
||||
});
|
|
@ -0,0 +1,30 @@
|
|||
/* 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) {
|
||||
"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);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Need register some extra API? override this!
|
||||
*
|
||||
* @param {ServiceWorkerRegistration} registration
|
||||
*/
|
||||
_onRegisterServiceWorker: function(registration) {
|
||||
console.log('[ServiceWorker] Registered:', registration);
|
||||
},
|
||||
});
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
/* 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);
|
||||
}
|
||||
}));
|
||||
});
|
||||
},
|
||||
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
/* 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([]));
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* Improved John Resig's inheritance, based on:
|
||||
*
|
||||
* Simple JavaScript Inheritance
|
||||
* By John Resig http://ejohn.org/
|
||||
* MIT Licensed.
|
||||
*
|
||||
* Adds "include()"
|
||||
*
|
||||
* Defines The Class object. That object can be used to define and inherit classes using
|
||||
* the extend() method.
|
||||
*
|
||||
* Example::
|
||||
*
|
||||
* var Person = Class.extend({
|
||||
* init: function(isDancing){
|
||||
* this.dancing = isDancing;
|
||||
* },
|
||||
* dance: function(){
|
||||
* return this.dancing;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* The init() method act as a constructor. This class can be instanced this way::
|
||||
*
|
||||
* var person = new Person(true);
|
||||
* person.dance();
|
||||
*
|
||||
* The Person class can also be extended again:
|
||||
*
|
||||
* var Ninja = Person.extend({
|
||||
* init: function(){
|
||||
* this._super( false );
|
||||
* },
|
||||
* dance: function(){
|
||||
* // Call the inherited version of dance()
|
||||
* return this._super();
|
||||
* },
|
||||
* swingSword: function(){
|
||||
* return true;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* When extending a class, each re-defined method can use this._super() to call the previous
|
||||
* implementation of that method.
|
||||
*
|
||||
* @class Class
|
||||
*/
|
||||
function OdooClass(){}
|
||||
|
||||
var initializing = false;
|
||||
var fnTest = /xyz/.test(function(){xyz();}) ? /\b_super\b/ : /.*/;
|
||||
|
||||
/**
|
||||
* Subclass an existing class
|
||||
*
|
||||
* @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class
|
||||
*/
|
||||
OdooClass.extend = function() {
|
||||
var _super = this.prototype;
|
||||
// Support mixins arguments
|
||||
var args = _.toArray(arguments);
|
||||
args.unshift({});
|
||||
var prop = _.extend.apply(_,args);
|
||||
|
||||
// Instantiate a web class (but only create the instance,
|
||||
// don't run the init constructor)
|
||||
initializing = true;
|
||||
var This = this;
|
||||
var prototype = new This();
|
||||
initializing = false;
|
||||
|
||||
// Copy the properties over onto the new prototype
|
||||
_.each(prop, function(val, name) {
|
||||
// Check if we're overwriting an existing function
|
||||
prototype[name] = typeof prop[name] == "function" &&
|
||||
fnTest.test(prop[name]) ?
|
||||
(function(name, fn) {
|
||||
return function() {
|
||||
var tmp = this._super;
|
||||
|
||||
// Add a new ._super() method that is the same
|
||||
// method but on the super-class
|
||||
this._super = _super[name];
|
||||
|
||||
// The method only need to be bound temporarily, so
|
||||
// we remove it when we're done executing
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
|
||||
return ret;
|
||||
};
|
||||
})(name, prop[name]) :
|
||||
prop[name];
|
||||
});
|
||||
|
||||
// The dummy class constructor
|
||||
function Class() {
|
||||
if(this.constructor !== OdooClass){
|
||||
throw new Error("You can only instanciate objects with the 'new' operator");
|
||||
}
|
||||
// All construction is actually done in the init method
|
||||
this._super = null;
|
||||
if (!initializing && this.init) {
|
||||
var ret = this.init.apply(this, arguments);
|
||||
if (ret) { return ret; }
|
||||
}
|
||||
return this;
|
||||
}
|
||||
Class.include = function (properties) {
|
||||
_.each(properties, function(val, name) {
|
||||
if (typeof properties[name] !== 'function'
|
||||
|| !fnTest.test(properties[name])) {
|
||||
prototype[name] = properties[name];
|
||||
} else if (typeof prototype[name] === 'function'
|
||||
&& prototype.hasOwnProperty(name)) {
|
||||
prototype[name] = (function (name, fn, previous) {
|
||||
return function () {
|
||||
var tmp = this._super;
|
||||
this._super = previous;
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
return ret;
|
||||
};
|
||||
})(name, properties[name], prototype[name]);
|
||||
} else if (typeof _super[name] === 'function') {
|
||||
prototype[name] = (function (name, fn) {
|
||||
return function () {
|
||||
var tmp = this._super;
|
||||
this._super = _super[name];
|
||||
var ret = fn.apply(this, arguments);
|
||||
this._super = tmp;
|
||||
return ret;
|
||||
};
|
||||
})(name, properties[name]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Populate our constructed prototype object
|
||||
Class.prototype = prototype;
|
||||
|
||||
// Enforce the constructor to be what we expect
|
||||
Class.constructor = Class;
|
||||
|
||||
// And make this class extendable
|
||||
Class.extend = this.extend;
|
||||
|
||||
return Class;
|
||||
};
|
|
@ -0,0 +1,62 @@
|
|||
/* Copyright 2020 Tecnativa - Alexandre D. Díaz
|
||||
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
|
||||
"use strict";
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {Promise}
|
||||
*/
|
||||
installWorker: function () {
|
||||
return this._prefetchData();
|
||||
},
|
||||
|
||||
/**
|
||||
* @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));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<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"/>
|
||||
<!-- 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"/>
|
||||
<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" />
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="assets_backend" name="web service worker assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/web_pwa_oca/static/src/js/pwa_manager.js"></script>
|
||||
<script type="text/javascript" src="/web_pwa_oca/static/src/js/pwa_install.js"></script>
|
||||
<script type="text/javascript" src="/web_pwa_oca/static/src/js/webclient.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<template id="pwa_init" name="PWA Initialization">
|
||||
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">
|
||||
evt.waitUntil(oca_pwa.installWorker());
|
||||
self.skipWaiting();
|
||||
</t>
|
||||
self.addEventListener('install', evt => {
|
||||
console.log('[ServiceWorker] Installing...');
|
||||
<t t-raw="evt_install" />
|
||||
});
|
||||
|
||||
<t t-set="evt_fetch">
|
||||
evt.respondWith(oca_pwa.processRequest(evt.request));
|
||||
</t>
|
||||
self.addEventListener('fetch', evt => {
|
||||
console.log('[ServiceWorker] Fetching...');
|
||||
<t t-raw="evt_fetch" />
|
||||
});
|
||||
|
||||
<t t-set="evt_activate">
|
||||
console.log('[ServiceWorker] Activating...');
|
||||
evt.waitUntil(oca_pwa.activateWorker());
|
||||
self.clients.claim();
|
||||
</t>
|
||||
self.addEventListener('activate', evt => {
|
||||
<t t-raw="evt_activate" />
|
||||
});
|
||||
</template>
|
||||
<template id="service_worker" name="PWA Service Worker">
|
||||
self.importScripts(...<t t-esc="str(pwa_scripts)" />);
|
||||
<t t-call="web_pwa_oca.pwa_init" />
|
||||
<t t-call="web_pwa_oca.pwa_core_events" />
|
||||
</template>
|
||||
|
||||
</odoo>
|
|
@ -1,105 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<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.json"/>
|
||||
<!-- 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"/>
|
||||
<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" />
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="assets_backend" name="PWA assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/web_pwa_oca/static/src/js/pwa_install.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
<template id="service_worker" name="PWA service worker">
|
||||
'use strict';
|
||||
const CACHE_NAME = '<t t-esc="pwa_cache_name"/>';
|
||||
const FILES_TO_CACHE = [
|
||||
<t t-foreach="pwa_files_to_cache" t-as="file_to_cache">
|
||||
'<t t-esc="file_to_cache"/>',
|
||||
</t>
|
||||
];
|
||||
self.addEventListener('install', function (evt) {
|
||||
console.log('[ServiceWorker] Install');
|
||||
evt.waitUntil(
|
||||
caches.open(CACHE_NAME).then(function (cache) {
|
||||
console.log('[ServiceWorker] Pre-caching offline page');
|
||||
return cache.addAll(FILES_TO_CACHE);
|
||||
})
|
||||
);
|
||||
self.skipWaiting();
|
||||
});
|
||||
self.addEventListener('activate', function(evt) {
|
||||
console.log('[ServiceWorker] Activate');
|
||||
evt.waitUntil(
|
||||
caches.keys().then(function(keyList) {
|
||||
return Promise.all(keyList.map(function(key) {
|
||||
if (key !== CACHE_NAME) {
|
||||
console.log('[ServiceWorker] Removing old cache', key);
|
||||
return caches.delete(key);
|
||||
}
|
||||
}));
|
||||
})
|
||||
);
|
||||
self.clients.claim();
|
||||
});
|
||||
self.addEventListener('fetch', function(evt) {
|
||||
if (evt.request.cache === 'only-if-cached' && evt.request.mode !== 'same-origin') {
|
||||
return;
|
||||
}
|
||||
console.log('[ServiceWorker] Fetch', evt.request.url);
|
||||
evt.respondWith(
|
||||
caches.open(CACHE_NAME).then(function(cache) {
|
||||
return cache.match(evt.request)
|
||||
.then(function(response) {
|
||||
return response || fetch(evt.request);
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
</template>
|
||||
|
||||
<template id="manifest" name="PWA manifest">
|
||||
{
|
||||
"name": "<t t-esc="pwa_name"/>",
|
||||
"short_name": "<t t-esc="pwa_short_name"/>",
|
||||
"icons": [{
|
||||
"src": "<t t-esc="icon128x128"/>",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "<t t-esc="icon144x144"/>",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "<t t-esc="icon152x152"/>",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "<t t-esc="icon192x192"/>",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "<t t-esc="icon256x256"/>",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "<t t-esc="icon512x512"/>",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}],
|
||||
"start_url": "/web",
|
||||
"display": "standalone",
|
||||
"background_color": "<t t-esc="background_color"/>",
|
||||
"theme_color": "<t t-esc="theme_color"/>"
|
||||
}
|
||||
</template>
|
||||
|
||||
</odoo>
|
Loading…
Reference in New Issue