diff --git a/web_pwa/__init__.py b/web_pwa/__init__.py new file mode 100644 index 000000000..e046e49fb --- /dev/null +++ b/web_pwa/__init__.py @@ -0,0 +1 @@ +from . import controllers diff --git a/web_pwa/__manifest__.py b/web_pwa/__manifest__.py new file mode 100644 index 000000000..d23a0c373 --- /dev/null +++ b/web_pwa/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright 2020 Lorenzo Battistini @ TAKOBI +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). + +{ + "name": "Progressive web application", + "summary": "Make Odoo a PWA", + "version": "12.0.1.0.0", + "development_status": "Beta", + "category": "Website", + "website": "https://github.com/OCA/web", + "author": "TAKOBI, Odoo Community Association (OCA)", + "maintainers": ["eLBati"], + "license": "LGPL-3", + "application": True, + "installable": True, + "depends": [ + 'web', + 'mail', + ], + "data": [ + "views/webclient_templates.xml", + ], + 'qweb': [ + 'static/src/xml/pwa_install.xml', + ], + 'images': ['static/description/pwa.png'], +} diff --git a/web_pwa/controllers/__init__.py b/web_pwa/controllers/__init__.py new file mode 100644 index 000000000..12a7e529b --- /dev/null +++ b/web_pwa/controllers/__init__.py @@ -0,0 +1 @@ +from . import main diff --git a/web_pwa/controllers/main.py b/web_pwa/controllers/main.py new file mode 100644 index 000000000..516919e95 --- /dev/null +++ b/web_pwa/controllers/main.py @@ -0,0 +1,69 @@ +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) + 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 + + @route('/service-worker.js', type='http', auth="public") + def service_worker(self): + qweb = request.env['ir.qweb'].sudo() + 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) + mimetype = 'text/javascript;charset=utf-8' + content = qweb.render('web_pwa.service_worker', { + 'pwa_cache_name': cache_version, + 'pwa_files_to_cache': urls, + }) + return request.make_response(content, [('Content-Type', mimetype)]) + + @route('/web_pwa/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( + "pwa.manifest.icon128x128", "/web_pwa/static/img/icons/icon-128x128.png") + icon144x144 = config_param.get_param( + "pwa.manifest.icon144x144", "/web_pwa/static/img/icons/icon-144x144.png") + icon152x152 = config_param.get_param( + "pwa.manifest.icon152x152", "/web_pwa/static/img/icons/icon-152x152.png") + icon192x192 = config_param.get_param( + "pwa.manifest.icon192x192", "/web_pwa/static/img/icons/icon-192x192.png") + icon256x256 = config_param.get_param( + "pwa.manifest.icon256x256", "/web_pwa/static/img/icons/icon-256x256.png") + icon512x512 = config_param.get_param( + "pwa.manifest.icon512x512", "/web_pwa/static/img/icons/icon-512x512.png") + background_color = config_param.get_param( + "pwa.manifest.background_color", "#2E69B5") + theme_color = config_param.get_param( + "pwa.manifest.theme_color", "#2E69B5") + mimetype = 'application/json;charset=utf-8' + content = qweb.render('web_pwa.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)]) diff --git a/web_pwa/readme/CONFIGURE.rst b/web_pwa/readme/CONFIGURE.rst new file mode 100644 index 000000000..be4586ab5 --- /dev/null +++ b/web_pwa/readme/CONFIGURE.rst @@ -0,0 +1,10 @@ +The following system parameters con be set to customize the appearance of the application + +* pwa.manifest.name (defaults to "Odoo PWA") +* pwa.manifest.short_name (defaults to "Odoo PWA") +* pwa.manifest.icon128x128 (defaults to "/web_pwa/static/img/icons/icon-128x128.png") +* pwa.manifest.icon144x144 (defaults to "/web_pwa/static/img/icons/icon-144x144.png") +* pwa.manifest.icon152x152 (defaults to "/web_pwa/static/img/icons/icon-152x152.png") +* pwa.manifest.icon192x192 (defaults to "/web_pwa/static/img/icons/icon-192x192.png") +* pwa.manifest.icon256x256 (defaults to "/web_pwa/static/img/icons/icon-256x256.png") +* pwa.manifest.icon512x512 (defaults to "/web_pwa/static/img/icons/icon-512x512.png") diff --git a/web_pwa/readme/CONTRIBUTORS.rst b/web_pwa/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..2b476d752 --- /dev/null +++ b/web_pwa/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `TAKOBI `_: + + * Lorenzo Battistini diff --git a/web_pwa/readme/DESCRIPTION.rst b/web_pwa/readme/DESCRIPTION.rst new file mode 100644 index 000000000..e2eb74425 --- /dev/null +++ b/web_pwa/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +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. diff --git a/web_pwa/readme/INSTALL.rst b/web_pwa/readme/INSTALL.rst new file mode 100644 index 000000000..3d3720a65 --- /dev/null +++ b/web_pwa/readme/INSTALL.rst @@ -0,0 +1,13 @@ +After having installed this module, browsing your odoo on mobile you will be able to install it as a PWA. + +It is strongly recommended to use this module with a responsive layout, like the one provided by web_responsive. + +This module is intended to be used by Odoo back-end users (employees). + +When a Progressive Web App is installed, it looks and behaves like all of the other installed apps. +It launches from the same place that other apps launch. It runs in an app without an address bar or other browser UI. +And like all other installed apps, it's a top level app in the task switcher. + +In Chrome, a Progressive Web App can either be installed through the three-dot context menu. + +This module also provides a "Install PWA" link in Odoo user menu. diff --git a/web_pwa/readme/ROADMAP.rst b/web_pwa/readme/ROADMAP.rst new file mode 100644 index 000000000..9e763ae62 --- /dev/null +++ b/web_pwa/readme/ROADMAP.rst @@ -0,0 +1,5 @@ +* 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 `_ +* Integrate `Web Share API `_ +* Create ``portal_pwa`` module, intended to be used by front-end users (customers, suppliers...) diff --git a/web_pwa/static/description/icon.png b/web_pwa/static/description/icon.png new file mode 100644 index 000000000..2927ee1d7 Binary files /dev/null and b/web_pwa/static/description/icon.png differ diff --git a/web_pwa/static/description/pwa.png b/web_pwa/static/description/pwa.png new file mode 100644 index 000000000..0591fd870 Binary files /dev/null and b/web_pwa/static/description/pwa.png differ diff --git a/web_pwa/static/img/icons/icon-128x128.png b/web_pwa/static/img/icons/icon-128x128.png new file mode 100644 index 000000000..b111f3697 Binary files /dev/null and b/web_pwa/static/img/icons/icon-128x128.png differ diff --git a/web_pwa/static/img/icons/icon-144x144.png b/web_pwa/static/img/icons/icon-144x144.png new file mode 100644 index 000000000..dd3fd020d Binary files /dev/null and b/web_pwa/static/img/icons/icon-144x144.png differ diff --git a/web_pwa/static/img/icons/icon-152x152.png b/web_pwa/static/img/icons/icon-152x152.png new file mode 100644 index 000000000..38023d8cf Binary files /dev/null and b/web_pwa/static/img/icons/icon-152x152.png differ diff --git a/web_pwa/static/img/icons/icon-192x192.png b/web_pwa/static/img/icons/icon-192x192.png new file mode 100644 index 000000000..4d46abbfb Binary files /dev/null and b/web_pwa/static/img/icons/icon-192x192.png differ diff --git a/web_pwa/static/img/icons/icon-256x256.png b/web_pwa/static/img/icons/icon-256x256.png new file mode 100644 index 000000000..a54e9d6fd Binary files /dev/null and b/web_pwa/static/img/icons/icon-256x256.png differ diff --git a/web_pwa/static/img/icons/icon-512x512.png b/web_pwa/static/img/icons/icon-512x512.png new file mode 100644 index 000000000..3be61eacb Binary files /dev/null and b/web_pwa/static/img/icons/icon-512x512.png differ diff --git a/web_pwa/static/src/js/pwa_install.js b/web_pwa/static/src/js/pwa_install.js new file mode 100644 index 000000000..4cb2f70b9 --- /dev/null +++ b/web_pwa/static/src/js/pwa_install.js @@ -0,0 +1,45 @@ +odoo.define('web_pwa.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); + return this._super.apply(this, arguments); + }, + 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; + }); + }, +}); + +}); diff --git a/web_pwa/static/src/xml/pwa_install.xml b/web_pwa/static/src/xml/pwa_install.xml new file mode 100644 index 000000000..a6b3da96a --- /dev/null +++ b/web_pwa/static/src/xml/pwa_install.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/web_pwa/views/webclient_templates.xml b/web_pwa/views/webclient_templates.xml new file mode 100644 index 000000000..1482d83f9 --- /dev/null +++ b/web_pwa/views/webclient_templates.xml @@ -0,0 +1,105 @@ + + + +