Source code for pysepal.frontend.styles

"""Helpers to customize the display of sepal-ui widgets and maps."""

from pathlib import Path
from types import SimpleNamespace
from typing import Tuple

import ipyvuetify as v
from IPython.display import HTML, Javascript, display
from ipyvuetify._version import semver
from ipywidgets import Widget
from traitlets import Bool, HasTraits, Unicode, link

import pysepal.scripts.utils as su
from pysepal.conf import config

################################################################################
# access the folders where style information is stored (layers, widgets)
#

# the colors are set using tables as follow.
# 1 (True): dark theme
# 0 (false): light theme
JSON_DIR: Path = Path(__file__).parent / "json"
"The path to the json style folder"

CSS_DIR: Path = Path(__file__).parent / "css"
"The path to the css style folder"

JS_DIR: Path = Path(__file__).parent / "js"
"The path to the js style folder"

################################################################################
# define all the colors that we want to use in the theme
#


TYPES: Tuple[str, ...] = (
    "info",
    "primary",
    "primary_contarst",
    "secondary",
    "secondary_contrast",
    "accent",
    "error",
    "success",
    "warning",
    "anchor",
    "main",
    "darker",
    "bg",
    "menu",
)
"The different types defined by ipyvuetify"


[docs] class ThemeColors(Widget): _model_name = Unicode("ThemeColorsModel").tag(sync=True) _model_module = Unicode("jupyter-vuetify").tag(sync=True) _view_module_version = Unicode(semver).tag(sync=True) _model_module_version = Unicode(semver).tag(sync=True) _theme_name = Unicode().tag(sync=True) primary = Unicode().tag(sync=True) primary_contrast = Unicode().tag(sync=True) secondary = Unicode().tag(sync=True) secondary_contrast = Unicode().tag(sync=True) accent = Unicode().tag(sync=True) error = Unicode().tag(sync=True) info = Unicode().tag(sync=True) success = Unicode().tag(sync=True) warning = Unicode().tag(sync=True) anchor = Unicode(None, allow_none=True).tag(sync=True) main = Unicode().tag(sync=True) bg = Unicode().tag(sync=True) menu = Unicode().tag(sync=True) darker = Unicode().tag(sync=True)
dark_theme_colors = ThemeColors( _theme_name="dark", primary="#76591e", primary_contrast="#bf8f2d", # a bit lighter than the primary color secondary="#363e4f", secondary_contrast="#5d76ab", error="#a63228", info="#c5c6c9", success="#3f802a", warning="#b8721d", accent="#272727", anchor="#f3f3f3", main="#24221f", darker="#1a1a1a", bg="#121212", menu="#424242", ) light_theme_colors = ThemeColors( _theme_name="light", primary="#5BB624", primary_contrast="#76b353", accent="#f3f3f3", anchor="#f3f3f3", secondary="#2199C4", secondary_contrast="#5d76ab", success=v.theme.themes.light.success, info=v.theme.themes.light.info, warning=v.theme.themes.light.warning, error=v.theme.themes.light.error, main="#2196f3", # used by appbar and versioncard darker="#ffffff", # used for the navdrawer bg="#FFFFFF", menu="#FFFFFF", ) DARK_THEME = {k: v for k, v in dark_theme_colors.__dict__["_trait_values"].items() if k in TYPES} "colors used for the dark theme" LIGHT_THEME = {k: v for k, v in light_theme_colors.__dict__["_trait_values"].items() if k in TYPES} "colors used for the light theme" # override the default theme with the custom ones v.theme.themes.light = light_theme_colors v.theme.themes.dark = dark_theme_colors ################################################################################ # define classes and method to make the application responsive #
[docs] def get_theme() -> str: """Get theme name from the config file (default to dark). Returns: The theme to use """ return config.get("sepal-ui", "theme", fallback="dark")
[docs] class SepalColor(HasTraits, SimpleNamespace): _dark_theme: Bool = Bool(True if get_theme() == "dark" else False).tag(sync=True) "Whether to use dark theme or not. By changing this value, the theme value will be stored in the conf file. Is only intended to be accessed in development mode."
[docs] def __init__(self) -> None: """Custom simple name space to store and access to the pysepal colors and with a magic method to display theme.""" link((self, "_dark_theme"), (v.theme, "dark")) v.theme.observe(lambda *x: self.set_colors(), "dark") self.set_colors()
[docs] def set_colors(self) -> None: """Set the current hexadecimal color in the object.""" # Get get current theme name self.theme_name = "dark" if self._dark_theme else "light" # Save "new" theme in configuration file su.set_config("theme", self.theme_name) self.colors_dict = DARK_THEME if self._dark_theme else LIGHT_THEME SimpleNamespace.__init__(self, **self.colors_dict)
def _repr_html_(self, *_) -> str: """Rich display of the color palette in an HTML frontend.""" s = 60 html = f"<h3>Current theme: {self.theme_name}</h3><table>" items = {k: v for k, v in self.colors_dict.items()}.items() for name, color in items: c = su.to_colors(color) html += f""" <th> <svg width='{s}' height='{s}'> <rect width='{s}' height='{s}' style='fill:{c}; stroke-width:1;stroke:rgb(255,255,255)'/> </svg> </th> """ html += "</tr><tr>" html += "".join([f"<td>{name}</br>{color}</td>" for name, color in items]) html += "</tr></table>" return html
# load custom styling of pysepal sepal_ui_css = HTML(f"<style>{(CSS_DIR / 'custom.css').read_text()}</style>") # load fa-6 fa_css = HTML( '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css"/>' ) # create a small hack to remove fontawesome from the html output clean_fa_js = Javascript((JS_DIR / "fontawesome.js").read_text()) # display all display(sepal_ui_css, fa_css, clean_fa_js)