From abe28c7a9ec56cd22ff577f85a7767f57ab5a1a4 Mon Sep 17 00:00:00 2001 From: fkantelberg Date: Tue, 29 Nov 2022 18:44:45 +0100 Subject: [PATCH] [ADD][16.0] Module web_dark_mode --- setup/web_dark_mode/odoo/addons/web_dark_mode | 1 + setup/web_dark_mode/setup.py | 6 + web_dark_mode/README.rst | 79 ++++ web_dark_mode/__init__.py | 4 + web_dark_mode/__manifest__.py | 28 ++ web_dark_mode/models/__init__.py | 4 + web_dark_mode/models/ir_http.py | 22 + web_dark_mode/models/res_users.py | 12 + web_dark_mode/readme/CONTRIBUTORS.rst | 1 + web_dark_mode/readme/DESCRIPTION.rst | 2 + web_dark_mode/readme/ROADMAP.rst | 1 + web_dark_mode/static/description/index.html | 427 ++++++++++++++++++ .../static/src/js/switch_item.esm.js | 49 ++ web_dark_mode/static/src/scss/variables.scss | 65 +++ web_dark_mode/tests/__init__.py | 4 + web_dark_mode/tests/test_dark_mode.py | 35 ++ web_dark_mode/views/res_users_views.xml | 14 + 17 files changed, 754 insertions(+) create mode 120000 setup/web_dark_mode/odoo/addons/web_dark_mode create mode 100644 setup/web_dark_mode/setup.py create mode 100644 web_dark_mode/README.rst create mode 100644 web_dark_mode/__init__.py create mode 100644 web_dark_mode/__manifest__.py create mode 100644 web_dark_mode/models/__init__.py create mode 100644 web_dark_mode/models/ir_http.py create mode 100644 web_dark_mode/models/res_users.py create mode 100644 web_dark_mode/readme/CONTRIBUTORS.rst create mode 100644 web_dark_mode/readme/DESCRIPTION.rst create mode 100644 web_dark_mode/readme/ROADMAP.rst create mode 100644 web_dark_mode/static/description/index.html create mode 100644 web_dark_mode/static/src/js/switch_item.esm.js create mode 100644 web_dark_mode/static/src/scss/variables.scss create mode 100644 web_dark_mode/tests/__init__.py create mode 100644 web_dark_mode/tests/test_dark_mode.py create mode 100644 web_dark_mode/views/res_users_views.xml diff --git a/setup/web_dark_mode/odoo/addons/web_dark_mode b/setup/web_dark_mode/odoo/addons/web_dark_mode new file mode 120000 index 000000000..820520268 --- /dev/null +++ b/setup/web_dark_mode/odoo/addons/web_dark_mode @@ -0,0 +1 @@ +../../../../web_dark_mode \ No newline at end of file diff --git a/setup/web_dark_mode/setup.py b/setup/web_dark_mode/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/web_dark_mode/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/web_dark_mode/README.rst b/web_dark_mode/README.rst new file mode 100644 index 000000000..f119aff9f --- /dev/null +++ b/web_dark_mode/README.rst @@ -0,0 +1,79 @@ +========= +Dark Mode +========= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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/16.0/web_dark_mode + :alt: OCA/web +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_dark_mode + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/webui/builds.html?repo=OCA/web&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This modules offers the dark mode for Odoo CE. The dark mode can be activated by +every user in the user menu in the top right. + +**Table of contents** + +.. contents:: + :local: + +Known issues / Roadmap +====================== + +- Implement dark mode for PoS with a glue module + +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 +~~~~~~~ + +* initOS GmbH + +Contributors +~~~~~~~~~~~~ + +* Florian Kantelberg + +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_dark_mode/__init__.py b/web_dark_mode/__init__.py new file mode 100644 index 000000000..1d408a8a0 --- /dev/null +++ b/web_dark_mode/__init__.py @@ -0,0 +1,4 @@ +# © 2022 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models diff --git a/web_dark_mode/__manifest__.py b/web_dark_mode/__manifest__.py new file mode 100644 index 000000000..b32c688e9 --- /dev/null +++ b/web_dark_mode/__manifest__.py @@ -0,0 +1,28 @@ +# © 2022 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Dark Mode", + "summary": "Enabled Dark Mode for the Odoo Backend", + "license": "AGPL-3", + "version": "16.0.1.0.0", + "website": "https://github.com/OCA/web", + "author": "initOS GmbH, Odoo Community Association (OCA)", + "depends": ["web"], + "excludes": ["web_enterprise"], + "installable": True, + "assets": { + "web.dark_mode_assets_common": [ + ("prepend", "web_dark_mode/static/src/scss/variables.scss"), + ], + "web.dark_mode_assets_backend": [ + ("prepend", "web_dark_mode/static/src/scss/variables.scss"), + ], + "web.assets_backend": [ + "web_dark_mode/static/src/js/switch_item.esm.js", + ], + }, + "data": [ + "views/res_users_views.xml", + ], +} diff --git a/web_dark_mode/models/__init__.py b/web_dark_mode/models/__init__.py new file mode 100644 index 000000000..d565f59dc --- /dev/null +++ b/web_dark_mode/models/__init__.py @@ -0,0 +1,4 @@ +# © 2022 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ir_http, res_users diff --git a/web_dark_mode/models/ir_http.py b/web_dark_mode/models/ir_http.py new file mode 100644 index 000000000..20755c49d --- /dev/null +++ b/web_dark_mode/models/ir_http.py @@ -0,0 +1,22 @@ +# © 2022 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models +from odoo.http import request + + +class IrHttp(models.AbstractModel): + _inherit = "ir.http" + + @classmethod + def _set_color_scheme(cls, response): + scheme = request.httprequest.cookies.get("color_scheme") + user = request.env.user + user_scheme = "dark" if user.dark_mode else "light" + if (not user.dark_mode_device_dependent) and scheme != user_scheme: + response.set_cookie("color_scheme", user_scheme) + + @classmethod + def _post_dispatch(cls, response): + cls._set_color_scheme(response) + return super()._post_dispatch(response) diff --git a/web_dark_mode/models/res_users.py b/web_dark_mode/models/res_users.py new file mode 100644 index 000000000..c1bb2a9df --- /dev/null +++ b/web_dark_mode/models/res_users.py @@ -0,0 +1,12 @@ +# © 2022 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + + +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + dark_mode = fields.Boolean() + dark_mode_device_dependent = fields.Boolean("Device Dependent Dark Mode") diff --git a/web_dark_mode/readme/CONTRIBUTORS.rst b/web_dark_mode/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..e20282612 --- /dev/null +++ b/web_dark_mode/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Florian Kantelberg diff --git a/web_dark_mode/readme/DESCRIPTION.rst b/web_dark_mode/readme/DESCRIPTION.rst new file mode 100644 index 000000000..acfc15cd7 --- /dev/null +++ b/web_dark_mode/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This modules offers the dark mode for Odoo CE. The dark mode can be activated by +every user in the user menu in the top right. diff --git a/web_dark_mode/readme/ROADMAP.rst b/web_dark_mode/readme/ROADMAP.rst new file mode 100644 index 000000000..b1437e5f3 --- /dev/null +++ b/web_dark_mode/readme/ROADMAP.rst @@ -0,0 +1 @@ +- Implement dark mode for PoS with a glue module diff --git a/web_dark_mode/static/description/index.html b/web_dark_mode/static/description/index.html new file mode 100644 index 000000000..87e4776f7 --- /dev/null +++ b/web_dark_mode/static/description/index.html @@ -0,0 +1,427 @@ + + + + + + +Dark Mode + + + +
+

Dark Mode

+ + +

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

+

This modules offers the dark mode for Odoo CE. The dark mode can be activated by +every user in the user menu in the top right.

+

Table of contents

+ +
+

Known issues / Roadmap

+
    +
  • Implement dark mode for PoS with a glue module
  • +
+
+
+

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

+
    +
  • initOS GmbH
  • +
+
+
+

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_dark_mode/static/src/js/switch_item.esm.js b/web_dark_mode/static/src/js/switch_item.esm.js new file mode 100644 index 000000000..1eb67e77e --- /dev/null +++ b/web_dark_mode/static/src/js/switch_item.esm.js @@ -0,0 +1,49 @@ +/** @odoo-module **/ + +import {browser} from "@web/core/browser/browser"; +import {registry} from "@web/core/registry"; + +export function darkModeSwitchItem(env) { + return { + type: "switch", + id: "color_scheme.switch", + description: env._t("Dark Mode"), + callback: () => { + env.services.color_scheme.switchColorScheme(); + }, + isChecked: env.services.cookie.current.color_scheme === "dark", + sequence: 40, + }; +} + +export const colorSchemeService = { + dependencies: ["cookie", "orm", "ui", "user"], + + async start(env, {cookie, orm, ui, user}) { + registry.category("user_menuitems").add("darkmode", darkModeSwitchItem); + + if (!cookie.current.color_scheme) { + const match_media = window.matchMedia("(prefers-color-scheme: dark)"); + const dark_mode = match_media.matches; + cookie.setCookie("color_scheme", dark_mode ? "dark" : "light"); + if (dark_mode) browser.location.reload(); + } + + return { + async switchColorScheme() { + const scheme = + cookie.current.color_scheme === "dark" ? "light" : "dark"; + cookie.setCookie("color_scheme", scheme); + + await orm.write("res.users", [user.userId], { + dark_mode: scheme === "dark", + }); + + ui.block(); + browser.location.reload(); + }, + }; + }, +}; + +registry.category("services").add("color_scheme", colorSchemeService); diff --git a/web_dark_mode/static/src/scss/variables.scss b/web_dark_mode/static/src/scss/variables.scss new file mode 100644 index 000000000..a32c59d44 --- /dev/null +++ b/web_dark_mode/static/src/scss/variables.scss @@ -0,0 +1,65 @@ +$o-webclient-color-scheme: dark; + +$o-white: #000000; +$o-black: #ffffff; + +$o-gray-100: #191c24; +$o-gray-200: #242733; +$o-gray-300: #3f4149; +$o-gray-400: #5f6167; +$o-gray-500: #797a80; +$o-gray-600: #94959a; +$o-gray-700: #b0b0b4; +$o-gray-800: #cccccf; +$o-gray-900: #e9e9eb; + +$o-community-color: $o-gray-400; +$o-enterprise-color: $o-gray-400; +$o-enterprise-primary-color: $o-gray-500; +$o-brand-primary: $o-gray-600; +$o-brand-secondary: $o-gray-700; + +$o-success: #28a745; +$o-info: #74dcf3; +$o-warning: #ff7b00; +$o-danger: #ff0020; + +$primary: $o-gray-800; + +$o-main-bg-color: #f0eeee; +$o-main-favorite-color: #f3cc00; +$o-main-code-color: #d2317b; + +$o-view-background-color: $o-white; +$o-view-background-color: $o-gray-100; +$o-shadow-color: #c0c0c0; + +$o-form-lightsecondary: #ccc; + +$o-list-footer-bg-color: #eee; + +$o-tooltip-arrow-color: white; + +// Layout +$o-dropdown-box-shadow: 0 1rem 1.1rem rgba(#fff, 0.1); + +// == List group + +$o-list-group-active-bg: lighten(saturate(adjust-hue($o-info, 15), 1.8), 5); +$o-list-footer-bg-color: $o-gray-200; + +// == Badges + +// Define a minimum width. This value is arbitrary and strictly font-related. +$o-datepicker-week-color: #8f8f8f; + +$component-active-bg: red; + +$o-webclient-background-color: $o-gray-300; + +.o-settings-form-view .o_base_settings { + --settings__tab-bg: #{$o-gray-100}; + --settings__tab-bg--active: #{$o-gray-300}; + --settings__tab-color: #{$o-gray-700}; + --settings__title-bg: #{$o-gray-100}; +} diff --git a/web_dark_mode/tests/__init__.py b/web_dark_mode/tests/__init__.py new file mode 100644 index 000000000..634cfc9d3 --- /dev/null +++ b/web_dark_mode/tests/__init__.py @@ -0,0 +1,4 @@ +# © 2022 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_dark_mode diff --git a/web_dark_mode/tests/test_dark_mode.py b/web_dark_mode/tests/test_dark_mode.py new file mode 100644 index 000000000..466ee7e3a --- /dev/null +++ b/web_dark_mode/tests/test_dark_mode.py @@ -0,0 +1,35 @@ +# © 2022 Florian Kantelberg - initOS GmbH +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from unittest.mock import MagicMock + +import odoo.http +from odoo.tests import common + + +class TestDarkMode(common.TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.request = MagicMock(env=cls.env) + odoo.http._request_stack.push(cls.request) + + def test_dark_mode_cookie(self): + response = MagicMock() + + # Cookie is set because the color_scheme changed + self.request.httprequest.cookies = {"color_scheme": "dark"} + self.env["ir.http"]._post_dispatch(response) + response.set_cookie.assert_called_with("color_scheme", "light") + + # Cookie isn't set because the color_scheme is the same + response.reset_mock() + self.request.httprequest.cookies = {"color_scheme": "light"} + self.env["ir.http"]._post_dispatch(response) + response.set_cookie.assert_not_called() + + # Cookie isn't set because it's device dependent + self.env.user.dark_mode_device_dependent = True + self.request.httprequest.cookies = {"color_scheme": "dark"} + self.env["ir.http"]._post_dispatch(response) + response.set_cookie.assert_not_called() diff --git a/web_dark_mode/views/res_users_views.xml b/web_dark_mode/views/res_users_views.xml new file mode 100644 index 000000000..47f02427f --- /dev/null +++ b/web_dark_mode/views/res_users_views.xml @@ -0,0 +1,14 @@ + + + + res.users + + + + + + + + + +