forked from Techsystech/web
[12.0][IMP] web_pwa_oca: configurable name and icon
Add ability to configure name, short name and icon for PWA in Odoo General Settings. The different icon sizes also get replaced by a single SVG TT2597912.0
parent
a74fbb34c0
commit
7473a485e9
|
@ -63,16 +63,25 @@ In case you previously installed `web_pwa`, run the following steps with `odoo s
|
|||
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 (like Chrome/Chromium)
|
||||
#. Install the PWA
|
||||
|
||||
Known issues / Roadmap
|
||||
======================
|
||||
|
@ -81,11 +90,11 @@ Known issues / Roadmap
|
|||
* 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:
|
||||
* 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
|
||||
|
||||
const MyClass = OdooClass.extend({
|
||||
var MyClass = OdooClass.extend({
|
||||
myFunc: async function() {
|
||||
const mydata = await ...do await stuff...
|
||||
return mydata;
|
||||
|
@ -95,7 +104,7 @@ Known issues / Roadmap
|
|||
- Same code with the workaround:
|
||||
.. code-block:: javascript
|
||||
|
||||
const MyClass = OdooClass.extend({
|
||||
var MyClass = OdooClass.extend({
|
||||
myFunc: function() {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const mydata = await ...do await stuff...
|
||||
|
@ -103,6 +112,7 @@ Known issues / Roadmap
|
|||
});
|
||||
}
|
||||
});
|
||||
* Fix issue when trying to run in localhost with several databases. The browser doesn't send the cookie and web manifest returns 404.
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
@ -133,6 +143,7 @@ Contributors
|
|||
* `Tecnativa <https://tecnativa.com>`_:
|
||||
|
||||
* Alexandre D. Díaz
|
||||
* João Marques
|
||||
|
||||
Maintainers
|
||||
~~~~~~~~~~~
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
from . import controllers
|
||||
from . import models
|
||||
|
|
|
@ -1,5 +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).
|
||||
|
||||
{
|
||||
|
@ -21,6 +22,7 @@
|
|||
"data": [
|
||||
"templates/assets.xml",
|
||||
"templates/service_worker.xml",
|
||||
"views/res_config_settings_views.xml",
|
||||
],
|
||||
'qweb': [
|
||||
'static/src/xml/pwa_install.xml',
|
||||
|
|
|
@ -1,32 +1,33 @@
|
|||
# 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 request, Controller, route
|
||||
|
||||
|
||||
class PWA(Controller):
|
||||
|
||||
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',
|
||||
"/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()
|
||||
"""Get all urls that have 'asset_xml_id'"""
|
||||
qweb_sudo = request.env["ir.qweb"].sudo()
|
||||
assets = qweb_sudo._get_asset_nodes(asset_xml_id, {}, True, True)
|
||||
urls = []
|
||||
for asset in assets:
|
||||
if asset[0] == 'link':
|
||||
urls.append(asset[1]['href'])
|
||||
if asset[0] == 'script':
|
||||
urls.append(asset[1]['src'])
|
||||
if asset[0] == "link":
|
||||
urls.append(asset[1]["href"])
|
||||
if asset[0] == "script":
|
||||
urls.append(asset[1]["src"])
|
||||
return urls
|
||||
|
||||
def _get_pwa_params(self):
|
||||
|
@ -36,86 +37,105 @@ class PWA(Controller):
|
|||
urls.extend(self._get_asset_urls("web.assets_backend"))
|
||||
version_list = []
|
||||
for url in urls:
|
||||
version_list.append(url.split('/')[3])
|
||||
cache_version = '-'.join(version_list)
|
||||
return [
|
||||
cache_version,
|
||||
urls
|
||||
]
|
||||
version_list.append(url.split("/")[3])
|
||||
cache_version = "-".join(version_list)
|
||||
return [cache_version, urls]
|
||||
|
||||
@route('/service-worker.js', type='http', auth="public")
|
||||
@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')])
|
||||
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_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": "%sx%s" % (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()
|
||||
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_sudo.get_param(
|
||||
"pwa.manifest.icon144x144",
|
||||
"/web_pwa_oca/static/img/icons/icon-144x144.png")
|
||||
icon152x152 = config_param_sudo.get_param(
|
||||
"pwa.manifest.icon152x152",
|
||||
"/web_pwa_oca/static/img/icons/icon-152x152.png")
|
||||
icon192x192 = config_param_sudo.get_param(
|
||||
"pwa.manifest.icon192x192",
|
||||
"/web_pwa_oca/static/img/icons/icon-192x192.png")
|
||||
icon256x256 = config_param_sudo.get_param(
|
||||
"pwa.manifest.icon256x256",
|
||||
"/web_pwa_oca/static/img/icons/icon-256x256.png")
|
||||
icon512x512 = config_param_sudo.get_param(
|
||||
"pwa.manifest.icon512x512",
|
||||
"/web_pwa_oca/static/img/icons/icon-512x512.png")
|
||||
"pwa.manifest.short_name", "Odoo PWA"
|
||||
)
|
||||
pwa_icon = (
|
||||
request.env["ir.attachment"]
|
||||
.sudo()
|
||||
.search([("url", "like", "/web_pwa_oca/icon.")])
|
||||
)
|
||||
background_color = config_param_sudo.get_param(
|
||||
"pwa.manifest.background_color", "#2E69B5")
|
||||
theme_color = config_param_sudo.get_param(
|
||||
"pwa.manifest.theme_color", "#2E69B5")
|
||||
"pwa.manifest.background_color", "#2E69B5"
|
||||
)
|
||||
theme_color = config_param_sudo.get_param("pwa.manifest.theme_color", "#2E69B5")
|
||||
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
|
||||
"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")
|
||||
@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')])
|
||||
headers=[("Content-Type", "text/javascript;charset=utf-8")],
|
||||
)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
from . import res_config_settings
|
|
@ -0,0 +1,146 @@
|
|||
# Copyright 2020 Tecnativa - João Marques
|
||||
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
|
||||
import sys
|
||||
import base64
|
||||
import io
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from odoo import api, exceptions, fields, models, _
|
||||
from odoo.tools.mimetypes import guess_mimetype
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
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_icon_url_base = "/web_pwa_oca/icon"
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
res = super(ResConfigSettings, self).get_values()
|
||||
res["pwa_name"] = (
|
||||
self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("pwa.manifest.name", default="")
|
||||
)
|
||||
res["pwa_short_name"] = (
|
||||
self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("pwa.manifest.short_name", default="")
|
||||
)
|
||||
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
|
||||
)
|
||||
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 = "%s%sx%s%s" % (
|
||||
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):
|
||||
res = super(ResConfigSettings, self).set_values()
|
||||
self.env["ir.config_parameter"].sudo().set_param(
|
||||
"pwa.manifest.name", self.pwa_name
|
||||
)
|
||||
self.env["ir.config_parameter"].sudo().set_param(
|
||||
"pwa.manifest.short_name", self.pwa_short_name
|
||||
)
|
||||
# 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
|
||||
)
|
|
@ -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**
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
* `Tecnativa <https://tecnativa.com>`_:
|
||||
|
||||
* Alexandre D. Díaz
|
||||
* João Marques
|
||||
|
|
|
@ -24,3 +24,4 @@
|
|||
});
|
||||
}
|
||||
});
|
||||
* Fix issue when trying to run in localhost with several databases. The browser doesn't send the cookie and web manifest returns 404.
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
To use your PWA:
|
||||
|
||||
#. Open the Odoo web app using a supported browser (like Chrome/Chromium)
|
||||
#. Install the PWA
|
|
@ -377,12 +377,13 @@ If you’re building a web app today, you’re already on the path towards build
|
|||
<ul class="simple">
|
||||
<li><a class="reference internal" href="#installation" id="id1">Installation</a></li>
|
||||
<li><a class="reference internal" href="#configuration" id="id2">Configuration</a></li>
|
||||
<li><a class="reference internal" href="#known-issues-roadmap" id="id3">Known issues / Roadmap</a></li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
|
||||
<li><a class="reference internal" href="#usage" id="id3">Usage</a></li>
|
||||
<li><a class="reference internal" href="#known-issues-roadmap" id="id4">Known issues / Roadmap</a></li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="id5">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="id6">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="id7">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="id8">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="id9">Maintainers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -406,20 +407,29 @@ And like all other installed apps, it’s a top level app in the task switcher.<
|
|||
</div>
|
||||
<div class="section" id="configuration">
|
||||
<h1><a class="toc-backref" href="#id2">Configuration</a></h1>
|
||||
<p>The following system parameters con be set to customize the appearance of the application</p>
|
||||
<p>This module allows you to set the following parameters under settings to customize the appearance of the application</p>
|
||||
<ul class="simple">
|
||||
<li>pwa.manifest.name (defaults to “Odoo PWA”)</li>
|
||||
<li>pwa.manifest.short_name (defaults to “Odoo PWA”)</li>
|
||||
<li>pwa.manifest.icon128x128 (defaults to “/web_pwa_oca/static/img/icons/icon-128x128.png”)</li>
|
||||
<li>pwa.manifest.icon144x144 (defaults to “/web_pwa_oca/static/img/icons/icon-144x144.png”)</li>
|
||||
<li>pwa.manifest.icon152x152 (defaults to “/web_pwa_oca/static/img/icons/icon-152x152.png”)</li>
|
||||
<li>pwa.manifest.icon192x192 (defaults to “/web_pwa_oca/static/img/icons/icon-192x192.png”)</li>
|
||||
<li>pwa.manifest.icon256x256 (defaults to “/web_pwa_oca/static/img/icons/icon-256x256.png”)</li>
|
||||
<li>pwa.manifest.icon512x512 (defaults to “/web_pwa_oca/static/img/icons/icon-512x512.png”)</li>
|
||||
<li>PWA Name (defaults to “Odoo PWA”)</li>
|
||||
<li>PWA Short Name (defaults to “Odoo PWA”)</li>
|
||||
<li>PWA Icon (<strong>SVG</strong>) (defaults to “/web_pwa_oca/static/img/icons/odoo-logo.svg”)</li>
|
||||
</ul>
|
||||
<p>To configure your PWA:</p>
|
||||
<ol class="arabic simple">
|
||||
<li>Go to <strong>Settings > General Settings > Progressive Web App</strong>.</li>
|
||||
<li>Set the parameters (<em>Note:</em> Icon <strong>must be a SVG file</strong>)</li>
|
||||
<li><strong>Save</strong></li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="section" id="usage">
|
||||
<h1><a class="toc-backref" href="#id3">Usage</a></h1>
|
||||
<p>To use your PWA:</p>
|
||||
<ol class="arabic simple">
|
||||
<li>Open the Odoo web app using a supported browser (like Chrome/Chromium)</li>
|
||||
<li>Install the PWA</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="section" id="known-issues-roadmap">
|
||||
<h1><a class="toc-backref" href="#id3">Known issues / Roadmap</a></h1>
|
||||
<h1><a class="toc-backref" href="#id4">Known issues / Roadmap</a></h1>
|
||||
<ul>
|
||||
<li><p class="first">Evaluate to extend <tt class="docutils literal">FILES_TO_CACHE</tt></p>
|
||||
</li>
|
||||
|
@ -430,12 +440,12 @@ And like all other installed apps, it’s a top level app in the task switcher.<
|
|||
<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>
|
||||
<dt>Current <tt class="docutils literal">John Resig's inheritance</tt> implementation doesn’t support <tt class="docutils literal">async</tt> functions because <tt class="docutils literal">this._super</tt> can’t be called inside a promise. So we need use the following workaround:</dt>
|
||||
<dd><ul class="first last">
|
||||
<li><dl class="first docutils">
|
||||
<dt>Natural ‘async/await’ example (This breaks “_super” call)</dt>
|
||||
<dd><pre class="code javascript first last literal-block">
|
||||
<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="kd">var</span> <span class="nx">MyClass</span> <span class="o">=</span> <span class="nx">OdooClass</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
|
||||
<span class="nx">myFunc</span><span class="o">:</span> <span class="nx">async</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
|
||||
<span class="kr">const</span> <span class="nx">mydata</span> <span class="o">=</span> <span class="nx">await</span> <span class="p">...</span><span class="k">do</span> <span class="nx">await</span> <span class="nx">stuff</span><span class="p">...</span>
|
||||
<span class="k">return</span> <span class="nx">mydata</span><span class="p">;</span>
|
||||
|
@ -448,7 +458,7 @@ And like all other installed apps, it’s a top level app in the task switcher.<
|
|||
<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="kd">var</span> <span class="nx">MyClass</span> <span class="o">=</span> <span class="nx">OdooClass</span><span class="p">.</span><span class="nx">extend</span><span class="p">({</span>
|
||||
<span class="nx">myFunc</span><span class="o">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
|
||||
<span class="k">return</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">(</span><span class="nx">async</span> <span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span>
|
||||
<span class="kr">const</span> <span class="nx">mydata</span> <span class="o">=</span> <span class="nx">await</span> <span class="p">...</span><span class="k">do</span> <span class="nx">await</span> <span class="nx">stuff</span><span class="p">...</span>
|
||||
|
@ -464,10 +474,12 @@ And like all other installed apps, it’s a top level app in the task switcher.<
|
|||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li><p class="first">Fix issue when trying to run in localhost with several databases. The browser doesn’t send the cookie and web manifest returns 404.</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1>
|
||||
<h1><a class="toc-backref" href="#id5">Bug Tracker</a></h1>
|
||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
|
||||
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
|
||||
|
@ -475,16 +487,16 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
|
|||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||
</div>
|
||||
<div class="section" id="credits">
|
||||
<h1><a class="toc-backref" href="#id5">Credits</a></h1>
|
||||
<h1><a class="toc-backref" href="#id6">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
|
||||
<h2><a class="toc-backref" href="#id7">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>TAKOBI</li>
|
||||
<li>Tecnativa</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
<h2><a class="toc-backref" href="#id7">Contributors</a></h2>
|
||||
<h2><a class="toc-backref" href="#id8">Contributors</a></h2>
|
||||
<ul class="simple">
|
||||
<li><a class="reference external" href="https://takobi.online">TAKOBI</a>:<ul>
|
||||
<li>Lorenzo Battistini</li>
|
||||
|
@ -492,12 +504,13 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
|
|||
</li>
|
||||
<li><a class="reference external" href="https://tecnativa.com">Tecnativa</a>:<ul>
|
||||
<li>Alexandre D. Díaz</li>
|
||||
<li>João Marques</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2>
|
||||
<h2><a class="toc-backref" href="#id9">Maintainers</a></h2>
|
||||
<p>This module is maintained by the OCA.</p>
|
||||
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
|
||||
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="919" height="495" viewBox="0 0 919 495"><g fill="none"><path fill="#8F8F8F" d="M695 346c-41.421 0-75-33.579-75-75s33.579-75 75-75 75 33.579 75 75-33.579 75-75 75zm0-31c24.3 0 44-19.7 44-44s-19.7-44-44-44-44 19.7-44 44 19.7 44 44 44zm-157 31c-41.421 0-75-33.579-75-75s33.579-75 75-75 75 33.579 75 75-33.579 75-75 75zm0-31c24.3 0 44-19.7 44-44s-19.7-44-44-44-44 19.7-44 44 19.7 44 44 44zm-82-45c0 41.935-33.592 76-75.009 76C339.575 346 306 312.005 306 270.07c0-41.936 30.5-74.07 74.991-74.07 16.442 0 31.647 3.496 44.007 12.58l.002-43.49c0-8.334 7.27-15.09 15.5-15.09 8.228 0 15.5 6.762 15.5 15.09V270zm-75 45c24.3 0 44-19.7 44-44s-19.7-44-44-44-44 19.7-44 44 19.7 44 44 44z"/><path fill="#875A7B" d="M224 346c-41.421 0-75-33.579-75-75s33.579-75 75-75 75 33.579 75 75-33.579 75-75 75zm0-31c24.3 0 44-19.7 44-44s-19.7-44-44-44-44 19.7-44 44 19.7 44 44 44z"/></g><script xmlns=""/><script xmlns="" type="text/javascript"/><script xmlns="" type="text/javascript"/></svg>
|
After Width: | Height: | Size: 1012 B |
|
@ -0,0 +1 @@
|
|||
from . import test_web_pwa_oca_controller
|
|
@ -0,0 +1,123 @@
|
|||
# Copyright 2020 João Marques
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
|
||||
import json
|
||||
import base64
|
||||
|
||||
import odoo.tests
|
||||
|
||||
from odoo import exceptions
|
||||
from odoo.modules.module import get_resource_path
|
||||
|
||||
|
||||
class TestUi(odoo.tests.HttpCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = self.env.ref("base.user_admin")
|
||||
self.res_config_settings_obj = (
|
||||
self.env["res.config.settings"].sudo(self.user.id).create({})
|
||||
)
|
||||
|
||||
def test_manifest_valid_json(self):
|
||||
# Call the manifest controller
|
||||
manifest_data = self.url_open("/web_pwa_oca/manifest.webmanifest")
|
||||
# should be valid json
|
||||
manifest_content_str = manifest_data.content.decode("utf-8")
|
||||
json.loads(manifest_content_str)
|
||||
|
||||
def test_manifest_correct_paramenters(self):
|
||||
# Set PWA parameters in settings
|
||||
self.res_config_settings_obj.pwa_name = "Test PWA"
|
||||
self.res_config_settings_obj.pwa_short_name = "Test"
|
||||
# icon should remain the default one
|
||||
self.res_config_settings_obj.pwa_icon = False
|
||||
self.res_config_settings_obj.set_values()
|
||||
|
||||
# Call the manifest controller
|
||||
manifest_data = self.url_open("/web_pwa_oca/manifest.webmanifest")
|
||||
manifest_content_str = manifest_data.content.decode("utf-8")
|
||||
manifest_content = json.loads(manifest_content_str)
|
||||
|
||||
self.assertEquals(manifest_content["name"], "Test PWA")
|
||||
self.assertEquals(manifest_content["short_name"], "Test")
|
||||
# icon should remain the default one
|
||||
self.assertEquals(
|
||||
manifest_content["icons"][0]["src"],
|
||||
"/web_pwa_oca/static/img/icons/icon-128x128.png",
|
||||
)
|
||||
self.assertEquals(manifest_content["icons"][0]["sizes"], "128x128")
|
||||
self.assertTrue(manifest_content["icons"][0]["type"].startswith("image/png"))
|
||||
|
||||
def test_manifest_logo_upload(self):
|
||||
with open(
|
||||
"%s/static/img/icons/odoo_logo.svg" % get_resource_path("web_pwa_oca"),
|
||||
"rb"
|
||||
) as fi:
|
||||
icon_to_send = base64.b64encode(fi.read())
|
||||
|
||||
# Set PWA icon in settings
|
||||
self.res_config_settings_obj.pwa_icon = icon_to_send
|
||||
self.res_config_settings_obj.set_values()
|
||||
|
||||
# Call the manifest controller
|
||||
manifest_data = self.url_open("/web_pwa_oca/manifest.webmanifest")
|
||||
manifest_content_str = manifest_data.content.decode("utf-8")
|
||||
manifest_content = json.loads(manifest_content_str)
|
||||
|
||||
self.assertEquals(manifest_content["icons"][0]["src"], "/web_pwa_oca/icon.svg")
|
||||
self.assertTrue(manifest_content["icons"][0]["type"].startswith("image/svg"))
|
||||
self.assertEquals(manifest_content["icons"][0]["sizes"], "128x128 144x144 152x152 192x192 256x256 512x512")
|
||||
|
||||
# Get the icon and compare it
|
||||
icon_data = self.url_open("/web_pwa_oca/icon.svg")
|
||||
icon_data_bytes = base64.b64encode(icon_data.content)
|
||||
self.assertEquals(icon_data_bytes, icon_to_send)
|
||||
|
||||
def test_png_logo_upload(self):
|
||||
with open(
|
||||
"%s/static/img/icons/icon-512x512.png" % get_resource_path("web_pwa_oca"),
|
||||
"rb"
|
||||
) as fi:
|
||||
icon_to_send = base64.b64encode(fi.read())
|
||||
|
||||
# Set PWA icon in settings
|
||||
self.res_config_settings_obj.pwa_icon = icon_to_send
|
||||
self.res_config_settings_obj.set_values()
|
||||
|
||||
# Call the manifest controller
|
||||
manifest_data = self.url_open("/web_pwa_oca/manifest.webmanifest")
|
||||
manifest_content_str = manifest_data.content.decode("utf-8")
|
||||
manifest_content = json.loads(manifest_content_str)
|
||||
|
||||
expected_vals = {
|
||||
"src": "/web_pwa_oca/icon512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
}
|
||||
self.assertTrue(expected_vals in manifest_content["icons"])
|
||||
|
||||
def test_manifest_logo_upload_big(self):
|
||||
# Set PWA icon in settings
|
||||
with self.assertRaises(exceptions.UserError):
|
||||
# Image with more than 2MB
|
||||
self.res_config_settings_obj.pwa_icon = b"a" * 3000000
|
||||
self.res_config_settings_obj.set_values()
|
||||
|
||||
def test_manifest_logo_upload_extension(self):
|
||||
with self.assertRaises(exceptions.UserError):
|
||||
# Image that is not SVG or PNG
|
||||
self.res_config_settings_obj.pwa_icon = b"a" * 1000
|
||||
self.res_config_settings_obj.set_values()
|
||||
|
||||
def test_manifest_logo_upload_small(self):
|
||||
icon_to_send = None
|
||||
with open(
|
||||
"%s/static/img/icons/icon-128x128.png" % get_resource_path("web_pwa_oca"),
|
||||
"rb"
|
||||
) as fi:
|
||||
icon_to_send = base64.b64encode(fi.read())
|
||||
# Set PWA icon in settings
|
||||
with self.assertRaises(exceptions.UserError):
|
||||
# Image smaller than 512X512
|
||||
self.res_config_settings_obj.pwa_icon = icon_to_send
|
||||
self.res_config_settings_obj.set_values()
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.pwa</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div id="emails" position='after'>
|
||||
<h2>Progressive Web App</h2>
|
||||
<div class="row mt16 o_settings_container" id="pwa_settings">
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="domain_setting">
|
||||
<div class="o_setting_right_pane">
|
||||
<label for="pwa_name" string="PWA Title"/>
|
||||
<span class="fa fa-lg fa-globe"/>
|
||||
<div class="text-muted">
|
||||
Name and icon of your PWA
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="row mt16">
|
||||
<label class="col-lg-3 o_light_label" string="Name" for="pwa_name"/>
|
||||
<field name="pwa_name"/>
|
||||
</div>
|
||||
<div class="row mt16">
|
||||
<label class="col-lg-3 o_light_label" string="Short Name" for="pwa_short_name"/>
|
||||
<field name="pwa_short_name"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-lg-3 o_light_label" for="pwa_icon" />
|
||||
<field name="pwa_icon" widget="image" class="float-left oe_avatar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
Loading…
Reference in New Issue