-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
251 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
"""The asset manager widget code and functionalities.""" | ||
from __future__ import annotations | ||
|
||
from typing import List | ||
|
||
import ee | ||
import geetools # noqa | ||
import ipyvuetify as v | ||
import traitlets as t | ||
from google.cloud.resourcemanager import ProjectsClient | ||
from natsort import humansorted | ||
|
||
from .decorator import switch | ||
|
||
ICON_STYLE = { | ||
"PARENT": {"color": "black", "icon": "mdi-folder-open"}, | ||
"PROJECT": {"color": "red", "icon": "mdi-google-cloud"}, | ||
"FOLDER": {"color": "grey", "icon": "mdi-folder"}, | ||
"IMAGE": {"color": "purple", "icon": "mdi-image-outline"}, | ||
"IMAGE_COLLECTION": {"color": "purple", "icon": "mdi-image-multiple-outline"}, | ||
"TABLE": {"color": "green", "icon": "mdi-table"}, | ||
"FEATURE_COLLECTION": {"color": "green", "icon": "mdi-tabe"}, | ||
} | ||
"The style to apply to each object" | ||
|
||
|
||
class AssetManager(v.Flex): | ||
"""A asset manager widget.""" | ||
|
||
# -- Variables ------------------------------------------------------------- | ||
|
||
folder: t.Unicode = t.Unicode(".").tag(sync=True) | ||
"The current folder that the user see" | ||
|
||
selected_item: t.Unicode = t.Unicode("").tag(sync=True) | ||
"The selected item of the asset manager" | ||
|
||
# -- Widgets --------------------------------------------------------------- | ||
|
||
w_new: v.Btn | ||
"The new btn on the top of the asset manager" | ||
|
||
w_reload: v.Btn | ||
"The reload btn at the top of the asset manager" | ||
|
||
w_search: v.Btn | ||
"The search button to crowl into the existing items" | ||
|
||
w_selected: v.TextField | ||
"The field where the user can see the asset Id of the selected item" | ||
|
||
w_list: v.List | ||
"The list of items displayed in the asset manager" | ||
|
||
w_card: v.Card | ||
"The card hosting the list of items" | ||
|
||
def __init__(self): | ||
"""Initialize the class.""" | ||
# start by defining al the widgets | ||
# We deactivated the formatting to define each one of them on 1 single line | ||
# fmt: off | ||
|
||
# add a line of buttons to reload and add new projects | ||
self.w_new = v.Btn(color="error", children="NEW", elevation=2, class_="ma-1") | ||
self.w_reload = v.Btn(children=[v.Icon(color="primary", children="mdi-reload")], elevation=2, class_="ma-1") | ||
self.w_search = v.Btn(children=[v.Icon(color="primary", children="mdi-magnify")], elevation=2, class_="ma-1") | ||
w_main_line = v.Flex(children=[self.w_new, self.w_reload, self.w_search]) | ||
|
||
# generate the asset selector and the CRUD buttons | ||
self.w_selected = v.TextField(readonly=True, placeholder="Selected item", v_model="", clearable=True, outlined=True, class_="ma-1") | ||
self.w_view = v.Btn(children=[v.Icon(color="primary", children="mdi-eye")]) | ||
self.w_copy = v.Btn(children=[v.Icon(color="primary", children="mdi-content-copy")]) | ||
self.w_move = v.Btn(children=[v.Icon(color="primary", children="mdi-file-move")]) | ||
self.w_delete = v.Btn(children=[v.Icon(color="primary", children="mdi-trash-can")]) | ||
w_btn_list = v.ItemGroup(class_="ma-1 v-btn-toggle",children=[self.w_view, self.w_copy, self.w_move, self.w_delete]) | ||
w_selected_line = v.Layout(row=True, children=[w_btn_list, self.w_selected], class_="ma-1") | ||
|
||
# generate the initial list | ||
w_group = v.ListItemGroup(children=self.get_items(), v_model="") | ||
self.w_list = v.List(dense=True, v_model=True, children=[w_group], outlined=True) | ||
self.w_card = v.Card(children=[self.w_list], outlined=True, class_="ma-1") | ||
|
||
super().__init__(children=[w_main_line, w_selected_line, self.w_card], v_model="", class_="ma-1") | ||
# fmt: on | ||
|
||
# add JS behaviour | ||
t.link((self, "selected_item"), (self, "v_model")) | ||
self.w_list.children[0].observe(self.on_item_select, "v_model") | ||
self.w_reload.on_event("click", self.on_reload) | ||
|
||
def get_items(self) -> List[v.ListItem]: | ||
"""Create the list of items inside a folder.""" | ||
# special case when we are at the root of everything | ||
# because of the specific display of cloud projects we will store both the name and the id of everything as a dict | ||
# for all other item types it will simply be the Name | ||
if self.folder == ".": | ||
list_items = [p.project_id for p in ProjectsClient().search_projects() if "earth-engine" in p.labels] # fmt: skip | ||
list_items = [{"id": f"projects/{i}/assets", "name": i} for i in list_items] | ||
else: | ||
list_items = [{"id": str(i), "name": i.name} for i in ee.Asset(self.folder).iterdir()] | ||
|
||
# split the folders and the files to display the folders first | ||
# cloud bucket will be considered as folders | ||
folder_list, file_list = [], [] # type: ignore[var-annotated] | ||
|
||
# walk the list of items and generate a list of ListItem using all the appropriate features | ||
# first we extract the type to deduce the icon and color, then we build the Items with the | ||
# ID and the display name and finally we split them in 2 groups the folders and the files | ||
for i in list_items: | ||
asset = ee.Asset(i["id"]) | ||
type = "PROJECT" if asset.is_project() else asset.type | ||
icon = ICON_STYLE[type]["icon"] | ||
color = ICON_STYLE[type]["color"] | ||
|
||
action = v.ListItemAction(children=[v.Icon(color=color, children=[icon])], class_="mr-1") | ||
content = v.ListItemContent(children=[v.ListItemTitle(children=[i["name"]])]) | ||
dst_list = folder_list if type in ["FOLDER", "PROJECT"] else file_list | ||
dst_list.append(v.ListItem(value=i["id"], children=[action, content])) | ||
|
||
# humanly sort the 2 lists so that number are treated nicely | ||
folder_list = humansorted(folder_list, key=lambda x: x.value) | ||
file_list = humansorted(file_list, key=lambda x: x.value) | ||
|
||
# add a parent items if necessary. We follow the same mechanism with specific verifications | ||
# if the parent is a project folder or the root | ||
if self.folder != ".": | ||
icon = ICON_STYLE["PARENT"]["icon"] | ||
color = ICON_STYLE["PARENT"]["color"] | ||
|
||
asset = ee.Asset(self.folder) | ||
parent = ee.Asset("") if asset.is_project() else asset.parent | ||
name = parent.parts[1] if parent.is_project() else parent.name | ||
name = name or "." # special case for the root | ||
|
||
action = v.ListItemAction(children=[v.Icon(color=color, children=[icon])], class_="mr-1") | ||
content = v.ListItemContent(children=[v.ListItemTitle(children=[name])]) | ||
item = v.ListItem(value=str(parent), children=[action, content]) | ||
|
||
folder_list.insert(0, item) | ||
|
||
# return the concatenation of the 2 lists | ||
return folder_list + file_list | ||
|
||
@switch("loading", "disabled", member="w_card") | ||
def on_item_select(self, change: dict): | ||
"""Act when an item is clicked by the user.""" | ||
# exit if nothing is changed to avoid infinite loop upon loading | ||
selected = change["new"] | ||
if not selected: | ||
return | ||
|
||
# select the item in the item TextField so user can interact with it | ||
self.w_selected.v_model = change["new"] | ||
|
||
# reset files. This is resetting the scroll to top without using js scripts | ||
# set the new content files and folders | ||
ee.Asset(change["new"]) | ||
if selected == "." or ee.Asset(selected).is_project() or ee.Asset(selected).is_folder(): | ||
self.folder = selected | ||
items = self.get_items() | ||
self.w_list.children[0].children = [] # trick to scroll up | ||
self.w_list.children[0].children = items | ||
|
||
def on_reload(self, *args): | ||
"""Reload the current folder.""" | ||
self.on_item_select(change={"new": self.folder}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
"""Decorators used in ipygee. | ||
ported from https://github.com/12rambau/sepal_ui/blob/main/sepal_ui/scripts/decorator.py | ||
""" | ||
from __future__ import annotations | ||
|
||
from functools import wraps | ||
from typing import Any, Optional | ||
|
||
|
||
def switch(*params, member: Optional[str] = None, debug: bool = True) -> Any: | ||
r"""Decorator to switch the state of input boolean parameters on class widgets or the class itself. | ||
If ``widget`` is defined, it will switch the state of every widget parameter, otherwise | ||
it will change the state of the class (self). You can also set two decorators on the same | ||
function, one could affect the class and other the widget. | ||
Args: | ||
*params: any boolean member of a Widget. | ||
member: THe widget on which the member are switched. Default to self. | ||
debug: Whether trigger or not an Exception if the decorated function fails. | ||
Returns: | ||
The return statement of the decorated method | ||
""" | ||
|
||
def decorator_switch(func): | ||
@wraps(func) | ||
def wrapper_switch(self, *args, **kwargs): | ||
|
||
# set the widget to work with. if nothing is set it will be self | ||
widget = getattr(self, member) if member else self | ||
|
||
# create the list of target values based on the initial values | ||
targets = [bool(getattr(widget, p)) for p in params] | ||
not_targets = [not t for t in targets] | ||
|
||
# assgn the parameters to the target inverse | ||
[setattr(widget, p, t) for p, t in zip(params, not_targets)] | ||
|
||
# execute the function and catch errors | ||
try: | ||
func(self, *args, **kwargs) | ||
except Exception as e: | ||
if debug: | ||
raise e | ||
finally: | ||
[setattr(widget, p, t) for p, t in zip(params, targets)] | ||
|
||
return wrapper_switch | ||
|
||
return decorator_switch |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
""""The different types created for this specific package.""" | ||
import os | ||
from pathlib import Path | ||
from typing import Union | ||
|
||
import ee | ||
import geetools # noqa: F401 | ||
|
||
pathlike = Union[os.PathLike, Path, ee.Asset] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.