Repository: 12rambau/sepal_ui

Issue:
add TYPES to documentation 
don't haw to deal with variables yet

Discussion:

Patch:
diff --git a/sepal_ui/__init__.py b/sepal_ui/__init__.py
index 5eab7e53..79ccdf6f 100644
--- a/sepal_ui/__init__.py
+++ b/sepal_ui/__init__.py
@@ -1,8 +1,11 @@
 import ipyvuetify as v
 
 from sepal_ui.conf import config, config_file
-from sepal_ui.frontend.styles import color, get_theme
+from sepal_ui.frontend.styles import SepalColor, get_theme
 
 __author__ = """Pierrick Rambaud"""
 __email__ = "pierrick.rambaud49@gmail.com"
 __version__ = "2.9.4"
+
+color = SepalColor()
+'color: the colors of sepal. members are in the following list: "main, darker, bg, primary, accent, secondary, success, info, warning, error, menu". They will render according to the selected theme.'
diff --git a/sepal_ui/aoi/aoi_model.py b/sepal_ui/aoi/aoi_model.py
index 330e4bfd..fb856a77 100644
--- a/sepal_ui/aoi/aoi_model.py
+++ b/sepal_ui/aoi/aoi_model.py
@@ -10,7 +10,7 @@ from ipyleaflet import GeoJSON
 from traitlets import Any
 
 from sepal_ui import color
-from sepal_ui.frontend.styles import AOI_STYLE
+from sepal_ui.frontend import styles as ss
 from sepal_ui.message import ms
 from sepal_ui.model import Model
 from sepal_ui.scripts import gee
@@ -619,7 +619,8 @@ class AoiModel(Model):
             f["properties"]["name"] = self.name
 
         # adapt the style to the theme
-        style = {**AOI_STYLE, "color": color.success, "fillColor": color.success}
+        style = json.loads((ss.JSON_DIR / "aoi.json").read_text())
+        style.update(color=color.success, fillColor=color.success)
 
         # create a GeoJSON object
         self.ipygeojson = GeoJSON(
diff --git a/sepal_ui/frontend/json/aoi.json b/sepal_ui/frontend/json/aoi.json
new file mode 100644
index 00000000..af272ded
--- /dev/null
+++ b/sepal_ui/frontend/json/aoi.json
@@ -0,0 +1,9 @@
+{
+    "stroke": true,
+    "color": "grey",
+    "weight": 2,
+    "opacity": 1,
+    "fill": true,
+    "fillColor": "grey",
+    "fillOpacity": 0.4
+}
\ No newline at end of file
diff --git a/sepal_ui/frontend/json/file_icons.json b/sepal_ui/frontend/json/file_icons.json
new file mode 100644
index 00000000..2b140fd1
--- /dev/null
+++ b/sepal_ui/frontend/json/file_icons.json
@@ -0,0 +1,12 @@
+{
+    "": {"color": ["#ffca28", "#ffc107"], "icon": "far fa-folder"},
+    ".csv": {"color": ["#4caf50", "#00c853"], "icon": "far fa-table"},
+    ".txt": {"color": ["#4caf50", "#00c853"], "icon": "far fa-table"},
+    ".tif": {"color": ["#9c27b0", "#673ab7"], "icon": "far fa-image"},
+    ".tiff": {"color": ["#9c27b0", "#673ab7"], "icon": "far fa-image"},
+    ".vrt": {"color": ["#9c27b0", "#673ab7"], "icon": "far fa-image"},
+    ".shp": {"color": ["#9c27b0", "#673ab7"], "icon": "far fa-vector-square"},
+    ".geojson": {"color": ["#9c27b0", "#673ab7"], "icon": "far fa-vector-square"},
+    "DEFAULT": {"color": ["#00bcd4", "#03a9f4"], "icon": "far fa-file"},
+    "PARENT": {"color": ["#424242", "#ffffff"], "icon": "far fa-folder-open"}
+}
\ No newline at end of file
diff --git a/sepal_ui/frontend/json/layer.json b/sepal_ui/frontend/json/layer.json
new file mode 100644
index 00000000..9976aff6
--- /dev/null
+++ b/sepal_ui/frontend/json/layer.json
@@ -0,0 +1,7 @@
+{
+    "stroke": true,
+    "weight": 2,
+    "opacity": 1,
+    "fill": true,
+    "fillOpacity": 0
+}
\ No newline at end of file
diff --git a/sepal_ui/frontend/json/layer_hover.json b/sepal_ui/frontend/json/layer_hover.json
new file mode 100644
index 00000000..c6b7ff45
--- /dev/null
+++ b/sepal_ui/frontend/json/layer_hover.json
@@ -0,0 +1,7 @@
+{
+    "stroke": true,
+    "weight": 5,
+    "opacity": 1,
+    "fill": true,
+    "fillOpacity": 0
+}
\ No newline at end of file
diff --git a/sepal_ui/frontend/json/map_btn.json b/sepal_ui/frontend/json/map_btn.json
new file mode 100644
index 00000000..79817a81
--- /dev/null
+++ b/sepal_ui/frontend/json/map_btn.json
@@ -0,0 +1,6 @@
+{
+    "padding": "0px",
+    "min-width": "0px",
+    "width": "30px",
+    "height": "30px"
+}
\ No newline at end of file
diff --git a/sepal_ui/frontend/json/progress_bar.json b/sepal_ui/frontend/json/progress_bar.json
new file mode 100644
index 00000000..ef7461ef
--- /dev/null
+++ b/sepal_ui/frontend/json/progress_bar.json
@@ -0,0 +1,3 @@
+{
+    "color": ["#2196f3", "#3f51b5"]
+}
\ No newline at end of file
diff --git a/sepal_ui/frontend/styles.py b/sepal_ui/frontend/styles.py
index 97a03dde..a8bfff93 100644
--- a/sepal_ui/frontend/styles.py
+++ b/sepal_ui/frontend/styles.py
@@ -8,6 +8,26 @@ from traitlets import Bool, HasTraits, Unicode, observe
 import sepal_ui.scripts.utils as su
 from sepal_ui 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(__file__).parent / "json"
+"pathlib.Path: the path to the json style folder"
+
+CSS_DIR = Path(__file__).parent / "css"
+"pathlib.Path: the path to the css style folder"
+
+JS_DIR = Path(__file__).parent / "js"
+"pathlib.Path: the path to the js style folder"
+
+################################################################################
+# define all the colors taht we want to use in the theme
+#
+
 DARK_THEME = {
     "primary": "#b3842e",
     "accent": "#a1458e",
@@ -21,6 +41,7 @@ DARK_THEME = {
     "bg": "#121212",  # Are not traits
     "menu": "#424242",  # Are not traits
 }
+"dict: colors used for the dark theme"
 
 LIGHT_THEME = {
     "primary": v.theme.themes.light.primary,
@@ -35,17 +56,19 @@ LIGHT_THEME = {
     "bg": "#FFFFFF",
     "menu": "#FFFFFF",
 }
+"dict: colors used for the light theme"
+TYPES = ("info", "primary", "secondary", "accent", "error", "success", "warning", "anchor")  # fmt: skip
+"tuple: the different types defined by ipyvuetify"
 
-if not DARK_THEME.keys() == LIGHT_THEME.keys():
-    raise Exception("Both dictionaries has to have the same color names")
+################################################################################
+# define classes and method to make the application resonsive
+#
 
 
 def get_theme():
     """
     get theme name from the config file (default to dark)
 
-    Args:
-
     Return:
         (str): the theme to use
     """
@@ -53,10 +76,11 @@ def get_theme():
 
 
 class SepalColor(HasTraits, SimpleNamespace):
-    """Custom simple name space to store and access to the sepal_ui colors and
-    with a magic method to display theme."""
+    """
+    Custom simple name space to store and access to the sepal_ui colors and
+    with a magic method to display theme.
+    """
 
-    # Initialize with the current theme
     _dark_theme = Bool(True if get_theme() == "dark" else False).tag(sync=True)
     "bool: 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."
 
@@ -117,10 +141,6 @@ class SepalColor(HasTraits, SimpleNamespace):
         return html
 
 
-color = SepalColor()
-'color: the colors of sepal. members are in the following list: "main, darker, bg, primary, accent, secondary, success, info, warning, error, menu". They will render according to the selected theme.'
-
-
 class Styles(v.VuetifyTemplate):
     """
     Fixed styles to fix display issues in the lib:
@@ -132,7 +152,7 @@ class Styles(v.VuetifyTemplate):
     - ensure that tqdm bars are using a transparent background when displayed in an alert
     """
 
-    css = (Path(__file__).parent / "css/custom.css").read_text()
+    css = (CSS_DIR / "custom.css").read_text()
     cdn = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"
     template = Unicode(
         f'<style>{css}</style><link rel="stylesheet" href="{cdn}"/>'
@@ -142,83 +162,3 @@ class Styles(v.VuetifyTemplate):
 
 styles = Styles()
 display(styles)
-
-# default styling of the aoi layer
-AOI_STYLE = {
-    "stroke": True,
-    "color": "grey",
-    "weight": 2,
-    "opacity": 1,
-    "fill": True,
-    "fillColor": "grey",
-    "fillOpacity": 0.4,
-}
-
-# the colors are set as follow.
-# 1 (True): dark theme
-# 0 (false): light theme
-# This will need to be changed if we want to support more than 2 theme
-COMPONENTS = {
-    "PROGRESS_BAR": {
-        "color": ["#2196f3", "#3f51b5"],
-    }
-}
-
-_folder = {"color": ["#ffca28", "#ffc107"], "icon": "far fa-folder"}
-_table = {"color": ["#4caf50", "#00c853"], "icon": "far fa-table"}
-_vector = {"color": ["#9c27b0", "#673ab7"], "icon": "far fa-vector-square"}
-_other = {"color": ["#00bcd4", "#03a9f4"], "icon": "far fa-file"}
-_parent = {"color": ["#424242", "#ffffff"], "icon": "far fa-folder-open"}
-_image = {"color": ["#9c27b0", "#673ab7"], "icon": "far fa-image"}
-
-ICON_TYPES = {
-    "": _folder,
-    ".csv": _table,
-    ".txt": _table,
-    ".tif": _image,
-    ".tiff": _image,
-    ".vrt": _image,
-    ".shp": _vector,
-    ".geojson": _vector,
-    "DEFAULT": _other,
-    "PARENT": _parent,
-}
-
-TYPES = (
-    "info",
-    "primary",
-    "secondary",
-    "accent",
-    "error",
-    "success",
-    "warning",
-    "anchor",
-)
-
-# Default styles to GeoJSON layers added to a SepalMap.
-
-layer_style = {
-    "stroke": True,
-    "color": color.primary,
-    "weight": 2,
-    "opacity": 1,
-    "fill": True,
-    "fillOpacity": 0,
-}
-
-layer_hover_style = {
-    "stroke": True,
-    "color": color.primary,
-    "weight": 5,
-    "opacity": 1,
-    "fill": True,
-    "fillOpacity": 0,
-}
-
-map_btn_style = {
-    "padding": "0px",
-    "min-width": "0px",
-    "width": "30px",
-    "height": "30px",
-    "background": color.bg,
-}
diff --git a/sepal_ui/mapping/map_btn.py b/sepal_ui/mapping/map_btn.py
index 0ea13364..109f13ee 100644
--- a/sepal_ui/mapping/map_btn.py
+++ b/sepal_ui/mapping/map_btn.py
@@ -1,7 +1,10 @@
+import json
+
 import ipyvuetify as v
 
+from sepal_ui import color
 from sepal_ui import sepalwidgets as sw
-from sepal_ui.frontend.styles import map_btn_style
+from sepal_ui.frontend import styles as ss
 
 
 class MapBtn(v.Btn, sw.SepalWidget):
@@ -22,10 +25,14 @@ class MapBtn(v.Btn, sw.SepalWidget):
         else:
             content = content[: min(3, len(content))].upper()
 
+        # create the style from default
+        style = json.loads((ss.JSON_DIR / "map_btn.json").read_text())
+        style.update(background=color.bg)
+
         # some parameters are overloaded to match the map requirements
         kwargs["color"] = "text-color"
         kwargs["outlined"] = True
-        kwargs["style_"] = " ".join([f"{k}: {v};" for k, v in map_btn_style.items()])
+        kwargs["style_"] = " ".join([f"{k}: {v};" for k, v in style.items()])
         kwargs["children"] = [content]
         kwargs["icon"] = False
 
diff --git a/sepal_ui/mapping/sepal_map.py b/sepal_ui/mapping/sepal_map.py
index 4ef41d1a..f746a72c 100644
--- a/sepal_ui/mapping/sepal_map.py
+++ b/sepal_ui/mapping/sepal_map.py
@@ -6,6 +6,7 @@ if "GDAL_DATA" in list(os.environ.keys()):
 if "PROJ_LIB" in list(os.environ.keys()):
     del os.environ["PROJ_LIB"]
 
+import json
 import math
 import random
 import string
@@ -27,7 +28,8 @@ from matplotlib import colorbar
 from matplotlib import colors as mpc
 from rasterio.crs import CRS
 
-import sepal_ui.frontend.styles as styles
+from sepal_ui import color
+from sepal_ui.frontend import styles as ss
 from sepal_ui.mapping.basemaps import basemap_tiles
 from sepal_ui.mapping.draw_control import DrawControl
 from sepal_ui.mapping.layer import EELayer
@@ -775,8 +777,18 @@ class SepalMap(ipl.Map):
 
         # apply default coloring for geoJson
         if isinstance(layer, ipl.GeoJSON):
-            layer.style = layer.style or styles.layer_style
-            hover_style = styles.layer_hover_style if hover else layer.hover_style
+
+            # define the default values
+            default_style = json.loads((ss.JSON_DIR / "layer.json").read_text())
+            default_style.update(color=color.primary)
+            default_hover_style = json.loads(
+                (ss.JSON_DIR / "layer_hover.json").read_text()
+            )
+            default_hover_style.update(color=color.primary)
+
+            # apply the style depending on the parameters
+            layer.style = layer.style or default_style
+            hover_style = default_hover_style if hover else layer.hover_style
             layer.hover_style = layer.hover_style or hover_style
 
         super().add_layer(layer)
diff --git a/sepal_ui/mapping/value_inspector.py b/sepal_ui/mapping/value_inspector.py
index 96508ba3..8149a078 100644
--- a/sepal_ui/mapping/value_inspector.py
+++ b/sepal_ui/mapping/value_inspector.py
@@ -1,3 +1,5 @@
+import json
+
 import ee
 import geopandas as gpd
 import ipyvuetify as v
@@ -10,7 +12,7 @@ from shapely import geometry as sg
 
 from sepal_ui import color
 from sepal_ui import sepalwidgets as sw
-from sepal_ui.frontend.styles import COMPONENTS
+from sepal_ui.frontend import styles as ss
 from sepal_ui.mapping.layer import EELayer
 from sepal_ui.mapping.map_btn import MapBtn
 from sepal_ui.message import ms
@@ -47,10 +49,11 @@ class ValueInspector(WidgetControl):
 
         # 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=COMPONENTS["PROGRESS_BAR"]["color"][v.theme.dark],
+            color=p_style["color"][v.theme.dark],
         )
 
         # create a clickable btn
diff --git a/sepal_ui/sepalwidgets/alert.py b/sepal_ui/sepalwidgets/alert.py
index 6ad4d5d0..f80b20ef 100644
--- a/sepal_ui/sepalwidgets/alert.py
+++ b/sepal_ui/sepalwidgets/alert.py
@@ -6,7 +6,8 @@ from ipywidgets import Output, jslink
 from tqdm.notebook import tqdm
 from traitlets import Bool, Unicode, directional_link, observe
 
-from sepal_ui.frontend.styles import TYPES, color
+from sepal_ui import color
+from sepal_ui.frontend.styles import TYPES
 from sepal_ui.message import ms
 from sepal_ui.scripts import utils as su
 from sepal_ui.scripts.utils import set_type
diff --git a/sepal_ui/sepalwidgets/inputs.py b/sepal_ui/sepalwidgets/inputs.py
index 38ffef00..438238e1 100644
--- a/sepal_ui/sepalwidgets/inputs.py
+++ b/sepal_ui/sepalwidgets/inputs.py
@@ -1,3 +1,4 @@
+import json
 from datetime import datetime
 from pathlib import Path
 
@@ -10,7 +11,7 @@ from natsort import humansorted
 from traitlets import Any, Bool, Dict, Int, List, Unicode, link, observe
 
 from sepal_ui import color
-from sepal_ui.frontend.styles import COMPONENTS, ICON_TYPES
+from sepal_ui.frontend import styles as ss
 from sepal_ui.message import ms
 from sepal_ui.scripts import gee
 from sepal_ui.scripts import utils as su
@@ -193,6 +194,9 @@ class FileInput(v.Flex, SepalWidget):
     v_model = Unicode(None, allow_none=True).tag(sync=True)
     "str: the v_model of the input"
 
+    ICON_STYLE = json.loads((ss.JSON_DIR / "file_icons.json").read_text())
+    "dict: the style applied to the icons in the file menu"
+
     def __init__(
         self,
         extentions=[],
@@ -216,10 +220,11 @@ class FileInput(v.Flex, SepalWidget):
             v_model=None,
         )
 
+        p_style = json.loads((ss.JSON_DIR / "progress_bar.json").read_text())
         self.loading = v.ProgressLinear(
             indeterminate=False,
             background_color=color.menu,
-            color=COMPONENTS["PROGRESS_BAR"]["color"][v.theme.dark],
+            color=p_style["color"][v.theme.dark],
         )
 
         self.file_list = v.List(
@@ -376,14 +381,14 @@ class FileInput(v.Flex, SepalWidget):
         for el in list_dir:
 
             if el.is_dir():
-                icon = ICON_TYPES[""]["icon"]
-                color = ICON_TYPES[""]["color"][v.theme.dark]
-            elif el.suffix in ICON_TYPES.keys():
-                icon = ICON_TYPES[el.suffix]["icon"]
-                color = ICON_TYPES[el.suffix]["color"][v.theme.dark]
+                icon = self.ICON_STYLE[""]["icon"]
+                color = self.ICON_STYLE[""]["color"][v.theme.dark]
+            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]
             else:
-                icon = ICON_TYPES["DEFAULT"]["icon"]
-                color = ICON_TYPES["DEFAULT"]["color"][v.theme.dark]
+                icon = self.ICON_STYLE["DEFAULT"]["icon"]
+                color = self.ICON_STYLE["DEFAULT"]["color"][v.theme.dark]
 
             children = [
                 v.ListItemAction(children=[v.Icon(color=color, children=[icon])]),
@@ -410,8 +415,8 @@ class FileInput(v.Flex, SepalWidget):
                 v.ListItemAction(
                     children=[
                         v.Icon(
-                            color=ICON_TYPES["PARENT"]["color"][v.theme.dark],
-                            children=[ICON_TYPES["PARENT"]["icon"]],
+                            color=self.ICON_STYLE["PARENT"]["color"][v.theme.dark],
+                            children=[self.ICON_STYLE["PARENT"]["icon"]],
                         )
                     ]
                 ),


Test patch:
diff --git a/tests/test_SepalMap.py b/tests/test_SepalMap.py
index 30a0d3eb..438ffa7a 100644
--- a/tests/test_SepalMap.py
+++ b/tests/test_SepalMap.py
@@ -1,3 +1,4 @@
+import json
 import math
 import random
 from pathlib import Path
@@ -7,9 +8,9 @@ import ee
 import pytest
 from ipyleaflet import GeoJSON, LocalTileLayer
 
-import sepal_ui.frontend.styles as styles
 from sepal_ui import get_theme
 from sepal_ui import mapping as sm
+from sepal_ui.frontend import styles as ss
 
 # create a seed so that we can check values
 random.seed(42)
@@ -289,8 +290,11 @@ class TestSepalMap:
         # Assert
         new_layer = m.layers[-1]
 
-        assert new_layer.style == styles.layer_style
-        assert new_layer.hover_style == styles.layer_hover_style
+        layer_style = json.loads((ss.JSON_DIR / "layer.json").read_text())
+        hover_style = json.loads((ss.JSON_DIR / "layer_hover.json").read_text())
+
+        assert all([new_layer.style[k] == v for k, v in layer_style.items()])
+        assert all([new_layer.hover_style[k] == v for k, v in hover_style.items()])
 
         # Arrange with style
         layer_style = {"color": "blue"}
diff --git a/tests/test_style.py b/tests/test_style.py
new file mode 100644
index 00000000..a52c5f5e
--- /dev/null
+++ b/tests/test_style.py
@@ -0,0 +1,19 @@
+from sepal_ui.frontend import styles as ss
+
+
+class TestStyle:
+    def test_folders(self):
+
+        assert ss.JSON_DIR.is_dir()
+        assert ss.CSS_DIR.is_dir()
+        assert ss.JS_DIR.is_dir()
+
+        return
+
+    def test_colors(self):
+
+        # test that colors have the same size and names
+        assert len(ss.DARK_THEME.keys()) == len(ss.LIGHT_THEME.keys())
+        assert all(dc in ss.LIGHT_THEME.keys() for dc in ss.DARK_THEME.keys())
+
+        return