diff --git a/web_pwa_oca/README.rst b/web_pwa_oca/README.rst index b7c494a1c..2f835517b 100644 --- a/web_pwa_oca/README.rst +++ b/web_pwa_oca/README.rst @@ -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:: @@ -51,30 +62,77 @@ 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. +In case you previously installed `web_pwa`, run the following steps with `odoo shell`, after having installed `openupgradelib`: + + +>>> from openupgradelib import openupgrade +>>> openupgrade.update_module_names(env.cr, [('web_pwa', 'web_pwa_oca')], merge_modules=False) +>>> env.cr.commit() Configuration ============= -The following system parameters con be set to customize the appearance of the application +This module allows you to set the following parameters under settings 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_oca/static/img/icons/icon-128x128.png") -* pwa.manifest.icon144x144 (defaults to "/web_pwa_oca/static/img/icons/icon-144x144.png") -* pwa.manifest.icon152x152 (defaults to "/web_pwa_oca/static/img/icons/icon-152x152.png") -* pwa.manifest.icon192x192 (defaults to "/web_pwa_oca/static/img/icons/icon-192x192.png") -* pwa.manifest.icon256x256 (defaults to "/web_pwa_oca/static/img/icons/icon-256x256.png") -* pwa.manifest.icon512x512 (defaults to "/web_pwa_oca/static/img/icons/icon-512x512.png") +* PWA Name (defaults to "Odoo PWA") +* PWA Short Name (defaults to "Odoo PWA") +* PWA Icon (**SVG**) (defaults to "/web_pwa_oca/static/img/icons/odoo-logo.svg") + +To configure your PWA: + +#. Go to **Settings > General Settings > Progressive Web App**. +#. Set the parameters (*Note:* Icon **must be a SVG file**) +#. **Save** + +Usage +===== + +To use your PWA: + +#. Open the Odoo web app using a supported browser (See https://caniuse.com/?search=A2HS) +#. Open the browser options +#. Click on 'Add to Home screen' (or 'Install' in other browsers) + +** Maybe you need refresh the page to load the service worker after using the option. 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 `_ * Integrate `Web Share API `_ * 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 to 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... + return 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. +* Firefox can't detect 'standalone' mode. See https://bugzilla.mozilla.org/show_bug.cgi?id=1285858 +* Firefox disable service worker in private mode. See https://bugzilla.mozilla.org/show_bug.cgi?id=1601916 Bug Tracker =========== @@ -93,6 +151,7 @@ Authors ~~~~~~~ * TAKOBI +* Tecnativa Contributors ~~~~~~~~~~~~ @@ -101,6 +160,11 @@ Contributors * Lorenzo Battistini +* `Tecnativa `_: + + * Alexandre D. Díaz + * João Marques + Maintainers ~~~~~~~~~~~ diff --git a/web_pwa_oca/__init__.py b/web_pwa_oca/__init__.py index e046e49fb..91c5580fe 100644 --- a/web_pwa_oca/__init__.py +++ b/web_pwa_oca/__init__.py @@ -1 +1,2 @@ from . import controllers +from . import models diff --git a/web_pwa_oca/__manifest__.py b/web_pwa_oca/__manifest__.py index 02e36408d..c9a00561f 100644 --- a/web_pwa_oca/__manifest__.py +++ b/web_pwa_oca/__manifest__.py @@ -1,4 +1,6 @@ # Copyright 2020 Lorenzo Battistini @ TAKOBI +# Copyright 2020 Tecnativa - Alexandre D. Díaz +# Copyright 2020 Tecnativa - João Marques # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). { @@ -8,13 +10,12 @@ "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, "installable": True, "depends": ["web", "mail"], - "data": ["views/webclient_templates.xml"], - "qweb": ["static/src/xml/pwa_install.xml"], + "data": ["templates/assets.xml", "views/res_config_settings_views.xml"], "images": ["static/description/pwa.png"], } diff --git a/web_pwa_oca/controllers/__init__.py b/web_pwa_oca/controllers/__init__.py index 12a7e529b..1fbcf85db 100644 --- a/web_pwa_oca/controllers/__init__.py +++ b/web_pwa_oca/controllers/__init__.py @@ -1 +1,4 @@ +# Copyright 2020 Lorenzo Battistini @ TAKOBI +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). from . import main +from . import service_worker diff --git a/web_pwa_oca/controllers/main.py b/web_pwa_oca/controllers/main.py index 194d091b6..fdaad56c1 100644 --- a/web_pwa_oca/controllers/main.py +++ b/web_pwa_oca/controllers/main.py @@ -1,77 +1,119 @@ +# Copyright 2020 Lorenzo Battistini @ TAKOBI +# Copyright 2020 Tecnativa - Alexandre D. Díaz +# Copyright 2020 Tecnativa - João Marques +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +import json + from odoo.http import Controller, request, 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 + 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/jquery-sw-compat.js", + "/web/static/src/js/promise_extension.js", + "/web/static/src/js/boot.js", + "/web/static/src/js/core/class.js", + "/web_pwa_oca/static/src/js/worker/pwa.js", + ] @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( + def render_service_worker(self): + """Route to register the service worker in the 'main' scope ('/')""" + return request.render( "web_pwa_oca.service_worker", - {"pwa_cache_name": cache_version, "pwa_files_to_cache": urls}, + { + "pwa_scripts": self._get_pwa_scripts(), + "pwa_params": self._get_pwa_params(), + }, + headers=[("Content-Type", "text/javascript;charset=utf-8")], ) - return request.make_response(content, [("Content-Type", mimetype)]) - @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( - "pwa.manifest.icon128x128", "/web_pwa_oca/static/img/icons/icon-128x128.png" + 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: + for size in [ + (128, 128), + (144, 144), + (152, 152), + (192, 192), + (256, 256), + (512, 512), + ]: + icons.append( + { + "src": "/web_pwa_oca/static/img/icons/icon-%sx%s.png" + % (str(size[0]), str(size[1])), + "sizes": "{}x{}".format(str(size[0]), str(size[1])), + "type": "image/png", + } + ) + elif not pwa_icon.mimetype.startswith("image/svg"): + all_icons = ( + request.env["ir.attachment"] + .sudo() + .search( + [ + ("url", "like", "/web_pwa_oca/icon"), + ( + "url", + "not like", + "/web_pwa_oca/icon.", + ), # Get only resized icons + ] + ) + ) + for icon in all_icons: + icon_size_name = icon.url.split("/")[-1].lstrip("icon").split(".")[0] + icons.append( + {"src": icon.url, "sizes": icon_size_name, "type": icon.mimetype} + ) + else: + icons = [ + { + "src": pwa_icon.url, + "sizes": "128x128 144x144 152x152 192x192 256x256 512x512", + "type": pwa_icon.mimetype, + } + ] + return icons + + 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" ) - icon144x144 = config_param.get_param( - "pwa.manifest.icon144x144", "/web_pwa_oca/static/img/icons/icon-144x144.png" + pwa_icon = ( + request.env["ir.attachment"] + .sudo() + .search([("url", "like", "/web_pwa_oca/icon.")]) ) - icon152x152 = config_param.get_param( - "pwa.manifest.icon152x152", "/web_pwa_oca/static/img/icons/icon-152x152.png" - ) - icon192x192 = config_param.get_param( - "pwa.manifest.icon192x192", "/web_pwa_oca/static/img/icons/icon-192x192.png" - ) - icon256x256 = config_param.get_param( - "pwa.manifest.icon256x256", "/web_pwa_oca/static/img/icons/icon-256x256.png" - ) - icon512x512 = config_param.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("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, - }, + theme_color = config_param_sudo.get_param("pwa.manifest.theme_color", "#2E69B5") + return { + "name": pwa_name, + "short_name": pwa_short_name, + "icons": self._get_pwa_manifest_icons(pwa_icon), + "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", "application/json;charset=utf-8")], ) - return request.make_response(content, [("Content-Type", mimetype)]) diff --git a/web_pwa_oca/controllers/service_worker.py b/web_pwa_oca/controllers/service_worker.py new file mode 100644 index 000000000..ffd8fd737 --- /dev/null +++ b/web_pwa_oca/controllers/service_worker.py @@ -0,0 +1,98 @@ +# Copyright 2021 Tecnativa - Alexandre D. Díaz +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +from odoo.http import request, route + +from .main import PWA + + +class ServiceWorker(PWA): + + JS_PWA_CORE_EVENT_INSTALL = """ + self.addEventListener('install', evt => {{ + console.log('[ServiceWorker] Installing...'); + {} + }}); + """ + + JS_PWA_CORE_EVENT_FETCH = """ + self.addEventListener('fetch', evt => {{ + {} + }}); + """ + + JS_PWA_CORE_EVENT_ACTIVATE = """ + self.addEventListener('activate', evt => {{ + {} + }}); + """ + + JS_PWA_MAIN = """ + self.importScripts(...{pwa_scripts}); + + odoo.define("web_pwa_oca.ServiceWorker", function (require) {{ + "use strict"; + + {pwa_requires} + + {pwa_init} + {pwa_core_event_install} + {pwa_core_event_activate} + {pwa_core_event_fetch} + }}); + """ + + def _get_js_pwa_requires(self): + return """ + const PWA = require('web_pwa_oca.PWA'); + """ + + def _get_js_pwa_init(self): + return """ + const oca_pwa = new PWA({}); + """.format( + self._get_pwa_params() + ) + + def _get_js_pwa_core_event_install_impl(self): + return """ + evt.waitUntil(oca_pwa.installWorker()); + self.skipWaiting(); + """ + + def _get_js_pwa_core_event_activate_impl(self): + return """ + console.log('[ServiceWorker] Activating...'); + evt.waitUntil(oca_pwa.activateWorker()); + self.clients.claim(); + """ + + def _get_js_pwa_core_event_fetch_impl(self): + return "" + + @route("/service-worker.js", type="http", auth="public") + def render_service_worker(self): + """Route to register the service worker in the 'main' scope ('/')""" + + sw_code = self.JS_PWA_MAIN.format( + **{ + "pwa_scripts": self._get_pwa_scripts(), + "pwa_requires": self._get_js_pwa_requires(), + "pwa_init": self._get_js_pwa_init(), + "pwa_core_event_install": self.JS_PWA_CORE_EVENT_INSTALL.format( + self._get_js_pwa_core_event_install_impl() + ), + "pwa_core_event_activate": self.JS_PWA_CORE_EVENT_ACTIVATE.format( + self._get_js_pwa_core_event_activate_impl() + ), + "pwa_core_event_fetch": self.JS_PWA_CORE_EVENT_FETCH.format( + self._get_js_pwa_core_event_fetch_impl() + ), + } + ) + return request.make_response( + sw_code, + [ + ("Content-Type", "text/javascript;charset=utf-8"), + ("Content-Length", len(sw_code)), + ], + ) diff --git a/web_pwa_oca/i18n/es.po b/web_pwa_oca/i18n/es.po index 6f724a6d6..b6612a269 100644 --- a/web_pwa_oca/i18n/es.po +++ b/web_pwa_oca/i18n/es.po @@ -6,173 +6,134 @@ msgid "" msgstr "" "Project-Id-Version: Odoo Server 13.0\n" "Report-Msgid-Bugs-To: \n" -"PO-Revision-Date: 2021-02-17 14:45+0000\n" +"POT-Creation-Date: 2021-03-16 18:49+0000\n" +"PO-Revision-Date: 2021-03-16 19:50+0100\n" "Last-Translator: claudiagn \n" "Language-Team: none\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" +"Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.3.2\n" +"X-Generator: Poedit 2.4.1\n" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"128x128\",\n" -" \"type\": \"image/png\"\n" -" }, {\n" -" \"src\": \"" -msgstr "" -"\",\n" -" \"sizes\": \"128x128\",\n" -" \"type\": \"image/png\"\n" -" }, {\n" -" \"src\": \"" +#: model:ir.model.fields,field_description:web_pwa_oca.field_res_config_settings__pwa_background_color +#, fuzzy +msgid "Background Color" +msgstr "Color de fondo" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"144x144\",\n" -" \"type\": \"image/png\"\n" -" }, {\n" -" \"src\": \"" -msgstr "" +#: model:ir.model,name:web_pwa_oca.model_res_config_settings +#, fuzzy +msgid "Config Settings" +msgstr "Ajustes de configuración" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"152x152\",\n" -" \"type\": \"image/png\"\n" -" }, {\n" -" \"src\": \"" -msgstr "" +#: model:ir.model.fields,field_description:web_pwa_oca.field_res_config_settings__pwa_icon +#, fuzzy +msgid "Icon" +msgstr "Icono" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"192x192\",\n" -" \"type\": \"image/png\"\n" -" }, {\n" -" \"src\": \"" -msgstr "" +#: model_terms:ir.ui.view,arch_db:web_pwa_oca.res_config_settings_view_form +#, fuzzy +msgid "Name" +msgstr "Nombre" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"256x256\",\n" -" \"type\": \"image/png\"\n" -" }, {\n" -" \"src\": \"" -msgstr "" +#: model_terms:ir.ui.view,arch_db:web_pwa_oca.res_config_settings_view_form +#, fuzzy +msgid "Name and icon of your PWA" +msgstr "Nombre e icono de su PWA" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"512x512\",\n" -" \"type\": \"image/png\"\n" -" }],\n" -" \"start_url\": \"/web\",\n" -" \"display\": \"standalone\",\n" -" \"background_color\": \"" -msgstr "" +#: model:ir.model.fields,help:web_pwa_oca.field_res_config_settings__pwa_name +#, fuzzy +msgid "Name of the Progressive Web Application" +msgstr "Nombre de la aplicación web progresiva" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"icons\": [{\n" -" \"src\": \"" -msgstr "" +#: model_terms:ir.ui.view,arch_db:web_pwa_oca.res_config_settings_view_form +#, fuzzy +msgid "PWA Title" +msgstr "Título de PWA" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"short_name\": \"" -msgstr "" +#: model_terms:ir.ui.view,arch_db:web_pwa_oca.res_config_settings_view_form +#, fuzzy +msgid "Progressive Web App" +msgstr "Aplicación web progresiva" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"theme_color\": \"" -msgstr "" +#: model:ir.model.fields,field_description:web_pwa_oca.field_res_config_settings__pwa_name +#, fuzzy +msgid "Progressive Web App Name" +msgstr "Nombre de la aplicación web progresiva" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.service_worker -msgid "" -"';\n" -"const FILES_TO_CACHE = [" -msgstr "" - -#. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.service_worker -msgid "" -"'use strict';\n" -"const CACHE_NAME = '" -msgstr "" +#: model:ir.model.fields,field_description:web_pwa_oca.field_res_config_settings__pwa_short_name +#, fuzzy +msgid "Progressive Web App Short Name" +msgstr "Nombre corto de la aplicación web progresiva" #. module: web_pwa_oca #. openerp-web -#: code:addons/web_pwa_oca/static/src/xml/pwa_install.xml:0 +#: code:addons/web_pwa_oca/static/src/js/pwa_manager.js:0 +#, fuzzy, python-format +msgid "" +"Service workers are not supported! Maybe you are not using HTTPS or you work " +"in private mode." +msgstr "" +"¡Los trabajadores de servicios no son compatibles! Quizás no esté usando " +"HTTPS o trabaje en modo privado." + +#. module: web_pwa_oca +#: model_terms:ir.ui.view,arch_db:web_pwa_oca.res_config_settings_view_form +#, fuzzy +msgid "Short Name" +msgstr "Nombre corto" + +#. module: web_pwa_oca +#: model:ir.model.fields,help:web_pwa_oca.field_res_config_settings__pwa_short_name +#, fuzzy +msgid "Short Name of the Progressive Web Application" +msgstr "Nombre corto de la aplicación web progresiva" + +#. module: web_pwa_oca +#: model:ir.model.fields,field_description:web_pwa_oca.field_res_config_settings__pwa_theme_color +#, fuzzy +msgid "Theme Color" +msgstr "Color del tema" + +#. module: web_pwa_oca +#: code:addons/web_pwa_oca/models/res_config_settings.py:0 +#, fuzzy, python-format +msgid "You can only upload PNG files bigger than 512x512" +msgstr "Solo puede cargar archivos PNG de más de 512 x 512" + +#. module: web_pwa_oca +#: code:addons/web_pwa_oca/models/res_config_settings.py:0 +#, fuzzy, python-format +msgid "You can only upload SVG or PNG files" +msgstr "Solo puede cargar archivos SVG o PNG" + +#. module: web_pwa_oca +#: code:addons/web_pwa_oca/models/res_config_settings.py:0 +#, fuzzy, python-format +msgid "You can't upload a file with more than 2 MB." +msgstr "No puede cargar un archivo con más de 2 MB." + +#. module: web_pwa_oca +#. openerp-web +#: code:addons/web_pwa_oca/static/src/js/pwa_manager.js:0 +#, fuzzy, python-format +msgid "[ServiceWorker] Registered:" +msgstr "[ServiceWorker] Registrada:" + +#. module: web_pwa_oca +#. openerp-web +#: code:addons/web_pwa_oca/static/src/js/pwa_manager.js:0 #, python-format -msgid "Install PWA" -msgstr "" - -#. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.service_worker -msgid "" -"];\n" -"self.addEventListener('install', function (evt) {\n" -" console.log('[ServiceWorker] Install');\n" -" evt.waitUntil(\n" -" caches.open(CACHE_NAME).then(function (cache) {\n" -" console.log('[ServiceWorker] Pre-caching offline page');\n" -" return cache.addAll(FILES_TO_CACHE);\n" -" })\n" -" );\n" -" self.skipWaiting();\n" -"});\n" -"self.addEventListener('activate', function(evt) {\n" -" console.log('[ServiceWorker] Activate');\n" -" evt.waitUntil(\n" -" caches.keys().then(function(keyList) {\n" -" return Promise.all(keyList.map(function(key) {\n" -" if (key !== CACHE_NAME) {\n" -" console.log('[ServiceWorker] Removing old cache', key);\n" -" return caches.delete(key);\n" -" }\n" -" }));\n" -" })\n" -" );\n" -" self.clients.claim();\n" -"});\n" -"self.addEventListener('fetch', function(evt) {\n" -" if (evt.request.cache === 'only-if-cached' && evt.request.mode !== 'same-origin') {\n" -" return;\n" -" }\n" -" console.log('[ServiceWorker] Fetch', evt.request.url);\n" -" evt.respondWith(\n" -" caches.open(CACHE_NAME).then(function(cache) {\n" -" return cache.match(evt.request)\n" -" .then(function(response) {\n" -" return response || fetch(evt.request);\n" -" });\n" -" })\n" -" );\n" -"});" -msgstr "" - -#. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"{\n" -" \"name\": \"" -msgstr "" +msgid "[ServiceWorker] Registration failed: " +msgstr "[ServiceWorker] Error en el registro: " diff --git a/web_pwa_oca/i18n/web_pwa_oca.pot b/web_pwa_oca/i18n/web_pwa_oca.pot index dab40c18a..e9be3b34a 100644 --- a/web_pwa_oca/i18n/web_pwa_oca.pot +++ b/web_pwa_oca/i18n/web_pwa_oca.pot @@ -14,157 +14,107 @@ msgstr "" "Plural-Forms: \n" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"128x128\",\n" -" \"type\": \"image/png\"\n" -" }, {\n" -" \"src\": \"" +#: model:ir.model.fields,field_description:web_pwa_oca.field_res_config_settings__pwa_background_color +msgid "Background Color" msgstr "" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"144x144\",\n" -" \"type\": \"image/png\"\n" -" }, {\n" -" \"src\": \"" +#: model:ir.model,name:web_pwa_oca.model_res_config_settings +msgid "Config Settings" msgstr "" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"152x152\",\n" -" \"type\": \"image/png\"\n" -" }, {\n" -" \"src\": \"" +#: model:ir.model.fields,field_description:web_pwa_oca.field_res_config_settings__pwa_icon +msgid "Icon" msgstr "" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"192x192\",\n" -" \"type\": \"image/png\"\n" -" }, {\n" -" \"src\": \"" +#: model_terms:ir.ui.view,arch_db:web_pwa_oca.res_config_settings_view_form +msgid "Name" msgstr "" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"256x256\",\n" -" \"type\": \"image/png\"\n" -" }, {\n" -" \"src\": \"" +#: model_terms:ir.ui.view,arch_db:web_pwa_oca.res_config_settings_view_form +msgid "Name and icon of your PWA" msgstr "" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"sizes\": \"512x512\",\n" -" \"type\": \"image/png\"\n" -" }],\n" -" \"start_url\": \"/web\",\n" -" \"display\": \"standalone\",\n" -" \"background_color\": \"" +#: model:ir.model.fields,help:web_pwa_oca.field_res_config_settings__pwa_name +msgid "Name of the Progressive Web Application" msgstr "" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"icons\": [{\n" -" \"src\": \"" +#: model_terms:ir.ui.view,arch_db:web_pwa_oca.res_config_settings_view_form +msgid "PWA Title" msgstr "" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"short_name\": \"" +#: model_terms:ir.ui.view,arch_db:web_pwa_oca.res_config_settings_view_form +msgid "Progressive Web App" msgstr "" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"\",\n" -" \"theme_color\": \"" +#: model:ir.model.fields,field_description:web_pwa_oca.field_res_config_settings__pwa_name +msgid "Progressive Web App Name" msgstr "" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.service_worker -msgid "" -"';\n" -"const FILES_TO_CACHE = [" -msgstr "" - -#. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.service_worker -msgid "" -"'use strict';\n" -"const CACHE_NAME = '" +#: model:ir.model.fields,field_description:web_pwa_oca.field_res_config_settings__pwa_short_name +msgid "Progressive Web App Short Name" msgstr "" #. module: web_pwa_oca #. openerp-web -#: code:addons/web_pwa_oca/static/src/xml/pwa_install.xml:0 +#: code:addons/web_pwa_oca/static/src/js/pwa_manager.js:0 #, python-format -msgid "Install PWA" +msgid "" +"Service workers are not supported! Maybe you are not using HTTPS or you work" +" in private mode." msgstr "" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.service_worker -msgid "" -"];\n" -"self.addEventListener('install', function (evt) {\n" -" console.log('[ServiceWorker] Install');\n" -" evt.waitUntil(\n" -" caches.open(CACHE_NAME).then(function (cache) {\n" -" console.log('[ServiceWorker] Pre-caching offline page');\n" -" return cache.addAll(FILES_TO_CACHE);\n" -" })\n" -" );\n" -" self.skipWaiting();\n" -"});\n" -"self.addEventListener('activate', function(evt) {\n" -" console.log('[ServiceWorker] Activate');\n" -" evt.waitUntil(\n" -" caches.keys().then(function(keyList) {\n" -" return Promise.all(keyList.map(function(key) {\n" -" if (key !== CACHE_NAME) {\n" -" console.log('[ServiceWorker] Removing old cache', key);\n" -" return caches.delete(key);\n" -" }\n" -" }));\n" -" })\n" -" );\n" -" self.clients.claim();\n" -"});\n" -"self.addEventListener('fetch', function(evt) {\n" -" if (evt.request.cache === 'only-if-cached' && evt.request.mode !== 'same-origin') {\n" -" return;\n" -" }\n" -" console.log('[ServiceWorker] Fetch', evt.request.url);\n" -" evt.respondWith(\n" -" caches.open(CACHE_NAME).then(function(cache) {\n" -" return cache.match(evt.request)\n" -" .then(function(response) {\n" -" return response || fetch(evt.request);\n" -" });\n" -" })\n" -" );\n" -"});" +#: model_terms:ir.ui.view,arch_db:web_pwa_oca.res_config_settings_view_form +msgid "Short Name" msgstr "" #. module: web_pwa_oca -#: model_terms:ir.ui.view,arch_db:web_pwa_oca.manifest -msgid "" -"{\n" -" \"name\": \"" +#: model:ir.model.fields,help:web_pwa_oca.field_res_config_settings__pwa_short_name +msgid "Short Name of the Progressive Web Application" +msgstr "" + +#. module: web_pwa_oca +#: model:ir.model.fields,field_description:web_pwa_oca.field_res_config_settings__pwa_theme_color +msgid "Theme Color" +msgstr "" + +#. module: web_pwa_oca +#: code:addons/web_pwa_oca/models/res_config_settings.py:0 +#, python-format +msgid "You can only upload PNG files bigger than 512x512" +msgstr "" + +#. module: web_pwa_oca +#: code:addons/web_pwa_oca/models/res_config_settings.py:0 +#, python-format +msgid "You can only upload SVG or PNG files" +msgstr "" + +#. module: web_pwa_oca +#: code:addons/web_pwa_oca/models/res_config_settings.py:0 +#, python-format +msgid "You can't upload a file with more than 2 MB." +msgstr "" + +#. module: web_pwa_oca +#. openerp-web +#: code:addons/web_pwa_oca/static/src/js/pwa_manager.js:0 +#, python-format +msgid "[ServiceWorker] Registered:" +msgstr "" + +#. module: web_pwa_oca +#. openerp-web +#: code:addons/web_pwa_oca/static/src/js/pwa_manager.js:0 +#, python-format +msgid "[ServiceWorker] Registration failed: " msgstr "" diff --git a/web_pwa_oca/models/__init__.py b/web_pwa_oca/models/__init__.py new file mode 100644 index 000000000..0deb68c46 --- /dev/null +++ b/web_pwa_oca/models/__init__.py @@ -0,0 +1 @@ +from . import res_config_settings diff --git a/web_pwa_oca/models/res_config_settings.py b/web_pwa_oca/models/res_config_settings.py new file mode 100644 index 000000000..274481a05 --- /dev/null +++ b/web_pwa_oca/models/res_config_settings.py @@ -0,0 +1,153 @@ +# Copyright 2020 Tecnativa - João Marques +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +import base64 +import io +import sys + +from odoo import _, api, exceptions, fields, models +from odoo.tools.mimetypes import guess_mimetype + +from PIL import Image + + +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" + ) + pwa_short_name = fields.Char( + "Progressive Web App Short Name", + help="Short Name of the Progressive Web Application", + ) + pwa_icon = fields.Binary("Icon", readonly=False) + 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"] = config_parameter_obj_sudo.get_param( + "pwa.manifest.name", default="Odoo PWA" + ) + res["pwa_short_name"] = config_parameter_obj_sudo.get_param( + "pwa.manifest.short_name", default="Odoo" + ) + pwa_icon_ir_attachment = ( + self.env["ir.attachment"] + .sudo() + .search([("url", "like", self._pwa_icon_url_base + ".")]) + ) + 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): + # Wrap decoded_icon in BytesIO object + decoded_icon = base64.b64decode(icon) + icon_bytes = io.BytesIO(decoded_icon) + return Image.open(icon_bytes) + + def _write_icon_to_attachment(self, extension, mimetype, size=None): + url = self._pwa_icon_url_base + extension + icon = self.pwa_icon + # Resize image + if size: + image = self._unpack_icon(icon) + resized_image = image.resize(size) + icon_bytes_output = io.BytesIO() + resized_image.save(icon_bytes_output, format=extension.lstrip(".").upper()) + icon = base64.b64encode(icon_bytes_output.getvalue()) + url = "{}{}x{}{}".format( + self._pwa_icon_url_base, str(size[0]), str(size[1]), extension, + ) + # Retreive existing attachment + existing_attachment = ( + self.env["ir.attachment"].sudo().search([("url", "like", url)]) + ) + # Write values to ir_attachment + values = { + "datas": icon, + "db_datas": icon, + "url": url, + "name": url, + "type": "binary", + "mimetype": mimetype, + } + # Rewrite if exists, else create + if existing_attachment: + existing_attachment.sudo().write(values) + else: + self.env["ir.attachment"].sudo().create(values) + + @api.model + def set_values(self): + config_parameter_obj_sudo = self.env["ir.config_parameter"].sudo() + res = super(ResConfigSettings, self).set_values() + config_parameter_obj_sudo.set_param("pwa.manifest.name", self.pwa_name) + 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"] + .sudo() + .search([("url", "like", self._pwa_icon_url_base)]) + ) + # Delete or ignore if no icon provided + if not self.pwa_icon: + if pwa_icon_ir_attachments: + pwa_icon_ir_attachments.unlink() + return res + # Fail if icon provided is larger than 2mb + if sys.getsizeof(self.pwa_icon) > 2196608: + raise exceptions.UserError( + _("You can't upload a file with more than 2 MB.") + ) + # Confirm if the pwa_icon binary content is an SVG or PNG + # and process accordingly + decoded_pwa_icon = base64.b64decode(self.pwa_icon) + # Full mimetype detection + pwa_icon_mimetype = guess_mimetype(decoded_pwa_icon) + pwa_icon_extension = "." + pwa_icon_mimetype.split("/")[-1].split("+")[0] + if not pwa_icon_mimetype.startswith( + "image/svg" + ) and not pwa_icon_mimetype.startswith("image/png"): + raise exceptions.UserError(_("You can only upload SVG or PNG files")) + # Delete all previous records if we are writting new ones + if pwa_icon_ir_attachments: + pwa_icon_ir_attachments.unlink() + self._write_icon_to_attachment(pwa_icon_extension, pwa_icon_mimetype) + # write multiple sizes if not SVG + if pwa_icon_extension != ".svg": + # Fail if provided PNG is smaller than 512x512 + if self._unpack_icon(self.pwa_icon).size < (512, 512): + raise exceptions.UserError( + _("You can only upload PNG files bigger than 512x512") + ) + for size in [ + (128, 128), + (144, 144), + (152, 152), + (192, 192), + (256, 256), + (512, 512), + ]: + self._write_icon_to_attachment( + pwa_icon_extension, pwa_icon_mimetype, size=size + ) diff --git a/web_pwa_oca/readme/CONFIGURE.rst b/web_pwa_oca/readme/CONFIGURE.rst index 9fd39a840..dbd0a83f7 100644 --- a/web_pwa_oca/readme/CONFIGURE.rst +++ b/web_pwa_oca/readme/CONFIGURE.rst @@ -1,10 +1,11 @@ -The following system parameters con be set to customize the appearance of the application +This module allows you to set the following parameters under settings 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_oca/static/img/icons/icon-128x128.png") -* pwa.manifest.icon144x144 (defaults to "/web_pwa_oca/static/img/icons/icon-144x144.png") -* pwa.manifest.icon152x152 (defaults to "/web_pwa_oca/static/img/icons/icon-152x152.png") -* pwa.manifest.icon192x192 (defaults to "/web_pwa_oca/static/img/icons/icon-192x192.png") -* pwa.manifest.icon256x256 (defaults to "/web_pwa_oca/static/img/icons/icon-256x256.png") -* pwa.manifest.icon512x512 (defaults to "/web_pwa_oca/static/img/icons/icon-512x512.png") +* PWA Name (defaults to "Odoo PWA") +* PWA Short Name (defaults to "Odoo PWA") +* PWA Icon (**SVG**) (defaults to "/web_pwa_oca/static/img/icons/odoo-logo.svg") + +To configure your PWA: + +#. Go to **Settings > General Settings > Progressive Web App**. +#. Set the parameters (*Note:* Icon **must be a SVG file**) +#. **Save** diff --git a/web_pwa_oca/readme/CONTRIBUTORS.rst b/web_pwa_oca/readme/CONTRIBUTORS.rst index 2b476d752..200dad8dc 100644 --- a/web_pwa_oca/readme/CONTRIBUTORS.rst +++ b/web_pwa_oca/readme/CONTRIBUTORS.rst @@ -1,3 +1,8 @@ * `TAKOBI `_: * Lorenzo Battistini + +* `Tecnativa `_: + + * Alexandre D. Díaz + * João Marques diff --git a/web_pwa_oca/readme/DESCRIPTION.rst b/web_pwa_oca/readme/DESCRIPTION.rst index e2eb74425..2b4b14ef4 100644 --- a/web_pwa_oca/readme/DESCRIPTION.rst +++ b/web_pwa_oca/readme/DESCRIPTION.rst @@ -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. diff --git a/web_pwa_oca/readme/INSTALL.rst b/web_pwa_oca/readme/INSTALL.rst index 3d3720a65..e82e32b91 100644 --- a/web_pwa_oca/readme/INSTALL.rst +++ b/web_pwa_oca/readme/INSTALL.rst @@ -10,4 +10,9 @@ 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. +In case you previously installed `web_pwa`, run the following steps with `odoo shell`, after having installed `openupgradelib`: + + +>>> from openupgradelib import openupgrade +>>> openupgrade.update_module_names(env.cr, [('web_pwa', 'web_pwa_oca')], merge_modules=False) +>>> env.cr.commit() diff --git a/web_pwa_oca/readme/ROADMAP.rst b/web_pwa_oca/readme/ROADMAP.rst index 9e763ae62..2f26e2693 100644 --- a/web_pwa_oca/readme/ROADMAP.rst +++ b/web_pwa_oca/readme/ROADMAP.rst @@ -1,5 +1,35 @@ -* 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...) +* 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: + + - 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 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. +* Firefox can't detect 'standalone' mode. See https://bugzilla.mozilla.org/show_bug.cgi?id=1285858 +* Firefox disable service worker in private mode. See https://bugzilla.mozilla.org/show_bug.cgi?id=1601916 diff --git a/web_pwa_oca/readme/USAGE.rst b/web_pwa_oca/readme/USAGE.rst new file mode 100644 index 000000000..584cd8b49 --- /dev/null +++ b/web_pwa_oca/readme/USAGE.rst @@ -0,0 +1,7 @@ +To use your PWA: + +#. Open the Odoo web app using a supported browser (See https://caniuse.com/?search=A2HS) +#. Open the browser options +#. Click on 'Add to Home screen' (or 'Install' in other browsers) + +** Maybe you need refresh the page to load the service worker after using the option. diff --git a/web_pwa_oca/static/description/index.html b/web_pwa_oca/static/description/index.html index e261e402c..0feda31ac 100644 --- a/web_pwa_oca/static/description/index.html +++ b/web_pwa_oca/static/description/index.html @@ -372,17 +372,26 @@ ul.auto-toc {

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.

Table of contents

  • Installation
  • Configuration
  • -
  • Known issues / Roadmap
  • -
  • Bug Tracker
  • -
  • Credits @@ -396,34 +405,86 @@ If you’re building a web app today, you’re already on the path towards build 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.

    +

    In case you previously installed web_pwa, run the following steps with odoo shell, after having installed openupgradelib:

    +
    +>>> from openupgradelib import openupgrade
    +>>> openupgrade.update_module_names(env.cr, [('web_pwa', 'web_pwa_oca')], merge_modules=False)
    +>>> env.cr.commit()
    +

Configuration

-

The following system parameters con be set to customize the appearance of the application

+

This module allows you to set the following parameters under settings 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_oca/static/img/icons/icon-128x128.png”)
  • -
  • pwa.manifest.icon144x144 (defaults to “/web_pwa_oca/static/img/icons/icon-144x144.png”)
  • -
  • pwa.manifest.icon152x152 (defaults to “/web_pwa_oca/static/img/icons/icon-152x152.png”)
  • -
  • pwa.manifest.icon192x192 (defaults to “/web_pwa_oca/static/img/icons/icon-192x192.png”)
  • -
  • pwa.manifest.icon256x256 (defaults to “/web_pwa_oca/static/img/icons/icon-256x256.png”)
  • -
  • pwa.manifest.icon512x512 (defaults to “/web_pwa_oca/static/img/icons/icon-512x512.png”)
  • +
  • PWA Name (defaults to “Odoo PWA”)
  • +
  • PWA Short Name (defaults to “Odoo PWA”)
  • +
  • PWA Icon (SVG) (defaults to “/web_pwa_oca/static/img/icons/odoo-logo.svg”)
+

To configure your PWA:

+
    +
  1. Go to Settings > General Settings > Progressive Web App.
  2. +
  3. Set the parameters (Note: Icon must be a SVG file)
  4. +
  5. Save
  6. +
+
+
+

Usage

+

To use your PWA:

+
    +
  1. Open the Odoo web app using a supported browser (See https://caniuse.com/?search=A2HS)
  2. +
  3. Open the browser options
  4. +
  5. Click on ‘Add to Home screen’ (or ‘Install’ in other browsers)
  6. +
+

** Maybe you need refresh the page to load the service worker after using the option.

-

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
  • -
  • Integrate Web Share API
  • -
  • Create portal_pwa module, intended to be used by front-end users (customers, suppliers…)
  • +

    Known issues / Roadmap

    +
      +
    • Integrate Notification API

      +
    • +
    • Integrate Web Share API

      +
    • +
    • 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 to use the following workaround:

      +
        +
      • Natural ‘async/await’ example (This breaks “_super” call):

        +
        +var MyClass = OdooClass.extend({
        +    myFunc: async function() {
        +        const mydata = await ...do await stuff...
        +        return mydata;
        +    }
        +});
        +
        +
      • +
      • Same code with the workaround:

        +
        +var MyClass = OdooClass.extend({
        +    myFunc: function() {
        +        return new Promise(async (resolve, reject) => {
        +            const mydata = await ...do await stuff...
        +            return 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.

      +
    • +
    • Firefox can’t detect ‘standalone’ mode. See https://bugzilla.mozilla.org/show_bug.cgi?id=1285858

      +
    • +
    • Firefox disable service worker in private mode. See https://bugzilla.mozilla.org/show_bug.cgi?id=1601916

      +
-

Bug Tracker

+

Bug Tracker

Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed @@ -431,24 +492,30 @@ If you spotted it first, help us smashing it by providing a detailed and welcome

Do not contact contributors directly about support or help with technical issues.

-

Credits

+

Credits

-

Authors

+

Authors

  • TAKOBI
  • +
  • Tecnativa
-

Contributors

+

Contributors

  • TAKOBI:
    • Lorenzo Battistini
  • +
  • Tecnativa:
      +
    • Alexandre D. Díaz
    • +
    • João Marques
    • +
    +
-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association

OCA, or the Odoo Community Association, is a nonprofit organization whose diff --git a/web_pwa_oca/static/img/icons/odoo_logo.svg b/web_pwa_oca/static/img/icons/odoo_logo.svg new file mode 100644 index 000000000..979e1e254 --- /dev/null +++ b/web_pwa_oca/static/img/icons/odoo_logo.svg @@ -0,0 +1 @@ + diff --git a/web_pwa_oca/static/src/js/pwa_install.js b/web_pwa_oca/static/src/js/pwa_install.js deleted file mode 100644 index 7b7d1f656..000000000 --- a/web_pwa_oca/static/src/js/pwa_install.js +++ /dev/null @@ -1,43 +0,0 @@ -odoo.define("web_pwa_oca.systray.install", function(require) { - "use strict"; - - 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_oca/static/src/js/pwa_manager.js b/web_pwa_oca/static/src/js/pwa_manager.js new file mode 100644 index 000000000..c4f27135c --- /dev/null +++ b/web_pwa_oca/static/src/js/pwa_manager.js @@ -0,0 +1,55 @@ +/* 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 core = require("web.core"); + var Widget = require("web.Widget"); + + var _t = core._t; + + var PWAManager = Widget.extend({ + /** + * @override + */ + init: function() { + this._super.apply(this, arguments); + if (!("serviceWorker" in navigator)) { + console.error( + _t( + "Service workers are not supported! Maybe you are not using HTTPS or you work in private mode." + ) + ); + } else { + this._service_worker = navigator.serviceWorker; + this.registerServiceWorker("/service-worker.js"); + } + }, + + /** + * @param {String} sw_script + * @returns {Promise} + */ + registerServiceWorker: function(sw_script) { + return this._service_worker + .register(sw_script) + .then(this._onRegisterServiceWorker) + .catch(function(error) { + console.log(_t("[ServiceWorker] Registration failed: "), error); + }); + }, + + /** + * Need register some extra API? override this! + * + * @private + * @param {ServiceWorkerRegistration} registration + */ + _onRegisterServiceWorker: function(registration) { + console.log(_t("[ServiceWorker] Registered:"), registration); + }, + }); + + return PWAManager; +}); diff --git a/web_pwa_oca/static/src/js/webclient.js b/web_pwa_oca/static/src/js/webclient.js new file mode 100644 index 000000000..84611d075 --- /dev/null +++ b/web_pwa_oca/static/src/js/webclient.js @@ -0,0 +1,19 @@ +/* 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); + return this._super.apply(this, arguments); + }, + }); +}); diff --git a/web_pwa_oca/static/src/js/worker/jquery-sw-compat.js b/web_pwa_oca/static/src/js/worker/jquery-sw-compat.js new file mode 100644 index 000000000..2e8aaf2a0 --- /dev/null +++ b/web_pwa_oca/static/src/js/worker/jquery-sw-compat.js @@ -0,0 +1,62 @@ +/* Copyright 2020 Tecnativa - Alexandre D. Díaz + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + +// Compatibility layer to load some Odoo modules +// This is a hack, not a complete implementation! +// only expected to be used by boot.js + +(function() { + "use strict"; + function JQuery(selector, context) { + return new JQuery.prototype.init(selector, context); + } + + JQuery.prototype = { + init: function(selector) { + if (typeof selector === "function") { + selector(); + } + }, + + deparam: function(data) { + const params = data.split(","); + const res = []; + for (const param of params) { + res.push(param.split("=")); + } + return _.object(res); + }, + + param: { + querystring: function() { + return "debug=1"; + }, + }, + + when: function(tasks) { + return Promise.all(tasks instanceof Array ? tasks : [tasks]).then( + results => { + return results.length === 1 ? results[0] : results; + } + ); + }, + }; + + class Deferred { + constructor() { + this.promise = new Promise((resolve, reject) => { + this.reject = reject; + this.resolve = resolve; + }); + } + } + + JQuery.prototype.Deferred = () => new Deferred(); + + self.$ = JQuery; + self.$.deparam = JQuery.prototype.deparam; + self.$.param = JQuery.prototype.param; + self.$.Deferred = JQuery.prototype.Deferred; + self.$.when = JQuery.prototype.when; + self.window = self; +})(); diff --git a/web_pwa_oca/static/src/js/worker/pwa.js b/web_pwa_oca/static/src/js/worker/pwa.js new file mode 100644 index 000000000..abef47070 --- /dev/null +++ b/web_pwa_oca/static/src/js/worker/pwa.js @@ -0,0 +1,40 @@ +/* Copyright 2020 Tecnativa - Alexandre D. Díaz + * License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */ + +/** + * 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. + */ + +odoo.define("web_pwa_oca.PWA", function(require) { + "use strict"; + + const OdooClass = require("web.Class"); + + const PWA = OdooClass.extend({ + // eslint-disable-next-line + init: function(params) { + // To be overridden + }, + + /** + * @returns {Promise} + */ + installWorker: function() { + // To be overridden + return Promise.resolve(); + }, + + /** + * @returns {Promise} + */ + activateWorker: function() { + // To be overridden + return Promise.resolve(); + }, + }); + + return PWA; +}); diff --git a/web_pwa_oca/static/src/xml/pwa_install.xml b/web_pwa_oca/static/src/xml/pwa_install.xml deleted file mode 100644 index 6f371785e..000000000 --- a/web_pwa_oca/static/src/xml/pwa_install.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - diff --git a/web_pwa_oca/templates/assets.xml b/web_pwa_oca/templates/assets.xml new file mode 100644 index 000000000..37554c6ba --- /dev/null +++ b/web_pwa_oca/templates/assets.xml @@ -0,0 +1,47 @@ + + + +