Compare commits
2 commits
827fe195a9
...
28e1d9a206
| Author | SHA1 | Date | |
|---|---|---|---|
|
28e1d9a206 |
|||
|
676b8dbee0 |
46 changed files with 214 additions and 1412 deletions
|
|
@ -21,13 +21,7 @@
|
|||
<artifactId>commons</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- Source: https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.17</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public class IngredientController {
|
|||
private final RestTemplate restTemplate = new RestTemplate(); // Simplified REST client
|
||||
|
||||
@FXML
|
||||
public void handleDeleteIngredient(ActionEvent event) {
|
||||
private void handleDeleteIngredient(ActionEvent event) {
|
||||
// Get selected ingredient
|
||||
Ingredient selectedIngredient = ingredientListView.getSelectionModel().getSelectedItem();
|
||||
if (selectedIngredient == null) {
|
||||
|
|
|
|||
|
|
@ -21,11 +21,6 @@ import client.scenes.nutrition.NutritionDetailsCtrl;
|
|||
import client.scenes.nutrition.NutritionViewCtrl;
|
||||
import client.scenes.recipe.IngredientListCtrl;
|
||||
import client.scenes.recipe.RecipeStepListCtrl;
|
||||
import client.scenes.shopping.ShoppingListCtrl;
|
||||
import client.scenes.shopping.ShoppingListNewItemPromptCtrl;
|
||||
import client.service.ShoppingListService;
|
||||
import client.service.ShoppingListServiceImpl;
|
||||
import client.service.ShoppingListViewModel;
|
||||
import client.utils.ConfigService;
|
||||
import client.utils.LocaleManager;
|
||||
import client.utils.server.ServerUtils;
|
||||
|
|
@ -62,10 +57,6 @@ public class MyModule implements Module {
|
|||
binder.bind(new TypeLiteral<WebSocketDataService<Long, Recipe>>() {}).toInstance(
|
||||
new WebSocketDataService<>()
|
||||
);
|
||||
binder.bind(ShoppingListNewItemPromptCtrl.class).in(Scopes.SINGLETON);
|
||||
binder.bind(ShoppingListCtrl.class).in(Scopes.SINGLETON);
|
||||
binder.bind(ShoppingListViewModel.class).toInstance(new ShoppingListViewModel());
|
||||
binder.bind(ShoppingListService.class).to(ShoppingListServiceImpl.class);
|
||||
binder.bind(new TypeLiteral<WebSocketDataService<Long, Ingredient>>() {}).toInstance(
|
||||
new WebSocketDataService<>()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package client;
|
|||
|
||||
import client.scenes.FoodpalApplicationCtrl;
|
||||
import client.scenes.MainCtrl;
|
||||
import client.scenes.ServerConnectionDialogCtrl;
|
||||
import client.utils.server.ServerUtils;
|
||||
import com.google.inject.Injector;
|
||||
import javafx.application.Application;
|
||||
|
|
@ -28,15 +27,10 @@ public class UI extends Application {
|
|||
|
||||
var serverUtils = INJECTOR.getInstance(ServerUtils.class);
|
||||
if (!serverUtils.isServerAvailable()) {
|
||||
var connectionHandler = INJECTOR.getInstance(ServerConnectionDialogCtrl.class);
|
||||
boolean serverConnected = connectionHandler.promptForURL();
|
||||
|
||||
if(!serverConnected){
|
||||
var msg = "User Cancelled Server connection. Shutting down";
|
||||
System.err.print(msg);
|
||||
var msg = "Server needs to be started before the client, but it does not seem to be available. Shutting down.";
|
||||
System.err.println(msg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
var foodpal = FXML.load(FoodpalApplicationCtrl.class, "client", "scenes", "FoodpalApplication.fxml");
|
||||
|
||||
var mainCtrl = INJECTOR.getInstance(MainCtrl.class);
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
package client.exception;
|
||||
|
||||
public class DuplicateIngredientException extends Exception{
|
||||
private final String ingredientName;
|
||||
|
||||
public DuplicateIngredientException(String ingredientName){
|
||||
super("An ingredient with name " + ingredientName + " already exists, please provide a different name");
|
||||
this.ingredientName = ingredientName;
|
||||
}
|
||||
|
||||
public DuplicateIngredientException(String ingredientName, String message){
|
||||
super(message);
|
||||
this.ingredientName = ingredientName;
|
||||
}
|
||||
|
||||
public String getIngredientName() {
|
||||
return ingredientName;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,17 +2,17 @@ package client.scenes;
|
|||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import client.UI;
|
||||
import client.exception.InvalidModificationException;
|
||||
import client.scenes.nutrition.NutritionViewCtrl;
|
||||
import client.scenes.recipe.RecipeDetailCtrl;
|
||||
|
||||
import client.scenes.shopping.ShoppingListCtrl;
|
||||
import client.utils.Config;
|
||||
import client.utils.ConfigService;
|
||||
import client.utils.DefaultValueFactory;
|
||||
|
|
@ -21,6 +21,7 @@ import client.utils.LocaleManager;
|
|||
import client.utils.server.ServerUtils;
|
||||
import client.utils.WebSocketDataService;
|
||||
import client.utils.WebSocketUtils;
|
||||
import commons.Ingredient;
|
||||
import commons.Recipe;
|
||||
|
||||
import commons.ws.Topics;
|
||||
|
|
@ -31,20 +32,14 @@ import commons.ws.messages.Message;
|
|||
import commons.ws.messages.UpdateRecipeMessage;
|
||||
import jakarta.inject.Inject;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
|
@ -70,7 +65,8 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
@FXML
|
||||
public ListView<Recipe> recipeList;
|
||||
|
||||
private final ListProperty<Recipe> favouriteRecipeList = new SimpleListProperty<>();
|
||||
@FXML
|
||||
private ListView<Ingredient> ingredientListView;
|
||||
|
||||
@FXML
|
||||
private Button addRecipeButton;
|
||||
|
|
@ -88,8 +84,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
@FXML
|
||||
private ToggleButton favouritesOnlyToggle;
|
||||
|
||||
@FXML
|
||||
private Button manageIngredientsButton;
|
||||
private List<Recipe> allRecipes = new ArrayList<>();
|
||||
|
||||
@FXML
|
||||
private Label updatedBadge;
|
||||
|
|
@ -205,12 +200,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
} //gives star in front fav items
|
||||
boolean fav = config.isFavourite(item.getId());
|
||||
setText((fav ? "★ " : "") + item.getName());
|
||||
|
||||
if(fav){
|
||||
setTextFill(Color.BLUE);
|
||||
}else{
|
||||
setTextFill(Color.BLACK);
|
||||
}
|
||||
}
|
||||
});
|
||||
// When your selection changes, update details in the panel
|
||||
|
|
@ -288,13 +277,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
openSelectedRecipe();
|
||||
}
|
||||
});
|
||||
recipeList.getItems().addListener((ListChangeListener.Change<? extends Recipe> c) -> {
|
||||
favouriteRecipeList.set(
|
||||
FXCollections.observableList(
|
||||
recipeList.getItems().stream().filter(r -> config.isFavourite(r.getId())).toList()
|
||||
));
|
||||
System.out.println(favouriteRecipeList);
|
||||
});
|
||||
|
||||
this.initializeSearchBar();
|
||||
refresh();
|
||||
|
|
@ -308,9 +290,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
removeRecipeButton.setText(getLocaleString("menu.button.remove.recipe"));
|
||||
cloneRecipeButton.setText(getLocaleString("menu.button.clone"));
|
||||
recipesLabel.setText(getLocaleString("menu.label.recipes"));
|
||||
|
||||
favouritesOnlyToggle.setText(getLocaleString("menu.button.favourites"));
|
||||
manageIngredientsButton.setText(getLocaleString("menu.button.ingredients"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -333,7 +312,8 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
logger.severe(msg);
|
||||
printError(msg);
|
||||
}
|
||||
recipeList.getItems().setAll(recipes);
|
||||
|
||||
allRecipes = new ArrayList<>(recipes);
|
||||
applyRecipeFilterAndKeepSelection();
|
||||
|
||||
showUpdatedBadge();
|
||||
|
|
@ -429,20 +409,16 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
public void applyRecipeFilterAndKeepSelection() {
|
||||
Recipe selected = recipeList.getSelectionModel().getSelectedItem();
|
||||
Long selectedId = selected == null ? null : selected.getId();
|
||||
List<Recipe> view = recipeList.getItems().stream().toList();
|
||||
|
||||
List<Recipe> view = allRecipes;
|
||||
|
||||
if (favouritesOnlyToggle != null && favouritesOnlyToggle.isSelected()) {
|
||||
view = favouriteRecipeList.get();
|
||||
view = allRecipes.stream()
|
||||
.filter(r -> config.isFavourite(r.getId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
recipeList.getItems().setAll(view);
|
||||
}
|
||||
try {
|
||||
if (favouritesOnlyToggle != null && !favouritesOnlyToggle.isSelected()) {
|
||||
recipeList.getItems().setAll(server.getRecipes(config.getRecipeLanguages()));
|
||||
}
|
||||
}
|
||||
catch (IOException | InterruptedException e) {
|
||||
logger.severe(e.getMessage());
|
||||
}
|
||||
|
||||
// restore selection if possible
|
||||
if (selectedId != null) {
|
||||
|
|
@ -463,6 +439,114 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
this.recipeDetailController.refreshFavouriteButton();
|
||||
this.recipeDetailController.setVisible(!recipeList.getItems().isEmpty());
|
||||
}
|
||||
|
||||
//Delete Ingredient button click
|
||||
@FXML
|
||||
private void handleDeleteIngredient() {
|
||||
// Get selected ingredient
|
||||
Ingredient selectedIngredient = ingredientListView.getSelectionModel().getSelectedItem();
|
||||
|
||||
if (selectedIngredient == null) {
|
||||
// Show an error message if no ingredient is selected
|
||||
showError("No ingredient selected", "Please select an ingredient to delete.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the ingredient is used in any recipe
|
||||
checkIngredientUsage(selectedIngredient);
|
||||
}
|
||||
|
||||
// Check if ingredient is used in any recipe before deleting
|
||||
private void checkIngredientUsage(Ingredient ingredient) {
|
||||
try {
|
||||
long usageCount = server.getIngredientUsage(ingredient.getId()); // Check ingredient usage via ServerUtils
|
||||
|
||||
if (usageCount > 0) {
|
||||
// If ingredient is used, show a warning dialog
|
||||
showWarningDialog(ingredient, usageCount);
|
||||
} else {
|
||||
// If not used, delete
|
||||
deleteIngredient(ingredient);
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
showError("Error", "Failed to check ingredient usage: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteIngredient(Ingredient ingredient) {
|
||||
try {
|
||||
server.deleteIngredient(ingredient.getId()); // Call ServerUtils to delete the ingredient
|
||||
showConfirmation("Success", "Ingredient '" + ingredient.getName() + "' has been deleted.");
|
||||
refreshIngredientList(); // refresh the ingredient list
|
||||
} catch (IOException | InterruptedException e) {
|
||||
showError("Error", "Failed to delete ingredient: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void showWarningDialog(Ingredient ingredient, long usedInRecipes) {
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||
}
|
||||
|
||||
private void showError(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void showConfirmation(String title, String message) {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText(message);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
private void refreshIngredientList() {
|
||||
// Refresh
|
||||
ingredientListView.getItems().clear();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void handleAddIngredient() {
|
||||
//ask the user for the ingredient name
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
dialog.setTitle("Add Ingredient");
|
||||
dialog.setHeaderText("Enter the ingredient name:");
|
||||
dialog.setContentText("Ingredient:");
|
||||
|
||||
// Wait for the user to enter a value
|
||||
Optional<String> result = dialog.showAndWait();
|
||||
|
||||
result.ifPresent(name -> {
|
||||
// Create a new Ingredient object
|
||||
Ingredient newIngredient = new Ingredient();
|
||||
newIngredient.setName(name);
|
||||
|
||||
// Add the new ingredient to the ListView
|
||||
ingredientListView.getItems().add(newIngredient);
|
||||
});
|
||||
}
|
||||
|
||||
// display ingredient name
|
||||
@FXML
|
||||
private void initializeIngredients() {
|
||||
ingredientListView.setCellFactory(list -> new ListCell<Ingredient>() {
|
||||
@Override
|
||||
protected void updateItem(Ingredient item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
setText(item.getName()); // Display the ingredient name in the ListView
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void openIngredientsPopup() {
|
||||
try {
|
||||
|
|
@ -474,7 +558,7 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
var root = pair.getValue();
|
||||
|
||||
var stage = new javafx.stage.Stage();
|
||||
stage.setTitle(getLocaleString("menu.ingredients.title"));
|
||||
stage.setTitle("Nutrition values view");
|
||||
stage.initModality(javafx.stage.Modality.APPLICATION_MODAL);
|
||||
stage.setScene(new javafx.scene.Scene(root));
|
||||
stage.showAndWait();
|
||||
|
|
@ -514,14 +598,6 @@ public class FoodpalApplicationCtrl implements LocaleAware {
|
|||
updatedBadgeTimer.playFromStart();
|
||||
}
|
||||
|
||||
public void openShoppingListWindow() throws IOException {
|
||||
var root = UI.getFXML().load(ShoppingListCtrl.class, "client", "scenes", "shopping", "ShoppingList.fxml");
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle(this.getLocaleString("menu.shopping.title"));
|
||||
stage.setScene(new Scene(root.getValue()));
|
||||
stage.show();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
package client.scenes.Ingredient;
|
||||
|
||||
import client.exception.DuplicateIngredientException;
|
||||
import client.scenes.nutrition.NutritionDetailsCtrl;
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import client.utils.server.ServerUtils;
|
||||
import commons.Ingredient;
|
||||
import jakarta.inject.Inject;
|
||||
import javafx.fxml.FXML;
|
||||
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
|
|
@ -25,18 +19,10 @@ import java.util.logging.Logger;
|
|||
//TODO and check for capital letter milk and MILK are seen as different
|
||||
|
||||
|
||||
public class IngredientListCtrl implements LocaleAware {
|
||||
public class IngredientListCtrl {
|
||||
|
||||
private final ServerUtils server;
|
||||
private final LocaleManager localeManager;
|
||||
private final Logger logger = Logger.getLogger(IngredientListCtrl.class.getName());
|
||||
|
||||
@FXML
|
||||
public Label ingredientsLabel;
|
||||
public Button addButton;
|
||||
public Button refreshButton;
|
||||
public Button deleteButton;
|
||||
public Button closeButton;
|
||||
|
||||
@FXML
|
||||
private ListView<Ingredient> ingredientListView;
|
||||
@FXML
|
||||
|
|
@ -45,30 +31,14 @@ public class IngredientListCtrl implements LocaleAware {
|
|||
@Inject
|
||||
public IngredientListCtrl(
|
||||
ServerUtils server,
|
||||
LocaleManager localeManager,
|
||||
NutritionDetailsCtrl nutritionDetailsCtrl
|
||||
) {
|
||||
this.server = server;
|
||||
this.localeManager = localeManager;
|
||||
this.nutritionDetailsCtrl = nutritionDetailsCtrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateText() {
|
||||
ingredientsLabel.setText(getLocaleString("menu.label.ingredients"));
|
||||
addButton.setText(getLocaleString("menu.button.add"));
|
||||
refreshButton.setText(getLocaleString("menu.button.refresh"));
|
||||
deleteButton.setText(getLocaleString("menu.button.delete"));
|
||||
closeButton.setText(getLocaleString("menu.button.close"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleManager getLocaleManager() {
|
||||
return this.localeManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeComponents() {
|
||||
@FXML
|
||||
public void initialize() {
|
||||
ingredientListView.setCellFactory(list -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(Ingredient item, boolean empty) {
|
||||
|
|
@ -90,7 +60,6 @@ public class IngredientListCtrl implements LocaleAware {
|
|||
|
||||
refresh();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void addIngredient() {
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
|
|
@ -114,11 +83,10 @@ public class IngredientListCtrl implements LocaleAware {
|
|||
refresh(); // reload list from server
|
||||
} catch (IOException | InterruptedException e) {
|
||||
showError("Failed to create ingredient: " + e.getMessage());
|
||||
} catch (DuplicateIngredientException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void refresh() {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package client.scenes;
|
||||
|
||||
import client.utils.Config;
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import com.google.inject.Inject;
|
||||
|
|
@ -42,12 +41,11 @@ public class LangSelectMenuCtrl implements LocaleAware {
|
|||
String lang = langSelectMenu.getSelectionModel().getSelectedItem();
|
||||
logger.info("Switching locale to " + lang);
|
||||
manager.setLocale(Locale.of(lang));
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeComponents() {
|
||||
langSelectMenu.getItems().setAll(Config.languages);
|
||||
langSelectMenu.getItems().setAll("en", "pl", "nl", "zht", "zhc", "tok", "tr");
|
||||
langSelectMenu.setValue(manager.getLocale().getLanguage());
|
||||
langSelectMenu.setConverter(new StringConverter<String>() {
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package client.scenes;
|
||||
|
||||
import client.utils.Config;
|
||||
import client.utils.ConfigService;
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
|
|
@ -55,7 +54,7 @@ public class LanguageFilterCtrl implements LocaleAware {
|
|||
public void initializeComponents() {
|
||||
var items = this.langFilterMenu.getItems();
|
||||
|
||||
final List<String> languages = List.of(Config.languages);
|
||||
final List<String> languages = List.of("en", "nl", "pl", "tok", "tr");
|
||||
this.selectedLanguages = this.configService.getConfig().getRecipeLanguages();
|
||||
this.updateMenuButtonDisplay();
|
||||
|
||||
|
|
|
|||
|
|
@ -10,15 +10,11 @@ import javafx.animation.PauseTransition;
|
|||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Controller for the search bar component.
|
||||
|
|
@ -103,11 +99,7 @@ public class SearchBarCtrl implements LocaleAware {
|
|||
currentSearchTask = new Task<>() {
|
||||
@Override
|
||||
protected List<Recipe> call() throws IOException, InterruptedException {
|
||||
var recipes = serverUtils.getRecipesFiltered(
|
||||
"",
|
||||
configService.getConfig().getRecipeLanguages()
|
||||
);
|
||||
return applyMultiTermAndFilter(recipes, filter);
|
||||
return serverUtils.getRecipesFiltered(filter, configService.getConfig().getRecipeLanguages());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -154,61 +146,8 @@ public class SearchBarCtrl implements LocaleAware {
|
|||
});
|
||||
|
||||
this.searchField.setOnKeyReleased(event -> {
|
||||
if (event.getCode() == KeyCode.ESCAPE) {
|
||||
searchField.clear();
|
||||
this.onSearch();
|
||||
return;
|
||||
}
|
||||
// This cancels the current debounce timer and restarts it.
|
||||
this.searchDebounce.playFromStart();
|
||||
});
|
||||
}
|
||||
private List<Recipe> applyMultiTermAndFilter(List<Recipe> recipes, String query) {
|
||||
if (recipes == null) {
|
||||
return List.of();
|
||||
}
|
||||
if (query == null || query.isBlank()) {
|
||||
return recipes;
|
||||
}
|
||||
|
||||
var tokens = Arrays.stream(query.toLowerCase(Locale.ROOT).split("[\\s,]+"))
|
||||
.map(String::trim)
|
||||
.filter(s -> !s.isBlank())
|
||||
.filter(s -> !s.equals("and"))
|
||||
.toList();
|
||||
|
||||
if (tokens.isEmpty()) {
|
||||
return recipes;
|
||||
}
|
||||
|
||||
return recipes.stream()
|
||||
.filter(r -> {
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (r.getName() != null) {
|
||||
sb.append(r.getName()).append(' ');
|
||||
}
|
||||
|
||||
if (r.getIngredients() != null) {
|
||||
r.getIngredients().forEach(i -> {
|
||||
if (i != null) {
|
||||
sb.append(i).append(' ');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (r.getPreparationSteps() != null) {
|
||||
r.getPreparationSteps().forEach(s -> {
|
||||
if (s != null) {
|
||||
sb.append(s).append(' ');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var haystack = sb.toString().toLowerCase(Locale.ROOT);
|
||||
return tokens.stream().allMatch(haystack::contains);
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
package client.scenes;
|
||||
|
||||
|
||||
import client.utils.ConfigService;
|
||||
import client.utils.server.ServerUtils;
|
||||
import com.google.inject.Inject;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
public class ServerConnectionDialogCtrl {
|
||||
|
||||
private final ConfigService configService;
|
||||
private final ServerUtils serverUtils;
|
||||
|
||||
|
||||
|
||||
@Inject
|
||||
public ServerConnectionDialogCtrl(ConfigService configService, ServerUtils serverUtils) {
|
||||
this.configService = configService;
|
||||
this.serverUtils = serverUtils;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return a boolean for if the user got connected to server or
|
||||
*/
|
||||
public boolean promptForURL(){
|
||||
Alert error = new Alert(Alert.AlertType.ERROR); //creates an error alert
|
||||
error.setTitle("Server is Unavailable");
|
||||
error.setHeaderText("Unable to connect to Server");
|
||||
error.setContentText("The server at " + configService.getConfig().getServerUrl()
|
||||
+ " is not available\n\n Would you like to try a different Server URL?");
|
||||
|
||||
error.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
|
||||
Optional<ButtonType> userChoice = error.showAndWait(); //asks if user wants to input server URL
|
||||
|
||||
if(userChoice.isEmpty() || userChoice.get() == ButtonType.NO){
|
||||
return false;
|
||||
}
|
||||
|
||||
while(true){ // Keeps asking the user until either a valid url is provided or the user exits
|
||||
TextInputDialog dialog =
|
||||
new TextInputDialog(configService.getConfig().getServerUrl());
|
||||
dialog.setTitle("Enter new server URL");
|
||||
dialog.setContentText("Server URL:");
|
||||
Optional<String> userRes = dialog.showAndWait();
|
||||
if(userRes.isEmpty()){
|
||||
return false; //user cancelled the operation
|
||||
}
|
||||
String newServer = userRes.get().trim();
|
||||
|
||||
if(newServer.isEmpty()){
|
||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
||||
alert.setTitle("Invalid Input");
|
||||
alert.setHeaderText("Invalid server URL");
|
||||
alert.setContentText("Please enter a valid URL");
|
||||
alert.showAndWait();
|
||||
continue;
|
||||
}
|
||||
configService.getConfig().setServerUrl(newServer);
|
||||
if(serverUtils.isServerAvailable()){
|
||||
configService.save();
|
||||
Alert success = new Alert(Alert.AlertType.INFORMATION);
|
||||
success.setTitle("Success");
|
||||
success.setHeaderText("Connected to Server");
|
||||
success.setContentText("Successfully connected to the server!");
|
||||
success.showAndWait();
|
||||
return true;
|
||||
}
|
||||
else{
|
||||
Alert retry = new Alert(Alert.AlertType.ERROR);
|
||||
retry.setTitle("Failed");
|
||||
retry.setHeaderText("Failed to connect to Server");
|
||||
retry.setContentText("Would you like to try another URL?");
|
||||
retry.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
|
||||
Optional<ButtonType> result = retry.showAndWait();
|
||||
if(result.isEmpty() || result.get() == ButtonType.NO){
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ public class NutritionDetailsCtrl implements LocaleAware {
|
|||
this.proteinInputElement.textProperty().bindBidirectional(vm.proteinProperty(), new NumberStringConverter());
|
||||
this.carbInputElement.textProperty().bindBidirectional(vm.carbsProperty(), new NumberStringConverter());
|
||||
this.estimatedKcalLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("Estimated energy value: %.1f kcal/100g", vm.getKcal()), vm.kcalProperty()
|
||||
() -> String.format("Estimated energy value: %.1f", vm.getKcal()), vm.kcalProperty()
|
||||
));
|
||||
});
|
||||
this.nutritionValueContainer.addEventHandler(KeyEvent.KEY_RELEASED, event -> {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import com.google.inject.Inject;
|
|||
import commons.FormalIngredient;
|
||||
import commons.Recipe;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
|
@ -79,13 +78,7 @@ public class IngredientListCtrl implements LocaleAware {
|
|||
if (recipe == null) {
|
||||
this.ingredients = FXCollections.observableArrayList(new ArrayList<>());
|
||||
} else {
|
||||
List<RecipeIngredient> ingredientList = recipe
|
||||
.getIngredients()
|
||||
.stream()
|
||||
.sorted(Comparator.comparing(ingredient -> ingredient
|
||||
.getIngredient()
|
||||
.getName()))
|
||||
.toList();
|
||||
List<RecipeIngredient> ingredientList = recipe.getIngredients();
|
||||
this.ingredients = FXCollections.observableArrayList(ingredientList);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package client.scenes.recipe;
|
||||
|
||||
import client.utils.server.ServerUtils;
|
||||
import client.exception.DuplicateIngredientException;
|
||||
import commons.Ingredient;
|
||||
import jakarta.inject.Inject;
|
||||
import javafx.fxml.FXML;
|
||||
|
|
@ -68,15 +67,8 @@ public class IngredientsPopupCtrl {
|
|||
server.createIngredient(name); // calls POST /api/ingredients
|
||||
refresh(); // reload list from server
|
||||
} catch (IOException | InterruptedException e) {
|
||||
|
||||
showError("Failed to create ingredient: " + e.getMessage());
|
||||
} catch (DuplicateIngredientException e) {
|
||||
showError("An ingredient with the name " + name + " already exists." +
|
||||
" Please provide a different name."); //checks if error received has the DUPLICATE string and creates a showError instance with an appropriate error message and description
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package client.scenes.recipe;
|
|||
|
||||
import client.exception.UpdateException;
|
||||
import client.scenes.FoodpalApplicationCtrl;
|
||||
import client.service.ShoppingListService;
|
||||
import client.utils.Config;
|
||||
import client.utils.ConfigService;
|
||||
import client.utils.LocaleAware;
|
||||
|
|
@ -11,7 +10,6 @@ import client.utils.PrintExportService;
|
|||
import client.utils.server.ServerUtils;
|
||||
import client.utils.WebSocketDataService;
|
||||
import com.google.inject.Inject;
|
||||
import commons.FormalIngredient;
|
||||
import commons.Recipe;
|
||||
|
||||
import java.io.File;
|
||||
|
|
@ -20,9 +18,6 @@ import java.nio.file.Path;
|
|||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
|
|
@ -49,12 +44,8 @@ public class RecipeDetailCtrl implements LocaleAware {
|
|||
private final FoodpalApplicationCtrl appCtrl;
|
||||
private final ConfigService configService;
|
||||
private final WebSocketDataService<Long, Recipe> webSocketDataService;
|
||||
private final ShoppingListService shoppingListService;
|
||||
|
||||
public Spinner<Double> scaleSpinner;
|
||||
public Label inferredKcalLabel;
|
||||
public Spinner<Integer> servingsSpinner;
|
||||
public Label inferredServeSizeLabel;
|
||||
|
||||
@FXML
|
||||
private IngredientListCtrl ingredientListController;
|
||||
|
|
@ -71,14 +62,12 @@ public class RecipeDetailCtrl implements LocaleAware {
|
|||
ServerUtils server,
|
||||
FoodpalApplicationCtrl appCtrl,
|
||||
ConfigService configService,
|
||||
ShoppingListService listService,
|
||||
WebSocketDataService<Long, Recipe> webSocketDataService) {
|
||||
this.localeManager = localeManager;
|
||||
this.server = server;
|
||||
this.appCtrl = appCtrl;
|
||||
this.configService = configService;
|
||||
this.webSocketDataService = webSocketDataService;
|
||||
this.shoppingListService = listService;
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
|
@ -165,20 +154,12 @@ public class RecipeDetailCtrl implements LocaleAware {
|
|||
|
||||
// If there is a scale
|
||||
// Prevents issues from first startup
|
||||
if (scaleSpinner.getValue() != null && servingsSpinner.getValue() != null) {
|
||||
if (scaleSpinner.getValue() != null) {
|
||||
Double scale = scaleSpinner.getValue();
|
||||
|
||||
// see impl. creates a scaled context for the recipe such that its non-scaled value is kept as a reference.
|
||||
this.recipeView = new ScalableRecipeView(recipe, scale);
|
||||
// TODO i18n
|
||||
inferredKcalLabel.textProperty().bind(Bindings.createStringBinding(() ->
|
||||
String.format("Inferred %.1f kcal/100g for this recipe",
|
||||
Double.isNaN(this.recipeView.scaledKcalProperty().get()) ?
|
||||
0.0 : this.recipeView.scaledKcalProperty().get())
|
||||
, this.recipeView.scaledKcalProperty()));
|
||||
recipeView.servingsProperty().set(servingsSpinner.getValue());
|
||||
inferredServeSizeLabel.textProperty().bind(Bindings.createStringBinding(
|
||||
() -> String.format("Inferred size per serving: %.1f g", recipeView.servingSizeProperty().get()),
|
||||
recipeView.servingSizeProperty()));
|
||||
|
||||
// expose the scaled view to list controllers
|
||||
this.ingredientListController.refetchFromRecipe(this.recipeView.getScaled());
|
||||
this.stepListController.refetchFromRecipe(this.recipeView.getScaled());
|
||||
|
|
@ -405,7 +386,7 @@ public class RecipeDetailCtrl implements LocaleAware {
|
|||
public void initializeComponents() {
|
||||
initStepsIngredientsList();
|
||||
// creates a new scale spinner with an arbitrary max scale
|
||||
scaleSpinner.setValueFactory(new SpinnerValueFactory.DoubleSpinnerValueFactory(1, Double.MAX_VALUE, 1));
|
||||
scaleSpinner.setValueFactory(new SpinnerValueFactory.DoubleSpinnerValueFactory(0, Double.MAX_VALUE, 1));
|
||||
scaleSpinner.setEditable(true);
|
||||
scaleSpinner.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == null) {
|
||||
|
|
@ -414,23 +395,6 @@ public class RecipeDetailCtrl implements LocaleAware {
|
|||
// triggers a UI update each time the spinner changes to a different value.
|
||||
setCurrentlyViewedRecipe(recipe);
|
||||
});
|
||||
servingsSpinner.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, Integer.MAX_VALUE, 1));
|
||||
servingsSpinner.setEditable(true);
|
||||
servingsSpinner.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue == null) {
|
||||
return;
|
||||
}
|
||||
setCurrentlyViewedRecipe(recipe);
|
||||
});
|
||||
langSelector.getItems().addAll(Config.languages);
|
||||
}
|
||||
|
||||
public void handleAddAllToShoppingList(ActionEvent actionEvent) {
|
||||
System.out.println("handleAddAllToShoppingList");
|
||||
// TODO BACKLOG Add overview screen
|
||||
recipe.getIngredients().stream()
|
||||
.filter(x -> x.getClass().equals(FormalIngredient.class))
|
||||
.map(FormalIngredient.class::cast)
|
||||
.forEach(x -> shoppingListService.putIngredient(x, recipe));
|
||||
langSelector.getItems().addAll("en", "nl", "pl", "tok");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,19 +4,14 @@ import commons.Recipe;
|
|||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
public class ScalableRecipeView {
|
||||
private final ObjectProperty<Recipe> recipe = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<Recipe> scaled = new SimpleObjectProperty<>();
|
||||
private final DoubleProperty scale = new SimpleDoubleProperty();
|
||||
private final DoubleProperty scaledKcal = new SimpleDoubleProperty();
|
||||
private final IntegerProperty servings = new SimpleIntegerProperty();
|
||||
private final DoubleProperty servingSize = new SimpleDoubleProperty();
|
||||
public ScalableRecipeView(
|
||||
Recipe recipe,
|
||||
Double scale
|
||||
|
|
@ -27,11 +22,10 @@ public class ScalableRecipeView {
|
|||
() -> Recipe.getScaled(this.recipe.get(), this.scale.get()),
|
||||
this.recipe, this.scale);
|
||||
this.scaled.bind(binding);
|
||||
this.scaledKcal.bind(Bindings.createDoubleBinding(() -> this.scaled.get().kcal(), this.scaled));
|
||||
this.servingSize.bind(Bindings.createDoubleBinding(
|
||||
() -> this.scaled.get().weight() * ( 1.0 / this.servings.get()),
|
||||
this.servings)
|
||||
);
|
||||
}
|
||||
|
||||
public double getScale() {
|
||||
return scale.get();
|
||||
}
|
||||
|
||||
public Recipe getRecipe() {
|
||||
|
|
@ -42,14 +36,15 @@ public class ScalableRecipeView {
|
|||
return scaled.get();
|
||||
}
|
||||
|
||||
public DoubleProperty scaledKcalProperty() {
|
||||
return scaledKcal;
|
||||
public DoubleProperty scaleProperty() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
public IntegerProperty servingsProperty() {
|
||||
return servings;
|
||||
public ObjectProperty<Recipe> scaledProperty() {
|
||||
return scaled;
|
||||
}
|
||||
public DoubleProperty servingSizeProperty() {
|
||||
return servingSize;
|
||||
|
||||
public ObjectProperty<Recipe> recipeProperty() {
|
||||
return recipe;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
package client.scenes.shopping;
|
||||
|
||||
import client.scenes.recipe.OrderedEditableListCell;
|
||||
import commons.FormalIngredient;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SpinnerValueFactory;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.util.Pair;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ShoppingListCell extends OrderedEditableListCell<Pair<FormalIngredient, Optional<String>>> {
|
||||
private Node makeEditor() {
|
||||
HBox editor = new HBox();
|
||||
Spinner<Double> amountInput = new Spinner<>();
|
||||
FormalIngredient ingredient = getItem().getKey();
|
||||
amountInput.setEditable(true);
|
||||
amountInput.setValueFactory(new SpinnerValueFactory.DoubleSpinnerValueFactory(0, Double.MAX_VALUE, ingredient.getAmount()));
|
||||
Label textLabel = new Label(getItem().getKey().getUnitSuffix() + " of " + getItem().getKey().getIngredient().getName());
|
||||
editor.getChildren().addAll(amountInput, textLabel);
|
||||
editor.addEventHandler(KeyEvent.KEY_RELEASED, e -> {
|
||||
if (e.getCode() != KeyCode.ENTER) {
|
||||
return;
|
||||
}
|
||||
Pair<FormalIngredient, Optional<String>> pair = getItem();
|
||||
pair.getKey().setAmount(amountInput.getValue());
|
||||
commitEdit(pair);
|
||||
});
|
||||
return editor;
|
||||
}
|
||||
@Override
|
||||
public void startEdit() {
|
||||
super.startEdit();
|
||||
this.setText("");
|
||||
this.setGraphic(makeEditor());
|
||||
}
|
||||
@Override
|
||||
protected void updateItem(Pair<FormalIngredient, Optional<String>> item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty) {
|
||||
this.setText("");
|
||||
return;
|
||||
}
|
||||
|
||||
String display = item.getKey().toString() +
|
||||
item.getValue().map(recipe -> {
|
||||
return " (" + recipe + ")";
|
||||
}).orElse("");
|
||||
|
||||
this.setText(display);
|
||||
}
|
||||
@Override
|
||||
public void cancelEdit() {
|
||||
super.cancelEdit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitEdit(Pair<FormalIngredient, Optional<String>> newValue) {
|
||||
super.commitEdit(newValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
package client.scenes.shopping;
|
||||
|
||||
import client.UI;
|
||||
import client.service.ShoppingListService;
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import com.google.inject.Inject;
|
||||
import commons.FormalIngredient;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Pair;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ShoppingListCtrl implements LocaleAware {
|
||||
ShoppingListService shopping;
|
||||
|
||||
LocaleManager localeManager;
|
||||
|
||||
@FXML
|
||||
private ListView<Pair<FormalIngredient, Optional<String>>> shoppingListView;
|
||||
@Inject
|
||||
public ShoppingListCtrl(
|
||||
ShoppingListService shopping,
|
||||
LocaleManager localeManager
|
||||
) {
|
||||
this.shopping = shopping;
|
||||
this.localeManager = localeManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateText() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleManager getLocaleManager() {
|
||||
return this.localeManager;
|
||||
}
|
||||
|
||||
public void initializeComponents() {
|
||||
this.shoppingListView.setEditable(true);
|
||||
this.shoppingListView.setCellFactory(l -> new ShoppingListCell());
|
||||
this.shoppingListView.getItems().setAll(
|
||||
this.shopping.getItems()
|
||||
);
|
||||
}
|
||||
private void refreshList() {
|
||||
this.shoppingListView.getItems().setAll(
|
||||
this.shopping.getItems()
|
||||
);
|
||||
}
|
||||
|
||||
public void handleAddItem(ActionEvent actionEvent) {
|
||||
Stage stage = new Stage();
|
||||
Pair<ShoppingListNewItemPromptCtrl, Parent> root = UI.getFXML().load(ShoppingListNewItemPromptCtrl.class,
|
||||
"client", "scenes", "shopping", "ShoppingListItemAddModal.fxml");
|
||||
root.getKey().setNewValueConsumer(fi -> {
|
||||
this.shopping.putIngredient(fi);
|
||||
refreshList();
|
||||
});
|
||||
stage.setScene(new Scene(root.getValue()));
|
||||
stage.setTitle("My modal window");
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(
|
||||
((Node)actionEvent.getSource()).getScene().getWindow() );
|
||||
stage.show();
|
||||
}
|
||||
|
||||
public void handleRemoveItem(ActionEvent actionEvent) {
|
||||
var x = this.shoppingListView.getSelectionModel().getSelectedItem();
|
||||
this.shopping.getItems().remove(x);
|
||||
refreshList();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
package client.scenes.shopping;
|
||||
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import client.utils.server.ServerUtils;
|
||||
import com.google.inject.Inject;
|
||||
import commons.FormalIngredient;
|
||||
import commons.Ingredient;
|
||||
import commons.Unit;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.MenuButton;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SpinnerValueFactory;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ShoppingListNewItemPromptCtrl implements LocaleAware {
|
||||
public MenuButton ingredientSelection;
|
||||
private final ObjectProperty<Ingredient> selected = new SimpleObjectProperty<>();
|
||||
private final ObjectProperty<Unit> selectedUnit = new SimpleObjectProperty<>();
|
||||
private final ServerUtils server;
|
||||
private final LocaleManager localeManager;
|
||||
private Consumer<FormalIngredient> newValueConsumer;
|
||||
public MenuButton unitSelect;
|
||||
public Spinner<Double> amountSelect;
|
||||
|
||||
@Inject
|
||||
public ShoppingListNewItemPromptCtrl(ServerUtils server, LocaleManager localeManager) {
|
||||
this.server = server;
|
||||
this.localeManager = localeManager;
|
||||
}
|
||||
|
||||
public void setNewValueConsumer(Consumer<FormalIngredient> consumer) {
|
||||
this.newValueConsumer = consumer;
|
||||
}
|
||||
|
||||
public void confirmAdd(ActionEvent actionEvent) {
|
||||
if (selected.get() == null || selectedUnit.get() == null) {
|
||||
System.err.println("You must select both an ingredient and an unit");
|
||||
return;
|
||||
}
|
||||
FormalIngredient fi = new FormalIngredient(selected.get(), amountSelect.getValue(), selectedUnit.get().suffix);
|
||||
newValueConsumer.accept(fi);
|
||||
Stage stage = (Stage) ingredientSelection.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
public void cancelAdd(ActionEvent actionEvent) {
|
||||
}
|
||||
private <T> void makeMenuItems(
|
||||
MenuButton menu,
|
||||
Iterable<T> items,
|
||||
Function<T, String> labelMapper,
|
||||
Consumer<T> onSelect) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void updateText() {
|
||||
|
||||
}
|
||||
@Override
|
||||
public void initializeComponents() {
|
||||
try {
|
||||
amountSelect.setValueFactory(
|
||||
new SpinnerValueFactory.DoubleSpinnerValueFactory(0, Double.MAX_VALUE, 0));
|
||||
amountSelect.setEditable(true);
|
||||
makeMenuItems(ingredientSelection, server.getIngredients(), Ingredient::getName, selected::set);
|
||||
makeMenuItems(unitSelect,
|
||||
Arrays.stream(Unit.values()).filter(u -> u.formal).toList(),
|
||||
Unit::toString, selectedUnit::set);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleManager getLocaleManager() {
|
||||
return localeManager;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
package client.service;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import commons.FormalIngredient;
|
||||
import commons.Recipe;
|
||||
import javafx.util.Pair;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class NonFunctionalShoppingListService extends ShoppingListService {
|
||||
@Inject
|
||||
public NonFunctionalShoppingListService(ShoppingListViewModel viewModel) {
|
||||
super(viewModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putIngredient(FormalIngredient ingredient) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putIngredient(FormalIngredient ingredient, Recipe recipe) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putIngredient(FormalIngredient ingredient, String recipeName) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putArbitraryItem(String name) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormalIngredient purgeIngredient(Long id) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormalIngredient purgeIngredient(String ingredientName) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<FormalIngredient, Optional<String>>> getItems() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makePrintable() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package client.service;
|
||||
|
||||
import commons.FormalIngredient;
|
||||
public class ShoppingListItem {
|
||||
private FormalIngredient i;
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
package client.service;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import commons.FormalIngredient;
|
||||
import commons.Recipe;
|
||||
import javafx.util.Pair;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class ShoppingListService {
|
||||
private ShoppingListViewModel viewModel;
|
||||
@Inject
|
||||
public ShoppingListService(ShoppingListViewModel viewModel) {
|
||||
this.viewModel = viewModel;
|
||||
}
|
||||
|
||||
public ShoppingListViewModel getViewModel() {
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
public void setViewModel(ShoppingListViewModel viewModel) {
|
||||
this.viewModel = viewModel;
|
||||
}
|
||||
|
||||
public abstract void putIngredient(FormalIngredient ingredient);
|
||||
public abstract void putIngredient(FormalIngredient ingredient, Recipe recipe);
|
||||
public abstract void putIngredient(FormalIngredient ingredient, String recipeName);
|
||||
public abstract void putArbitraryItem(String name);
|
||||
|
||||
public abstract FormalIngredient purgeIngredient(Long id);
|
||||
public abstract FormalIngredient purgeIngredient(String ingredientName);
|
||||
|
||||
public abstract void reset();
|
||||
|
||||
public abstract List<Pair<FormalIngredient, Optional<String>>> getItems();
|
||||
public abstract String makePrintable();
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
package client.service;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import commons.FormalIngredient;
|
||||
import commons.Recipe;
|
||||
import javafx.util.Pair;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class ShoppingListServiceImpl extends ShoppingListService {
|
||||
private final Logger logger = Logger.getLogger(ShoppingListServiceImpl.class.getName());
|
||||
@Inject
|
||||
public ShoppingListServiceImpl(
|
||||
ShoppingListViewModel model
|
||||
) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putIngredient(FormalIngredient ingredient) {
|
||||
getViewModel().getListItems().add(new Pair<>(ingredient, Optional.empty()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putIngredient(FormalIngredient ingredient, Recipe recipe) {
|
||||
Pair<FormalIngredient, Optional<String>> val = new Pair<>(ingredient, Optional.of(recipe.getName()));
|
||||
logger.info("putting ingredients into shopping list: " + val);
|
||||
getViewModel().getListItems().add(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putIngredient(FormalIngredient ingredient, String recipeName) {
|
||||
getViewModel().getListItems().add(new Pair<>(ingredient, Optional.of(recipeName)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putArbitraryItem(String name) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormalIngredient purgeIngredient(Long id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormalIngredient purgeIngredient(String ingredientName) {
|
||||
FormalIngredient fi = getViewModel().getListItems().stream()
|
||||
.filter(i ->
|
||||
i.getKey().getIngredient().getName().equals(ingredientName))
|
||||
.findFirst().orElseThrow(NullPointerException::new).getKey();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
getViewModel().getListItems().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<FormalIngredient, Optional<String>>> getItems() {
|
||||
return getViewModel().getListItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makePrintable() {
|
||||
return "TODO";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
package client.service;
|
||||
|
||||
import commons.FormalIngredient;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.util.Pair;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ShoppingListViewModel {
|
||||
/**
|
||||
* The formal ingredient provides the ingredient and its amount,
|
||||
* and the string (optional) describes the recipe where it came from.
|
||||
*/
|
||||
private final ListProperty<Pair<FormalIngredient, Optional<String>>> listItems = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
public void addArbitrary() {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ObservableList<Pair<FormalIngredient, Optional<String>>> getListItems() {
|
||||
return listItems.get();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import java.util.List;
|
|||
|
||||
public class Config {
|
||||
private String language = "en";
|
||||
public static String[] languages = {"en", "nl", "pl", "tok", "zhc", "zht"};
|
||||
private List<String> recipeLanguages = new ArrayList<>();
|
||||
private String serverUrl = "http://localhost:8080";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ import client.utils.ConfigService;
|
|||
import com.google.inject.Inject;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
public class Endpoints {
|
||||
|
|
@ -83,23 +81,9 @@ public class Endpoints {
|
|||
}
|
||||
|
||||
public HttpRequest.Builder getRecipesWith(String params) {
|
||||
if (params != null && params.contains("search=")) {
|
||||
int start = params.indexOf("search=") + "search=".length();
|
||||
int end = params.indexOf('&', start);
|
||||
if (end == -1) {
|
||||
end = params.length();
|
||||
}
|
||||
|
||||
String rawValue = params.substring(start, end);
|
||||
String encodedValue = URLEncoder.encode(rawValue, StandardCharsets.UTF_8);
|
||||
|
||||
params = params.substring(0, start) + encodedValue + params.substring(end);
|
||||
}
|
||||
|
||||
return this.http(this.createApiUrl("/recipes?" + params)).GET();
|
||||
}
|
||||
|
||||
|
||||
public HttpRequest.Builder createIngredient(HttpRequest.BodyPublisher body) {
|
||||
String url = this.createApiUrl("/ingredients");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
package client.utils.server;
|
||||
|
||||
import client.utils.ConfigService;
|
||||
import client.exception.DuplicateIngredientException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.inject.Inject;
|
||||
import commons.Ingredient;
|
||||
import commons.Recipe;
|
||||
import commons.RecipeIngredient;
|
||||
import jakarta.ws.rs.ProcessingException;
|
||||
import jakarta.ws.rs.client.ClientBuilder;
|
||||
import org.glassfish.jersey.client.ClientConfig;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
|
|
@ -170,9 +171,10 @@ public class ServerUtils {
|
|||
.target(this.endpoints.baseUrl()) //
|
||||
.request(APPLICATION_JSON) //
|
||||
.get();
|
||||
} catch (ProcessingException e) {
|
||||
if (e.getCause() instanceof ConnectException) {
|
||||
return false;
|
||||
}
|
||||
catch(Exception e){
|
||||
return false; //any exception caught will return false, not just processing exception.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -268,7 +270,7 @@ public class ServerUtils {
|
|||
|
||||
//creates new ingredients in the ingredient list
|
||||
|
||||
public Ingredient createIngredient(String name) throws IOException, InterruptedException, DuplicateIngredientException {
|
||||
public Ingredient createIngredient(String name) throws IOException, InterruptedException {
|
||||
Ingredient ingredient = new Ingredient(name, 0.0, 0.0, 0.0);
|
||||
String json = objectMapper.writeValueAsString(ingredient);
|
||||
|
||||
|
|
@ -276,11 +278,6 @@ public class ServerUtils {
|
|||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
final int DUPLICATE_STATUS_CODE = 409;
|
||||
if (response.statusCode() == DUPLICATE_STATUS_CODE) {
|
||||
throw new DuplicateIngredientException(name);
|
||||
}
|
||||
|
||||
if (response.statusCode() != statusOK) {
|
||||
throw new IOException("Failed to create ingredient. Server responds with: " + response.body());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,9 +70,6 @@
|
|||
<ToggleButton fx:id="favouritesOnlyToggle" text="Favourites" onAction="#toggleFavouritesView" />
|
||||
</HBox>
|
||||
|
||||
<Button fx:id="shoppingListButton"
|
||||
onAction="#openShoppingListWindow"
|
||||
text="Shopping List" />
|
||||
<Button fx:id="manageIngredientsButton"
|
||||
onAction="#openIngredientsPopup"
|
||||
text="Ingredients..." />
|
||||
|
|
|
|||
|
|
@ -15,16 +15,16 @@
|
|||
<Insets top="12" right="12" bottom="12" left="12"/>
|
||||
</padding>
|
||||
|
||||
<Label fx:id="ingredientsLabel" text="Ingredients" style="-fx-font-size: 18px; -fx-font-weight: bold;"/>
|
||||
<Label text="Ingredients" style="-fx-font-size: 18px; -fx-font-weight: bold;"/>
|
||||
|
||||
<ListView fx:id="ingredientListView" VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<ButtonBar>
|
||||
<buttons>
|
||||
<Button fx:id="addButton" text="Add" onAction="#addIngredient"/>
|
||||
<Button fx:id="refreshButton" text="Refresh" onAction="#refresh"/>
|
||||
<Button fx:id="deleteButton" text="Delete" onAction="#deleteSelected"/>
|
||||
<Button fx:id="closeButton" text="Close" onAction="#close"/>
|
||||
<Button text="Add" onAction="#addIngredient"/>
|
||||
<Button text="Refresh" onAction="#refresh"/>
|
||||
<Button text="Delete" onAction="#deleteSelected"/>
|
||||
<Button text="Close" onAction="#close"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
|
|
|
|||
|
|
@ -25,23 +25,16 @@
|
|||
<Button fx:id="removeRecipeButton" mnemonicParsing="false" onAction="#removeSelectedRecipe" text="Remove Recipe" />
|
||||
<Button fx:id="printRecipeButton" mnemonicParsing="false" onAction="#printRecipe" text="Print Recipe" />
|
||||
<Button fx:id="favouriteButton" onAction="#toggleFavourite" text="☆" />
|
||||
<Button onAction="#handleAddAllToShoppingList">Shop</Button>
|
||||
</HBox>
|
||||
|
||||
<HBox>
|
||||
<ComboBox fx:id="langSelector" onAction="#changeLanguage" />
|
||||
<Label>Scale: </Label>
|
||||
<Spinner fx:id="scaleSpinner" />
|
||||
<Label>Servings: </Label>
|
||||
<Spinner fx:id="servingsSpinner" />
|
||||
</HBox>
|
||||
|
||||
<ComboBox fx:id="langSelector" onAction="#changeLanguage" />
|
||||
|
||||
<!-- Ingredients -->
|
||||
<fx:include source="RecipeIngredientList.fxml" fx:id="ingredientList" />
|
||||
|
||||
<!-- Preparation -->
|
||||
<fx:include source="RecipeStepList.fxml" fx:id="stepList"
|
||||
VBox.vgrow="ALWAYS" maxWidth="Infinity" />
|
||||
<Label fx:id="inferredServeSizeLabel" />
|
||||
<Label fx:id="inferredKcalLabel" />
|
||||
</VBox>
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.control.TitledPane?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<TitledPane animated="false" collapsible="false" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
|
||||
minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" text="Shopping List"
|
||||
fx:controller="client.scenes.shopping.ShoppingListCtrl"
|
||||
xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/25">
|
||||
<VBox>
|
||||
<ListView fx:id="shoppingListView" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0"
|
||||
AnchorPane.topAnchor="0.0"/>
|
||||
<HBox>
|
||||
<Button onAction="#handleAddItem">Add</Button>
|
||||
<Button onAction="#handleRemoveItem">Delete</Button>
|
||||
</HBox>
|
||||
</VBox>
|
||||
</TitledPane>
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import java.util.*?>
|
||||
<?import javafx.scene.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="client.scenes.shopping.ShoppingListNewItemPromptCtrl"
|
||||
prefHeight="400.0" prefWidth="600.0">
|
||||
<VBox>
|
||||
<HBox>
|
||||
<Spinner fx:id="amountSelect" />
|
||||
<MenuButton fx:id="unitSelect">Unit...</MenuButton>
|
||||
<MenuButton fx:id="ingredientSelection">Your ingredient...</MenuButton>
|
||||
</HBox>
|
||||
<HBox>
|
||||
<Button onAction="#confirmAdd">Confirm</Button>
|
||||
<Button onAction="#cancelAdd">Cancel</Button>
|
||||
</HBox>
|
||||
</VBox>
|
||||
</AnchorPane>
|
||||
|
|
@ -16,8 +16,6 @@ menu.label.preparation=Preparation
|
|||
menu.button.add.recipe=Add Recipe
|
||||
menu.button.add.ingredient=Add Ingredient
|
||||
menu.button.add.step=Add Step
|
||||
menu.button.favourites=Favourites
|
||||
menu.button.ingredients=Ingredients
|
||||
|
||||
menu.button.remove.recipe=Remove Recipe
|
||||
menu.button.remove.ingredient=Remove Ingredient
|
||||
|
|
@ -27,15 +25,6 @@ menu.button.edit=Edit
|
|||
menu.button.clone=Clone
|
||||
menu.button.print=Print recipe
|
||||
|
||||
menu.ingredients.title=Nutrition value
|
||||
|
||||
menu.button.add=Add
|
||||
menu.button.refresh=Refresh
|
||||
menu.button.delete=Delete
|
||||
menu.button.close=Close
|
||||
|
||||
menu.search=Search...
|
||||
|
||||
menu.label.selected-langs=Languages
|
||||
|
||||
lang.en.display=English
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ menu.label.preparation=Preparation
|
|||
menu.button.add.recipe=Add Recipe
|
||||
menu.button.add.ingredient=Add Ingredient
|
||||
menu.button.add.step=Add Step
|
||||
menu.button.favourites=Favourites
|
||||
menu.button.ingredients=Ingredients
|
||||
|
||||
menu.button.remove.recipe=Remove Recipe
|
||||
menu.button.remove.ingredient=Remove Ingredient
|
||||
|
|
@ -27,22 +25,13 @@ menu.button.edit=Edit
|
|||
menu.button.clone=Clone
|
||||
menu.button.print=Print recipe
|
||||
|
||||
menu.ingredients.title=Nutrition value
|
||||
|
||||
menu.button.add=Add
|
||||
menu.button.refresh=Refresh
|
||||
menu.button.delete=Delete
|
||||
menu.button.close=Close
|
||||
|
||||
menu.search=Search...
|
||||
|
||||
menu.label.selected-langs=Languages
|
||||
|
||||
menu.shopping.title=Shopping list
|
||||
|
||||
lang.en.display=English
|
||||
lang.nl.display=Dutch
|
||||
lang.pl.display=Polish
|
||||
lang.nl.display=Nederlands
|
||||
lang.pl.display=Polski
|
||||
lang.tok.display=toki pona
|
||||
lang.tr.display=T\u00FCrk\u00E7e
|
||||
lang.zht.display=中文(台灣)
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ menu.label.preparation=Bereiding
|
|||
menu.button.add.recipe=Recept toevoegen
|
||||
menu.button.add.ingredient=Ingrediënt toevoegen
|
||||
menu.button.add.step=Stap toevoegen
|
||||
menu.button.favourites=Favorieten
|
||||
menu.button.ingredients=Ingrediënten
|
||||
|
||||
menu.button.remove.recipe=Recept verwijderen
|
||||
menu.button.remove.ingredient=Ingrediënt verwijderen
|
||||
|
|
@ -27,21 +25,12 @@ menu.button.edit=Bewerken
|
|||
menu.button.clone=Dupliceren
|
||||
menu.button.print=Recept afdrukken
|
||||
|
||||
menu.ingredients.title=Voedingswaarden
|
||||
|
||||
menu.button.add=Toevoegen
|
||||
menu.button.refresh=Verversen
|
||||
menu.button.delete=Verwijderen
|
||||
menu.button.close=Sluiten
|
||||
|
||||
menu.label.selected-langs=Talen
|
||||
|
||||
menu.shopping.title=Boodschappenlijst
|
||||
|
||||
menu.search=Zoeken...
|
||||
lang.en.display=Engels
|
||||
lang.en.display=English
|
||||
lang.nl.display=Nederlands
|
||||
lang.pl.display=Pools
|
||||
lang.pl.display=Polski
|
||||
lang.tok.display=toki pona
|
||||
lang.tr.display=T\u00FCrk\u00E7e
|
||||
lang.zht.display=中文(台灣)
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ menu.label.preparation=Przygotowanie
|
|||
menu.button.add.recipe=Dodaj przepis
|
||||
menu.button.add.ingredient=Dodaj składnik
|
||||
menu.button.add.step=Dodaj instrukcję
|
||||
menu.button.favourites=Ulubione
|
||||
menu.button.ingredients=Składniki
|
||||
|
||||
menu.button.remove.recipe=Usuń przepis
|
||||
menu.button.remove.ingredient=Usuń składnik
|
||||
|
|
@ -27,21 +25,12 @@ menu.button.edit=Edytuj
|
|||
menu.button.clone=Duplikuj
|
||||
menu.button.print=Drukuj przepis
|
||||
|
||||
menu.ingredients.title=wartości odżywcze
|
||||
|
||||
menu.button.add=Dodaj
|
||||
menu.button.refresh=Odśwież
|
||||
menu.button.delete=Usuń
|
||||
menu.button.close=Zamknij
|
||||
|
||||
menu.search=Szukaj...
|
||||
|
||||
menu.label.selected-langs=Języki
|
||||
|
||||
menu.shopping.title=Lista zakupów
|
||||
|
||||
lang.en.display=Inglisz
|
||||
lang.nl.display=Holenderski
|
||||
lang.en.display=English
|
||||
lang.nl.display=Nederlands
|
||||
lang.pl.display=Polski
|
||||
lang.tok.display=toki pona
|
||||
lang.tr.display=T\u00FCrk\u00E7e
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ menu.label.preparation=nasin pi pali moku ni
|
|||
menu.button.add.recipe=o pali e lipu moku sin
|
||||
menu.button.add.ingredient=o pali e kipisi moku sin
|
||||
menu.button.add.step=o pali e nasin pi pali moku ni
|
||||
menu.button.favourites=ijo pi pona mute tawa sina
|
||||
menu.button.ingredients=kipisi moku mute
|
||||
|
||||
menu.button.remove.recipe=o weka e lipu moku ni
|
||||
menu.button.remove.ingredient=o weka e kipisi moku ni
|
||||
|
|
@ -27,19 +25,10 @@ menu.button.edit=o pali
|
|||
menu.button.clone=o sama
|
||||
menu.button.print=o tawa lon lipu
|
||||
|
||||
menu.ingredients.title=nanpa moku
|
||||
|
||||
menu.button.add=o pali
|
||||
menu.button.refresh=o pali sin
|
||||
menu.button.delete=o weka
|
||||
menu.button.close=o pini
|
||||
|
||||
menu.search=o alasa
|
||||
|
||||
menu.label.selected-langs=toki wile
|
||||
|
||||
menu.shopping.title=ijo wile mani mute
|
||||
|
||||
lang.en.display=toki Inli
|
||||
lang.nl.display=toki Netelan
|
||||
lang.pl.display=toki Posuka
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ menu.label.preparation=Haz\u0131rl\u0131k
|
|||
menu.button.add.recipe=Tarif Ekle
|
||||
menu.button.add.ingredient=Malzeme Ekle
|
||||
menu.button.add.step=Ad\u0131m Ekle
|
||||
menu.button.favourites=Favoriler
|
||||
menu.button.ingredients=Malzemeler
|
||||
|
||||
menu.button.remove.recipe=Tarifi Sil
|
||||
menu.button.remove.ingredient=Malzemeyi Sil
|
||||
|
|
@ -27,18 +25,9 @@ menu.button.edit=D\u00FCzenle
|
|||
menu.button.clone=Kopyala
|
||||
menu.button.print=Tarifi Yazd\u0131r
|
||||
|
||||
menu.ingredients.title=besin değerleri
|
||||
|
||||
menu.button.add=Ekle
|
||||
menu.button.refresh=yenilemek
|
||||
menu.button.delete=sil
|
||||
menu.button.close=kapat
|
||||
|
||||
menu.search=Arama...
|
||||
|
||||
menu.label.selected-langs=Diller
|
||||
|
||||
menu.shopping.title=Al??veri? listesi
|
||||
menu.search=Aramak
|
||||
|
||||
lang.en.display=English
|
||||
lang.nl.display=Nederlands
|
||||
|
|
|
|||
|
|
@ -1,176 +0,0 @@
|
|||
package client.Ingredient;
|
||||
|
||||
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
|
||||
import commons.Ingredient;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.delete;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.deleteRequestedFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.get;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.ok;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.okJson;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
@EnabledIfEnvironmentVariable(named = "DISPLAY", matches = ".+")
|
||||
@WireMockTest(httpPort = 8080)
|
||||
class IngredientControllerMockTest {
|
||||
|
||||
private IngredientController controller;
|
||||
private ListView<Ingredient> ingredientListView;
|
||||
|
||||
// starting javaFX and allow use of listview and alert usage
|
||||
@BeforeAll
|
||||
static void initJavaFx() throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
try {
|
||||
Platform.startup(latch::countDown);
|
||||
} catch (IllegalStateException alreadyStarted) {
|
||||
latch.countDown();
|
||||
}
|
||||
assertTrue(latch.await(3, TimeUnit.SECONDS), "JavaFX Platform failed to start");
|
||||
Platform.setImplicitExit(false);
|
||||
}
|
||||
//inject fxml fields and create controller + mock UI
|
||||
@BeforeEach
|
||||
void setup() throws Exception {
|
||||
controller = new IngredientController();
|
||||
|
||||
ingredientListView = new ListView<>();
|
||||
ingredientListView.setItems(FXCollections.observableArrayList(
|
||||
new Ingredient("Bread", 1, 2, 3),
|
||||
new Ingredient("Cheese", 2, 2, 2),
|
||||
new Ingredient("Ham", 3, 3, 3)
|
||||
));
|
||||
|
||||
setPrivateField(controller, "ingredientListView", ingredientListView);
|
||||
setPrivateField(controller, "deleteButton", new Button("Delete"));
|
||||
}
|
||||
|
||||
// pick ingredient -> backend says not in use -> fake delete ingredient
|
||||
@Test
|
||||
void deleteIngredientWhenNotUsedCallsUsageThenDeleteAndClearsList() throws Exception {
|
||||
Ingredient selected = ingredientListView.getItems().get(0);
|
||||
ingredientListView.getSelectionModel().select(selected);
|
||||
|
||||
stubFor(get(urlEqualTo("/api/ingredients/" + selected.getId() + "/usage"))
|
||||
.willReturn(okJson("{\"ingredientId\":" + selected.getId() + ",\"usedInRecipes\":0}")));
|
||||
|
||||
stubFor(delete(urlEqualTo("/api/ingredients/" + selected.getId()))
|
||||
.willReturn(ok()));
|
||||
|
||||
// safe close for show and wait, run controller on JavaFX
|
||||
try (DialogCloser closer = startDialogCloser()) {
|
||||
runOnFxThreadAndWait(() -> controller.handleDeleteIngredient(new ActionEvent()));
|
||||
}
|
||||
|
||||
verify(getRequestedFor(urlEqualTo("/api/ingredients/" + selected.getId() + "/usage")));
|
||||
verify(deleteRequestedFor(urlEqualTo("/api/ingredients/" + selected.getId())));
|
||||
|
||||
assertEquals(0, ingredientListView.getItems().size());
|
||||
}
|
||||
|
||||
//select ingredient -> if used backend says it and show warning -> safety delete but shouldn't happen
|
||||
@Test
|
||||
void deleteIngredientWhenUsedShowsWarningAndDoesNotDeleteIfDialogClosed() throws Exception {
|
||||
Ingredient selected = ingredientListView.getItems().get(1);
|
||||
ingredientListView.getSelectionModel().select(selected);
|
||||
|
||||
stubFor(get(urlEqualTo("/api/ingredients/" + selected.getId() + "/usage"))
|
||||
.willReturn(okJson("{\"ingredientId\":" + selected.getId() + ",\"usedInRecipes\":2}")));
|
||||
|
||||
stubFor(delete(urlEqualTo("/api/ingredients/" + selected.getId()))
|
||||
.willReturn(ok()));
|
||||
|
||||
//safe close as if user selected cancel
|
||||
try (DialogCloser closer = startDialogCloser()) {
|
||||
runOnFxThreadAndWait(() -> controller.handleDeleteIngredient(new ActionEvent()));
|
||||
}
|
||||
|
||||
// check usage but not delete
|
||||
verify(getRequestedFor(urlEqualTo("/api/ingredients/" + selected.getId() + "/usage")));
|
||||
verify(0, deleteRequestedFor(urlEqualTo("/api/ingredients/" + selected.getId())));
|
||||
|
||||
assertEquals(3, ingredientListView.getItems().size());
|
||||
}
|
||||
|
||||
// fxml helper
|
||||
private static void setPrivateField(Object target, String fieldName, Object value) throws Exception {
|
||||
Field f = target.getClass().getDeclaredField(fieldName);
|
||||
f.setAccessible(true);
|
||||
f.set(target, value);
|
||||
}
|
||||
|
||||
//controller on JavaFX
|
||||
private static void runOnFxThreadAndWait(Runnable action) throws Exception {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
action.run();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
assertTrue(latch.await(8, TimeUnit.SECONDS), "FX action timed out");
|
||||
}
|
||||
|
||||
// safe close so that show and wait doesn't wait forever
|
||||
private static DialogCloser startDialogCloser() {
|
||||
AtomicBoolean running = new AtomicBoolean(true);
|
||||
|
||||
Thread t = new Thread(() -> {
|
||||
while (running.get()) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
for (Window w : Window.getWindows()) {
|
||||
if (w instanceof Stage stage && stage.isShowing()) {
|
||||
stage.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, "javafx-dialog-closer");
|
||||
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
|
||||
return new DialogCloser(running);
|
||||
}
|
||||
|
||||
// dialog closer
|
||||
private static final class DialogCloser implements AutoCloseable {
|
||||
private final AtomicBoolean running;
|
||||
|
||||
private DialogCloser(AtomicBoolean running) {
|
||||
this.running = running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
running.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -76,15 +76,4 @@ public class FormalIngredient extends RecipeIngredient implements Scalable<Forma
|
|||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), amount, unitSuffix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getKcal() {
|
||||
final double PER_GRAMS = 100;
|
||||
return ingredient.kcalPer100g() * amountInBaseUnit() / PER_GRAMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getBaseAmount() {
|
||||
return amountInBaseUnit();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,15 +198,7 @@ public class Recipe {
|
|||
default -> throw new IllegalStateException("Unexpected value: " + ri);
|
||||
}).toList();
|
||||
return new Recipe(recipe.getId(), recipe.getName(), recipe.getLocale(), i, recipe.getPreparationSteps());
|
||||
}
|
||||
public double kcal() {
|
||||
final double PER = 100; // Gram
|
||||
return
|
||||
this.ingredients.stream().mapToDouble(RecipeIngredient::getKcal).sum() /
|
||||
weight() * PER;
|
||||
}
|
||||
public double weight() {
|
||||
return this.ingredients.stream().mapToDouble(RecipeIngredient::getBaseAmount).sum();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package commons;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import jakarta.persistence.Entity;
|
||||
|
|
@ -93,8 +92,4 @@ public abstract class RecipeIngredient {
|
|||
public int hashCode() {
|
||||
return Objects.hash(id, ingredient);
|
||||
}
|
||||
@JsonIgnore
|
||||
public abstract double getKcal();
|
||||
@JsonIgnore
|
||||
public abstract double getBaseAmount();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,13 +50,4 @@ public class VagueIngredient extends RecipeIngredient {
|
|||
public int hashCode() {
|
||||
return Objects.hashCode(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getKcal() {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public double getBaseAmount() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
102
locc.sh
102
locc.sh
|
|
@ -1,102 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -u
|
||||
|
||||
# 1. Check for git repo
|
||||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||
echo "Error: current directory is not a git repository." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. Define path patterns
|
||||
PATH_CLIENT='client/src/**/*.java'
|
||||
PATH_SERVER='server/src/**/*.java'
|
||||
PATH_COMMONS='commons/src/**/*.java'
|
||||
PATH_PROD='*/src/main/*.java'
|
||||
PATH_TEST='*/src/test/*.java'
|
||||
PATH_ALL='*.java'
|
||||
|
||||
# 3. Helper functions
|
||||
|
||||
# Standard count: Includes imports, respects WS changes
|
||||
count_lines_standard() {
|
||||
git show --format="" --patch "$1" -- "$2" \
|
||||
| grep -E '^\+[^+/][^/]+$' \
|
||||
| grep -v '+ *[*@]' \
|
||||
| wc -l
|
||||
}
|
||||
|
||||
# Strict count: Ignores imports, ignores pure WS changes
|
||||
count_lines_strict() {
|
||||
git show --format="" --patch -w "$1" -- "$2" \
|
||||
| grep -E '^\+[^+/][^/]+$' \
|
||||
| grep -v 'import' \
|
||||
| grep -v '+ *[*@]' \
|
||||
| wc -l
|
||||
}
|
||||
|
||||
echo "Analyzing commits on 'main'..." >&2
|
||||
|
||||
# 4. Main Loop
|
||||
# Use %ae for Author Email
|
||||
git log --no-merges --pretty=format:'%H %ae' main | {
|
||||
|
||||
declare -A client_count
|
||||
declare -A server_count
|
||||
declare -A commons_count
|
||||
declare -A prod_count
|
||||
declare -A test_count
|
||||
declare -A total_count
|
||||
declare -A strict_count
|
||||
declare -A seen_users
|
||||
|
||||
while read -r hash raw_email; do
|
||||
# Normalize email to lowercase
|
||||
email=$(echo "$raw_email" | tr '[:upper:]' '[:lower:]')
|
||||
seen_users["$email"]=1
|
||||
|
||||
# Run counts (Standard)
|
||||
c_add=$(count_lines_standard "$hash" "$PATH_CLIENT")
|
||||
s_add=$(count_lines_standard "$hash" "$PATH_SERVER")
|
||||
k_add=$(count_lines_standard "$hash" "$PATH_COMMONS")
|
||||
p_add=$(count_lines_standard "$hash" "$PATH_PROD")
|
||||
t_add=$(count_lines_standard "$hash" "$PATH_TEST")
|
||||
all_add=$(count_lines_standard "$hash" "$PATH_ALL")
|
||||
|
||||
# Run count (Strict)
|
||||
strict_add=$(count_lines_strict "$hash" "$PATH_ALL")
|
||||
|
||||
# Accumulate
|
||||
client_count["$email"]=$(( ${client_count["$email"]:-0} + c_add ))
|
||||
server_count["$email"]=$(( ${server_count["$email"]:-0} + s_add ))
|
||||
commons_count["$email"]=$(( ${commons_count["$email"]:-0} + k_add ))
|
||||
prod_count["$email"]=$(( ${prod_count["$email"]:-0} + p_add ))
|
||||
test_count["$email"]=$(( ${test_count["$email"]:-0} + t_add ))
|
||||
total_count["$email"]=$(( ${total_count["$email"]:-0} + all_add ))
|
||||
strict_count["$email"]=$(( ${strict_count["$email"]:-0} + strict_add ))
|
||||
|
||||
printf "." >&2
|
||||
done
|
||||
|
||||
echo "" >&2
|
||||
echo "Done." >&2
|
||||
|
||||
# 5. Print Table
|
||||
# Widths: Email=40, Others=10, Strict=13
|
||||
printf "%-40s | %-10s | %-10s | %-10s | %-10s | %-10s | %-10s | %-13s\n" \
|
||||
"User Email" "Client" "Server" "Commons" "Prod" "Test" "Total" "Total (Strict)"
|
||||
printf "%s\n" "-----------------------------------------|------------|------------|------------|------------|------------|------------|----------------"
|
||||
|
||||
for email in "${!seen_users[@]}"; do
|
||||
echo "$email"
|
||||
done | sort | while read -r e; do
|
||||
printf "%-40s | %-10d | %-10d | %-10d | %-10d | %-10d | %-10d | %-13d\n" \
|
||||
"$e" \
|
||||
"${client_count[$e]:-0}" \
|
||||
"${server_count[$e]:-0}" \
|
||||
"${commons_count[$e]:-0}" \
|
||||
"${prod_count[$e]:-0}" \
|
||||
"${test_count[$e]:-0}" \
|
||||
"${total_count[$e]:-0}" \
|
||||
"${strict_count[$e]:-0}"
|
||||
done
|
||||
}
|
||||
|
|
@ -31,37 +31,31 @@ class PortCheckerTest {
|
|||
}
|
||||
}
|
||||
@Test
|
||||
void findNotDefaultFreePort() throws IOException {
|
||||
void invalidPort(){
|
||||
PortChecker checker = new PortChecker();
|
||||
|
||||
assertThrows(IllegalArgumentException.class, ()-> {
|
||||
checker.isPortAvailable(-1);
|
||||
}
|
||||
);
|
||||
assertThrows(IllegalArgumentException.class, ()-> {
|
||||
checker.isPortAvailable(65536);
|
||||
}
|
||||
);
|
||||
}
|
||||
@Test
|
||||
void findFreePort() throws IOException {
|
||||
PortChecker checker = new PortChecker();
|
||||
|
||||
int port = checker.findFreePort();
|
||||
int lowestPossiblePort = 0;
|
||||
int highestPossiblePort = 65535;
|
||||
int defaultPort = 8080;
|
||||
int lastPort = 8090;
|
||||
|
||||
assertTrue(port > lowestPossiblePort);
|
||||
assertTrue(port <= highestPossiblePort);
|
||||
assertTrue(checker.isPortAvailable(port));
|
||||
}
|
||||
@Test
|
||||
void findDefaultFreePort() throws IOException {
|
||||
PortChecker checker = new PortChecker();
|
||||
boolean greaterOrEqual = port >= defaultPort;
|
||||
boolean lessOrEqual = port <= lastPort;
|
||||
boolean inRange = greaterOrEqual && lessOrEqual;
|
||||
boolean isItFree = checker.isPortAvailable(port);
|
||||
|
||||
boolean free = checker.isPortAvailable(8080);
|
||||
|
||||
assertTrue(free);
|
||||
assertEquals(checker.findFreePort(),8080);
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidFreePort(){
|
||||
PortChecker checker = new PortChecker();
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
checker.isPortAvailable(-1)
|
||||
);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
checker.isPortAvailable(65536)
|
||||
);
|
||||
assertTrue(inRange);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ import server.service.RecipeService;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.LongStream;
|
||||
|
|
@ -68,9 +67,7 @@ public class RecipeControllerTest {
|
|||
.mapToObj(x -> new Recipe(
|
||||
null,
|
||||
"Recipe " + x,
|
||||
"en",
|
||||
List.of(),
|
||||
List.of()))
|
||||
"en", List.of(), List.of()))
|
||||
.toList();
|
||||
controller = new RecipeController(
|
||||
recipeService,
|
||||
|
|
@ -83,11 +80,8 @@ public class RecipeControllerTest {
|
|||
if (tags.contains("test-from-init-data")) {
|
||||
ids = LongStream
|
||||
.range(0, NUM_RECIPES)
|
||||
.map(idx -> recipeRepository
|
||||
.save(recipes.get((int) idx))
|
||||
.getId())
|
||||
.boxed()
|
||||
.toList();
|
||||
.map(idx -> recipeRepository.save(recipes.get((int) idx)).getId())
|
||||
.boxed().toList();
|
||||
}
|
||||
|
||||
// Some tests need to know the stored IDs of objects
|
||||
|
|
@ -123,65 +117,9 @@ public class RecipeControllerTest {
|
|||
controller.getRecipes(
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.of(List.of("en", "nl")))
|
||||
.getBody()
|
||||
.size());
|
||||
Optional.of(List.of("en", "nl"))).getBody().size());;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("test-from-init-data")
|
||||
public void getRecipesWithNegativeLimit(){
|
||||
assertEquals(0,
|
||||
controller.getRecipes(Optional.empty(),
|
||||
Optional.of(-2),
|
||||
Optional.of(List.of("en")))
|
||||
.getBody().size());
|
||||
|
||||
}
|
||||
@Test
|
||||
@Tag("test-from-init-data")
|
||||
public void getRecipesWithSearch() {
|
||||
|
||||
recipeRepository.save(new Recipe(
|
||||
null,
|
||||
"banana pie",
|
||||
"en",
|
||||
List.of(),
|
||||
List.of()));
|
||||
|
||||
assertEquals(1, controller.getRecipes(
|
||||
Optional.of("banana"),
|
||||
Optional.empty(),
|
||||
Optional.of(List.of("en"))).getBody().size());
|
||||
|
||||
assertEquals("banana pie", Objects.requireNonNull(controller.getRecipes(
|
||||
Optional.of("banana"),
|
||||
Optional.empty(),
|
||||
Optional.of(List.of("en"))).getBody()).getFirst().getName());
|
||||
}
|
||||
@Test
|
||||
@Tag("test-from-init-data")
|
||||
public void getRecipesWithZeroLimit() {
|
||||
assertEquals(0, controller.getRecipes(
|
||||
Optional.empty(),
|
||||
Optional.of(0),
|
||||
Optional.of(List.of("en"))).getBody().size());
|
||||
}
|
||||
@Test
|
||||
@Tag("test-from-init-data")
|
||||
public void getRecipesWithEmptySearch() {
|
||||
var response = controller.getRecipes(
|
||||
Optional.of(" "),
|
||||
Optional.empty(),
|
||||
Optional.of(List.of("en"))
|
||||
);
|
||||
assertEquals(recipes.size(), response.getBody().size());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
@Tag("test-from-init-data")
|
||||
public void getSomeRecipes() {
|
||||
|
|
@ -191,9 +129,7 @@ public class RecipeControllerTest {
|
|||
controller.getRecipes(
|
||||
Optional.empty(),
|
||||
Optional.of(LIMIT),
|
||||
Optional.of(List.of("en")))
|
||||
.getBody()
|
||||
.size());
|
||||
Optional.of(List.of("en"))).getBody().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -204,9 +140,7 @@ public class RecipeControllerTest {
|
|||
controller.getRecipes(
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.of(List.of("en", "nl")))
|
||||
.getBody()
|
||||
.size());
|
||||
Optional.of(List.of("en", "nl"))).getBody().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -217,9 +151,7 @@ public class RecipeControllerTest {
|
|||
controller.getRecipes(
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.of(List.of("nl")))
|
||||
.getBody()
|
||||
.size());
|
||||
Optional.of(List.of("nl"))).getBody().size());
|
||||
}
|
||||
@Test
|
||||
@Tag("test-from-init-data")
|
||||
|
|
@ -229,9 +161,7 @@ public class RecipeControllerTest {
|
|||
controller.getRecipes(
|
||||
Optional.empty(),
|
||||
Optional.of(LIMIT),
|
||||
Optional.of(List.of("en", "nl")))
|
||||
.getBody()
|
||||
.size());
|
||||
Optional.of(List.of("en", "nl"))).getBody().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -242,8 +172,7 @@ public class RecipeControllerTest {
|
|||
// The third item in the input list is the same as the third item retrieved from the database
|
||||
assertEquals(
|
||||
recipes.get(CHECK_INDEX),
|
||||
controller.getRecipe(recipeIds.get(CHECK_INDEX))
|
||||
.getBody());
|
||||
controller.getRecipe(recipeIds.get(CHECK_INDEX)).getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -252,8 +181,7 @@ public class RecipeControllerTest {
|
|||
// There does not exist a recipe with ID=3 since there are no items in the repository.
|
||||
assertEquals(
|
||||
HttpStatus.NOT_FOUND,
|
||||
controller.getRecipe((long) CHECK_INDEX)
|
||||
.getStatusCode());
|
||||
controller.getRecipe((long) CHECK_INDEX).getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -264,8 +192,7 @@ public class RecipeControllerTest {
|
|||
|
||||
// The object has been successfully deleted
|
||||
assertEquals(HttpStatus.OK,
|
||||
controller.deleteRecipe(recipeIds.get(DELETE_INDEX))
|
||||
.getStatusCode());
|
||||
controller.deleteRecipe(recipeIds.get(DELETE_INDEX)).getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -284,8 +211,7 @@ public class RecipeControllerTest {
|
|||
public void deleteOneRecipeFail() {
|
||||
final Long DELETE_INDEX = 5L;
|
||||
assertEquals(HttpStatus.BAD_REQUEST,
|
||||
controller.deleteRecipe(DELETE_INDEX)
|
||||
.getStatusCode());
|
||||
controller.deleteRecipe(DELETE_INDEX).getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -293,16 +219,10 @@ public class RecipeControllerTest {
|
|||
@Tag("need-ids")
|
||||
public void updateOneRecipeHasNewData() {
|
||||
final int UPDATE_INDEX = 5;
|
||||
Recipe newRecipe = controller.getRecipe(recipeIds
|
||||
.get(UPDATE_INDEX))
|
||||
.getBody();
|
||||
|
||||
Recipe newRecipe = controller.getRecipe(recipeIds.get(UPDATE_INDEX)).getBody();
|
||||
newRecipe.setName("New recipe");
|
||||
controller.updateRecipe(newRecipe.getId(), newRecipe);
|
||||
|
||||
assertEquals("New recipe",
|
||||
recipeRepository.getReferenceById(recipeIds
|
||||
.get(UPDATE_INDEX))
|
||||
.getName());
|
||||
recipeRepository.getReferenceById(recipeIds.get(UPDATE_INDEX)).getName());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue