From bf73e5b317d5022c01ad0eece3897f4f52c423e9 Mon Sep 17 00:00:00 2001 From: fkantelberg Date: Tue, 29 Nov 2022 18:44:45 +0100 Subject: [PATCH 01/32] [ADD][16.0] Module web_dark_mode --- web_dark_mode/README.rst | 79 ++++ web_dark_mode/__init__.py | 4 + web_dark_mode/__manifest__.py | 28 ++ web_dark_mode/i18n/web_dark_mode.pot | 37 ++ 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 + 16 files changed, 784 insertions(+) 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/i18n/web_dark_mode.pot 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/web_dark_mode/README.rst b/web_dark_mode/README.rst new file mode 100644 index 000000000000..d29503d0000b --- /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/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/162/16.0 + :alt: Try me on Runbot + +|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 000000000000..1d408a8a039e --- /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 000000000000..b32c688e9b70 --- /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/i18n/web_dark_mode.pot b/web_dark_mode/i18n/web_dark_mode.pot new file mode 100644 index 000000000000..f3daced67eea --- /dev/null +++ b/web_dark_mode/i18n/web_dark_mode.pot @@ -0,0 +1,37 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_dark_mode +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: web_dark_mode +#. odoo-javascript +#: code:addons/web_dark_mode/static/src/js/switch_item.esm.js:0 +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode +#, python-format +msgid "Dark Mode" +msgstr "" + +#. module: web_dark_mode +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode_device_dependent +msgid "Device Dependent Dark Mode" +msgstr "" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_ir_http +msgid "HTTP Routing" +msgstr "" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_res_users +msgid "User" +msgstr "" diff --git a/web_dark_mode/models/__init__.py b/web_dark_mode/models/__init__.py new file mode 100644 index 000000000000..d565f59dcc8d --- /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 000000000000..20755c49de24 --- /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 000000000000..c1bb2a9df8be --- /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 000000000000..e2028261215c --- /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 000000000000..acfc15cd7266 --- /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 000000000000..b1437e5f398e --- /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 000000000000..273e0bae3607 --- /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 Runbot

+

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 000000000000..1eb67e77e45d --- /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 000000000000..a32c59d44373 --- /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 000000000000..634cfc9d3dc0 --- /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 000000000000..466ee7e3ac19 --- /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 000000000000..47f02427f757 --- /dev/null +++ b/web_dark_mode/views/res_users_views.xml @@ -0,0 +1,14 @@ + + + + res.users + + + + + + + + + + From 62c1e3bd6d221eca4b3d96de23b409dd61f0c2de Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Wed, 7 Dec 2022 08:43:49 +0000 Subject: [PATCH 02/32] [ADD] icon.png --- web_dark_mode/static/description/icon.png | Bin 0 -> 9455 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 web_dark_mode/static/description/icon.png diff --git a/web_dark_mode/static/description/icon.png b/web_dark_mode/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 From f747ced30bd8c8875a57fe338f320cd5de5298e8 Mon Sep 17 00:00:00 2001 From: GoodERP Jeff Wang Date: Sun, 11 Dec 2022 14:12:31 +0000 Subject: [PATCH 03/32] Added translation using Weblate (Chinese (zh)) --- web_dark_mode/i18n/zh.po | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 web_dark_mode/i18n/zh.po diff --git a/web_dark_mode/i18n/zh.po b/web_dark_mode/i18n/zh.po new file mode 100644 index 000000000000..ad8461437429 --- /dev/null +++ b/web_dark_mode/i18n/zh.po @@ -0,0 +1,38 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_dark_mode +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: zh\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: web_dark_mode +#. odoo-javascript +#: code:addons/web_dark_mode/static/src/js/switch_item.esm.js:0 +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode +#, python-format +msgid "Dark Mode" +msgstr "" + +#. module: web_dark_mode +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode_device_dependent +msgid "Device Dependent Dark Mode" +msgstr "" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_ir_http +msgid "HTTP Routing" +msgstr "" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_res_users +msgid "User" +msgstr "" From 57c59142b64695c761a65f9a420e26bc0843b01b Mon Sep 17 00:00:00 2001 From: Ignacio Buioli Date: Sun, 1 Jan 2023 19:25:21 +0000 Subject: [PATCH 04/32] Added translation using Weblate (Spanish (Argentina)) --- web_dark_mode/i18n/es_AR.po | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 web_dark_mode/i18n/es_AR.po diff --git a/web_dark_mode/i18n/es_AR.po b/web_dark_mode/i18n/es_AR.po new file mode 100644 index 000000000000..1ad2d1f79e73 --- /dev/null +++ b/web_dark_mode/i18n/es_AR.po @@ -0,0 +1,40 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_dark_mode +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-01-01 21:45+0000\n" +"Last-Translator: Ignacio Buioli \n" +"Language-Team: none\n" +"Language: es_AR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.14.1\n" + +#. module: web_dark_mode +#. odoo-javascript +#: code:addons/web_dark_mode/static/src/js/switch_item.esm.js:0 +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode +#, python-format +msgid "Dark Mode" +msgstr "Modo Oscuro" + +#. module: web_dark_mode +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode_device_dependent +msgid "Device Dependent Dark Mode" +msgstr "Dispositivo que Depende del Modo Oscuro" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_ir_http +msgid "HTTP Routing" +msgstr "Ruteo HTTP" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_res_users +msgid "User" +msgstr "Usuario" From ac7a464074ba3a482d899180aa23244029e69e54 Mon Sep 17 00:00:00 2001 From: fkantelberg Date: Mon, 6 Feb 2023 09:03:16 +0100 Subject: [PATCH 05/32] [FIX] web_dark_mode: Fix uninstallation --- web_dark_mode/models/ir_http.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web_dark_mode/models/ir_http.py b/web_dark_mode/models/ir_http.py index 20755c49de24..e2c90b53773f 100644 --- a/web_dark_mode/models/ir_http.py +++ b/web_dark_mode/models/ir_http.py @@ -12,8 +12,9 @@ class IrHttp(models.AbstractModel): 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: + user_scheme = "dark" if getattr(user, "dark_mode", None) else "light" + device_dependent = getattr(user, "dark_mode_device_dependent", None) + if (not device_dependent) and scheme != user_scheme: response.set_cookie("color_scheme", user_scheme) @classmethod From b4c5c333c92050f46287f9ea5b094f661d60ed0d Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 6 Feb 2023 10:44:45 +0000 Subject: [PATCH 06/32] web_dark_mode 16.0.1.0.1 --- web_dark_mode/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_dark_mode/__manifest__.py b/web_dark_mode/__manifest__.py index b32c688e9b70..aeb8ededdc93 100644 --- a/web_dark_mode/__manifest__.py +++ b/web_dark_mode/__manifest__.py @@ -5,7 +5,7 @@ "name": "Dark Mode", "summary": "Enabled Dark Mode for the Odoo Backend", "license": "AGPL-3", - "version": "16.0.1.0.0", + "version": "16.0.1.0.1", "website": "https://github.com/OCA/web", "author": "initOS GmbH, Odoo Community Association (OCA)", "depends": ["web"], From 5f5be2aa2206603193c37e30e72f4f3644290725 Mon Sep 17 00:00:00 2001 From: Bole Date: Thu, 16 Feb 2023 11:59:58 +0000 Subject: [PATCH 07/32] Added translation using Weblate (Croatian) --- web_dark_mode/i18n/hr.po | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 web_dark_mode/i18n/hr.po diff --git a/web_dark_mode/i18n/hr.po b/web_dark_mode/i18n/hr.po new file mode 100644 index 000000000000..21e8842000f7 --- /dev/null +++ b/web_dark_mode/i18n/hr.po @@ -0,0 +1,41 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_dark_mode +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-02-16 14:23+0000\n" +"Last-Translator: Bole \n" +"Language-Team: none\n" +"Language: hr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.14.1\n" + +#. module: web_dark_mode +#. odoo-javascript +#: code:addons/web_dark_mode/static/src/js/switch_item.esm.js:0 +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode +#, python-format +msgid "Dark Mode" +msgstr "Tamni način" + +#. module: web_dark_mode +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode_device_dependent +msgid "Device Dependent Dark Mode" +msgstr "Tamni način zavisi od uređaja" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_ir_http +msgid "HTTP Routing" +msgstr "HTTP Routing" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_res_users +msgid "User" +msgstr "Korisnik" From 60fa66e3663b6449b5c4dfe71306416675cc66fa Mon Sep 17 00:00:00 2001 From: Ediz Duman Date: Mon, 6 Mar 2023 18:42:13 +0000 Subject: [PATCH 08/32] Added translation using Weblate (Turkish) --- web_dark_mode/i18n/tr.po | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 web_dark_mode/i18n/tr.po diff --git a/web_dark_mode/i18n/tr.po b/web_dark_mode/i18n/tr.po new file mode 100644 index 000000000000..0cd019cf46af --- /dev/null +++ b/web_dark_mode/i18n/tr.po @@ -0,0 +1,40 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_dark_mode +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-03-06 20:08+0000\n" +"Last-Translator: Ediz Duman \n" +"Language-Team: none\n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.14.1\n" + +#. module: web_dark_mode +#. odoo-javascript +#: code:addons/web_dark_mode/static/src/js/switch_item.esm.js:0 +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode +#, python-format +msgid "Dark Mode" +msgstr "Koyu Mod" + +#. module: web_dark_mode +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode_device_dependent +msgid "Device Dependent Dark Mode" +msgstr "Cihaza Bağlı Karanlık Mod" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_ir_http +msgid "HTTP Routing" +msgstr "HTTP Yönlendirme" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_res_users +msgid "User" +msgstr "Kullanıcı" From 5cc349e115b032672e9eb94088a2d4b795eae754 Mon Sep 17 00:00:00 2001 From: Ivorra78 Date: Sun, 27 Aug 2023 18:12:44 +0000 Subject: [PATCH 09/32] Added translation using Weblate (Spanish) --- web_dark_mode/README.rst | 15 ++++---- web_dark_mode/i18n/es.po | 40 +++++++++++++++++++++ web_dark_mode/static/description/index.html | 38 ++++++++++---------- 3 files changed, 69 insertions(+), 24 deletions(-) create mode 100644 web_dark_mode/i18n/es.po diff --git a/web_dark_mode/README.rst b/web_dark_mode/README.rst index d29503d0000b..9e71bbed992f 100644 --- a/web_dark_mode/README.rst +++ b/web_dark_mode/README.rst @@ -2,10 +2,13 @@ Dark Mode ========= -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:50b8425c7b12c40118ea9e976812acf55ac6d96d5eb1a31524ad1e9586f9f267 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -19,11 +22,11 @@ Dark Mode .. |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/runbot-Try%20me-875A7B.png - :target: https://runbot.odoo-community.org/runbot/162/16.0 - :alt: Try me on Runbot +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=16.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|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. @@ -43,7 +46,7 @@ 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 +If you spotted it first, help us to smash it by providing a detailed and welcomed `feedback `_. Do not contact contributors directly about support or help with technical issues. diff --git a/web_dark_mode/i18n/es.po b/web_dark_mode/i18n/es.po new file mode 100644 index 000000000000..903638c864d5 --- /dev/null +++ b/web_dark_mode/i18n/es.po @@ -0,0 +1,40 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * web_dark_mode +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-09-02 20:35+0000\n" +"Last-Translator: Ivorra78 \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" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: web_dark_mode +#. odoo-javascript +#: code:addons/web_dark_mode/static/src/js/switch_item.esm.js:0 +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode +#, python-format +msgid "Dark Mode" +msgstr "Modo Oscuro" + +#. module: web_dark_mode +#: model:ir.model.fields,field_description:web_dark_mode.field_res_users__dark_mode_device_dependent +msgid "Device Dependent Dark Mode" +msgstr "Modo Oscuro en Función del Dispositivo" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_ir_http +msgid "HTTP Routing" +msgstr "Enrutamiento HTTP" + +#. module: web_dark_mode +#: model:ir.model,name:web_dark_mode.model_res_users +msgid "User" +msgstr "Usuario" diff --git a/web_dark_mode/static/description/index.html b/web_dark_mode/static/description/index.html index 273e0bae3607..538ec20760bd 100644 --- a/web_dark_mode/static/description/index.html +++ b/web_dark_mode/static/description/index.html @@ -1,20 +1,20 @@ - + - + Dark Mode -
-

Dark Mode

+
+ + +Odoo Community Association + +
+

Dark Mode

-

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

+

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

@@ -386,13 +391,13 @@

Dark Mode

-

Known issues / Roadmap

+

Known issues / Roadmap

  • Implement dark mode for PoS with a glue module
-

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 to smash it by providing a detailed and welcomed @@ -400,21 +405,25 @@

Bug Tracker

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

+
diff --git a/web_dark_mode/static/src/views/form/form_controller.dark.scss b/web_dark_mode/static/src/views/form/form_controller.dark.scss new file mode 100644 index 000000000000..5b6014e7d4a2 --- /dev/null +++ b/web_dark_mode/static/src/views/form/form_controller.dark.scss @@ -0,0 +1,3 @@ +.o_form_view .o_field_widget .o_list_renderer { + --ListRenderer-thead-bg-active-color: #{$o-gray-300}; +} From dfa60205fe42c16f5effea608a19b02aea64a8f5 Mon Sep 17 00:00:00 2001 From: Liam Noonan Date: Sun, 4 Jan 2026 01:22:26 +0300 Subject: [PATCH 28/32] [IMP] web_dark_mode: pre-commit auto fixes --- web_dark_mode/README.rst | 10 +++++----- web_dark_mode/__manifest__.py | 2 +- web_dark_mode/static/description/index.html | 6 +++--- .../static/src/scss/secondary_variables.dark.scss | 14 ++++++++------ 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/web_dark_mode/README.rst b/web_dark_mode/README.rst index bdebf789ad24..cbca31952ffc 100644 --- a/web_dark_mode/README.rst +++ b/web_dark_mode/README.rst @@ -21,13 +21,13 @@ Dark Mode :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/18.0/web_dark_mode + :target: https://github.com/OCA/web/tree/19.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-18-0/web-18-0-web_dark_mode + :target: https://translation.odoo-community.org/projects/web-19-0/web-19-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/builds?repo=OCA/web&target_branch=18.0 + :target: https://runboat.odoo-community.org/builds?repo=OCA/web&target_branch=19.0 :alt: Try me on Runboat |badge1| |badge2| |badge3| |badge4| |badge5| @@ -51,7 +51,7 @@ 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 to smash it by providing a detailed and welcomed -`feedback `_. +`feedback `_. Do not contact contributors directly about support or help with technical issues. @@ -84,6 +84,6 @@ 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. +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/__manifest__.py b/web_dark_mode/__manifest__.py index 44ea0acfcf69..9912281fa140 100644 --- a/web_dark_mode/__manifest__.py +++ b/web_dark_mode/__manifest__.py @@ -5,7 +5,7 @@ "name": "Dark Mode", "summary": "Enabled Dark Mode for the Odoo Backend", "license": "AGPL-3", - "version": "18.0.1.0.0", + "version": "19.0.1.0.0", "website": "https://github.com/OCA/web", "author": "initOS GmbH, Odoo Community Association (OCA)", "depends": ["web"], diff --git a/web_dark_mode/static/description/index.html b/web_dark_mode/static/description/index.html index 660e33ec5346..392251616ddd 100644 --- a/web_dark_mode/static/description/index.html +++ b/web_dark_mode/static/description/index.html @@ -374,7 +374,7 @@

Dark Mode

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! source digest: sha256:412634e63cb76e61a9461ecdc1ffef9271510b8a8a1d6e63ee98df7eeeedaaae !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> -

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

+

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

@@ -401,7 +401,7 @@

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 to smash it by providing a detailed and welcomed -feedback.

+feedback.

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

@@ -431,7 +431,7 @@

Maintainers

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.

+

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/scss/secondary_variables.dark.scss b/web_dark_mode/static/src/scss/secondary_variables.dark.scss index 04cb45d48407..ffba52cc1be8 100644 --- a/web_dark_mode/static/src/scss/secondary_variables.dark.scss +++ b/web_dark_mode/static/src/scss/secondary_variables.dark.scss @@ -1,12 +1,14 @@ -$o-colors: #a2a2a2, #ee2d2d, #dc8534, #e8bb1d, #5794dd, #9f628f, #db8865, #41a9a2, - #304be0, #ee2f8a, #61c36e, #9872e6 !default; +$o-colors: + #a2a2a2, #ee2d2d, #dc8534, #e8bb1d, #5794dd, #9f628f, #db8865, #41a9a2, #304be0, + #ee2f8a, #61c36e, #9872e6 !default; // An epically lazy solution to colors that do not do well on the dark background: just delete them. // Unfortunately this does introduce a problem where, when there are more calendar users than colors, // the event gets a $o-webclient-background-color background and the filter check gets // $form-check-input-checked-bg-color. This problem exists in enterprise too, // so hopefully they will fix it. -$o-colors-secondary: #aa4b6b, #30c381, #f7cd1f, #4285f4, #d6145f, #aa3a38, #6be585, - #e9d362, #b56969, #bdc3c7, #ea00ff, #ff0026, #8bcc00, #00bfaf, #006aff, #af00bf, - #bf6300, #8cff00, #00f2ff, #ff00d0, #ffa600, #3acc00, #00b6bf, #0048ff, #bf7c00, - #04ff00, #00d0ff, #ff008c, #00bf49, #0092b3, #0004ff, #b20062 !default; +$o-colors-secondary: + #aa4b6b, #30c381, #f7cd1f, #4285f4, #d6145f, #aa3a38, #6be585, #e9d362, #b56969, + #bdc3c7, #ea00ff, #ff0026, #8bcc00, #00bfaf, #006aff, #af00bf, #bf6300, #8cff00, + #00f2ff, #ff00d0, #ffa600, #3acc00, #00b6bf, #0048ff, #bf7c00, #04ff00, #00d0ff, + #ff008c, #00bf49, #0092b3, #0004ff, #b20062 !default; From f6b47313cf1fa33780c05a707c3cad29ab99a56d Mon Sep 17 00:00:00 2001 From: Liam Noonan Date: Mon, 5 Jan 2026 22:41:10 +0300 Subject: [PATCH 29/32] [MIG] web_dark_mode Migrate to 19.0 The relevant changes to odoo code: https://github.com/odoo/odoo/commit/2120e4c6539b0ad8fadaf249c883b0529ad613f4 --- web_dark_mode/models/ir_http.py | 7 +++++++ web_dark_mode/views/res_users_views.xml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/web_dark_mode/models/ir_http.py b/web_dark_mode/models/ir_http.py index e2c90b53773f..e72052569f63 100644 --- a/web_dark_mode/models/ir_http.py +++ b/web_dark_mode/models/ir_http.py @@ -8,6 +8,13 @@ class IrHttp(models.AbstractModel): _inherit = "ir.http" + def color_scheme(self): + scheme = request.httprequest.cookies.get("color_scheme") + if scheme: + return scheme + else: + return "light" + @classmethod def _set_color_scheme(cls, response): scheme = request.httprequest.cookies.get("color_scheme") diff --git a/web_dark_mode/views/res_users_views.xml b/web_dark_mode/views/res_users_views.xml index 47f02427f757..520923af2f93 100644 --- a/web_dark_mode/views/res_users_views.xml +++ b/web_dark_mode/views/res_users_views.xml @@ -4,7 +4,7 @@ res.users - + From 80e35f66407506e1284ca40e321e604f4b74e440 Mon Sep 17 00:00:00 2001 From: Liam Noonan Date: Tue, 6 Jan 2026 04:46:08 +0300 Subject: [PATCH 30/32] [MIG] web_dark_mode Migrate styles --- .../static/src/core/badge/badge.dark.scss | 17 +++++++++++++++ .../core/bottom_sheet/bottom_sheet.dark.scss | 3 +++ .../src/core/colorlist/colorlist.dark.scss | 21 +++++++++---------- .../src/core/notebook/notebook.dark.scss | 5 ++--- .../src/core/tags_list/tags_list.dark.scss | 4 ++-- .../src/views/form/form_controller.dark.scss | 3 --- .../views/kanban/kanban_controller.dark.scss | 3 +++ 7 files changed, 37 insertions(+), 19 deletions(-) create mode 100644 web_dark_mode/static/src/core/badge/badge.dark.scss create mode 100644 web_dark_mode/static/src/core/bottom_sheet/bottom_sheet.dark.scss delete mode 100644 web_dark_mode/static/src/views/form/form_controller.dark.scss diff --git a/web_dark_mode/static/src/core/badge/badge.dark.scss b/web_dark_mode/static/src/core/badge/badge.dark.scss new file mode 100644 index 000000000000..6338c6705427 --- /dev/null +++ b/web_dark_mode/static/src/core/badge/badge.dark.scss @@ -0,0 +1,17 @@ +// Use the same formula from web_dark_mode/static/src/core/tags_list/tags_list.dark.scss +.badge { + @for $size from 1 through length($o-colors) { + &.o_badge_color_#{$size - 1} { + background-color: mix( + nth($o-colors, $size), + $o-view-background-color, + 25% + ) !important; + color: scale-color( + nth($o-colors, $size), + $lightness: 70%, + $saturation: 100% + ) !important; + } + } +} diff --git a/web_dark_mode/static/src/core/bottom_sheet/bottom_sheet.dark.scss b/web_dark_mode/static/src/core/bottom_sheet/bottom_sheet.dark.scss new file mode 100644 index 000000000000..8a80561d1d82 --- /dev/null +++ b/web_dark_mode/static/src/core/bottom_sheet/bottom_sheet.dark.scss @@ -0,0 +1,3 @@ +.o_bottom_sheet .o_bottom_sheet_sheet { + border-top: $border-width solid $o-enterprise-color; +} diff --git a/web_dark_mode/static/src/core/colorlist/colorlist.dark.scss b/web_dark_mode/static/src/core/colorlist/colorlist.dark.scss index 901c78c8e758..20158c991f8e 100644 --- a/web_dark_mode/static/src/core/colorlist/colorlist.dark.scss +++ b/web_dark_mode/static/src/core/colorlist/colorlist.dark.scss @@ -1,13 +1,12 @@ -@for $size from 2 through length($o-colors) { - .o_colorlist_item_color_#{$size - 1} { - --background-color: #{mix( - nth($o-colors, $size), - $o-view-background-color, - 95% - )}; - // As far as I can tell this is never used to display text. The only use i found for - // It was around the border of the colorlist color square when clicked. I'm just - // setting it to contrast just in case it ever gets used for text. - --color: #{color-contrast(nth($o-colors, $size))}; +.o_colorlist > button { + @for $size from 2 through length($o-colors) { + &.o_colorlist_item_color_#{$size - 1} { + --background-color: #{mix( + nth($o-colors, $size), + $o-view-background-color, + 95% + )}; + --color: #{color-contrast(nth($o-colors, $size))}; + } } } diff --git a/web_dark_mode/static/src/core/notebook/notebook.dark.scss b/web_dark_mode/static/src/core/notebook/notebook.dark.scss index 63f0dc7ac951..044b220482eb 100644 --- a/web_dark_mode/static/src/core/notebook/notebook.dark.scss +++ b/web_dark_mode/static/src/core/notebook/notebook.dark.scss @@ -1,5 +1,4 @@ .o_notebook { - --notebook-link-border-color: #{$nav-tabs-border-color}; - --notebook-link-border-color-hover: #{$nav-tabs-border-color}; - --notebook-link-border-color-active-accent: #{$o-main-link-color}; + --Notebook__link-border-color--hover: #{$border-color}; + --Notebook__link-border-top-color--active: #{$o-action}; } diff --git a/web_dark_mode/static/src/core/tags_list/tags_list.dark.scss b/web_dark_mode/static/src/core/tags_list/tags_list.dark.scss index 2b0e6c63f3b0..3f9611bb05cc 100644 --- a/web_dark_mode/static/src/core/tags_list/tags_list.dark.scss +++ b/web_dark_mode/static/src/core/tags_list/tags_list.dark.scss @@ -1,10 +1,10 @@ .o_tag { @for $size from 1 through length($o-colors) { &.o_tag_color_#{$size - 1} { - $-bg: mix(nth($o-colors, $size), $o-view-background-color, 10%); + $-bg: mix(nth($o-colors, $size), $o-view-background-color, 25%); $-color: scale-color( nth($o-colors, $size), - $lightness: 65%, + $lightness: 70%, $saturation: 100% ); diff --git a/web_dark_mode/static/src/views/form/form_controller.dark.scss b/web_dark_mode/static/src/views/form/form_controller.dark.scss deleted file mode 100644 index 5b6014e7d4a2..000000000000 --- a/web_dark_mode/static/src/views/form/form_controller.dark.scss +++ /dev/null @@ -1,3 +0,0 @@ -.o_form_view .o_field_widget .o_list_renderer { - --ListRenderer-thead-bg-active-color: #{$o-gray-300}; -} diff --git a/web_dark_mode/static/src/views/kanban/kanban_controller.dark.scss b/web_dark_mode/static/src/views/kanban/kanban_controller.dark.scss index 265dd424f30a..74c896a9cc86 100644 --- a/web_dark_mode/static/src/views/kanban/kanban_controller.dark.scss +++ b/web_dark_mode/static/src/views/kanban/kanban_controller.dark.scss @@ -1,6 +1,9 @@ .o_kanban_renderer { --KanbanColumn__highlight-background: #{mix($o-action, $o-gray-100, 10%)}; --KanbanColumn__highlight-border: #{rgba($o-action, 0.5)}; + // Mirror the color of selected row in list view using the same logic as applied there: + // https://github.com/odoo/odoo/blob/19.0/addons/web/static/lib/bootstrap/scss/_variables.scss#L775 + --KanbanColumn__highlight-selected: #{shift-color($info, $table-bg-scale)}; // For some reason attachment images do not get the o_field_image class .o_kanban_record:not(.o_legacy_kanban_record) { From 5a4f288f419abf4bc2764c8a4ca96a0b0b036b62 Mon Sep 17 00:00:00 2001 From: Liam Noonan Date: Tue, 6 Jan 2026 22:39:59 +0300 Subject: [PATCH 31/32] [FIX] web_dark_mode style fixes for both 18 and 19 1) Fix calendar events with color_0 which is used for private tasks, for example. Most noticable for private tasks in calendar view. 2) Adjust bootstrap bg-subtle and text-emphasis for info, success, warning, and danger. These are used for the epomnymous banners in html and in 19.0 for the purchase dashboard status cards. . Separate commit so that it can be backported to 18.0 --- .../static/src/scss/bootstrap_overridden.dark.scss | 12 ++++++++++++ .../static/src/scss/secondary_variables.dark.scss | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/web_dark_mode/static/src/scss/bootstrap_overridden.dark.scss b/web_dark_mode/static/src/scss/bootstrap_overridden.dark.scss index 17861d9d6fa3..09d82b856c16 100644 --- a/web_dark_mode/static/src/scss/bootstrap_overridden.dark.scss +++ b/web_dark_mode/static/src/scss/bootstrap_overridden.dark.scss @@ -1,6 +1,18 @@ // This overrides the overrides in web/static/src/scss/bootstrap_overridden.scss // and also some additional overrides to web/static/lib/bootstrap/scss/_variables.scss +// web/static/lib/bootstrap/scss/_variables.scss sets these colors with tint-color() and shade-color() +// but we inversed those functions in web_dark_mode/static/src/scss/bs_functions_overrides.dark.scss +// so we have to adjust a bit. Most noticable in purchase dashboard (19.0) and banners in html editor +$info-text-emphasis: shade-color($o-info, 75%) !default; +$info-bg-subtle: tint-color($o-info, 65%) !default; +$success-text-emphasis: shade-color($o-success, 75%) !default; +$success-bg-subtle: tint-color($o-success, 65%) !default; +$warning-text-emphasis: shade-color($o-warning, 75%) !default; +$warning-bg-subtle: tint-color($o-warning, 65%) !default; +$danger-text-emphasis: shade-color($o-danger, 75%) !default; +$danger-bg-subtle: tint-color($o-danger, 65%) !default; + // Shadows $box-shadow: 0 0.5rem 1rem rgba($o-white, 0.4) !default; $box-shadow-sm: 0 0.125rem 0.25rem rgba($o-white, 0.2) !default; diff --git a/web_dark_mode/static/src/scss/secondary_variables.dark.scss b/web_dark_mode/static/src/scss/secondary_variables.dark.scss index ffba52cc1be8..8af23ad7998e 100644 --- a/web_dark_mode/static/src/scss/secondary_variables.dark.scss +++ b/web_dark_mode/static/src/scss/secondary_variables.dark.scss @@ -1,5 +1,7 @@ +// Note that first color will be used as color_0 in various places, but most noticably +// for private tasks in calendar view. $o-colors: - #a2a2a2, #ee2d2d, #dc8534, #e8bb1d, #5794dd, #9f628f, #db8865, #41a9a2, #304be0, + #899db1, #ee2d2d, #dc8534, #e8bb1d, #5794dd, #9f628f, #db8865, #41a9a2, #304be0, #ee2f8a, #61c36e, #9872e6 !default; // An epically lazy solution to colors that do not do well on the dark background: just delete them. From 5ef5802c7c2d41660e58057def531a7ea65f4416 Mon Sep 17 00:00:00 2001 From: Liam Noonan Date: Wed, 14 Jan 2026 01:28:21 +0300 Subject: [PATCH 32/32] [FIX] web_dark_mode Fix device dependent Previously, device dependent was strictly frontend and was set only if the color_scheme cookie had not already been set. This meant that it was useless unless you cleared your cookies. This fix implements two separete methods of detecting device preference: 1) Server side Adds request for Sec-CH-Prefers-Color-Scheme hint and uses it to serve up the correct asset bundle on the first load. This is mostly just for Chromium browsers. 2) Client side If Sec-CH-Prefers-Color-Scheme is None, client side js will check user.settings.dark_mode_device_dependent which, thanks to moving these prefs to res_users_settings is already loaded without a separate orm call, if true it will then check device preference and current cookie. If resetting the cookie is required, it will then trigger a reload. This is, of course, very bad, as it causes flicker and loads the assets twice, but thankfully it ony happens the first time you login or enable device dependent, or if you switch your device's system theme. This only applies to Firefox, Safari, or to Chromium browsers if the user has enabled anti fingerprinting settings. Sec-CH-Prefers-Color-Scheme docs are here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-CH-Prefers-Color-Scheme --- web_dark_mode/models/__init__.py | 2 +- web_dark_mode/models/ir_http.py | 54 ++++-- web_dark_mode/models/res_users.py | 11 +- web_dark_mode/models/res_users_settings.py | 14 ++ .../static/src/js/switch_item.esm.js | 24 ++- web_dark_mode/tests/__init__.py | 2 +- web_dark_mode/tests/test_dark_mode.py | 35 ---- web_dark_mode/tests/test_ir_http.py | 155 ++++++++++++++++++ 8 files changed, 241 insertions(+), 56 deletions(-) create mode 100644 web_dark_mode/models/res_users_settings.py delete mode 100644 web_dark_mode/tests/test_dark_mode.py create mode 100644 web_dark_mode/tests/test_ir_http.py diff --git a/web_dark_mode/models/__init__.py b/web_dark_mode/models/__init__.py index d565f59dcc8d..b15df3d10a44 100644 --- a/web_dark_mode/models/__init__.py +++ b/web_dark_mode/models/__init__.py @@ -1,4 +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 +from . import ir_http, res_users, res_users_settings diff --git a/web_dark_mode/models/ir_http.py b/web_dark_mode/models/ir_http.py index e72052569f63..367caf88b476 100644 --- a/web_dark_mode/models/ir_http.py +++ b/web_dark_mode/models/ir_http.py @@ -1,4 +1,5 @@ # © 2022 Florian Kantelberg - initOS GmbH +# © 2026 Liam Noonan - Pyxiris # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import models @@ -9,22 +10,55 @@ class IrHttp(models.AbstractModel): _inherit = "ir.http" def color_scheme(self): - scheme = request.httprequest.cookies.get("color_scheme") - if scheme: - return scheme + target_scheme, existing_scheme = self._get_color_scheme() + if target_scheme: + return target_scheme + elif existing_scheme: + return existing_scheme else: - return "light" + return super().color_scheme() @classmethod - def _set_color_scheme(cls, response): - scheme = request.httprequest.cookies.get("color_scheme") + def _get_color_scheme(cls): user = request.env.user - user_scheme = "dark" if getattr(user, "dark_mode", None) else "light" - device_dependent = getattr(user, "dark_mode_device_dependent", None) - if (not device_dependent) and scheme != user_scheme: - response.set_cookie("color_scheme", user_scheme) + existing_scheme_cookie = None + target_scheme = None + + if user and user._is_internal(): + # Existing + existing_scheme_cookie = request.httprequest.cookies.get("color_scheme") + browser_preference_header = request.httprequest.headers.get( + "Sec-CH-Prefers-Color-Scheme" + ) + browser_scheme = ( + browser_preference_header + if browser_preference_header in ("dark", "light") + else None + ) + # User preference + user_scheme = "dark" if getattr(user, "dark_mode", None) else "light" + user_device_dependant_scheme = getattr( + user, "dark_mode_device_dependent", None + ) + + if user_device_dependant_scheme: + if browser_scheme and existing_scheme_cookie != browser_scheme: + target_scheme = browser_scheme + + elif existing_scheme_cookie != user_scheme: + target_scheme = user_scheme + + return target_scheme, existing_scheme_cookie + + @classmethod + def _set_color_scheme(cls, response): + target_scheme, _ = cls._get_color_scheme() + if target_scheme: + response.set_cookie("color_scheme", target_scheme) @classmethod def _post_dispatch(cls, response): cls._set_color_scheme(response) + response.headers.add("Vary", "Sec-CH-Prefers-Color-Scheme") + response.headers.add("Accept-CH", "Sec-CH-Prefers-Color-Scheme") return super()._post_dispatch(response) diff --git a/web_dark_mode/models/res_users.py b/web_dark_mode/models/res_users.py index be6fd3ebc05a..77459d0090a3 100644 --- a/web_dark_mode/models/res_users.py +++ b/web_dark_mode/models/res_users.py @@ -1,4 +1,5 @@ # © 2022 Florian Kantelberg - initOS GmbH +# © 2026 Liam Noonan - Pyxiris # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). @@ -8,8 +9,14 @@ class ResUsers(models.Model): _inherit = "res.users" - dark_mode = fields.Boolean() - dark_mode_device_dependent = fields.Boolean("Device Dependent Dark Mode") + dark_mode = fields.Boolean( + related="res_users_settings_id.dark_mode", readonly=False + ) + dark_mode_device_dependent = fields.Boolean( + related="res_users_settings_id.dark_mode_device_dependent", + readonly=False, + string="Device Dependent Dark Mode", + ) @property def SELF_READABLE_FIELDS(self): diff --git a/web_dark_mode/models/res_users_settings.py b/web_dark_mode/models/res_users_settings.py new file mode 100644 index 000000000000..97a54da14852 --- /dev/null +++ b/web_dark_mode/models/res_users_settings.py @@ -0,0 +1,14 @@ +# © 2022 Florian Kantelberg - initOS GmbH +# © 2026 Liam Noonan - Pyxiris +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResUsersSettings(models.Model): + _inherit = "res.users.settings" + + # These fields should be here in order to be accessible via in js + # as user.settings.dark_mode, etc. + dark_mode = fields.Boolean() + dark_mode_device_dependent = fields.Boolean() diff --git a/web_dark_mode/static/src/js/switch_item.esm.js b/web_dark_mode/static/src/js/switch_item.esm.js index 63530ae974e9..79c273ba1789 100644 --- a/web_dark_mode/static/src/js/switch_item.esm.js +++ b/web_dark_mode/static/src/js/switch_item.esm.js @@ -23,13 +23,23 @@ export const colorSchemeService = { dependencies: ["orm", "ui"], async start(env, {orm, ui}) { - registry.category("user_menuitems").add("darkmode", darkModeSwitchItem); - - if (!cookie.get("color_scheme")) { - const match_media = window.matchMedia("(prefers-color-scheme: dark)"); - const dark_mode = match_media.matches; - cookie.set("color_scheme", dark_mode ? "dark" : "light"); - if (dark_mode) browser.location.reload(); + // This is only for browsers like Firefox and Safari that do not support + // Sec-CH-Prefers-Color-Scheme. Browsers that do support it will have already + // set the correct cookie value on first request due to server side handling + // in ir.http. + if (user.settings.dark_mode_device_dependent === true) { + const device_preference = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light"; + if (cookie.get("color_scheme") !== device_preference) { + cookie.set("color_scheme", device_preference); + // This will cause a bit of flicker as odoo loads the wrong assets + // before it can determine the browser system color scheme. + browser.location.reload(); + } + } else { + registry.category("user_menuitems").add("darkmode", darkModeSwitchItem); } return { diff --git a/web_dark_mode/tests/__init__.py b/web_dark_mode/tests/__init__.py index 634cfc9d3dc0..fd9ea3035a34 100644 --- a/web_dark_mode/tests/__init__.py +++ b/web_dark_mode/tests/__init__.py @@ -1,4 +1,4 @@ # © 2022 Florian Kantelberg - initOS GmbH # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from . import test_dark_mode +from . import test_ir_http diff --git a/web_dark_mode/tests/test_dark_mode.py b/web_dark_mode/tests/test_dark_mode.py deleted file mode 100644 index 466ee7e3ac19..000000000000 --- a/web_dark_mode/tests/test_dark_mode.py +++ /dev/null @@ -1,35 +0,0 @@ -# © 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/tests/test_ir_http.py b/web_dark_mode/tests/test_ir_http.py new file mode 100644 index 000000000000..dae2a63896fa --- /dev/null +++ b/web_dark_mode/tests/test_ir_http.py @@ -0,0 +1,155 @@ +# © 2022 Florian Kantelberg - initOS GmbH +# © 2026 Liam Noonan - Pyxiris +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.tests import HttpCase, new_test_user, tagged + +HOST = "127.0.0.1" + + +@tagged("post_install", "-at_install") +class TestColorScheme(HttpCase): + def setUp(self): + super().setUp() + self.test_portal_user = new_test_user( + self.env, "test_portal_user", groups="base.group_portal" + ) + self.test_internal_user = new_test_user( + self.env, "test_internal_user", groups="base.group_user" + ) + self.test_internal_user.write( + { + "dark_mode": False, + "dark_mode_device_dependent": False, + } + ) + + # Non internal user -> skip logic, do nothing + def test_01_non_internal_user_ignored(self): + self.authenticate(self.test_portal_user.login, self.test_portal_user.login) + response = self.url_open("/my") + cookie_header = response.headers.get("Set-Cookie", "") + self.assertNotIn( + "color_scheme", + cookie_header, + "Color scheme logic should not run for non-internal users", + ) + + # No user preference, no cookie -> set light + def test_02_no_user_settings_no_cookie(self): + self.opener.cookies.clear() + self.authenticate(self.test_internal_user.login, self.test_internal_user.login) + response = self.url_open("/odoo") + cookie_header = response.headers.get("Set-Cookie", "") + self.assertIn("color_scheme=light", cookie_header) + self.assertEqual(self.opener.cookies.get("color_scheme"), "light") + + # No user preference, light cookie -> do nothing + def test_03_no_user_settings_light_cookie(self): + self.authenticate(self.test_internal_user.login, self.test_internal_user.login) + self.opener.cookies.set("color_scheme", "light", domain=HOST, path="/") + response = self.url_open("/odoo") + cookie_header = response.headers.get("Set-Cookie", "") + self.assertNotIn( + "color_scheme", + cookie_header, + "The server should not set the cookie if already exists and is correct", + ) + self.assertEqual(self.opener.cookies.get("color_scheme"), "light") + + # User dark, cookie light -> set dark + def test_04_user_dark_cookie_light(self): + self.test_internal_user.write( + { + "dark_mode": True, + "dark_mode_device_dependent": False, + } + ) + self.authenticate(self.test_internal_user.login, self.test_internal_user.login) + self.opener.cookies.set("color_scheme", "light", domain=HOST, path="/") + response = self.url_open("/odoo") + cookie_header = response.headers.get("Set-Cookie", "") + self.assertIn("color_scheme=dark", cookie_header) + self.assertEqual(self.opener.cookies.get("color_scheme"), "dark") + + # User dark, cookie dark -> do nothing + def test_05_user_dark_cookie_dark(self): + self.test_internal_user.write( + { + "dark_mode": True, + "dark_mode_device_dependent": False, + } + ) + self.authenticate(self.test_internal_user.login, self.test_internal_user.login) + self.opener.cookies.set("color_scheme", "dark", domain=HOST, path="/") + response = self.url_open("/odoo") + cookie_header = response.headers.get("Set-Cookie", "") + self.assertNotIn( + "color_scheme", + cookie_header, + "The server should not set the cookie if already exists and is correct", + ) + self.assertEqual(self.opener.cookies.get("color_scheme"), "dark") + + # User dev dep + dark, browser none, cookie none -> do nothing + def test_06_user_dev_dep_browser_none_cookie_none(self): + self.test_internal_user.write( + { + "dark_mode": True, + "dark_mode_device_dependent": True, + } + ) + self.authenticate(self.test_internal_user.login, self.test_internal_user.login) + headers = {"Sec-CH-Prefers-Color-Scheme": None} + response = self.url_open("/odoo", headers=headers) + cookie_header = response.headers.get("Set-Cookie", "") + # This also makes sure that device dependent is overruling regular dark mode + self.assertNotIn( + "color_scheme", + cookie_header, + "The server should not set the cookie as it will be set by client side js", + ) + + # User dev dep, browser light, cookie light -> do nothing + def test_07_user_dev_dep_browser_light_cookie_light(self): + self.test_internal_user.write( + { + "dark_mode": True, + "dark_mode_device_dependent": True, + } + ) + self.authenticate(self.test_internal_user.login, self.test_internal_user.login) + self.opener.cookies.set("color_scheme", "light", domain=HOST, path="/") + headers = {"Sec-CH-Prefers-Color-Scheme": "light"} + response = self.url_open("/odoo", headers=headers) + cookie_header = response.headers.get("Set-Cookie", "") + self.assertNotIn( + "color_scheme", + cookie_header, + "The server should not set the cookie if already exists and is correct", + ) + self.assertEqual(self.opener.cookies.get("color_scheme"), "light") + + # User dev dep, browser dark, cookie light -> set dark + def test_08_user_dev_dep_browser_dark_cookie_light(self): + self.test_internal_user.write( + { + "dark_mode": False, + "dark_mode_device_dependent": True, + } + ) + self.authenticate(self.test_internal_user.login, self.test_internal_user.login) + self.opener.cookies.set("color_scheme", "light", domain=HOST, path="/") + headers = {"Sec-CH-Prefers-Color-Scheme": "dark"} + response = self.url_open("/odoo", headers=headers) + cookie_header = response.headers.get("Set-Cookie", "") + self.assertIn("color_scheme=dark", cookie_header) + self.assertEqual(self.opener.cookies.get("color_scheme"), "dark") + + def test_09_vary_headers(self): + self.authenticate(self.test_internal_user.login, self.test_internal_user.login) + response = self.url_open("/odoo") + self.assertIn("Sec-CH-Prefers-Color-Scheme", response.headers.get("Vary", "")) + self.assertIn( + "Sec-CH-Prefers-Color-Scheme", response.headers.get("Accept-CH", "") + )