diff --git a/src/app.py b/src/app.py index b783cf9..97abb4a 100644 --- a/src/app.py +++ b/src/app.py @@ -36,7 +36,7 @@ def index(session): layout = Layout(session_instance, "Testing Layout") layout.set_footer("Goodbye World") - tabs_manager = TabsManager(layout, _id=f"{TabsManager.compute_prefix()}-main") + tabs_manager = TabsManager(layout, _id=f"-tabs_manager") btn_show_right_drawer = mk.button("show", command=layout.commands.toggle_drawer("right"), id="btn_show_right_drawer_id") @@ -55,7 +55,7 @@ def index(session): btn_file_upload = mk.label("Upload", icon=folder_open20_regular, - command=tabs_manager.commands.add_tab("File Open", FileUpload(layout)), + command=tabs_manager.commands.add_tab("File Open", FileUpload(layout, _id="-file_upload")), id="file_upload_id") layout.header_left.add(tabs_manager.add_tab_btn()) @@ -64,8 +64,11 @@ def index(session): layout.left_drawer.add(btn_show_commands_debugger, "Debugger") layout.left_drawer.add(btn_file_upload, "Test") layout.set_main(tabs_manager) - keyboard = Keyboard(layout).add("ctrl+o", tabs_manager.commands.add_tab("File Open", FileUpload(layout))) - keyboard.add("ctrl+n", tabs_manager.commands.add_tab("File Open", FileUpload(layout))) + keyboard = Keyboard(layout, _id="-keyboard").add("ctrl+o", + tabs_manager.commands.add_tab("File Open", + FileUpload(layout, + _id="-file_upload"))) + keyboard.add("ctrl+n", tabs_manager.commands.add_tab("File Open", FileUpload(layout, _id="-file_upload"))) return layout, keyboard diff --git a/src/myfasthtml/controls/InstancesDebugger.py b/src/myfasthtml/controls/InstancesDebugger.py index 7428b0d..fcec443 100644 --- a/src/myfasthtml/controls/InstancesDebugger.py +++ b/src/myfasthtml/controls/InstancesDebugger.py @@ -13,7 +13,7 @@ class InstancesDebugger(SingleInstance): nodes, edges = from_parent_child_list( instances, id_getter=lambda x: x.get_full_id(), - label_getter=lambda x: f"{s_name(x.get_session())}-{x.get_prefix()}", + label_getter=lambda x: f"{x.get_id()}", parent_getter=lambda x: x.get_full_parent_id() ) for edge in edges: @@ -23,7 +23,7 @@ class InstancesDebugger(SingleInstance): for node in nodes: node["shape"] = "box" - vis_network = VisNetwork(self, nodes=nodes, edges=edges) + vis_network = VisNetwork(self, nodes=nodes, edges=edges, _id="-vis") # vis_network.add_to_options(physics={"wind": {"x": 0, "y": 1}}) return vis_network diff --git a/src/myfasthtml/controls/TabsManager.py b/src/myfasthtml/controls/TabsManager.py index ccf99d4..0bf2fa3 100644 --- a/src/myfasthtml/controls/TabsManager.py +++ b/src/myfasthtml/controls/TabsManager.py @@ -84,7 +84,8 @@ class TabsManager(MultipleInstance): self._search = Search(self, items=self._get_tab_list(), get_attr=lambda x: x["label"], - template=self._mk_tab_button) + template=self._mk_tab_button, + _id="-search") logger.debug(f"TabsManager created with id: {self._id}") logger.debug(f" tabs : {self._get_ordered_tabs()}") logger.debug(f" active tab : {self._state.active_tab}") diff --git a/src/myfasthtml/core/instances.py b/src/myfasthtml/core/instances.py index cd74baa..a3a12e3 100644 --- a/src/myfasthtml/core/instances.py +++ b/src/myfasthtml/core/instances.py @@ -31,9 +31,8 @@ class BaseInstance: session = args[1] if len(args) > 1 and isinstance(args[1], dict) else kwargs.get("session", None) _id = args[2] if len(args) > 2 and isinstance(args[2], str) else kwargs.get("_id", None) - # Compute _id if not provided - if _id is None: - _id = cls.compute_id() + # Compute _id + _id = cls.compute_id(_id, parent) if session is None: if parent is not None: @@ -68,7 +67,7 @@ class BaseInstance: self._parent = parent self._session = session or (parent.get_session() if parent else None) - self._id = _id or self.compute_id() + self._id = self.compute_id(_id, parent) self._prefix = self._id if isinstance(self, (UniqueInstance, SingleInstance)) else self.compute_prefix() if auto_register: @@ -98,12 +97,18 @@ class BaseInstance: return f"mf-{pascal_to_snake(cls.__name__)}" @classmethod - def compute_id(cls): - prefix = cls.compute_prefix() - if issubclass(cls, SingleInstance): - _id = prefix - else: - _id = f"{prefix}-{str(uuid.uuid4())}" + def compute_id(cls, _id: Optional[str], parent: Optional['BaseInstance']): + if _id is None: + prefix = cls.compute_prefix() + if issubclass(cls, SingleInstance): + _id = prefix + else: + _id = f"{prefix}-{str(uuid.uuid4())}" + return _id + + if _id.startswith("-") and parent is not None: + return f"{parent.get_prefix()}{_id}" + return _id diff --git a/src/myfasthtml/core/network_utils.py b/src/myfasthtml/core/network_utils.py index bb23880..4840cf0 100644 --- a/src/myfasthtml/core/network_utils.py +++ b/src/myfasthtml/core/network_utils.py @@ -1,5 +1,7 @@ from collections.abc import Callable +ROOT_COLOR = "#ff9999" +GHOST_COLOR = "#cccccc" def from_nested_dict(trees: list[dict]) -> tuple[list, list]: """ @@ -147,8 +149,8 @@ def from_parent_child_list( id_getter: Callable = None, label_getter: Callable = None, parent_getter: Callable = None, - ghost_color: str = "#cccccc", - root_color: str | None = "#ff9999" + ghost_color: str = GHOST_COLOR, + root_color: str | None = ROOT_COLOR ) -> tuple[list, list]: """ Convert a list of items with parent references to vis.js nodes and edges format. diff --git a/tests/core/test_instances.py b/tests/core/test_instances.py index 5f57ea1..358bcca 100644 --- a/tests/core/test_instances.py +++ b/tests/core/test_instances.py @@ -129,9 +129,9 @@ class TestBaseInstance: assert instance.get_id() is not None assert instance.get_id().startswith("mf-base_instance-") - def test_i_can_get_prefix_from_class_name(self): + def test_i_can_get_prefix_from_class_name(self, session): """Test that get_prefix() returns the correct snake_case prefix.""" - prefix = BaseInstance.get_prefix() + prefix = BaseInstance(None, session).get_prefix() assert prefix == "mf-base_instance" @@ -182,7 +182,7 @@ class TestSingleInstance: instance = SingleInstance(parent=None, session=session) assert instance.get_id() == "mf-single_instance" - assert instance.get_id() == SingleInstance.get_prefix() + assert instance.get_prefix() == "mf-single_instance" class TestSingleInstanceSubclass: @@ -249,6 +249,18 @@ class TestMultipleInstance: instance2 = MultipleInstance(parent=root_instance, session=session, _id=custom_id) assert instance1 is instance2 + + def test_key_prefixed_by_underscore_uses_the_parent_id_as_prefix(self, root_instance): + """Test that key prefixed by underscore uses the parent id as prefix.""" + instance = MultipleInstance(parent=root_instance, _id="-test_id") + + assert instance.get_id() == f"{root_instance.get_id()}-test_id" + + def test_no_parent_id_as_prefix_if_parent_is_none(self, session, root_instance): + """Test that key prefixed by underscore does not use the parent id as prefix if parent is None.""" + instance = MultipleInstance(parent=None, session=session, _id="-test_id") + + assert instance.get_id() == "-test_id" class TestMultipleInstanceSubclass: @@ -295,9 +307,9 @@ class TestMultipleInstanceSubclass: with pytest.raises(TypeError): MultipleInstance(parent=root_instance, _id=instance1.get_id()) - def test_i_can_get_correct_prefix_for_multiple_subclass(self): + def test_i_can_get_correct_prefix_for_multiple_subclass(self, root_instance): """Test that subclass has correct auto-generated prefix.""" - prefix = SubMultipleInstance.get_prefix() + prefix = SubMultipleInstance(root_instance).get_prefix() assert prefix == "mf-sub_multiple_instance" diff --git a/tests/core/test_network_utils.py b/tests/core/test_network_utils.py index 3c33235..b819029 100644 --- a/tests/core/test_network_utils.py +++ b/tests/core/test_network_utils.py @@ -405,7 +405,7 @@ class TestFromParentChildList: ghost_node = [n for n in nodes if n["id"] == "ghost"][0] assert "color" in ghost_node - assert ghost_node["color"] == "#ff9999" + assert ghost_node["color"] == "#cccccc" def test_i_can_use_custom_ghost_color(self): """Test that custom ghost_color parameter is applied."""