nicegui icon indicating copy to clipboard operation
nicegui copied to clipboard

Allow easy configuration for page background

Open rodja opened this issue 2 years ago • 25 comments

As discussed in #407 it would be nice to have the possibility to set the background color to ui.colors().

rodja avatar Feb 21 '23 04:02 rodja

Is anyone interested in implementing this? Should be fairly easy with the code provided in #407. It would be a good way to get yourself acquainted with the code base.

rodja avatar Feb 21 '23 06:02 rodja

Maybe something like this?

colors.js

export default {
  template: '<span style="display:none"></span>',
  mounted() {
    for (let name in this.$props) {
      document.body.style.setProperty("--q-" + name, this.$props[name]);
    };
    document.body.style.setProperty("background-color", this.$props['background-color']);
  },
  props: {
    primary: String,
    secondary: String,
    accent: String,
    positive: String,
    negative: String,
    info: String,
    warning: String,
    background-color: String,
  },
};

colors.py

from ..dependencies import register_component
from ..element import Element

register_component('colors', __file__, 'colors.js')


class Colors(Element):

    def __init__(self, *,
                 primary='#5898d4',
                 secondary='#26a69a',
                 accent='#9c27b0',
                 positive='#21ba45',
                 negative='#c10015',
                 info='#31ccec',
                 warning='#f2c037'
		 background-color='white') -> None:
        """Color Theming
        Sets the main colors (primary, secondary, accent, ...) used by `Quasar <https://quasar.dev/>`_.
        """
        super().__init__('colors')
        self._props['primary'] = primary
        self._props['secondary'] = secondary
        self._props['accent'] = accent
        self._props['positive'] = positive
        self._props['negative'] = negative
        self._props['info'] = info
        self._props['warning'] = warning
        self._props['background-color'] = background-color
        self.update()

Allen-Taylor avatar Feb 21 '23 14:02 Allen-Taylor

Looks good. Could you create a pull request? This would honor your contribution for all eternity :-)

rodja avatar Feb 21 '23 16:02 rodja

Looks good. Could you create a pull request? This would honor your contribution for all eternity :-)

It doesn't work, I'm looking for another solution. For some reason it isn't taking.

Allen-Taylor avatar Feb 21 '23 16:02 Allen-Taylor

Okay I found a solution, I don't know if it is the best. You only need to change the color.py file.

from ..dependencies import register_component
from ..element import Element
from .. import globals

register_component('colors', __file__, 'colors.js')

class Colors(Element):

    def __init__(self, *,
                 primary='#5898d4',
                 secondary='#26a69a',
                 accent='#9c27b0',
                 positive='#21ba45',
                 negative='#c10015',
                 info='#31ccec',
                 warning='#f2c037',
                 background='white') -> None:
        """Color Theming

        Sets the main colors (primary, secondary, accent, ...) used by `Quasar <https://quasar.dev/>`_.
        """
        super().__init__('colors')
        self._props['primary'] = primary
        self._props['secondary'] = secondary
        self._props['accent'] = accent
        self._props['positive'] = positive
        self._props['negative'] = negative
        self._props['info'] = info
        self._props['warning'] = warning
        self._set_background_color(background)
        self.update()
    
    def _set_background_color(self, code):
        globals.get_client().head_html += f"<style>body {{ background-color: {code} }}</style>" + '\n'

image

image

Allen-Taylor avatar Feb 21 '23 17:02 Allen-Taylor

I think it would be better to use the Quasar color palette and use an Enum of all the possible colors.

https://quasar.dev/style/color-palette#introduction

Allen-Taylor avatar Feb 21 '23 17:02 Allen-Taylor

What would you think about something like this?

image

image

from ..dependencies import register_component
from ..element import Element
from .. import globals

register_component('colors', __file__, 'colors.js')


class Colors(Element):

    def __init__(self, *,
                 primary='#5898d4',
                 secondary='#26a69a',
                 accent='#9c27b0',
                 positive='#21ba45',
                 negative='#c10015',
                 info='#31ccec',
                 warning='#f2c037',
                 background='white') -> None:
        """Color Theming

        Sets the main colors (primary, secondary, accent, ...) used by `Quasar <https://quasar.dev/>`_.
        """
        super().__init__('colors')
        self._props['primary'] = primary
        self._props['secondary'] = secondary
        self._props['accent'] = accent
        self._props['positive'] = positive
        self._props['negative'] = negative
        self._props['info'] = info
        self._props['warning'] = warning
        self._set_background(background)
        self.update()
    
    def _set_background(self, color):
        globals.get_client().head_html += f"""<body class={color.value}></body>""" + '\n'


Allen-Taylor avatar Feb 21 '23 18:02 Allen-Taylor

Interesting. Its a bit frustrating that Quasar does not provide it's colors for normal css (eg. background-color). Something like

<style>body {background-color: blue-4; }</style>

does not work. I guess that's why you suggested the introduction of QuasarColors. This may affect #117 and specifically the suggested implementation from PR #364. I would like to not mix the two topics if possible. Can we not make the colors available in css in some way? Or maybe we can lookup the color somehow in the ui.colors implementation and then internally set the hex code according to the provided string?

rodja avatar Feb 21 '23 18:02 rodja

Interesting. Its a bit frustrating that Quasar does not provide it's colors for normal css (eg. background-color). Something like

<style>body {background-color: blue-4; }</style>

does not work. I guess that's why you suggested the introduction of QuasarColors. This may affect #117 and specifically the suggested implementation from PR #364. I would like to not mix the two topics if possible. Can we not make the colors available in css in some way? Or maybe we can lookup the color somehow in the ui.colors implementation and then internally set the hex code according to the provided string?

We can have a dict that returns the hex value for the Quasar colors in the _set_background() function.

We can also have a regex that checks for RGB or HEX format so it's super flexible.

The enum can be inside the colors.py.

Allen-Taylor avatar Feb 21 '23 18:02 Allen-Taylor

Sounds good

rodja avatar Feb 21 '23 20:02 rodja

How about this?

from nicegui import ui

ui.colors(background="red-14") # works
ui.colors(background="#f44336") # works
ui.colors(background="rgb(244,67,54)")  # works
ui.colors(background="pizza") # fails with no errors 

with ui.card().classes("m-auto").style("width: 500px"):
    ui.label("Background Test").classes("text-h3 m-auto")

ui.run()

from ..dependencies import register_component
from ..element import Element
from .. import globals
import re

rgb_pattern = re.compile(r'^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$')
hex_pattern = re.compile(r'^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$')

register_component('colors', __file__, 'colors.js')


class Colors(Element):

    def __init__(self, *,
                 primary='#5898d4',
                 secondary='#26a69a',
                 accent='#9c27b0',
                 positive='#21ba45',
                 negative='#c10015',
                 info='#31ccec',
                 warning='#f2c037',
                 background='white') -> None:
        """Color Theming

        Sets the main colors (primary, secondary, accent, ...) used by `Quasar <https://quasar.dev/>`_.
        """
        super().__init__('colors')
        self._props['primary'] = primary
        self._props['secondary'] = secondary
        self._props['accent'] = accent
        self._props['positive'] = positive
        self._props['negative'] = negative
        self._props['info'] = info
        self._props['warning'] = warning
        self._set_background(background)
        self.update()

    def _set_background(self, color):
        if color is not None:
            if rgb_pattern.match(color):
                globals.get_client().head_html += f"<style>body {{ background-color: {color} }}</style>" + '\n'
            elif hex_pattern.match(color):
                globals.get_client().head_html += f"<style>body {{ background-color: {color} }}</style>" + '\n'
            else:
                globals.get_client().head_html += f"<body class=bg-{color}></body>" + '\n'



Allen-Taylor avatar Feb 21 '23 20:02 Allen-Taylor

does this work when dark theme is True ?

morningstarsabrina avatar Feb 22 '23 07:02 morningstarsabrina

does this work when dark theme is True ?

That's a great question. I'll test it out later.

Allen-Taylor avatar Feb 22 '23 07:02 Allen-Taylor

when I tried ui.add_head_html('<style>body {background-color: #81D4FA; }</style>') I had to make dark theme False

morningstarsabrina avatar Feb 22 '23 08:02 morningstarsabrina

when I tried ui.add_head_html('<style>body {background-color: #81D4FA; }</style>') I had to make dark theme False

So dark mode overrides it? That is fine. There is a Quasar stylus variable that controls dark mode background color.

Allen-Taylor avatar Feb 22 '23 08:02 Allen-Taylor

when I tried ui.add_head_html('<style>body {background-color: #81D4FA; }</style>') I had to make dark theme False

So dark mode overrides it? That is fine. There is a Quasar stylus variable that controls dark mode background color.

yes

morningstarsabrina avatar Feb 22 '23 09:02 morningstarsabrina

Sounds good

Is this fine? It accepts the rgb(), hex and quasar formats. Also, if there is a incorrect color value, it doesn't error.

    def _set_background(self, color):
        if color is not None:
            if rgb_pattern.match(color):
                globals.get_client().head_html += f"<style>body {{ background-color: {color} }}</style>" + '\n'
            elif hex_pattern.match(color):
                globals.get_client().head_html += f"<style>body {{ background-color: {color} }}</style>" + '\n'
            else:
                globals.get_client().head_html += f"<body class=bg-{color}></body>" + '\n'

Allen-Taylor avatar Feb 22 '23 10:02 Allen-Taylor

I have some concerns about this approach:

  • Until now, ui.colors only manipulates CSS variables. The background color would be quite an exception. But ok, as long as the user doesn't need to known such implementation details, why not.
  • The colors in ui.colors should behave consistently. But now some colors "auto-detect" CSS vs. Quasar classes, others don't. That's unexpected from the user's perspective.
  • Adding a body tag into the head tag (what is done in the else block) is invalid HTML.
  • Toggling from "red-4" to "#ffffff" does not work, because the Quasar class bg-red-4 takes precedence over the style definition. But the user might want to change the background color programmatically.
  • Setting a CSS color name does not work. For the regex (and the user!) there is not even a chance to distinguish between CSS "red" and Quasar's "red". But the color values are different and all other colors accept CSS color names. In my point of view this makes the "auto-detection" of CSS names vs. Quasar classes impossible.

I even wonder if extending ui.colors is the right approach. Next time the user might want to add a background image and we need a new way to access the body tag. On the other hand we already can access the page layout and modify it with .style(), .classes, and .props(). This is technically not the same like styling the body tag, but the results are similar.

Long story short: Let's re-think whether and in which way we want to extend ui.colors, or if there is another more generic way to style the HTML body.

falkoschindler avatar Feb 23 '23 19:02 falkoschindler

I have some concerns about this approach:

  • Until now, ui.colors only manipulates CSS variables. The background color would be quite an exception. But ok, as long as the user doesn't need to known such implementation details, why not.
  • The colors in ui.colors should behave consistently. But now some colors "auto-detect" CSS vs. Quasar classes, others don't. That's unexpected from the user's perspective.
  • Adding a body tag into the head tag (what is done in the else block) is invalid HTML.
  • Toggling from "red-4" to "#ffffff" does not work, because the Quasar class bg-red-4 takes precedence over the style definition. But the user might want to change the background color programmatically.
  • Setting a CSS color name does not work. For the regex (and the user!) there is not even a chance to distinguish between CSS "red" and Quasar's "red". But the color values are different and all other colors accept CSS color names. In my point of view this makes the "auto-detection" of CSS names vs. Quasar classes impossible.

I even wonder if extending ui.colors is the right approach. Next time the user might want to add a background image and we need a new way to access the body tag. On the other hand we already can access the page layout and modify it with .style(), .classes, and .props(). This is technically not the same like styling the body tag, but the results are similar.

Long story short: Let's re-think whether and in which way we want to extend ui.colors, or if there is another more generic way to style the HTML body.

Great points. For now, what is the best way to change the background color?

Allen-Taylor avatar Feb 23 '23 20:02 Allen-Taylor

@Allen-Taylor See #407 for a discussion and two approaches with the current version of NiceGUI. Which one is better depends on the application, I guess. Personally I would simply stick with the accepted answer:

ui.add_head_html('<style>body {background-color: #81D4FA; }</style>')

falkoschindler avatar Feb 24 '23 11:02 falkoschindler

Is there a special reason why the background control is under ui.colors?

Can't we implement a ui.background element to control everything related to the app's background?

The ui.background would be specialized, we could have different 'initiators', like: color, image or video

Diegiwg avatar Feb 24 '23 17:02 Diegiwg

I like the approach @Diegiwg has taken with PR #433. But it does not support setting Quasar colors yet. Instead of duplicating the palette in Python we could use hex_color = await ui.run_javascript(f'Quasar.colors.getPaletteColor("{color}")') to compute the background color on the fly. It seems that the JS function getPaletteColor returns #000000 if it's not a Quasar color. So hex_color = hex_color if hex_color != '#000000' else color should also enable passing any other css color.

rodja avatar Feb 25 '23 08:02 rodja

I reviewed @Diegiwg's PR #433 and came up with an alternative implementation #448.

falkoschindler avatar Mar 01 '23 07:03 falkoschindler

I gave both PR's some thought. While #411 is more generic it tries to mimic the behaviour of other elements. But it's a singleton (it will not create a new element like the other ones but just gives you access to the body tag). Thereby it behaves quite strange. For example

with ui.background():
   ui.label('this is not shown')

I think deriving from ui.element is not the best approach. This thing is not an element. In my opinion it should have an different API.

Also the name is not perfect in my opinion. You can use it to set padding or font colors for the whole document which is nice but does not relate well to "background".

We have some other topics which may relate to this:

  • existing functions which access/manipulate the body-tag
    • ui.colors
    • ui.add_body_html
  • #117
  • #364

I still can't wrap my head around this all.

rodja avatar Mar 07 '23 09:03 rodja

We discussed this issue once more and noticed the following shortcomings of #411:

  1. The ui.background element suggests to be a regular Element, but you can't insert child elements, for example.
  2. The name "background" is not optimal, because you can affect more than just the background.
  3. The background element is limited to manipulating the body tag. But one might want to access the head or html tags as well.
  4. The current implementation does not work on individual pages built with a ui.page decorator, because the background element is a server-wide singleton.

Therefore we came up with the following new idea as an extension of #411:

  • We introduce a ui.query(selector: str) that returns a Query element which basically holds its CSS selector string.
  • Multiple queries on the same client with the same selector string return the same Query element.
  • A Query has methods add_classes, remove_classes, add_style, remove_style, add_props, remove_props, append(html: str), prepend(html: str), and maybe more to modify the queried HTML elements.
  • Once ui.query is implemented, ui.colors can be simplified using ui.query. It can also allow setting the background color so that we can control all colors with one function.

The main challenge is managing the query elements, i.e. avoid duplicates or leftovers after a client disconnects. The mechanism for regular elements would work, but - as mentioned above - we would like ui.query not to be a regular element. Maybe it's a function that looks into the current client's elements dictionary and wraps it into a Query object.

falkoschindler avatar Mar 07 '23 11:03 falkoschindler