diff --git a/shiny/bookmark/_bookmark.py b/shiny/bookmark/_bookmark.py index 28abdb139..933024256 100644 --- a/shiny/bookmark/_bookmark.py +++ b/shiny/bookmark/_bookmark.py @@ -558,6 +558,13 @@ async def do_bookmark(self) -> None: await self.show_bookmark_url_modal(full_url) except Exception as e: + # TODO: REMOVE TEMP CODE w/ TRACEBACKS + import sys + import traceback + + # Starting in Python 3.10 this could be traceback.print_exception(e) + traceback.print_exception(*sys.exc_info()) + msg = f"Error bookmarking state: {e}" from ..ui._notification import notification_show diff --git a/shiny/render/_data_frame.py b/shiny/render/_data_frame.py index b70534c1c..4b590224a 100644 --- a/shiny/render/_data_frame.py +++ b/shiny/render/_data_frame.py @@ -562,6 +562,8 @@ def _reset_reactives(self) -> None: self._updated_data.unset() def _init_reactives(self) -> None: + from ..bookmark._restore_state import RestoreState + from ..bookmark._save_state import BookmarkState # Init self._value = reactive.Value(None) @@ -588,6 +590,55 @@ async def _(): # It currently is, as `@reactive.event()` is being used await self._attempt_update_cell_style() + def bookmark_cell_patch_id() -> str: + return f"{self.output_id}--cell_patch_map" + + def bookmark_data_id() -> str: + return f"{self.output_id}--data" + + @self._get_session().bookmark.on_bookmark + def _(state: BookmarkState): + cell_patch_map = self._cell_patch_map() + if cell_patch_map: + cell_patch_id_val = bookmark_cell_patch_id() + if cell_patch_id_val in state.values: + raise RuntimeError( + f"Bookmark state already contains a value for {cell_patch_id_val}. Please use a different ID." + ) + state.values[cell_patch_id_val] = cell_patch_map + + if self._updated_data.is_set(): + # TODO-barret-render.data_frame; Handle restoring updated data + # Related: Restoring Chat UI: https://github.com/posit-dev/py-shiny/issues/1952 + raise RuntimeError( + "Bookmarking a manually set data frame is not supported." + ) + + @self._get_session().bookmark.on_restored + async def _(state: RestoreState): + print("Available inputs:", list(state.input.keys())) + cell_selection_key = f"{self.output_id}_cell_selection" + column_sort_key = f"{self.output_id}_column_sort" + column_filter_key = f"{self.output_id}_column_filter" + if cell_selection_key in state.input: + print("setting cell selection", state.input[cell_selection_key]) + await self.update_cell_selection(state.input[cell_selection_key]) + print("done setting cell selection") + if column_sort_key in state.input: + await self.update_sort(state.input[column_sort_key]) + if column_filter_key in state.input: + await self.update_filter(state.input[column_filter_key]) + + cell_patch_map = state.values.get(bookmark_cell_patch_id(), None) + if cell_patch_map: + self._cell_patch_map.set(cell_patch_map) + self._updated_data.set(self._nw_data_to_original_type(self._nw_data())) + + updated_data = state.values.get(bookmark_data_id(), None) + if updated_data: + # TODO-barret-render.data_frame; Handle restoring updated data + raise RuntimeError("Restoring an updated data frame is not supported.") + def _get_session(self) -> Session: if self._session is None: raise RuntimeError( diff --git a/tests/playwright/ai_generated_apps/bookmark/dataframe/app-express.py b/tests/playwright/ai_generated_apps/bookmark/dataframe/app-express.py new file mode 100644 index 000000000..b08f214c4 --- /dev/null +++ b/tests/playwright/ai_generated_apps/bookmark/dataframe/app-express.py @@ -0,0 +1,80 @@ +from palmerpenguins import load_penguins + +from shiny import reactive +from shiny.express import app_opts, input, render, session, ui + +penguins = load_penguins() + +app_opts(bookmark_store="url") + +ui.input_bookmark_button() + + +@session.bookmark.on_bookmarked +async def _(url: str): + await session.bookmark.update_query_string(url) + + +ui.h2("Palmer Penguins") + +ui.h5("Current selection: ", {"class": "pt-2"}) + + +@render.code +def _(): + return penguins_df.cell_selection()["rows"] + + +ui.br() + + +@render.data_frame +def penguins_df(): + return render.DataGrid(penguins, selection_mode="rows") + + +@render.text +def penguins_df_text(): + return f"Selection mode: rows - Selected: {penguins_df.cell_selection()['rows']}" + + +ui.br() + + +@render.data_frame +def penguins_row_df(): + return render.DataGrid(penguins, selection_mode="row") + + +@render.text +def penguins_row_df_text(): + return f"Selection mode: row - Selected: {penguins_row_df.cell_selection()['rows']}" + + +ui.br() + + +@render.data_frame +def penguins_filter_df(): + return render.DataGrid(penguins, filters=True) + + +@render.text +def penguins_filter_df_text(): + return f"Filters enabled - Selected: {penguins_filter_df.cell_selection()['rows']}" + + +ui.br() + + +@render.data_frame +def penguins_editable_df(): + return render.DataGrid(penguins, editable=True) + + +@render.text +def penguins_editable_df_text(): + return f"Editable grid - Selected: {penguins_editable_df.cell_selection()['rows']}" + + +ui.br()