Skip to content

Commit

Permalink
[IMP] web_pwa_oca: Added css classes to control the visibility of vie…
Browse files Browse the repository at this point in the history
…w fields
  • Loading branch information
Tardo authored and sergio-teruel committed Sep 5, 2023
1 parent 5d77fcb commit 7e3ad78
Show file tree
Hide file tree
Showing 21 changed files with 943 additions and 59 deletions.
30 changes: 28 additions & 2 deletions web_pwa_oca/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,40 @@ If you're building a web app today, you're already on the path towards building

+ 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.
The service worker is constructed using 'Odoo Class' to have the same class inheritance behaviour that in the 'user pages'. Be noticed
that 'Odoo Bootstrap' is supported so, you can 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.

+ CSS Classes to use in qweb templates

- oe_pwa_standalone_invisible : Can be used in the "form" element to get a "empty" form (all is unavailable/invisible).
- oe_pwa_standalone_no_chatter : Can be used in the "form" element to avoid render chatter zone.
- oe_pwa_standalone_no_sheet : Can be used in the "form" element to don't use the sheet style.
- oe_pwa_standalone_visible : Can be used in a element when the form has the 'oe_pwa_standalone_invisible' class to make the element available.
- oe_pwa_standalone_omit : Can be used in elements to make them unavailable in standalone mode.
- oe_pwa_standalone_only : Can be used in elements to make them available only in standalone mode.

What does 'unavailable/invisible' mean?
This 'invisible' state is not only the same as use "invisible=1" in qweb. Here invisible means that the client will not process the fields completely (no rpc calls will be made).

** All ancestors of an element that is visible will also be visible.
** All invisible fields (invisible=1 in qweb definition) will be available by default.
** 'oe_pwa_standalone_omit' and 'oe_pwa_standalone_only' will be filtered on the server side too.

+ XML Attributes to use in qweb templates

- standalone-attrs : Used to extend the node attrs in standalone mode.

+ Tips for developers to update templates with an standalone mode

- Odoo gets the first element when no index is used in qweb xpath, so... duplicated fields must be added after "the original" to ensure that other modules changes the correct element.
- If you define duplicated fields use the 'oe_pwa_standalone_omit' class in the original and 'oe_pwa_standalone_only' class in the duplicated one to avoid problems.
- Set a a very low priority (high value) in your 'standalone view' template.

**Table of contents**

.. contents::
Expand Down
20 changes: 7 additions & 13 deletions web_pwa_oca/controllers/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from odoo.http import Controller, request, route

from ..models.res_config_settings import PWA_ICON_SIZES


class PWA(Controller):
def _get_pwa_scripts(self):
Expand Down Expand Up @@ -38,14 +40,7 @@ def _get_pwa_params(self):
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),
]:
for size in PWA_ICON_SIZES:
icons.append(
{
"src": "/web_pwa_oca/static/img/icons/icon-%sx%s.png"
Expand Down Expand Up @@ -75,12 +70,11 @@ def _get_pwa_manifest_icons(self, pwa_icon):
{"src": icon.url, "sizes": icon_size_name, "type": icon.mimetype}
)
else:
icon_sizes = " ".join(
map(lambda size: "{}x{}".format(size[0], size[1]), PWA_ICON_SIZES,)
)
icons = [
{
"src": pwa_icon.url,
"sizes": "128x128 144x144 152x152 192x192 256x256 512x512",
"type": pwa_icon.mimetype,
}
{"src": pwa_icon.url, "sizes": icon_sizes, "type": pwa_icon.mimetype}
]
return icons

Expand Down
41 changes: 35 additions & 6 deletions web_pwa_oca/controllers/service_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@


class ServiceWorker(PWA):
_pwa_sw_version = "0.1.0"

JS_PWA_CORE_EVENT_INSTALL = """
self.addEventListener('install', evt => {{
Expand Down Expand Up @@ -48,26 +49,54 @@ def _get_js_pwa_requires(self):

def _get_js_pwa_init(self):
return """
const oca_pwa = new PWA({});
let promise_start = Promise.resolve();
if (typeof self.oca_pwa === "undefined") {{
self.oca_pwa = new PWA({});
promise_start = self.oca_pwa.start();
if (self.serviceWorker.state === "activated") {{
promise_start = promise_start.then(
() => self.oca_pwa.activateWorker(true));
}}
}}
""".format(
self._get_pwa_params()
)

def _get_js_pwa_core_event_install_impl(self):
return """
evt.waitUntil(oca_pwa.installWorker());
self.skipWaiting();
evt.waitUntil(promise_start.then(() => self.oca_pwa.installWorker()));
"""

def _get_js_pwa_core_event_activate_impl(self):
return """
console.log('[ServiceWorker] Activating...');
evt.waitUntil(oca_pwa.activateWorker());
self.clients.claim();
evt.waitUntil(promise_start.then(() => self.oca_pwa.activateWorker()));
"""

def _get_js_pwa_core_event_fetch_impl(self):
return ""
return """
if (evt.request.url.startsWith(self.registration.scope)) {
evt.respondWith(promise_start.then(
() => self.oca_pwa.processRequest(evt.request)));
}
"""

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",
]

def _get_pwa_params(self):
"""Get javascript PWA class initialzation params"""
return {
"sw_version": self._pwa_sw_version,
}

@route("/service-worker.js", type="http", auth="public")
def render_service_worker(self):
Expand Down
1 change: 1 addition & 0 deletions web_pwa_oca/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from . import res_config_settings
from . import base
36 changes: 36 additions & 0 deletions web_pwa_oca/models/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright 2022 Tecnativa - Alexandre D. Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from lxml import etree

from odoo import api, models


class BaseModel(models.BaseModel):
_inherit = "base"

def _fields_view_get(
self, view_id=None, view_type="form", toolbar=False, submenu=False
):
"""Remove unused nodes depend on the standalone mode
This is necessary to avoid problems with duplicated fields
"""
res = super()._fields_view_get(
view_id=view_id, view_type=view_type, toolbar=toolbar, submenu=submenu
)
is_client_standalone = self.env.context.get("client_standalone", False)
doc = etree.XML(res["arch"])
if is_client_standalone:
for node in doc.xpath("//*[contains(@class, 'oe_pwa_standalone_omit')]"):
node.getparent().remove(node)

Check warning on line 24 in web_pwa_oca/models/base.py

View check run for this annotation

Codecov / codecov/patch

web_pwa_oca/models/base.py#L24

Added line #L24 was not covered by tests
else:
for node in doc.xpath("//*[contains(@class, 'oe_pwa_standalone_only')]"):
node.getparent().remove(node)

Check warning on line 27 in web_pwa_oca/models/base.py

View check run for this annotation

Codecov / codecov/patch

web_pwa_oca/models/base.py#L27

Added line #L27 was not covered by tests
res["arch"] = etree.tostring(doc, encoding="unicode")
return res

@api.model
def load_views(self, views, options=None):
standalone = options.get("standalone", False) if options else False
return super(
BaseModel, self.with_context(client_standalone=standalone)
).load_views(views, options=options)
54 changes: 43 additions & 11 deletions web_pwa_oca/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2020 Tecnativa - João Marques
# Copyright 2021 Tecnativa - Alexandre D. Díaz
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl).
import base64
import io
Expand All @@ -9,6 +10,12 @@
from odoo import _, api, exceptions, fields, models
from odoo.tools.mimetypes import guess_mimetype

DEFAULT_ICON_SIZE = 512
PWA_ICON_SIZES = (
(512, 512),
(192, 192),
)


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
Expand All @@ -24,6 +31,12 @@ class ResConfigSettings(models.TransientModel):
pwa_icon = fields.Binary("Icon", readonly=False)
pwa_background_color = fields.Char("Background Color")
pwa_theme_color = fields.Char("Theme Color")
pwa_action_id = fields.Many2one(
"ir.actions.actions",
string="Home Action",
help="If specified, this action will be opened at pwa opened, in addition "
"to the standard menu.",
)

@api.model
def get_values(self):
Expand All @@ -49,8 +62,20 @@ def get_values(self):
res["pwa_theme_color"] = config_parameter_obj_sudo.get_param(
"pwa.manifest.theme_color", default="#2E69B5"
)
action_id = config_parameter_obj_sudo.get_param(
"pwa.config.action_id", default=False
)
res["pwa_action_id"] = action_id and int(action_id)
return res

@api.model
def get_pwa_home_action(self):
return (

Check warning on line 73 in web_pwa_oca/models/res_config_settings.py

View check run for this annotation

Codecov / codecov/patch

web_pwa_oca/models/res_config_settings.py#L73

Added line #L73 was not covered by tests
self.env["ir.config_parameter"]
.sudo()
.get_param("pwa.config.action_id", default=False)
)

def _unpack_icon(self, icon):
# Wrap decoded_icon in BytesIO object
decoded_icon = base64.b64decode(icon)
Expand Down Expand Up @@ -103,12 +128,18 @@ def set_values(self):
config_parameter_obj_sudo.set_param(
"pwa.manifest.theme_color", self.pwa_theme_color
)
config_parameter_obj_sudo.set_param(
"pwa.config.action_id", self.pwa_action_id.id
)
# 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)])
)
config_parameter_obj_sudo.set_param(
"pwa.manifest.custom_icon", True if self.pwa_icon else False
)
# Delete or ignore if no icon provided
if not self.pwa_icon:
if pwa_icon_ir_attachments:
Expand Down Expand Up @@ -138,19 +169,20 @@ def set_values(self):
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):
# Fail if provided PNG is smaller than DEFAULT_ICON_SIZE
if self._unpack_icon(self.pwa_icon).size < (
DEFAULT_ICON_SIZE,
DEFAULT_ICON_SIZE,
):
raise exceptions.UserError(
_("You can only upload PNG files bigger than 512x512")
_(
"You can only upload PNG files bigger "
+ "than {icon_size}x{icon_size}".format(
icon_size=DEFAULT_ICON_SIZE
)
)
)
for size in [
(128, 128),
(144, 144),
(152, 152),
(192, 192),
(256, 256),
(512, 512),
]:
for size in PWA_ICON_SIZES:
self._write_icon_to_attachment(
pwa_icon_extension, pwa_icon_mimetype, size=size
)
30 changes: 28 additions & 2 deletions web_pwa_oca/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,36 @@ If you're building a web app today, you're already on the path towards building

+ 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.
The service worker is constructed using 'Odoo Class' to have the same class inheritance behaviour that in the 'user pages'. Be noticed
that 'Odoo Bootstrap' is supported so, you can 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.

+ CSS Classes to use in qweb templates

- oe_pwa_standalone_invisible : Can be used in the "form" element to get a "empty" form (all is unavailable/invisible).
- oe_pwa_standalone_no_chatter : Can be used in the "form" element to avoid render chatter zone.
- oe_pwa_standalone_no_sheet : Can be used in the "form" element to don't use the sheet style.
- oe_pwa_standalone_visible : Can be used in a element when the form has the 'oe_pwa_standalone_invisible' class to make the element available.
- oe_pwa_standalone_omit : Can be used in elements to make them unavailable in standalone mode.
- oe_pwa_standalone_only : Can be used in elements to make them available only in standalone mode.

What does 'unavailable/invisible' mean?
This 'invisible' state is not only the same as use "invisible=1" in qweb. Here invisible means that the client will not process the fields completely (no rpc calls will be made).

** All ancestors of an element that is visible will also be visible.
** All invisible fields (invisible=1 in qweb definition) will be available by default.
** 'oe_pwa_standalone_omit' and 'oe_pwa_standalone_only' will be filtered on the server side too.

+ XML Attributes to use in qweb templates

- standalone-attrs : Used to extend the node attrs in standalone mode.

+ Tips for developers to update templates with an standalone mode

- Odoo gets the first element when no index is used in qweb xpath, so... duplicated fields must be added after "the original" to ensure that other modules changes the correct element.
- If you define duplicated fields use the 'oe_pwa_standalone_omit' class in the original and 'oe_pwa_standalone_only' class in the duplicated one to avoid problems.
- Set a a very low priority (high value) in your 'standalone view' template.
44 changes: 44 additions & 0 deletions web_pwa_oca/static/src/js/app_menus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* Copyright 2021 Tecnativa - Alexandre D. Díaz
* License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). */
odoo.define("web_pwa_oca.AppsMenu", function(require) {
"use strict";

const AppsMenu = require("web.AppsMenu");
require("web_pwa_oca.webclient");
const WebClientObj = require("web.web_client");

// This is used to reload last action when "prefetching" is done
// to ensure display updated records
AppsMenu.include({
/**
* @override
*/
openFirstApp: function() {
const is_standalone = WebClientObj.pwa_manager.isPWAStandalone();
if (is_standalone) {
const _sup = this._super;
WebClientObj.menu_dp
.add(
this._rpc({
model: "res.config.settings",
method: "get_pwa_home_action",
})
)
.then(action_id => {
if (action_id) {
return this.do_action(action_id).then(() => {
WebClientObj.menu.change_menu_section(
WebClientObj.menu.action_id_to_primary_menu_id(
action_id
)
);
});
}
return _sup.apply(this, arguments);
});
} else {
return this._super(this, arguments);
}
},
});
});
Loading

0 comments on commit 7e3ad78

Please sign in to comment.