diff --git a/web_company_color/README.rst b/web_company_color/README.rst new file mode 100644 index 00000000..f293d67e --- /dev/null +++ b/web_company_color/README.rst @@ -0,0 +1,88 @@ +================= +Web Company Color +================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github + :target: https://github.com/OCA/web/tree/12.0/web_company_color + :alt: OCA/web +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-12-0/web-12-0-web_company_color + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/162/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module change navbar colors based in the company logo colors. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Go to company record and set a logo. Can see/modify applied colors on the "Navbar" section. + +For optimal results use images with alpha channel. + +Known issues / Roadmap +====================== + +White color is omitted in the addition operation to support images without alpha channel. + +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 +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Alexandre Díaz + +Contributors +~~~~~~~~~~~~ + +* Jordi Ballester Alomar (Eficent) +* Lois Rilo (Eficent) +* Simone Orsi +* Jairo Llopis + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/web `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/web_company_color/__init__.py b/web_company_color/__init__.py new file mode 100644 index 00000000..62048b1d --- /dev/null +++ b/web_company_color/__init__.py @@ -0,0 +1,5 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from .hooks import post_init_hook, uninstall_hook +from . import utils diff --git a/web_company_color/__manifest__.py b/web_company_color/__manifest__.py new file mode 100644 index 00000000..62a5f55f --- /dev/null +++ b/web_company_color/__manifest__.py @@ -0,0 +1,22 @@ +# Odoo, Open Source Web Company Color +# Copyright (C) 2019 Alexandre Díaz +# +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).# +{ + 'name': "Web Company Color", + 'category': "web", + 'version': "12.0.1.0.0", + "author": "Alexandre Díaz, " + "Odoo Community Association (OCA)", + 'website': 'https://github.com/OCA/web', + 'depends': ['web', 'base_sparse_field', 'web_widget_color'], + 'data': [ + 'view/assets.xml', + 'view/res_company.xml' + ], + 'uninstall_hook': 'uninstall_hook', + 'post_init_hook': 'post_init_hook', + 'license': 'AGPL-3', + 'auto_install': False, + 'installable': True, +} diff --git a/web_company_color/hooks.py b/web_company_color/hooks.py new file mode 100644 index 00000000..a095f8e7 --- /dev/null +++ b/web_company_color/hooks.py @@ -0,0 +1,14 @@ +# Copyright 2019 Alexandre Díaz +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, SUPERUSER_ID +from .models.res_company import URL_BASE + + +def uninstall_hook(cr, registry): + env = api.Environment(cr, SUPERUSER_ID, {}) + env["ir.attachment"].search([('url', 'like', '%s%%' % URL_BASE)]).unlink() + + +def post_init_hook(cr, registry): + env = api.Environment(cr, SUPERUSER_ID, {}) + env['res.company'].search([]).scss_create_or_update_attachment() diff --git a/web_company_color/models/__init__.py b/web_company_color/models/__init__.py new file mode 100644 index 00000000..a12c1b8e --- /dev/null +++ b/web_company_color/models/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import res_company diff --git a/web_company_color/models/res_company.py b/web_company_color/models/res_company.py new file mode 100644 index 00000000..c1590080 --- /dev/null +++ b/web_company_color/models/res_company.py @@ -0,0 +1,165 @@ +# Copyright 2019 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import base64 +import time +from colorsys import rgb_to_hls, hls_to_rgb +from odoo import models, fields, api +from ..utils import image_to_rgb, convert_to_image, n_rgb_to_hex + + +URL_BASE = '/web_company_color/static/src/scss/' +URL_SCSS_GEN_TEMPLATE = URL_BASE + 'custom_colors.%d.%s.gen.scss' + + +class ResCompany(models.Model): + _inherit = 'res.company' + + SCSS_TEMPLATE = """ + .o_main_navbar { + background-color: %(color_navbar_bg)s !important; + color: %(color_navbar_text)s !important; + + > .o_menu_brand { + color: %(color_navbar_text)s !important; + &:hover, &:focus, &:active, &:focus:active { + background-color: %(color_navbar_bg_hover)s !important; + } + } + + .show { + .dropdown-toggle { + background-color: %(color_navbar_bg_hover)s !important; + } + } + + > ul { + > li { + > a, > label { + color: %(color_navbar_text)s !important; + + &:hover, &:focus, &:active, &:focus:active { + background-color: %(color_navbar_bg_hover)s !important; + } + } + } + } + } + """ + + company_colors = fields.Serialized() + color_navbar_bg = fields.Char('Navbar Background Color', + sparse='company_colors') + color_navbar_bg_hover = fields.Char( + 'Navbar Background Color Hover', sparse='company_colors') + color_navbar_text = fields.Char('Navbar Text Color', + sparse='company_colors') + scss_modif_timestamp = fields.Char('SCSS Modif. Timestamp') + + @api.model + def create(self, values): + record = super().create(values) + record.scss_create_or_update_attachment() + return record + + @api.multi + def unlink(self): + result = super().unlink() + IrAttachmentObj = self.env['ir.attachment'] + for record in self: + IrAttachmentObj.sudo().search([ + ('url', 'like', '%s%%' % record._scss_get_url_simplified()), + ]).sudo().unlink() + return result + + @api.multi + def write(self, values): + if not self.env.context.get('ignore_company_color', False): + fields_to_check = ('color_navbar_bg', + 'color_navbar_bg_hover', + 'color_navbar_text') + if 'logo' in values: + if values['logo']: + _r, _g, _b = image_to_rgb(convert_to_image(values['logo'])) + # Make color 10% darker + _h, _l, _s = rgb_to_hls(_r, _g, _b) + _l = max(0, _l - 0.1) + _rd, _gd, _bd = hls_to_rgb(_h, _l, _s) + # Calc. optimal text color (b/w) + # Grayscale human vision perception (Rec. 709 values) + _a = 1 - (0.2126 * _r + 0.7152 * _g + 0.0722 * _b) + values.update({ + 'color_navbar_bg': n_rgb_to_hex(_r, _g, _b), + 'color_navbar_bg_hover': n_rgb_to_hex(_rd, _gd, _bd), + 'color_navbar_text': '#000' if _a < 0.5 else '#fff', + }) + else: + values.update(self.default_get(fields_to_check)) + + result = super().write(values) + + if any([field in values for field in fields_to_check]): + self.scss_create_or_update_attachment() + else: + result = super().write(values) + return result + + @api.multi + def _scss_get_sanitized_values(self): + self.ensure_one() + values = dict(self.company_colors) + values.update({ + 'color_navbar_bg': values['color_navbar_bg'] or '$o-brand-odoo', + 'color_navbar_bg_hover': values['color_navbar_bg_hover'] + or '$o-navbar-inverse-link-hover-bg', + 'color_navbar_text': values['color_navbar_text'] or '#FFF', + }) + return values + + @api.multi + def _scss_generate_content(self): + self.ensure_one() + # ir.attachment need files with content to work + if not self.company_colors: + return "// No Web Company Color SCSS Content\n" + return self.SCSS_TEMPLATE % self._scss_get_sanitized_values() + + # URL to scss related with this company, without timestamp + # /web_company_color/static/src/scss/custom_colors. + def _scss_get_url_simplified(self): + self.ensure_one() + NTEMPLATE = '.'.join(URL_SCSS_GEN_TEMPLATE.split('.')[:2]) + return NTEMPLATE % self.id + + @api.multi + def scss_get_url(self, timestamp=None): + self.ensure_one() + return URL_SCSS_GEN_TEMPLATE % (self.id, + timestamp or self.scss_modif_timestamp) + + @api.multi + def scss_create_or_update_attachment(self): + IrAttachmentObj = self.env['ir.attachment'] + modif_timestamp = str(int(time.time())) # One second resolution + for record in self: + datas = base64.b64encode( + record._scss_generate_content().encode('utf-8')) + custom_attachment = IrAttachmentObj.sudo().search([ + ('url', 'like', '%s%%' % record._scss_get_url_simplified()) + ]) + custom_url = record.scss_get_url(timestamp=modif_timestamp) + values = { + 'datas': datas, + 'url': custom_url, + 'name': custom_url, + 'datas_fname': custom_url.split("/")[-1], + } + if custom_attachment: + custom_attachment.sudo().write(values) + else: + values.update({ + 'type': 'binary', + 'mimetype': 'text/scss', + }) + IrAttachmentObj.sudo().create(values) + self.write({'scss_modif_timestamp': modif_timestamp}) + self.env['ir.qweb'].sudo().clear_caches() diff --git a/web_company_color/readme/CONTRIBUTORS.rst b/web_company_color/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..1cb0a653 --- /dev/null +++ b/web_company_color/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* Jordi Ballester Alomar (Eficent) +* Lois Rilo (Eficent) +* Simone Orsi +* Jairo Llopis diff --git a/web_company_color/readme/DESCRIPTION.rst b/web_company_color/readme/DESCRIPTION.rst new file mode 100644 index 00000000..b164fc0f --- /dev/null +++ b/web_company_color/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module change navbar colors based in the company logo colors. diff --git a/web_company_color/readme/ROADMAP.rst b/web_company_color/readme/ROADMAP.rst new file mode 100644 index 00000000..2b669379 --- /dev/null +++ b/web_company_color/readme/ROADMAP.rst @@ -0,0 +1 @@ +White color is omitted in the addition operation to support images without alpha channel. diff --git a/web_company_color/readme/USAGE.rst b/web_company_color/readme/USAGE.rst new file mode 100644 index 00000000..0143fee7 --- /dev/null +++ b/web_company_color/readme/USAGE.rst @@ -0,0 +1,3 @@ +Go to company record and set a logo. Can see/modify applied colors on the "Navbar" section. + +For optimal results use images with alpha channel. diff --git a/web_company_color/static/description/index.html b/web_company_color/static/description/index.html new file mode 100644 index 00000000..84b559e5 --- /dev/null +++ b/web_company_color/static/description/index.html @@ -0,0 +1,433 @@ + + + + + + +Web Company Color + + + +
+

Web Company Color

+ + +

Beta License: AGPL-3 OCA/web Translate me on Weblate Try me on Runbot

+

This module change navbar colors based in the company logo colors.

+

Table of contents

+ +
+

Usage

+

Go to company record and set a logo. Can see/modify applied colors on the “Navbar” section.

+

For optimal results use images with alpha channel.

+
+
+

Known issues / Roadmap

+

White color is omitted in the addition operation to support images without alpha channel.

+
+
+

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 +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • Alexandre Díaz
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/web project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/web_company_color/tests/__init__.py b/web_company_color/tests/__init__.py new file mode 100644 index 00000000..2c9568e6 --- /dev/null +++ b/web_company_color/tests/__init__.py @@ -0,0 +1 @@ +from . import test_res_company diff --git a/web_company_color/tests/test_res_company.py b/web_company_color/tests/test_res_company.py new file mode 100644 index 00000000..54bf06f4 --- /dev/null +++ b/web_company_color/tests/test_res_company.py @@ -0,0 +1,35 @@ +# Copyright 2019 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo.tests import common +from ..models.res_company import URL_BASE + + +class TestResCompany(common.TransactionCase): + IMG_GREEN = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUl' \ + + 'EQVR42mNk+M/wHwAEBgIApD5fRAAAAABJRU5ErkJggg==' + + def test_scss_attachment(self): + num_scss = self.env['ir.attachment'].search_count([ + ('url', 'ilike', '%s%%' % URL_BASE) + ]) + num_companies = self.env['res.company'].search_count([]) + self.assertEqual(num_scss, num_companies, "Invalid scss attachments") + + def test_change_logo(self): + company_id = self.env['res.company'].search([], limit=1) + company_id.sudo().write({'logo': self.IMG_GREEN}) + self.assertEqual(company_id.color_navbar_bg, '#00ff00', + "Invalid Navbar Background Color") + + def test_create_unlink_company(self): + company_id = self.env['res.company'].create({ + 'name': 'Company Test' + }) + self.assertEqual(company_id.color_navbar_bg, False, + "Invalid Navbar Background Color") + self.test_scss_attachment() + company_id.sudo().write({'logo': self.IMG_GREEN}) + self.assertEqual(company_id.color_navbar_bg, '#00ff00', + "Invalid Navbar Background Color") + company_id.sudo().unlink() + self.test_scss_attachment() diff --git a/web_company_color/utils.py b/web_company_color/utils.py new file mode 100644 index 00000000..f2ac423b --- /dev/null +++ b/web_company_color/utils.py @@ -0,0 +1,41 @@ +# Copyright 2019 Alexandre Díaz +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +import base64 +import math +from PIL import Image +from io import BytesIO + + +def n_rgb_to_hex(_r, _g, _b): + return '#%02x%02x%02x' % (int(255*_r), int(255*_g), int(255*_b)) + + +def convert_to_image(field_binary): + return Image.open(BytesIO(base64.b64decode(field_binary))) + + +def image_to_rgb(img): + def normalize_vec3(vec3): + _l = 1.0 / math.sqrt(vec3[0]*vec3[0] + + vec3[1]*vec3[1] + + vec3[2]*vec3[2]) + return (vec3[0]*_l, vec3[1]*_l, vec3[2]*_l) + + # Force Alpha Channel + if img.mode != 'RGBA': + img = img.convert('RGBA') + width, height = img.size + # Reduce pixels + width, height = (max(1, int(width/4)), max(1, int(height/4))) + img = img.resize((width, height)) + rgb_sum = [0, 0, 0] + # Mix. image colors using addition method + RGBA_WHITE = (255, 255, 255, 255) + for i in range(0, height*width): + rgba = img.getpixel((i % width, i / width)) + if rgba[3] > 128 and rgba != RGBA_WHITE: + rgb_sum[0] += rgba[0] + rgb_sum[1] += rgba[1] + rgb_sum[2] += rgba[2] + _r, _g, _b = normalize_vec3(rgb_sum) + return (_r, _g, _b) diff --git a/web_company_color/view/assets.xml b/web_company_color/view/assets.xml new file mode 100644 index 00000000..d84a3e0b --- /dev/null +++ b/web_company_color/view/assets.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/web_company_color/view/res_company.xml b/web_company_color/view/res_company.xml new file mode 100644 index 00000000..4551d03c --- /dev/null +++ b/web_company_color/view/res_company.xml @@ -0,0 +1,33 @@ + + + + + + + + res.company + + + + + + + + + + + + + + + +