[IMP] web_pwa_oca: Refactor to use OdooClass

pull/1628/head
Alexandre Díaz 2020-06-28 04:33:08 +02:00 committed by Alexandre D. Díaz
parent 366b35dc10
commit a74fbb34c0
17 changed files with 630 additions and 196 deletions

View File

@ -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
~~~~~~~~~~~

View File

@ -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',

View File

@ -1 +1,3 @@
# Copyright 2020 Lorenzo Battistini @ TAKOBI
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
from . import main

View File

@ -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')])

View File

@ -1,3 +1,7 @@
* `TAKOBI <https://takobi.online>`_:
* Lorenzo Battistini
* `Tecnativa <https://tecnativa.com>`_:
* Alexandre D. Díaz

View File

@ -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);
});
}
});

View File

@ -420,12 +420,50 @@ And like all other installed apps, its 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> doesnt 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">=&gt;</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">

View File

@ -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);

View File

@ -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;
});

View File

@ -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);
},
});
});

View File

@ -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);
}
}));
});
},
});

View File

@ -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([]));
}

View File

@ -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;
};

View File

@ -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));
});
});
},
});

View File

@ -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>

View File

@ -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 =&gt; {
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 =&gt; {
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 =&gt; {
<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>

View File

@ -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' &amp;&amp; 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>