From f87b4e7d37f58bec150992a7ceb6e0a1b9d79141 Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Fri, 16 Jan 2026 18:27:49 +0100 Subject: [PATCH 1/2] feat(client/ingredients): make into dropdown It is now possible to see a list of existing ingredients to choose from in a dropdown menu. Adds a server dependency to the ingredient list cell. Not ideal but could be revamped in the future. --- .../scenes/recipe/IngredientListCell.java | 96 +++++++++++++++++-- .../scenes/recipe/IngredientListCtrl.java | 9 +- 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/client/src/main/java/client/scenes/recipe/IngredientListCell.java b/client/src/main/java/client/scenes/recipe/IngredientListCell.java index b6c22bf..f29dd8d 100644 --- a/client/src/main/java/client/scenes/recipe/IngredientListCell.java +++ b/client/src/main/java/client/scenes/recipe/IngredientListCell.java @@ -1,17 +1,28 @@ package client.scenes.recipe; +import client.utils.ServerUtils; +import com.google.inject.Inject; import commons.FormalIngredient; import commons.Ingredient; import commons.RecipeIngredient; import commons.Unit; import commons.VagueIngredient; +import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.scene.control.ChoiceBox; +import javafx.scene.control.MenuButton; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.TextField; import javafx.scene.input.KeyCode; import javafx.scene.layout.HBox; +import org.checkerframework.checker.nullness.qual.NonNull; +import java.io.IOException; import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.logging.Logger; /** @@ -22,6 +33,13 @@ import java.util.Optional; * @see IngredientListCtrl */ public class IngredientListCell extends OrderedEditableListCell { + private final ServerUtils server; + private final Logger logger = Logger.getLogger(IngredientListCell.class.getName()); + private final SimpleObjectProperty selectedIngredient = new SimpleObjectProperty<>(); + @Inject + public IngredientListCell(ServerUtils server) { + this.server = server; + } @Override public void commitEdit(RecipeIngredient newValue) { super.commitEdit(newValue); @@ -64,29 +82,89 @@ public class IngredientListCell extends OrderedEditableListCell void makeMenuItems( + MenuButton menu, + Iterable items, + Function labelMapper, + Consumer onSelect, + HBox container) { + MenuItem newIngredientItem = new MenuItem(); + newIngredientItem.setText("Something new..."); + newIngredientItem.setOnAction(_ -> { + menu.setText("New Ingredient"); + selectedIngredient.setValue(null); + logger.info("Making new ingredient"); + // TODO printError() integration + TextField newIngredientNameInput = makeNewIngredientNameField(selectedIngredient::set); + container.getChildren().add(newIngredientNameInput); + }); + menu.getItems().addAll(newIngredientItem, new SeparatorMenuItem()); + for (T item : items) { + MenuItem mi = new MenuItem(); + mi.setText(labelMapper.apply(item)); + mi.setOnAction(_ -> { + menu.setText(labelMapper.apply(item)); + onSelect.accept(item); + }); + menu.getItems().add(mi); + } + } private HBox makeInputLine(RecipeIngredient ingredient, TextField amountInput, ChoiceBox unitChoice) { - TextField nameInput = new TextField(ingredient.ingredient.name); - HBox container = new HBox(amountInput, unitChoice, nameInput); + MenuButton ingredientSelect = new MenuButton("Select your ingredient"); + HBox container = new HBox(amountInput, unitChoice, ingredientSelect); + try { + makeMenuItems(ingredientSelect, server.getIngredients(), Ingredient::getName, i -> { + selectedIngredient.set(i); + logger.info("Selected ingredient: " + i); + }, container); + } catch (IOException | InterruptedException e) { + logger.severe("Exception while making ingredient selection"); + } container.setOnKeyReleased(event -> { if (event.getCode() != KeyCode.ENTER) { return; } Unit unit = unitChoice.getValue(); - String name = nameInput.getText(); + Ingredient i = selectedIngredient.getValue(); + if (i == null) { + + + return; // The user is forced to kindly try again until something valid comes up. + } if (unit == null || !unit.isFormal()) { String desc = amountInput.getText(); - if (desc.isEmpty() || name.isEmpty()) { - // TODO printError() integration + if (desc.isEmpty()) { + logger.warning("Invalid description"); return; // The user is forced to kindly try again until something valid comes up. } - Ingredient newIngredient = new Ingredient(name, 0., 0., 0.); - commitEdit(new VagueIngredient(newIngredient, desc)); + commitEdit(new VagueIngredient(i, desc)); return; } double amount = Double.parseDouble(amountInput.getText()); - Ingredient newIngredient = new Ingredient(name, 0., 0., 0.); - commitEdit(new FormalIngredient(newIngredient, amount, unit.suffix)); + commitEdit(new FormalIngredient(i, amount, unit.suffix)); }); return container; } + + private @NonNull TextField makeNewIngredientNameField(Consumer onSubmit) { + TextField newIngredientNameInput = new TextField("New ingredient..."); + newIngredientNameInput.setOnKeyReleased(e -> { + if (e.getCode() != KeyCode.ENTER) { + return; + } + String newIngredientName = newIngredientNameInput.getText(); + if (newIngredientName.isEmpty()) { + // printerr + logger.warning("invalid name received!"); + return; + } + Ingredient newIngredient = new Ingredient( + newIngredientName, + 0., + 0., + 0.); + onSubmit.accept(newIngredient); + }); + return newIngredientNameInput; + } } diff --git a/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java b/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java index 5eb1030..359ba18 100644 --- a/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java +++ b/client/src/main/java/client/scenes/recipe/IngredientListCtrl.java @@ -3,6 +3,7 @@ package client.scenes.recipe; import client.utils.DefaultValueFactory; import client.utils.LocaleAware; import client.utils.LocaleManager; +import client.utils.ServerUtils; import com.google.inject.Inject; import commons.FormalIngredient; import commons.Recipe; @@ -28,8 +29,12 @@ import javafx.scene.control.ListView.EditEvent; */ public class IngredientListCtrl implements LocaleAware { private final LocaleManager localeManager; + private final ServerUtils server; @Inject - public IngredientListCtrl(LocaleManager localeManager) { + public IngredientListCtrl( + ServerUtils server, + LocaleManager localeManager) { + this.server = server; this.localeManager = localeManager; } @@ -161,7 +166,7 @@ public class IngredientListCtrl implements LocaleAware { this.ingredientListView.setEditable(true); this.ingredientListView.setCellFactory( list -> { - return new IngredientListCell(); + return new IngredientListCell(this.server); }); this.ingredientListView.setOnEditCommit(this::handleIngredientEdit); From 5942250f11e6438f1d89665d4881172d883c8fd5 Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Sat, 17 Jan 2026 13:37:24 +0100 Subject: [PATCH 2/2] docs: add notes to makeMenuItems --- .../scenes/recipe/IngredientListCell.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/client/src/main/java/client/scenes/recipe/IngredientListCell.java b/client/src/main/java/client/scenes/recipe/IngredientListCell.java index f29dd8d..a96c5fc 100644 --- a/client/src/main/java/client/scenes/recipe/IngredientListCell.java +++ b/client/src/main/java/client/scenes/recipe/IngredientListCell.java @@ -82,6 +82,18 @@ public class IngredientListCell extends OrderedEditableListCellmenu reference. + * @param menu The MenuButton to add items to. + * @param items The items to add to the dropdown menu. + * @param labelMapper A function that outputs a string from the object to display on the label. + * @param onSelect Callback that triggers upon selection. + * @param container Parent container of the menu. + * Required since the UI logic needs to append a field to the end of the HBox upon selection + * @param The type this menu concerns itself with. + */ private void makeMenuItems( MenuButton menu, Iterable items, @@ -90,15 +102,21 @@ public class IngredientListCell extends OrderedEditableListCell { - menu.setText("New Ingredient"); - selectedIngredient.setValue(null); + menu.setText("New Ingredient"); // otherwise the text label on menu refuses to update + selectedIngredient.setValue(null); // indicate null to signal a new ingredient creation logger.info("Making new ingredient"); // TODO printError() integration TextField newIngredientNameInput = makeNewIngredientNameField(selectedIngredient::set); container.getChildren().add(newIngredientNameInput); }); + + // Puts the add ingredient button and a separator on top of the dropdown menu. menu.getItems().addAll(newIngredientItem, new SeparatorMenuItem()); + + // Iterates over the list of items and applies the label and onSelect handlers. for (T item : items) { MenuItem mi = new MenuItem(); mi.setText(labelMapper.apply(item)); @@ -127,8 +145,6 @@ public class IngredientListCell extends OrderedEditableListCell