diff --git a/component/model/recipe.py b/component/model/recipe.py index d94968c..8b8f9d7 100644 --- a/component/model/recipe.py +++ b/component/model/recipe.py @@ -25,16 +25,9 @@ class Recipe(HasTraits): recipe_session_path = Unicode("", allow_none=True).tag(sync=True) """The path to the recipe session file. This value will come from the recipe view, it will be used by the export csv function and to create the names of the assets to export""" - def __init__(self): + def __init__(self, **delete_aoi): super().__init__() - self.seplan_aoi = None - self.benefit_model = None - self.constraint_model = None - self.cost_model = None - self.seplan = None - - def load_model(self, **delete_aoi): - """Define all the models required by the module.""" + self.seplan_aoi = SeplanAoi(**delete_aoi) self.benefit_model = cmod.BenefitModel() self.constraint_model = cmod.ConstraintModel() @@ -52,8 +45,6 @@ def load_model(self, **delete_aoi): self.constraint_model.observe(self.update_changes, "new_changes") self.cost_model.observe(self.update_changes, "new_changes") - return self - def update_changes(self, change): """Increment the new_changes counter by 1.""" self.new_changes += 1 @@ -66,6 +57,9 @@ def load(self, recipe_path: str): with recipe_path.open() as f: data = json.loads(f.read()) + # Remove all the "updated" keys from the data in the second level + [validation.remove_key(data, key) for key in ["updated", "new_changes"]] + # load the aoi_model self.seplan_aoi.import_data(data["aoi"]) self.benefit_model.import_data(data["benefits"]) diff --git a/component/scripts/validation.py b/component/scripts/validation.py index 5ff4958..359e323 100644 --- a/component/scripts/validation.py +++ b/component/scripts/validation.py @@ -1,4 +1,5 @@ """functions to read and validate a recipe and return meaningful errors.""" + import json from pathlib import Path from typing import Optional @@ -79,3 +80,15 @@ def validate_recipe( raise ValidationError(e) return recipe_path + + +def remove_key(data, key_to_remove): + """Remove a key from a dictionary.""" + if isinstance(data, dict): + if key_to_remove in data: + del data[key_to_remove] + for key, value in list(data.items()): + remove_key(value, key_to_remove) + elif isinstance(data, list): + for item in data: + remove_key(item, key_to_remove) diff --git a/component/tile/custom_aoi_tile.py b/component/tile/custom_aoi_tile.py index 5d47e88..4c8529b 100644 --- a/component/tile/custom_aoi_tile.py +++ b/component/tile/custom_aoi_tile.py @@ -19,16 +19,12 @@ class AoiTile(sw.Layout): """Overwrite the map of the tile to replace it with a customMap.""" - def __init__(self): + def __init__(self, recipe: Recipe): self.class_ = "d-block custom_map" self._metadata = {"mount_id": "aoi_tile"} super().__init__() - def build(self, recipe: Recipe, build_alert: AlertState): - """Build the custom aoi tile.""" - build_alert.set_state("new", "aoi", "building") - self.map_ = SepalMap(gee=True) self.map_.dc.hide() self.map_.min_zoom = 3 @@ -47,8 +43,6 @@ def build(self, recipe: Recipe, build_alert: AlertState): # bind an extra js behaviour self.view.observe(self._check_lmic, "updated") - build_alert.set_state("new", "aoi", "done") - def _check_lmic(self, _): """Every time a new aoi is set check if it fits the LMIC country list.""" # check over the lmic country number diff --git a/component/tile/dashboard_tile.py b/component/tile/dashboard_tile.py index a37136a..6c99f4e 100644 --- a/component/tile/dashboard_tile.py +++ b/component/tile/dashboard_tile.py @@ -19,16 +19,13 @@ class DashboardTile(sw.Layout): - def __init__(self): + def __init__(self, recipe: Recipe): super().__init__() self._metadata = {"mount_id": "dashboard_tile"} self.class_ = "d-block" self.summary_stats = None - def build(self, recipe: Recipe, build_alert: AlertState): - """Build the dashboard tile.""" - build_alert.set_state("new", "dashboard", "building") self.recipe = recipe dash_toolbar = DashToolBar(recipe.seplan) @@ -58,8 +55,6 @@ def build(self, recipe: Recipe, build_alert: AlertState): dash_toolbar.btn_dashboard.on_event("click", self._dashboard) dash_toolbar.btn_download.on_event("click", self.csv_export) - build_alert.set_state("new", "dashboard", "done") - self.recipe.dash_model.observe(self.reset, "reset_count") def csv_export(self, *_): diff --git a/component/tile/map_tile.py b/component/tile/map_tile.py index d9a3f31..1025f5a 100644 --- a/component/tile/map_tile.py +++ b/component/tile/map_tile.py @@ -11,7 +11,7 @@ class MapTile(sw.Layout): - def __init__(self, app_model: AppModel = None): + def __init__(self, recipe: Recipe, app_model: AppModel = None): """Define the map tile layout. Args: @@ -24,13 +24,6 @@ def __init__(self, app_model: AppModel = None): super().__init__() - def build( - self, - recipe: Recipe, - build_alert: AlertState, - ): - build_alert.set_state("new", "map", "building") - self.recipe = recipe self.colors = [] self.alert = Alert() @@ -58,8 +51,6 @@ def build( # # add js behaviour self.map_toolbar.btn_compute.on_event("click", self._compute) - build_alert.set_state("new", "map", "done") - self.recipe.seplan_aoi.observe(self._update_aoi, "updated") # This will open the info dialog when the map_tile drawer is clicked diff --git a/component/tile/questionnaire_tile.py b/component/tile/questionnaire_tile.py index b8664f6..593525a 100644 --- a/component/tile/questionnaire_tile.py +++ b/component/tile/questionnaire_tile.py @@ -11,17 +11,13 @@ class QuestionnaireTile(sw.Layout): - def __init__(self): + def __init__(self, recipe: Recipe): # name the tile self._metadata = {"mount_id": "questionnaire_tile"} self.class_ = "d-block" super().__init__() - def build(self, recipe: Recipe, build_alert: AlertState): - """Build the questionnaire tile.""" - build_alert.set_state("new", "questionnaire", "building") - self.alert = Alert() alert_dialog = AlertDialog(self.alert) @@ -74,5 +70,3 @@ def build(self, recipe: Recipe, build_alert: AlertState): ) self.set_children([alert_dialog, preview_map] + [tabs], position="last") - - build_alert.set_state("new", "questionnaire", "done") diff --git a/component/tile/recipe_tile.py b/component/tile/recipe_tile.py index d656903..05bb3e3 100644 --- a/component/tile/recipe_tile.py +++ b/component/tile/recipe_tile.py @@ -1,4 +1,3 @@ -import concurrent.futures from datetime import datetime from pathlib import Path from typing import Literal @@ -134,11 +133,11 @@ class RecipeView(sw.Card): app_model: AppModel """It will be used to listen the on_save trait and trigger the save button here""" - def __init__(self, app_model: AppModel = None): + def __init__(self, recipe: Recipe = None, app_model: AppModel = None): self.attributes = {"_metadata": "recipe_tile"} super().__init__() - self.recipe = Recipe() + self.recipe = recipe or Recipe() self.alert = AlertState() self.alert_dialog = AlertDialog(self.alert) @@ -205,12 +204,6 @@ def session_path_handler(self, change): self.card_save.recipe_name = str(Path(change["new"]).stem) - def new_recipe(self): - """Initialize a new recipe.""" - - self.recipe.load_model() - self.create_view += 1 - @switch("disabled", on_widgets=["card_new", "card_load", "card_save"]) @switch("loading", on_widgets=["card_new"]) def new_event(self, *_): @@ -220,14 +213,9 @@ def new_event(self, *_): if not self.card_new.recipe_name: raise ValueError(cm.recipe.error.no_name) - # consider first when theres is not a recipe loaded - if not self.recipe.seplan_aoi: - print("no seplan") - self.new_recipe() - else: - self.alert.set_state("reset", "all", "building") - self.recipe.reset() - self.alert.set_state("reset", "all", "done") + self.alert.set_state("reset", "all", "building") + self.recipe.reset() + self.alert.set_state("reset", "all", "done") # update current session path self.recipe_session_path = self.recipe.get_recipe_path( @@ -253,9 +241,6 @@ def load_event(self, *_): self.load_dialog.v_model = False - if not self.recipe.seplan_aoi: - self.new_recipe() - self.alert.set_state("load", "all", "building") self.recipe.load(recipe_path=self.load_dialog.load_recipe_path) @@ -368,31 +353,18 @@ def cancel(self, *args): class RecipeTile(sw.Layout): - def __init__( - self, - app_model: AppModel, - aoi_tile: AoiTile, - questionnaire_tile: QuestionnaireTile, - map_tile: MapTile, - dashboard_tile: DashboardTile, - ): + def __init__(self, recipe: Recipe, app_model: AppModel): self._metadata = {"mount_id": "recipe_tile"} self.class_ = "d-block pa-2" super().__init__() + self.recipe = recipe self.app_model = app_model - self.recipe_view = RecipeView(app_model=app_model) - - self.aoi_tile = aoi_tile - self.questionnaire_tile = questionnaire_tile - self.map_tile = map_tile - self.dashboard_tile = dashboard_tile + self.recipe_view = RecipeView(recipe=recipe, app_model=app_model) self.children = [self.recipe_view] - self.recipe_view.observe(self.render, "create_view") - directional_link( (self.recipe_view, "recipe_session_path"), (self.app_model, "recipe_name") ) @@ -402,47 +374,5 @@ def __init__( (self.recipe_view.recipe, "new_changes"), (self.app_model, "new_changes") ) - def render(self, *_): - """Render all the different tiles. - - This element is intended to be used only once, when the app has to start. - """ - try: - with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: - futures = [ - executor.submit( - self.aoi_tile.build, - self.recipe_view.recipe, - self.recipe_view.alert, - ), - executor.submit( - self.questionnaire_tile.build, - self.recipe_view.recipe, - self.recipe_view.alert, - ), - executor.submit( - self.map_tile.build, - self.recipe_view.recipe, - self.recipe_view.alert, - ), - executor.submit( - self.dashboard_tile.build, - self.recipe_view.recipe, - self.recipe_view.alert, - ), - ] - - # Check if any future has raised an exception - for future in concurrent.futures.as_completed(futures): - e = future.exception() - if e: - raise e # Rethrow the first exception encountered - - # concurrent.futures.wait(futures) - except Exception as e: - # If something fails, I want to return the recipe to its original state - self.recipe_view.recipe.__init__() - raise e - # This trait will let know the app drawers that the app is ready to be used self.app_model.ready = True diff --git a/component/widget/custom_widgets.py b/component/widget/custom_widgets.py index d457f2e..9f10a47 100644 --- a/component/widget/custom_widgets.py +++ b/component/widget/custom_widgets.py @@ -155,10 +155,6 @@ def __init__(self, *args, **kwargs) -> None: self.attributes = {"id": kwargs["card"]} - # Only hide the drawers that are not the main ones - if self.attributes["id"] not in (["recipe_tile", "about_tile"]): - self.viz = False - self.observe(self.add_notif, "alert") def add_notif(self, change: dict) -> None: diff --git a/requirements.txt b/requirements.txt index 04bc77b..159a1a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ voila toml # Fixes authentication process -git+https://github.com/12rambau/sepal_ui.git@sepal_pre_release +sepal_ui==2.18.1 # https://github.com/openforis/sepal/issues/321 ipyleaflet==0.18.2 diff --git a/ui.ipynb b/ui.ipynb index 70604a8..275508e 100644 --- a/ui.ipynb +++ b/ui.ipynb @@ -24,6 +24,7 @@ "from component.tile.map_tile import MapTile\n", "from component.tile.questionnaire_tile import QuestionnaireTile\n", "from component.tile.recipe_tile import RecipeTile\n", + "from component.model.recipe import Recipe\n", "\n", "from component.message import cm" ] @@ -54,6 +55,16 @@ "disclaimer_tile = sw.TileDisclaimer()" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "184f41b0", + "metadata": {}, + "outputs": [], + "source": [ + "recipe = Recipe()" + ] + }, { "cell_type": "code", "execution_count": null, @@ -63,13 +74,11 @@ }, "outputs": [], "source": [ - "aoi_tile = AoiTile()\n", - "questionnaire_tile = QuestionnaireTile()\n", - "map_tile = MapTile(app_model)\n", - "dashboard_tile = DashboardTile()\n", - "recipe_tile = RecipeTile(\n", - " app_model, aoi_tile, questionnaire_tile, map_tile, dashboard_tile\n", - ")" + "recipe_tile = RecipeTile(recipe=recipe, app_model=app_model)\n", + "aoi_tile = AoiTile(recipe=recipe)\n", + "questionnaire_tile = QuestionnaireTile(recipe=recipe)\n", + "map_tile = MapTile(recipe=recipe, app_model=app_model)\n", + "dashboard_tile = DashboardTile(recipe=recipe)" ] }, {