diff --git a/eit_dash/callbacks/load_callbacks.py b/eit_dash/callbacks/load_callbacks.py index 32958fa..0e25925 100644 --- a/eit_dash/callbacks/load_callbacks.py +++ b/eit_dash/callbacks/load_callbacks.py @@ -189,7 +189,7 @@ def show_info( start_sample = file_data.continuous_data[RAW_EIT_LABEL].time[0] stop_sample = file_data.continuous_data[RAW_EIT_LABEL].time[-1] - dataset_name = f"Dataset {data_object.get_sequence_list_length()}" + dataset_name = data_object.get_next_dataset_label() selected_signals = selected_signals or [] # get the name of the selected continuous signals @@ -210,7 +210,7 @@ def show_info( data_object.add_sequence(cut_data) # create the info summary card - card = create_info_card(cut_data) + card = create_info_card(cut_data, remove_button=True) # add the card to the current results if container_state: @@ -288,6 +288,33 @@ def store_clicked_file(n_clicks, title): return None +@callback( + Output(ids.DATASET_CONTAINER, "children", allow_duplicate=True), + [ + Input({"type": ids.REMOVE_DATA_BUTTON, "index": ALL}, "n_clicks"), + ], + [ + State(ids.DATASET_CONTAINER, "children"), + ], + prevent_initial_call=True, +) +def remove_dataset(n_clicks, container): + """React to clicking the remove button of a dataset. + + Removes the card from the results and the dataset from the loaded selections. + """ + # at the element creation time, the update should be avoided + if all(element is None for element in n_clicks): + raise PreventUpdate + + input_id = ctx.triggered_id["index"] + + # remove from the singleton + data_object.remove_data(input_id) + + return [card for card in container if f"'index': '{input_id}'" not in str(card)] + + # Repopulate data after reload @callback( Output(ids.DATASET_CONTAINER, "children", allow_duplicate=True), @@ -298,4 +325,4 @@ def repopulate_data(reload): """Repopulate data after reloading page.""" # create the info summary card reloaded_data = data_object.get_all_sequences() - return [create_info_card(element) for element in reloaded_data] + return [create_info_card(element, remove_button=True) for element in reloaded_data] diff --git a/eit_dash/definitions/element_ids.py b/eit_dash/definitions/element_ids.py index 9c8c0da..ef4d1af 100644 --- a/eit_dash/definitions/element_ids.py +++ b/eit_dash/definitions/element_ids.py @@ -30,6 +30,7 @@ LOAD_RESULTS_TITLE = "load-results-title" PARENT_DIR = "parent-dir" POPULATE_DATA = "populate-data" +REMOVE_DATA_BUTTON = "remove-data-button" STORED_CWD = "stored-cwd" SELECT_CONFIRM_BUTTON = "select-confirm-button" diff --git a/eit_dash/utils/common.py b/eit_dash/utils/common.py index 87262a6..0f7fe1c 100644 --- a/eit_dash/utils/common.py +++ b/eit_dash/utils/common.py @@ -43,11 +43,12 @@ def create_filter_results_card(parameters: dict) -> dbc.Card: return dbc.Card(dbc.CardBody(card_list), id=ids.FILTERING_SAVED_CARD) -def create_info_card(dataset: Sequence) -> dbc.Card: +def create_info_card(dataset: Sequence, remove_button: bool = False) -> dbc.Card: """Create the card with the information on the loaded dataset to be displayed in the Results section. Args: dataset: Sequence object containing the selected dataset + remove_button: add the remove button if set to True """ info_data = { "Name": dataset.eit_data["raw"].path.name, @@ -64,8 +65,14 @@ def create_info_card(dataset: Sequence) -> dbc.Card: html.H6(dataset.eit_data["raw"].vendor, className="card-subtitle"), ] card_list += [dbc.Row(f"{data}: {value}", style=styles.INFO_CARD) for data, value in info_data.items()] - - return dbc.Card(dbc.CardBody(card_list), id="card-1") + if remove_button: + card_list += [ + dbc.Button( + "Remove", + id={"type": ids.REMOVE_DATA_BUTTON, "index": dataset.label}, + ), + ] + return dbc.Card(dbc.CardBody(card_list), id=dataset.label) def create_selected_period_card( diff --git a/eit_dash/utils/data_singleton.py b/eit_dash/utils/data_singleton.py index 0536117..457e065 100644 --- a/eit_dash/utils/data_singleton.py +++ b/eit_dash/utils/data_singleton.py @@ -63,6 +63,22 @@ def get_sequence_at(self, index: int): return self._data[index] + def get_dataset_labels(self) -> list[int]: + """Get a list of the labels of the datasets currently available.""" + return [dataset.label for dataset in self._data] + + def get_next_dataset_label(self): + """Determines the label to be assigned to the next dataset.""" + available_labels = self.get_dataset_labels() + label = f"Dataset {self.get_sequence_list_length()}" + + if label in available_labels: + for i in range(self.get_sequence_list_length()): + label = f"Dataset {i}" + if label not in available_labels: + break + return label + def dataset_exists(self, index: int) -> bool: """Verify that a sequence with the provided index exists. @@ -100,6 +116,20 @@ def add_stable_period( self._stable_periods.append(Period(data, dataset_index, period_index)) + def remove_data(self, label: str): + """Remove a sequence from data from the singleton. + + Args: + label: label of the sequence to be removed. + """ + for sequence in self._data: + if sequence.label == label: + self._data.remove(sequence) + return + + msg = f"Sequence with label {label} not found" + raise ValueError(msg) + def remove_stable_period(self, index: int): """Remove a stable period from the singleton. diff --git a/tests/unit_tests/test_load_callbacks.py b/tests/unit_tests/test_load_callbacks.py index b7b6519..80f4817 100644 --- a/tests/unit_tests/test_load_callbacks.py +++ b/tests/unit_tests/test_load_callbacks.py @@ -113,7 +113,13 @@ def test_show_info_callback(file_data: Sequence, expected_cut_info_data: dict): html.H6(Vendor.DRAEGER, className="card-subtitle"), ] card_list += [dbc.Row(f"{data}: {value}", style=styles.INFO_CARD) for data, value in expected_cut_info_data.items()] - mock_data_card = [dbc.Card(dbc.CardBody(card_list), id="card-1")] + card_list += [ + dbc.Button( + "Remove", + id={"type": ids.REMOVE_DATA_BUTTON, "index": "Dataset 0"}, + ), + ] + mock_data_card = [dbc.Card(dbc.CardBody(card_list), id="Dataset 0")] # Assessing the string converted objects, to check that the properties are the same. # Assessing for the equivalence of the objects directly will fail