Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure toolbar buttons toggle widgets, and turn off when widgets are closed. #1763

Merged
merged 10 commits into from Oct 7, 2023

Conversation

sufyanAbbasi
Copy link
Collaborator

Objective

To design a scalable system to make it easy to implement new toolbar items that clean themselves up when closed or un-toggled.

Implementation

  1. Added a new decorator that wraps toggle button callbacks that will automatically take care of constructing and deconstructing the widget when the respective toggle is pressed. It also ensures that when the "close" button is clicked on the respective widgets, the toolbar toggle is turned off automatically.
  2. It works by ensuring that the decorated callback returns the control widget that has a cleanup property that is a method that safely removes it from the map and clean up any state. Then, the decorator modifies the cleanup property to also call the toggle_off method which turns off the respective toggle in the toolbar. Then, when the "close" button is clicked, the callback uses the modified version of the cleanup so that cleanup and toggle off both occur.

Backwards Incompatibility Notes

  1. Two widgets can't be added at the same time. I think this is done for safety as the map.tool_control property is shared amongst widgets.
  2. The whitebox toolbox doesn't turn off its own toggle when closed since that tool is implemented in a different repository.

Tested

Added unit tests to test cleanup behaviors and tested in Jupyter-Lab and Colab: https://colab.research.google.com/drive/1HHQomXhy-vS68qiMu6AOhDmrsLq6OZwe#scrollTo=6-4Mn2sAFNrO

@giswqs
Copy link
Member

giswqs commented Oct 6, 2023

@sufyanAbbasi Thanks a lot for the great improvement. I am on travel today. I might not be able to review this until tonight or tomorrow. If @naschmitz or @bengalin has time, please feel free to review and merge it.

@naschmitz
Copy link
Collaborator

@sufyanAbbasi – Currently reviewing the PR. How difficult would it be to update this PR to support multiple widgets being open at the same time? It's a common use case.

Copy link
Collaborator

@naschmitz naschmitz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Some nits, but approach looks good. It would be nice to do away with the toggle_off attr if possible.

@@ -15,9 +15,11 @@
import ipyevents
import ipyleaflet
import ipywidgets as widgets

from ipywidgets.widgets import Widget
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: ipywidgets.widgets.Widget

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed and using widgets.Widget below.

reset: bool = True
control: Optional[Widget] = None
toggle_button: Any = None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional[ipywidgets.ToggleButton]? Will this ever be something else?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, done.

geemap/core.py Outdated
if selected:
common.open_url("https://geemap.org")

def _toolbar_main_tools(self) -> List[toolbar.Toolbar.Item]:
@toolbar._cleanup_toolbar_item
def inspector_tool_callback(map, _, item):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Could you add type annotations here + call del on the unused properties?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if selected:
common.open_url("https://geemap.org")

def _toolbar_main_tools(self) -> List[toolbar.Toolbar.Item]:
@toolbar._cleanup_toolbar_item
def inspector_tool_callback(map, _, item):
map.add("inspector")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if add should return the ipywidget widget that was just added... It would make it quite a bit more useful. We'd be changing the public API, but I'm okay with that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be a great addition to the API, but I'm less inclined to make the change in this PR.

geemap/core.py Outdated
del host_map # Unused.
del item # Unused.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: del host_map, item # Unused.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -163,7 +179,7 @@ def _reset_others(self, current):
if other is not current:
other.value = False

def _toggle_callback(self, m, selected):
def _toggle_callback(self, m, selected, _):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: _toggle_callback(self, m, selected, item) and then call del item # Unused..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


if hasattr(m, "pixel_values"):
delattr(m, "pixel_values")
if hasattr(m, "marker_cluster"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't it necessary to delattr pixel_values anymore?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See line 414, I think this is a diff artifact.

@@ -587,6 +585,7 @@ def handle_interaction(**kwargs):
toolbar_control = ipyleaflet.WidgetControl(
widget=toolbar_widget, position="topright"
)
setattr(toolbar_control, "cleanup", cleanup)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toolbar_control.cleanup = cleanup

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done and below.

close_button.observe(close_btn_click, "value")

toolbar_button.value = True
if m is not None:
toolbar_control = ipyleaflet.WidgetControl(
widget=toolbar_widget, position="topright"
)

setattr(toolbar_control, "cleanup", cleanup)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toolbar_control.cleanup = cleanup

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done and below.

toolbar_button.value = True
if m is not None:
toolbar_control = ipyleaflet.WidgetControl(
widget=toolbar_widget, position="topright"
)
setattr(toolbar_control, "cleanup", cleanup)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here and some other place in the code.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done and below.

@sufyanAbbasi
Copy link
Collaborator Author

Currently reviewing the PR. How difficult would it be to update this PR to support multiple widgets being open at the same time? It's a common use case.

Very easy, just removed two lines in curry_callback to make it work! The only thing is that it may be unclear how certain widgets interact with each other while both are open, since some state is shared between components, however, I expect those interactions to be worked out later!

@giswqs giswqs merged commit 6aad7d2 into gee-community:master Oct 7, 2023
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants