diff --git a/sepal_ui/frontend/css/custom.css b/sepal_ui/frontend/css/custom.css index d8eed29a..6dbda56f 100644 --- a/sepal_ui/frontend/css/custom.css +++ b/sepal_ui/frontend/css/custom.css @@ -19,10 +19,6 @@ main.v-content { padding-top: 0px !important; } -/* remove all the backgrounds from the controls and widget to be colored naturelly by the map */ -.leaflet-control-container .vuetify-styles .v-application { - background: rgb(0, 0, 0, 0); -} .v-alert__wrapper .progress { background-color: transparent; } diff --git a/sepal_ui/frontend/json/file_icons.json b/sepal_ui/frontend/json/file_icons.json index eef512cf..3cf63e6b 100644 --- a/sepal_ui/frontend/json/file_icons.json +++ b/sepal_ui/frontend/json/file_icons.json @@ -1,21 +1,21 @@ { - "": { "color": ["#ffca28", "#ffc107"], "icon": "fa-regular fa-folder" }, - ".csv": { "color": ["#4caf50", "#00c853"], "icon": "fa-solid fa-table" }, - ".txt": { "color": ["#4caf50", "#00c853"], "icon": "fa-solid fa-table" }, - ".tif": { "color": ["#9c27b0", "#673ab7"], "icon": "fa-regular fa-image" }, - ".tiff": { "color": ["#9c27b0", "#673ab7"], "icon": "fa-regular fa-image" }, - ".vrt": { "color": ["#9c27b0", "#673ab7"], "icon": "fa-regular fa-image" }, + "": { "color": "primary_contrast", "icon": "fa-regular fa-folder" }, + ".csv": { "color": "secondary_contrast", "icon": "fa-solid fa-table" }, + ".txt": { "color": "secondary_contrast", "icon": "fa-solid fa-table" }, + ".tif": { "color": "secondary_contrast", "icon": "fa-regular fa-image" }, + ".tiff": { "color": "secondary_contrast", "icon": "fa-regular fa-image" }, + ".vrt": { "color": "secondary_contrast", "icon": "fa-regular fa-image" }, ".shp": { - "color": ["#9c27b0", "#673ab7"], + "color": "secondary_contrast", "icon": "fa-solid fa-vector-square" }, ".geojson": { - "color": ["#9c27b0", "#673ab7"], + "color": "secondary_contrast", "icon": "fa-solid fa-vector-square" }, - "DEFAULT": { "color": ["#00bcd4", "#03a9f4"], "icon": "fa-regular fa-file" }, + "DEFAULT": { "color": "anchor", "icon": "fa-regular fa-file" }, "PARENT": { - "color": ["#424242", "#ffffff"], + "color": "anchor", "icon": "fa-regular fa-folder-open" } } diff --git a/sepal_ui/frontend/json/progress_bar.json b/sepal_ui/frontend/json/progress_bar.json deleted file mode 100644 index 50696a42..00000000 --- a/sepal_ui/frontend/json/progress_bar.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "color": ["#2196f3", "#3f51b5"] -} diff --git a/sepal_ui/frontend/styles.py b/sepal_ui/frontend/styles.py index 679c9ade..696980a8 100644 --- a/sepal_ui/frontend/styles.py +++ b/sepal_ui/frontend/styles.py @@ -2,11 +2,13 @@ from pathlib import Path from types import SimpleNamespace -from typing import Dict, Tuple +from typing import Tuple import ipyvuetify as v from IPython.display import HTML, Javascript, display -from traitlets import Bool, HasTraits, observe +from ipyvuetify._version import semver +from ipywidgets import Widget +from traitlets import Bool, HasTraits, Unicode, link import sepal_ui.scripts.utils as su from sepal_ui.conf import config @@ -31,48 +33,101 @@ # define all the colors that we want to use in the theme # -DARK_THEME: Dict[str, str] = { - "primary": "#b3842e", - "accent": "#a1458e", - "secondary": "#324a88", - "success": "#3f802a", - "info": "#79b1c9", - "warning": "#b8721d", - "error": "#a63228", - "main": "#24221f", # Are not traits - "darker": "#1a1a1a", # Are not traits - "bg": "#121212", # Are not traits - "menu": "#424242", # Are not traits -} -"colors used for the dark theme" - -LIGHT_THEME: Dict[str, str] = { - "primary": v.theme.themes.light.primary, - "accent": v.theme.themes.light.accent, - "secondary": v.theme.themes.light.secondary, - "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": "#2e7d32", - "darker": "#005005", - "bg": "#FFFFFF", - "menu": "#FFFFFF", -} -"colors used for the light 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" + +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 # @@ -92,47 +147,29 @@ 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." - new_colors: dict = {} - "Dictionary with name:color structure." - - @observe("_dark_theme") - def __init__(self, *_, **new_colors) -> None: - """Custom simple name space to store and access to the sepal_ui colors and with a magic method to display theme. + def __init__(self) -> None: + """Custom simple name space to store and access to the sepal_ui 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") - Args: - **new_colors (optional): the new colors to set in hexadecimal as a dict (experimental) - """ - # set vuetify theme - v.theme.dark = self._dark_theme + self.set_colors() + 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.kwargs = DARK_THEME if self._dark_theme else LIGHT_THEME - self.kwargs = new_colors or self.kwargs - - # Even if the theme.themes.dark_theme trait could trigger the change on all elms - # we have to replace the default values every time: - theme = getattr(v.theme.themes, self.theme_name) - - # TODO: Would be awesome to find a way to create traits for the new colors and - # assign them here directly - [setattr(theme, color_name, color) for color_name, color in self.kwargs.items()] - - # Now instantiate the namespace - SimpleNamespace.__init__(self, **self.kwargs) - HasTraits.__init__(self) - - return + 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"

Current theme: {self.theme_name}

" - items = {k: v for k, v in self.kwargs.items()}.items() + items = {k: v for k, v in self.colors_dict.items()}.items() for name, color in items: c = su.to_colors(color) diff --git a/sepal_ui/mapping/inspector_control.py b/sepal_ui/mapping/inspector_control.py index 85e02bcf..35931965 100644 --- a/sepal_ui/mapping/inspector_control.py +++ b/sepal_ui/mapping/inspector_control.py @@ -1,6 +1,5 @@ """Customized ``Control`` to display the value of all available layers on a specific pixel.""" -import json from pathlib import Path from typing import Optional, Sequence, Union @@ -15,9 +14,7 @@ from shapely import geometry as sg from traitlets import Bool -from sepal_ui import color from sepal_ui import sepalwidgets as sw -from sepal_ui.frontend import styles as ss from sepal_ui.mapping.layer import EELayer from sepal_ui.mapping.menu_control import MenuControl from sepal_ui.message import ms @@ -61,11 +58,9 @@ def __init__(self, m: Map, open_tree: bool = True, **kwargs) -> None: # create a loading to place it on top of the card. It will always be visible # even when the card is scrolled - p_style = json.loads((ss.JSON_DIR / "progress_bar.json").read_text()) self.w_loading = sw.ProgressLinear( indeterminate=False, - background_color=color.menu, - color=p_style["color"][v.theme.dark], + background_color="menu", ) # set up the content diff --git a/sepal_ui/mapping/layers_control.py b/sepal_ui/mapping/layers_control.py index 326888b4..a5e1a090 100644 --- a/sepal_ui/mapping/layers_control.py +++ b/sepal_ui/mapping/layers_control.py @@ -1,15 +1,12 @@ """Extend functionalities of the ipyleaflet layer control.""" -import json + from types import SimpleNamespace from typing import Optional -import ipyvuetify as v from ipyleaflet import GeoJSON, Map, TileLayer from ipywidgets import link -from sepal_ui import color from sepal_ui import sepalwidgets as sw -from sepal_ui.frontend import styles as ss from sepal_ui.mapping.menu_control import MenuControl from sepal_ui.message import ms @@ -82,7 +79,7 @@ def __init__(self, layer: TileLayer) -> None: """ # create the checkbox, by default layer are visible self.w_checkbox = sw.SimpleCheckbox( - v_model=True, small=True, label=layer.name, color=color.primary + v_model=True, small=True, label=layer.name, color="primary" ) kwargs = {"style": "width: 10%;", "tag": "td"} checkbox_cell = sw.Html(children=[self.w_checkbox], **kwargs) @@ -128,7 +125,7 @@ def __init__(self, layer: TileLayer) -> None: """ # create the checkbox, by default layer are visible self.w_checkbox = sw.SimpleCheckbox( - v_model=True, small=True, label=layer.name, color=color.primary + v_model=True, small=True, label=layer.name, color="primary" ) kwargs = {"style": "width: 10%;", "tag": "td"} checkbox_cell = sw.Html(children=[self.w_checkbox], **kwargs) @@ -170,11 +167,9 @@ def __init__(self, m: Map, **kwargs) -> None: # create a loading to place it on top of the card. It will always be visible # even when the card is scrolled - p_style = json.loads((ss.JSON_DIR / "progress_bar.json").read_text()) self.w_loading = sw.ProgressLinear( indeterminate=False, - background_color=color.menu, - color=p_style["color"][v.theme.dark], + background_color="menu", ) self.tile = sw.Tile("nested", "") diff --git a/sepal_ui/mapping/map_btn.py b/sepal_ui/mapping/map_btn.py index 5266d7bf..ee84cea1 100644 --- a/sepal_ui/mapping/map_btn.py +++ b/sepal_ui/mapping/map_btn.py @@ -2,7 +2,6 @@ import ipyvuetify as v -from sepal_ui import color from sepal_ui import sepalwidgets as sw @@ -24,9 +23,7 @@ def __init__(self, content: str, **kwargs) -> None: content = content[: min(3, len(content))].upper() # some parameters are overloaded to match the map requirements - kwargs["color"] = "text-color" kwargs["outlined"] = True - kwargs["style_"] = f"background: {color.bg};" kwargs["children"] = [content] kwargs["icon"] = False kwargs.setdefault("class_", "v-map-btn") diff --git a/sepal_ui/mapping/menu_control.py b/sepal_ui/mapping/menu_control.py index b1c74c2f..6b220dd3 100644 --- a/sepal_ui/mapping/menu_control.py +++ b/sepal_ui/mapping/menu_control.py @@ -7,7 +7,6 @@ from traitlets import Bool, Int from typing_extensions import Self -from sepal_ui import color from sepal_ui import sepalwidgets as sw from sepal_ui.mapping.map_btn import MapBtn @@ -170,7 +169,6 @@ def activate(self, *args) -> None: """Change the background color of the btn with respect to the status.""" # grey is contrasted enough for both light and dark theme # could be customized further if requested - bg_color = "gray" if self.menu.v_model is True else color.bg - self.menu.v_slots[0]["children"].style_ = f"background: {bg_color};" + self.menu.v_slots[0]["children"].style_ = "background: gray;" if self.menu.v_model else "" return diff --git a/sepal_ui/mapping/sepal_map.py b/sepal_ui/mapping/sepal_map.py index 0928dee6..7e568032 100644 --- a/sepal_ui/mapping/sepal_map.py +++ b/sepal_ui/mapping/sepal_map.py @@ -150,6 +150,17 @@ def __init__( self._id = "".join(random.choice(string.ascii_lowercase) for i in range(6)) self.add_class(self._id) + v.theme.observe(self._on_theme_change, "dark") + + def _on_theme_change(self, _) -> None: + """Change the url of the basemaps.""" + light = "https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png" + dark = "https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png" + + for layer in self.layers: + if layer.base and layer.url in [light, dark]: + layer.url = dark if v.theme.dark is True else light + @deprecated(version="2.8.0", reason="the local_layer stored list has been dropped") def _remove_local_raster(self, local_layer: str) -> Self: """Remove local layer from memory. diff --git a/sepal_ui/sepalwidgets/app.py b/sepal_ui/sepalwidgets/app.py index e99540e5..106e0494 100644 --- a/sepal_ui/sepalwidgets/app.py +++ b/sepal_ui/sepalwidgets/app.py @@ -24,7 +24,6 @@ from traitlets import link, observe from typing_extensions import Self -from sepal_ui import color from sepal_ui.frontend.resize_trigger import ResizeTrigger, rt from sepal_ui.frontend.styles import get_theme from sepal_ui.message import ms @@ -99,7 +98,7 @@ def __init__(self, translator: Optional[Translator] = None, **kwargs) -> None: self.language_list = v.List( dense=True, flat=True, - color=color.menu, + color="menu", v_model=True, max_height="300px", style_="overflow: auto; border-radius: 0 0 0 0;", @@ -206,19 +205,21 @@ def __init__(self, **kwargs) -> None: def toggle_theme(self, *args) -> None: """Toggle the btn icon from dark to light and adapt the configuration file.""" # use a cycle to go through the themes + theme_cycle = cycle(self.THEME_ICONS.keys()) next(t for t in theme_cycle if t == self.theme) self.theme = next(t for t in theme_cycle) + v.theme.dark = self.theme == "dark" + # change icon - self.color = "info" self.children[0].children = [self.THEME_ICONS[self.theme]] # change the parameter file su.set_config("theme", self.theme) - # trigger other events by changing v_model - self.v_model = self.theme + # # trigger other events by changing v_model + # self.v_model = self.theme return @@ -251,7 +252,7 @@ def __init__( """ self.toggle_button = v.Btn( icon=True, - children=[v.Icon(class_="white--text", children=["fa-solid fa-ellipsis-v"])], + children=[v.Icon(class_="white--text", children=["fa-solid fa-bars"])], ) self.title = v.ToolbarTitle(children=[title]) @@ -260,8 +261,8 @@ def __init__( self.theme = ThemeSelect() # set the default parameters - kwargs.setdefault("color", color.main) kwargs.setdefault("class_", "white--text") + kwargs.setdefault("color", "main") kwargs.setdefault("dense", True) kwargs["app"] = True kwargs["children"] = [ @@ -328,8 +329,8 @@ def __init__( icon = icon if icon else "fa-regular fa-folder" children = [ - v.ListItemAction(children=[v.Icon(class_="white--text", children=[icon])]), - v.ListItemContent(children=[v.ListItemTitle(class_="white--text", children=[title])]), + v.ListItemAction(children=[v.Icon(children=[icon])]), + v.ListItemContent(children=[v.ListItemTitle(children=[title])]), ] # set default parameters @@ -351,7 +352,7 @@ def __init__( # cannot be set as a class member because it will be shared with all # the other draweritems. self.alert_badge = v.ListItemAction( - children=[v.Icon(children=["fa-solid fa-circle"], x_small=True, color="red")] + children=[v.Icon(children=["fa-solid fa-circle"], x_small=True, color="error")] ) if model: @@ -465,7 +466,7 @@ def __init__( v_slots = [{"name": "append", "children": [version_card]}] children = [ - v.List(dense=True, children=self.items), + v.List(dense=True, class_="pa-0", children=self.items), v.Divider(), v.List(dense=True, children=code_link), ] @@ -473,7 +474,7 @@ def __init__( # set default parameters kwargs.setdefault("v_model", True) kwargs["app"] = True - kwargs.setdefault("color", color.darker) + kwargs.setdefault("color", "darker") kwargs["children"] = children kwargs.setdefault("v_slots", v_slots) @@ -524,8 +525,7 @@ def __init__(self, text: str = "", **kwargs) -> None: text = text if text != "" else "SEPAL \u00A9 {}".format(datetime.today().year) # set default parameters - kwargs.setdefault("color", color.main) - kwargs.setdefault("class_", "white--text") + kwargs.setdefault("color", "main") kwargs["app"] = True kwargs["children"] = [text] @@ -607,7 +607,7 @@ def __init__( app_children.append(self.footer) # create a negative overlay to force the background color - bg = v.Overlay(color=color.bg, opacity=1, style_="transition:unset", z_index=-1) + bg = v.Overlay(color="bg", opacity=1, style_="transition:unset", z_index=-1) # set default parameters kwargs.setdefault("_metadata", {"mount_id": "content"}) @@ -625,7 +625,6 @@ def __init__( # add js event self.appBar.locale.observe(self._locale_info, "value") - self.appBar.theme.observe(self._theme_info, "v_model") def show_tile(self, name: str) -> Self: """Select the tile to display when the app is launched. @@ -706,14 +705,6 @@ def _locale_info(self, change: dict) -> None: return - def _theme_info(self, change: dict) -> None: - """Display information about the theme change.""" - if change["new"] != "": - msg = ms.theme.change.format(change["new"]) - self.add_banner(msg) - - return - def _remove_banner(self, change: dict) -> None: """Remove banner and adapt display of the others. @@ -792,14 +783,15 @@ def VersionCard(repo_folder: str = Path.cwd()) -> Optional[v.Card]: w_version = v.Card( class_="text-center", + height="48px", tile=True, - color=color.main, + color="accent", children=[ v.CardText( children=[ ms.widgets.navdrawer.changelog.version.format(app_version), w_changelog, - ] + ], ), ], ) diff --git a/sepal_ui/sepalwidgets/inputs.py b/sepal_ui/sepalwidgets/inputs.py index e85db44b..c2da3687 100644 --- a/sepal_ui/sepalwidgets/inputs.py +++ b/sepal_ui/sepalwidgets/inputs.py @@ -26,7 +26,6 @@ from traitlets import link, observe from typing_extensions import Self -from sepal_ui import color from sepal_ui.frontend import styles as ss from sepal_ui.message import ms from sepal_ui.scripts import decorator as sd @@ -249,16 +248,14 @@ def __init__( v_model="", ) - p_style = json.loads((ss.JSON_DIR / "progress_bar.json").read_text()) self.loading = v.ProgressLinear( indeterminate=False, - background_color=color.menu, - color=p_style["color"][v.theme.dark], + background_color="menu", ) self.file_list = v.List( dense=True, - color=color.menu, + color="menu", flat=True, v_model=True, max_height="300px", @@ -418,13 +415,13 @@ def _get_items(self) -> List[v.ListItem]: for el in list_dir: if el.is_dir(): icon = self.ICON_STYLE[""]["icon"] - color = self.ICON_STYLE[""]["color"][v.theme.dark] + color = self.ICON_STYLE[""]["color"] elif el.suffix in self.ICON_STYLE.keys(): icon = self.ICON_STYLE[el.suffix]["icon"] - color = self.ICON_STYLE[el.suffix]["color"][v.theme.dark] + color = self.ICON_STYLE[el.suffix]["color"] else: icon = self.ICON_STYLE["DEFAULT"]["icon"] - color = self.ICON_STYLE["DEFAULT"]["color"][v.theme.dark] + color = self.ICON_STYLE["DEFAULT"]["color"] children = [ v.ListItemAction(children=[v.Icon(color=color, children=[icon])]), @@ -447,7 +444,7 @@ def _get_items(self) -> List[v.ListItem]: v.ListItemAction( children=[ v.Icon( - color=self.ICON_STYLE["PARENT"]["color"][v.theme.dark], + color=self.ICON_STYLE["PARENT"]["color"], children=[self.ICON_STYLE["PARENT"]["icon"]], ) ] diff --git a/sepal_ui/sepalwidgets/tile.py b/sepal_ui/sepalwidgets/tile.py index 580e2c54..0558ac49 100644 --- a/sepal_ui/sepalwidgets/tile.py +++ b/sepal_ui/sepalwidgets/tile.py @@ -198,6 +198,16 @@ def __init__(self) -> None: This tile will have the "about_widget" id and "Disclaimer" title. """ + super().__init__("about_tile", "Disclaimer") + + self.card = v.Card(class_="pa-5", raised=True, xs12=True, children=[]) + self.set_disclaimer() + v.theme.observe(self.set_disclaimer, "dark") + + self.children = [self.card] + + def set_disclaimer(self, _=None) -> List[Markdown]: + """Rebuild the disclaimer element when the theme changes.""" # create the tile content on the fly disclaimer = " \n".join(ms.disclaimer.p) disclaimer += " \n" @@ -216,6 +226,4 @@ def __init__(self) -> None: # close the file disclaimer += "" - content = Markdown(disclaimer) - - super().__init__("about_tile", "Disclaimer", inputs=[content]) + self.card.children = [self.title] + [Markdown(disclaimer)] diff --git a/sepal_ui/sepalwidgets/widget.py b/sepal_ui/sepalwidgets/widget.py index f13f8eee..667089e6 100644 --- a/sepal_ui/sepalwidgets/widget.py +++ b/sepal_ui/sepalwidgets/widget.py @@ -19,7 +19,6 @@ from markdown import markdown from traitlets import link, observe -from sepal_ui import color from sepal_ui.model import Model from sepal_ui.sepalwidgets.sepalwidget import SepalWidget, Tooltip @@ -140,8 +139,8 @@ def __init__( # init the states default_states = { - "valid": ("Valid", color.success), - "non_valid": ("Not valid", color.error), + "valid": ("Valid", "success"), + "non_valid": ("Not valid", "error"), } self.states = default_states if not states else states diff --git a/sepal_ui/templates/panel_app/component/message/en/app.json b/sepal_ui/templates/panel_app/component/message/en/app.json index 17776219..bf2d88cb 100644 --- a/sepal_ui/templates/panel_app/component/message/en/app.json +++ b/sepal_ui/templates/panel_app/component/message/en/app.json @@ -4,7 +4,8 @@ "footer": "The sky is the limit \u00A9 {}", "banner": "This is a automatically generated application. Remove this banner once your application is ready.", "drawer_item": { - "about": "About" + "about": "About", + "widgets": "Widgets" } } } diff --git a/sepal_ui/templates/panel_app/component/tile/widget_tile.py b/sepal_ui/templates/panel_app/component/tile/widget_tile.py new file mode 100644 index 00000000..612a7751 --- /dev/null +++ b/sepal_ui/templates/panel_app/component/tile/widget_tile.py @@ -0,0 +1,96 @@ +"""A demo tile showing some of the most frequent widgets available in the sepal_ui library.""" + +import ipyvuetify as v +import sepal_ui.sepalwidgets as sw +from sepal_ui.mapping.sepal_map import SepalMap + + +class WidgetTile(sw.Card): + def __init__(self): + """A demo tile showing all the widgets available in the sepal_ui library.""" + # create the card + super().__init__(row=True, wrap=True, _metadata={"mount_id": "widget_tile"}) + + # Create widgets + + select = sw.Select( + label="Select", + v_model="option1", + items=["option1", "option2", "option3"], + outlined=True, + ) + + slider = sw.Slider( + label="Slider", + v_model=50, + max=100, + min=0, + thumb_label=True, + step=1, + ) + + btn = sw.Btn("Button", color="primary", outlined=True) + btn1 = sw.Btn("Button", color="secondary", outlined=True) + btn2 = sw.Btn("Button", color="primary") + btn3 = sw.Btn("Button", color="secondary") + file_input = sw.FileInput() + map_ = SepalMap(zoom=10) + + title = v.CardTitle(children=["test widgets tile"]) + + card = v.Card(class_="mb-4 pa-2", raised=True, xs12=True, children=[title, map_]) + + loading_card = v.Card( + class_="mb-6 pa-2", + raised=True, + xs12=True, + children=[title] + ["content " * 100], + loading=True, + ) + + warning_alert = sw.Alert().add_msg("This is a warning alert", type_="warning") + error_alert = sw.Alert().add_msg("This is an error alert", type_="error") + info_alert = sw.Alert().add_msg("This is an info alert", type_="info") + success_alert = sw.Alert().add_msg("This is a success alert", type_="success") + + # Add widgets to the layout + + self.children = [ + card, + v.Flex(class_="mb-2", xs12=True, lg6=True, xl4=True, children=[select]), + v.Flex(class_="mb-2", xs12=True, lg6=True, xl4=True, children=[file_input]), + v.Flex(class_="mb-2", xs12=True, lg6=True, xl4=True, children=[slider]), + v.Flex(class_="mb-2", xs12=True, lg6=True, xl4=True, children=[btn]), + v.Flex(class_="mb-2", xs12=True, lg6=True, xl4=True, children=[btn1]), + v.Flex(class_="mb-2", xs12=True, lg6=True, xl4=True, children=[btn2]), + v.Flex(class_="mb-2", xs12=True, lg6=True, xl4=True, children=[btn3]), + v.Flex( + class_="mb-2", + xs12=True, + lg6=True, + xl4=True, + children=[warning_alert], + ), + v.Flex( + class_="mb-2", + xs12=True, + lg6=True, + xl4=True, + children=[error_alert], + ), + v.Flex( + class_="mb-2", + xs12=True, + lg6=True, + xl4=True, + children=[info_alert], + ), + v.Flex( + class_="mb-2", + xs12=True, + lg6=True, + xl4=True, + children=[success_alert], + ), + loading_card, + ] diff --git a/sepal_ui/templates/panel_app/ui.ipynb b/sepal_ui/templates/panel_app/ui.ipynb index 67515fed..07a4358f 100644 --- a/sepal_ui/templates/panel_app/ui.ipynb +++ b/sepal_ui/templates/panel_app/ui.ipynb @@ -14,6 +14,7 @@ "outputs": [], "source": [ "from sepal_ui import sepalwidgets as sw\n", + "from component.tile.widget_tile import WidgetTile\n", "from component.message import cm" ] }, @@ -45,9 +46,9 @@ "source": [ "# load the partial files\n", "%run \"about_ui.ipynb\"\n", - "\n", + "widget_tile = WidgetTile()\n", "# Gather all the partial tiles that you created previously\n", - "app_content = [about_tile, disclaimer_tile]" + "app_content = [about_tile, disclaimer_tile, widget_tile]" ] }, { @@ -60,6 +61,7 @@ "# fmt: off\n", "items = [\n", " sw.DrawerItem(cm.app.drawer_item.about, \"fa-solid fa-question-circle\", card=\"about_tile\"),\n", + " sw.DrawerItem(cm.app.drawer_item.widgets, \"fa-solid fa-wrench\", card=\"widget_tile\"),\n", "]\n", "# fmt: on\n", "\n", @@ -115,7 +117,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.10.12" }, "voila": { "theme": "dark" diff --git a/tests/test_frontend/test_Frontend.py b/tests/test_frontend/test_Frontend.py index 322132df..e1ec4391 100644 --- a/tests/test_frontend/test_Frontend.py +++ b/tests/test_frontend/test_Frontend.py @@ -40,26 +40,26 @@ def test_conf() -> None: def test_repr_html() -> None: """Check the html representation of a color object.""" # Arrange - expected_title_dark = "

Current theme: dark

" - expected_dark = "primary
#b3842e" - # select dark theme color = SepalColor() color._dark_theme = True + expected_title_dark = "

Current theme: dark

" + expected_dark = f"primary
{color.primary}" + # read the html result and assert that they look like expected html = color._repr_html_().__str__() html = re.sub(r"[\n] [ ]+", "", html) assert expected_title_dark in html assert expected_dark in html - # same for light theme - expected_title_light = "

Current theme: light

" - expected_light = "primary
#1976D2" - # select light theme color._dark_theme = False + # same for light theme + expected_title_light = "

Current theme: light

" + expected_light = f"primary
{color.primary}" + # read html and assert the values of some produced items html = color._repr_html_().__str__() html = re.sub(r"[\n] [ ]+", "", html) diff --git a/tests/test_mapping/test_MenuControl.py b/tests/test_mapping/test_MenuControl.py index b55c6c40..071cafb4 100644 --- a/tests/test_mapping/test_MenuControl.py +++ b/tests/test_mapping/test_MenuControl.py @@ -1,6 +1,5 @@ """Test the Menu Control.""" -from sepal_ui import color from sepal_ui import mapping as sm from sepal_ui import sepalwidgets as sw @@ -135,6 +134,6 @@ def test_activate() -> None: # close the controls tile_control.menu.v_model = False - assert btn.style_ == f"background: {color.bg};" + assert btn.style_ == "" return diff --git a/tests/test_sepalwidgets/test_StateIcon.py b/tests/test_sepalwidgets/test_StateIcon.py index 3b6b88ad..91b6017e 100644 --- a/tests/test_sepalwidgets/test_StateIcon.py +++ b/tests/test_sepalwidgets/test_StateIcon.py @@ -23,7 +23,7 @@ def test_init(model: LocalModel) -> None: # Test with default states state_icon = sw.StateIcon(model, "state_value") - assert state_icon.icon.color == color.success + assert state_icon.icon.color == "success" assert state_icon.children[0] == "Valid" # Test with custom states @@ -48,7 +48,7 @@ def test_swap(model: LocalModel) -> None: state_icon = sw.StateIcon(model, "state_value") model.state_value = "non_valid" - assert state_icon.icon.color == color.error + assert state_icon.icon.color == "error" assert state_icon.children[0] == "Not valid" # Test raise exception