Merge branch 'client/feature-ingredient-select-dropdown' into 'main'
feat(client/ingredients): make ingredient list into dropdown Closes #67 See merge request cse1105/2025-2026/teams/csep-team-76!64
This commit is contained in:
commit
717e44154f
2 changed files with 110 additions and 11 deletions
|
|
@ -1,17 +1,28 @@
|
||||||
package client.scenes.recipe;
|
package client.scenes.recipe;
|
||||||
|
|
||||||
|
import client.utils.ServerUtils;
|
||||||
|
import com.google.inject.Inject;
|
||||||
import commons.FormalIngredient;
|
import commons.FormalIngredient;
|
||||||
import commons.Ingredient;
|
import commons.Ingredient;
|
||||||
import commons.RecipeIngredient;
|
import commons.RecipeIngredient;
|
||||||
import commons.Unit;
|
import commons.Unit;
|
||||||
import commons.VagueIngredient;
|
import commons.VagueIngredient;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.scene.control.ChoiceBox;
|
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.control.TextField;
|
||||||
import javafx.scene.input.KeyCode;
|
import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
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
|
* @see IngredientListCtrl
|
||||||
*/
|
*/
|
||||||
public class IngredientListCell extends OrderedEditableListCell<RecipeIngredient> {
|
public class IngredientListCell extends OrderedEditableListCell<RecipeIngredient> {
|
||||||
|
private final ServerUtils server;
|
||||||
|
private final Logger logger = Logger.getLogger(IngredientListCell.class.getName());
|
||||||
|
private final SimpleObjectProperty<Ingredient> selectedIngredient = new SimpleObjectProperty<>();
|
||||||
|
@Inject
|
||||||
|
public IngredientListCell(ServerUtils server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void commitEdit(RecipeIngredient newValue) {
|
public void commitEdit(RecipeIngredient newValue) {
|
||||||
super.commitEdit(newValue);
|
super.commitEdit(newValue);
|
||||||
|
|
@ -64,29 +82,105 @@ public class IngredientListCell extends OrderedEditableListCell<RecipeIngredient
|
||||||
this.setText(null);
|
this.setText(null);
|
||||||
this.setGraphic(container);
|
this.setGraphic(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a menu of ingredients from the provided iterable list of items.
|
||||||
|
* Modifies the provided <code>menu</code> 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 <T> The type this menu concerns itself with.
|
||||||
|
*/
|
||||||
|
private <T> void makeMenuItems(
|
||||||
|
MenuButton menu,
|
||||||
|
Iterable<T> items,
|
||||||
|
Function<T, String> labelMapper,
|
||||||
|
Consumer<T> onSelect,
|
||||||
|
HBox container) {
|
||||||
|
MenuItem newIngredientItem = new MenuItem();
|
||||||
|
newIngredientItem.setText("Something new...");
|
||||||
|
|
||||||
|
// on new ingredient click
|
||||||
|
newIngredientItem.setOnAction(_ -> {
|
||||||
|
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));
|
||||||
|
mi.setOnAction(_ -> {
|
||||||
|
menu.setText(labelMapper.apply(item));
|
||||||
|
onSelect.accept(item);
|
||||||
|
});
|
||||||
|
menu.getItems().add(mi);
|
||||||
|
}
|
||||||
|
}
|
||||||
private HBox makeInputLine(RecipeIngredient ingredient, TextField amountInput, ChoiceBox<Unit> unitChoice) {
|
private HBox makeInputLine(RecipeIngredient ingredient, TextField amountInput, ChoiceBox<Unit> unitChoice) {
|
||||||
TextField nameInput = new TextField(ingredient.ingredient.name);
|
MenuButton ingredientSelect = new MenuButton("Select your ingredient");
|
||||||
HBox container = new HBox(amountInput, unitChoice, nameInput);
|
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 -> {
|
container.setOnKeyReleased(event -> {
|
||||||
if (event.getCode() != KeyCode.ENTER) {
|
if (event.getCode() != KeyCode.ENTER) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Unit unit = unitChoice.getValue();
|
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()) {
|
if (unit == null || !unit.isFormal()) {
|
||||||
String desc = amountInput.getText();
|
String desc = amountInput.getText();
|
||||||
if (desc.isEmpty() || name.isEmpty()) {
|
if (desc.isEmpty()) {
|
||||||
// TODO printError() integration
|
logger.warning("Invalid description");
|
||||||
return; // The user is forced to kindly try again until something valid comes up.
|
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(i, desc));
|
||||||
commitEdit(new VagueIngredient(newIngredient, desc));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double amount = Double.parseDouble(amountInput.getText());
|
double amount = Double.parseDouble(amountInput.getText());
|
||||||
Ingredient newIngredient = new Ingredient(name, 0., 0., 0.);
|
commitEdit(new FormalIngredient(i, amount, unit.suffix));
|
||||||
commitEdit(new FormalIngredient(newIngredient, amount, unit.suffix));
|
|
||||||
});
|
});
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @NonNull TextField makeNewIngredientNameField(Consumer<Ingredient> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package client.scenes.recipe;
|
||||||
import client.utils.DefaultValueFactory;
|
import client.utils.DefaultValueFactory;
|
||||||
import client.utils.LocaleAware;
|
import client.utils.LocaleAware;
|
||||||
import client.utils.LocaleManager;
|
import client.utils.LocaleManager;
|
||||||
|
import client.utils.ServerUtils;
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import commons.FormalIngredient;
|
import commons.FormalIngredient;
|
||||||
import commons.Recipe;
|
import commons.Recipe;
|
||||||
|
|
@ -28,8 +29,12 @@ import javafx.scene.control.ListView.EditEvent;
|
||||||
*/
|
*/
|
||||||
public class IngredientListCtrl implements LocaleAware {
|
public class IngredientListCtrl implements LocaleAware {
|
||||||
private final LocaleManager localeManager;
|
private final LocaleManager localeManager;
|
||||||
|
private final ServerUtils server;
|
||||||
@Inject
|
@Inject
|
||||||
public IngredientListCtrl(LocaleManager localeManager) {
|
public IngredientListCtrl(
|
||||||
|
ServerUtils server,
|
||||||
|
LocaleManager localeManager) {
|
||||||
|
this.server = server;
|
||||||
this.localeManager = localeManager;
|
this.localeManager = localeManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,7 +166,7 @@ public class IngredientListCtrl implements LocaleAware {
|
||||||
this.ingredientListView.setEditable(true);
|
this.ingredientListView.setEditable(true);
|
||||||
this.ingredientListView.setCellFactory(
|
this.ingredientListView.setCellFactory(
|
||||||
list -> {
|
list -> {
|
||||||
return new IngredientListCell();
|
return new IngredientListCell(this.server);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ingredientListView.setOnEditCommit(this::handleIngredientEdit);
|
this.ingredientListView.setOnEditCommit(this::handleIngredientEdit);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue