Merge remote-tracking branch 'origin/main' into feature/backend-websockets
This commit is contained in:
commit
cfacc37739
37 changed files with 2335 additions and 256 deletions
|
|
@ -17,15 +17,14 @@ package client;
|
|||
|
||||
import static com.google.inject.Guice.createInjector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import client.scenes.AddStepsCtrl;
|
||||
import client.scenes.MainCtrl;
|
||||
import client.scenes.AddIngredientCtrl;
|
||||
import client.scenes.AddNameCtrl;
|
||||
import client.scenes.FoodpalApplicationCtrl;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
import client.scenes.AddQuoteCtrl;
|
||||
import client.scenes.MainCtrl;
|
||||
import client.scenes.QuoteOverviewCtrl;
|
||||
import client.utils.ServerUtils;
|
||||
import client.utils.ServerUtilsExample;
|
||||
import javafx.application.Application;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
|
|
@ -34,24 +33,26 @@ public class Main extends Application {
|
|||
private static final Injector INJECTOR = createInjector(new MyModule());
|
||||
private static final MyFXML FXML = new MyFXML(INJECTOR);
|
||||
|
||||
public static void main(String[] args) throws URISyntaxException, IOException {
|
||||
public static void main(String[] args){
|
||||
launch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
|
||||
var serverUtils = INJECTOR.getInstance(ServerUtils.class);
|
||||
var serverUtils = INJECTOR.getInstance(ServerUtilsExample.class);
|
||||
if (!serverUtils.isServerAvailable()) {
|
||||
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 overview = FXML.load(QuoteOverviewCtrl.class, "client", "scenes", "QuoteOverview.fxml");
|
||||
var add = FXML.load(AddQuoteCtrl.class, "client", "scenes", "AddQuote.fxml");
|
||||
var addName = FXML.load(AddNameCtrl.class, "client", "scenes", "AddName.fxml");
|
||||
var foodpal = FXML.load(FoodpalApplicationCtrl.class, "client", "scenes", "FoodpalApplication.fxml");
|
||||
var addIngredient = FXML.load(AddIngredientCtrl.class, "client", "scenes", "AddIngredient.fxml");
|
||||
var addStep = FXML.load(AddStepsCtrl.class, "client", "scenes", "AddSteps.fxml");
|
||||
|
||||
var mainCtrl = INJECTOR.getInstance(MainCtrl.class);
|
||||
mainCtrl.initialize(primaryStage, overview, add);
|
||||
mainCtrl.setup(primaryStage, addName, foodpal, addIngredient, addStep);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,20 +15,23 @@
|
|||
*/
|
||||
package client;
|
||||
|
||||
import client.scenes.FoodpalApplicationCtrl;
|
||||
import client.utils.LocaleManager;
|
||||
import com.google.inject.Binder;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Scopes;
|
||||
|
||||
import client.scenes.AddQuoteCtrl;
|
||||
import client.scenes.AddNameCtrl;
|
||||
import client.scenes.MainCtrl;
|
||||
import client.scenes.QuoteOverviewCtrl;
|
||||
|
||||
public class MyModule implements Module {
|
||||
|
||||
@Override
|
||||
public void configure(Binder binder) {
|
||||
binder.bind(MainCtrl.class).in(Scopes.SINGLETON);
|
||||
binder.bind(AddQuoteCtrl.class).in(Scopes.SINGLETON);
|
||||
binder.bind(QuoteOverviewCtrl.class).in(Scopes.SINGLETON);
|
||||
binder.bind(AddNameCtrl.class).in(Scopes.SINGLETON);
|
||||
binder.bind(FoodpalApplicationCtrl.class).in(Scopes.SINGLETON);
|
||||
|
||||
binder.bind(LocaleManager.class).in(Scopes.SINGLETON);
|
||||
}
|
||||
}
|
||||
115
client/src/main/java/client/scenes/AddIngredientCtrl.java
Normal file
115
client/src/main/java/client/scenes/AddIngredientCtrl.java
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright 2021 Delft University of Technology
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package client.scenes;
|
||||
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import client.utils.ServerUtils;
|
||||
import com.google.inject.Inject;
|
||||
import commons.Recipe;
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.stage.Modality;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AddIngredientCtrl implements LocaleAware {
|
||||
|
||||
private final ServerUtils server;
|
||||
private final MainCtrl mainCtrl;
|
||||
private final FoodpalApplicationCtrl foodpalCtrl;
|
||||
|
||||
private final LocaleManager localeManager;
|
||||
|
||||
@FXML
|
||||
public TextField ingredient;
|
||||
|
||||
@FXML
|
||||
public Button okButton;
|
||||
|
||||
@FXML
|
||||
public Button cancelButton;
|
||||
|
||||
@FXML
|
||||
public Label ingredientLabel;
|
||||
|
||||
@Inject
|
||||
public AddIngredientCtrl(ServerUtils server, MainCtrl mainCtrl,
|
||||
FoodpalApplicationCtrl foodpalCtrl, LocaleManager localeManager) {
|
||||
this.mainCtrl = mainCtrl;
|
||||
this.server = server;
|
||||
this.foodpalCtrl = foodpalCtrl;
|
||||
this.localeManager = localeManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateText() {
|
||||
ingredientLabel.setText(getLocaleString("add.ingredient.label"));
|
||||
okButton.setText(getLocaleString("button.ok"));
|
||||
cancelButton.setText(getLocaleString("button.cancel"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleManager getLocaleManager() {
|
||||
return localeManager;
|
||||
}
|
||||
|
||||
public void cancel() throws IOException, InterruptedException {
|
||||
clearFields();
|
||||
mainCtrl.showFoodpal();
|
||||
}
|
||||
|
||||
public void ok() throws IOException, InterruptedException {
|
||||
try {
|
||||
Recipe selected = foodpalCtrl.getSelectedRecipe();
|
||||
server.addRecipeIngredient(selected, ingredient.getText());
|
||||
} catch (WebApplicationException e) {
|
||||
|
||||
var alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.initModality(Modality.APPLICATION_MODAL);
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
clearFields();
|
||||
mainCtrl.showFoodpal();
|
||||
}
|
||||
|
||||
private void clearFields() {
|
||||
ingredient.clear();
|
||||
}
|
||||
|
||||
public void keyPressed(KeyEvent e) throws IOException, InterruptedException {
|
||||
switch (e.getCode()) {
|
||||
case ENTER:
|
||||
ok();
|
||||
break;
|
||||
case ESCAPE:
|
||||
cancel();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -15,47 +15,68 @@
|
|||
*/
|
||||
package client.scenes;
|
||||
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import client.utils.ServerUtils;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import client.utils.ServerUtils;
|
||||
import commons.Person;
|
||||
import commons.Quote;
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.stage.Modality;
|
||||
|
||||
public class AddQuoteCtrl {
|
||||
import java.io.IOException;
|
||||
|
||||
public class AddNameCtrl implements LocaleAware {
|
||||
|
||||
private final ServerUtils server;
|
||||
private final MainCtrl mainCtrl;
|
||||
|
||||
@FXML
|
||||
private TextField firstName;
|
||||
private final LocaleManager localeManager;
|
||||
|
||||
@FXML
|
||||
private TextField lastName;
|
||||
public TextField recipeName;
|
||||
|
||||
@FXML
|
||||
private TextField quote;
|
||||
public Label recipeNameLabel;
|
||||
|
||||
@FXML
|
||||
public Button cancelButton;
|
||||
|
||||
@FXML
|
||||
public Button okButton;
|
||||
|
||||
@Inject
|
||||
public AddQuoteCtrl(ServerUtils server, MainCtrl mainCtrl) {
|
||||
public AddNameCtrl(ServerUtils server, MainCtrl mainCtrl, LocaleManager localeManager) {
|
||||
this.mainCtrl = mainCtrl;
|
||||
this.server = server;
|
||||
|
||||
this.localeManager = localeManager;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
@Override
|
||||
public void updateText() {
|
||||
recipeNameLabel.setText(getLocaleString("add.recipe.label"));
|
||||
okButton.setText(getLocaleString("button.ok"));
|
||||
cancelButton.setText(getLocaleString("button.cancel"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleManager getLocaleManager() {
|
||||
return localeManager;
|
||||
}
|
||||
|
||||
public void cancel() throws IOException, InterruptedException {
|
||||
clearFields();
|
||||
mainCtrl.showOverview();
|
||||
mainCtrl.showFoodpal();
|
||||
}
|
||||
|
||||
public void ok() {
|
||||
public void ok() throws IOException, InterruptedException {
|
||||
try {
|
||||
server.addQuote(getQuote());
|
||||
server.addRecipeName(recipeName.getText());
|
||||
} catch (WebApplicationException e) {
|
||||
|
||||
var alert = new Alert(Alert.AlertType.ERROR);
|
||||
|
|
@ -63,25 +84,19 @@ public class AddQuoteCtrl {
|
|||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
return;
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
clearFields();
|
||||
mainCtrl.showOverview();
|
||||
}
|
||||
|
||||
private Quote getQuote() {
|
||||
var p = new Person(firstName.getText(), lastName.getText());
|
||||
var q = quote.getText();
|
||||
return new Quote(p, q);
|
||||
mainCtrl.showFoodpal();
|
||||
}
|
||||
|
||||
private void clearFields() {
|
||||
firstName.clear();
|
||||
lastName.clear();
|
||||
quote.clear();
|
||||
recipeName.clear();
|
||||
}
|
||||
|
||||
public void keyPressed(KeyEvent e) {
|
||||
public void keyPressed(KeyEvent e) throws IOException, InterruptedException {
|
||||
switch (e.getCode()) {
|
||||
case ENTER:
|
||||
ok();
|
||||
|
|
@ -93,4 +108,5 @@ public class AddQuoteCtrl {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
114
client/src/main/java/client/scenes/AddStepsCtrl.java
Normal file
114
client/src/main/java/client/scenes/AddStepsCtrl.java
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* Copyright 2021 Delft University of Technology
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package client.scenes;
|
||||
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import client.utils.ServerUtils;
|
||||
import com.google.inject.Inject;
|
||||
import commons.Recipe;
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.stage.Modality;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AddStepsCtrl implements LocaleAware {
|
||||
|
||||
private final ServerUtils server;
|
||||
private final MainCtrl mainCtrl;
|
||||
private final FoodpalApplicationCtrl foodpalCtrl;
|
||||
private final LocaleManager localeManager;
|
||||
|
||||
@FXML
|
||||
public TextField preparationStep;
|
||||
|
||||
@FXML
|
||||
public Button okButton;
|
||||
|
||||
@FXML
|
||||
public Button cancelButton;
|
||||
|
||||
@FXML
|
||||
public Label preparationStepLabel;
|
||||
|
||||
@Inject
|
||||
public AddStepsCtrl(ServerUtils server, MainCtrl mainCtrl,
|
||||
FoodpalApplicationCtrl foodpalCtrl, LocaleManager localeManager) {
|
||||
this.mainCtrl = mainCtrl;
|
||||
this.server = server;
|
||||
this.foodpalCtrl = foodpalCtrl;
|
||||
this.localeManager = localeManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateText() {
|
||||
preparationStepLabel.setText(getLocaleString("add.step.label"));
|
||||
okButton.setText(getLocaleString("button.ok"));
|
||||
cancelButton.setText(getLocaleString("button.cancel"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleManager getLocaleManager() {
|
||||
return localeManager;
|
||||
}
|
||||
|
||||
public void cancel() throws IOException, InterruptedException {
|
||||
clearFields();
|
||||
mainCtrl.showFoodpal();
|
||||
}
|
||||
|
||||
public void ok() throws IOException, InterruptedException {
|
||||
try {
|
||||
Recipe selected = foodpalCtrl.getSelectedRecipe();
|
||||
server.addRecipeStep(selected, preparationStep.getText());
|
||||
} catch (WebApplicationException e) {
|
||||
|
||||
var alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.initModality(Modality.APPLICATION_MODAL);
|
||||
alert.setContentText(e.getMessage());
|
||||
alert.showAndWait();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
clearFields();
|
||||
mainCtrl.showFoodpal();
|
||||
}
|
||||
|
||||
private void clearFields() {
|
||||
preparationStep.clear();
|
||||
}
|
||||
|
||||
public void keyPressed(KeyEvent e) throws IOException, InterruptedException {
|
||||
switch (e.getCode()) {
|
||||
case ENTER:
|
||||
ok();
|
||||
break;
|
||||
case ESCAPE:
|
||||
cancel();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
276
client/src/main/java/client/scenes/FoodpalApplicationCtrl.java
Normal file
276
client/src/main/java/client/scenes/FoodpalApplicationCtrl.java
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
package client.scenes;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import client.utils.ServerUtils;
|
||||
import commons.Recipe;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
|
||||
public class FoodpalApplicationCtrl implements LocaleAware {
|
||||
private final MainCtrl mainCtrl;
|
||||
private final ServerUtils server;
|
||||
private final LocaleManager localeManager;
|
||||
|
||||
// everything in the top bar ===
|
||||
@FXML
|
||||
private Button flagEnButton; //already here for advanced stuff
|
||||
|
||||
@FXML
|
||||
private Button flagNlButton; //already here for advanced stuff
|
||||
|
||||
@FXML
|
||||
private Button flagPlButton; //already here for advanced stuff
|
||||
|
||||
// everything in the left lane
|
||||
@FXML
|
||||
public Label recipesLabel;
|
||||
|
||||
@FXML
|
||||
private ListView<Recipe> recipeList;
|
||||
|
||||
@FXML
|
||||
private Button addRecipeButton;
|
||||
|
||||
@FXML
|
||||
private Button removeRecipeButton;
|
||||
|
||||
@FXML
|
||||
public Button cloneRecipeButton;
|
||||
|
||||
// === CENTER: RECIPE DETAILS ===
|
||||
@FXML
|
||||
private Label recipeNameLabel;
|
||||
|
||||
@FXML
|
||||
private Button editRecipeTitleButton;
|
||||
|
||||
@FXML
|
||||
public Button removeRecipeButton2;
|
||||
|
||||
@FXML
|
||||
public Button printRecipeButton;
|
||||
|
||||
@FXML
|
||||
public Label ingredientsLabel;
|
||||
|
||||
@FXML
|
||||
public Label preparationLabel;
|
||||
|
||||
@FXML
|
||||
private ListView<String> ingredientsListView;
|
||||
|
||||
@FXML
|
||||
private Button addIngredientButton;
|
||||
|
||||
@FXML
|
||||
private ListView<String> preparationListView;
|
||||
|
||||
@FXML
|
||||
private Button addPreparationStepButton;
|
||||
|
||||
@Inject
|
||||
public FoodpalApplicationCtrl(MainCtrl mainCtrl, ServerUtils server, LocaleManager localeManager) {
|
||||
this.mainCtrl = mainCtrl;
|
||||
this.server = server;
|
||||
this.localeManager = localeManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeComponents() {
|
||||
// Show recipe name in the list
|
||||
recipeList.setCellFactory(list -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(Recipe item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
setText(empty || item == null ? "" : item.getName());
|
||||
}
|
||||
});
|
||||
|
||||
// When your selection changes, update details in the panel
|
||||
recipeList.getSelectionModel().selectedItemProperty().addListener(
|
||||
(obs, oldRecipe, newRecipe) -> showRecipeDetails(newRecipe)
|
||||
);
|
||||
|
||||
// Double-click to go to detail screen
|
||||
recipeList.setOnMouseClicked(event -> {
|
||||
final int DOUBLE_CLICK = 2; //to not get magic number:P
|
||||
if (event.getClickCount() == DOUBLE_CLICK) {
|
||||
openSelectedRecipe();
|
||||
}
|
||||
});
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateText() {
|
||||
addRecipeButton.setText(getLocaleString("menu.button.add.recipe"));
|
||||
removeRecipeButton.setText(getLocaleString("menu.button.remove.recipe"));
|
||||
cloneRecipeButton.setText(getLocaleString("menu.button.clone"));
|
||||
|
||||
editRecipeTitleButton.setText(getLocaleString("menu.button.edit"));
|
||||
removeRecipeButton2.setText(getLocaleString("menu.button.remove.recipe"));
|
||||
printRecipeButton.setText(getLocaleString("menu.button.print"));
|
||||
|
||||
recipesLabel.setText(getLocaleString("menu.label.recipes"));
|
||||
ingredientsLabel.setText(getLocaleString("menu.label.ingredients"));
|
||||
preparationLabel.setText(getLocaleString("menu.label.preparation"));
|
||||
|
||||
addIngredientButton.setText(getLocaleString("menu.button.add.ingredient"));
|
||||
addPreparationStepButton.setText(getLocaleString("menu.button.add.step"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocaleManager getLocaleManager() {
|
||||
return localeManager;
|
||||
}
|
||||
|
||||
// till the all the code from everyone is implemented for now to not have errors
|
||||
private void showRecipeDetails(Recipe recipe) {
|
||||
if (recipe == null) {
|
||||
recipeNameLabel.setText("");
|
||||
ingredientsListView.getItems().clear();
|
||||
preparationListView.getItems().clear();
|
||||
return;
|
||||
}
|
||||
|
||||
recipeNameLabel.setText(recipe.getName());
|
||||
ingredientsListView.getItems().setAll(recipe.getIngredients());
|
||||
preparationListView.getItems().setAll(recipe.getPreparationSteps());
|
||||
}
|
||||
|
||||
// Button handlers
|
||||
@FXML
|
||||
public void refresh() {
|
||||
// TODO: someone else doing this
|
||||
|
||||
List<Recipe> recipes;
|
||||
try {
|
||||
recipes = server.getRecipes();
|
||||
} catch (IOException | InterruptedException e) {
|
||||
recipes = Collections.emptyList();
|
||||
System.err.println("Failed to load recipes: " + e.getMessage());
|
||||
}
|
||||
|
||||
recipeList.getItems().setAll(recipes);
|
||||
|
||||
// Select first recipe in the list by default
|
||||
if (!recipes.isEmpty()) {
|
||||
recipeList.getSelectionModel().selectFirst();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a recipe, by going to a different scene, there you insert the title and bam recipe created
|
||||
*/
|
||||
@FXML
|
||||
private void addRecipe() {
|
||||
// Navigate to "create recipe" screen (should be like a pop-up or new screen or just another place in the app)
|
||||
mainCtrl.showAddName();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void removeSelectedRecipe() throws IOException, InterruptedException {
|
||||
Recipe selected = recipeList.getSelectionModel().getSelectedItem();
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
server.deleteRecipe(selected.getId());
|
||||
|
||||
recipeList.getItems().remove(selected);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void openSelectedRecipe() {
|
||||
Recipe selected = recipeList.getSelectionModel().getSelectedItem();
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
// Let MainCtrl open the full detail screen
|
||||
mainCtrl.showAddName(); //I had showrecipedetail but intelij says showoverview
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void editRecipeTitle() {
|
||||
// TODO: someone else todo
|
||||
// For now reuse openSelectedRecipe()
|
||||
openSelectedRecipe();
|
||||
}
|
||||
|
||||
/**
|
||||
* You can add ingredients. you get send to a different scene that creates the ingredient
|
||||
* It doesn't automatically refresh yet so before you can see the new ingredient you have to first click on a
|
||||
* different recipe and come back after
|
||||
*/
|
||||
@FXML
|
||||
private void addIngredient() {
|
||||
System.out.println("Add ingredient clicked");
|
||||
mainCtrl.showAddIngredient();
|
||||
}
|
||||
|
||||
/**
|
||||
* You can add steps. you get send to a different scene that creates the Step
|
||||
* It doesn't automatically refresh yet so before you can see the new step you have to first click on a
|
||||
* different recipe and come back after
|
||||
*/
|
||||
@FXML
|
||||
private void addPreparationStep() {
|
||||
System.out.println("Add preparation step clicked");
|
||||
mainCtrl.showAddSteps();
|
||||
|
||||
}
|
||||
|
||||
// Language buttons
|
||||
@FXML
|
||||
private void switchLocale(ActionEvent event) {
|
||||
Button button = (Button)event.getSource();
|
||||
String lang = (String)button.getUserData();
|
||||
localeManager.setLocale(Locale.of(lang));
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void makePrintable() {
|
||||
System.out.println("Recipe printed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the recipe that was selected
|
||||
* @return the selected recipe
|
||||
*/
|
||||
public Recipe getSelectedRecipe() {
|
||||
return recipeList.getSelectionModel().getSelectedItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones a recipe, when clicking on the button "clone"
|
||||
*/
|
||||
public void cloneRecipe() throws IOException, InterruptedException {
|
||||
Recipe selected = recipeList.getSelectionModel().getSelectedItem();
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
server.cloneRecipe(selected.getId());
|
||||
refresh();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -15,43 +15,127 @@
|
|||
*/
|
||||
package client.scenes;
|
||||
|
||||
import client.utils.LocaleAware;
|
||||
import client.utils.LocaleManager;
|
||||
import jakarta.inject.Inject;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Pair;
|
||||
|
||||
public class MainCtrl {
|
||||
import java.io.IOException;
|
||||
|
||||
public class MainCtrl implements LocaleAware {
|
||||
private final LocaleManager localeManager;
|
||||
|
||||
@FXML
|
||||
private Stage primaryStage;
|
||||
|
||||
private QuoteOverviewCtrl overviewCtrl;
|
||||
private Scene overview;
|
||||
@FXML
|
||||
private AddNameCtrl addNameCtrl;
|
||||
private Scene addName;
|
||||
|
||||
private AddQuoteCtrl addCtrl;
|
||||
private Scene add;
|
||||
@FXML
|
||||
private FoodpalApplicationCtrl foodpalCtrl;
|
||||
private Scene foodpal;
|
||||
|
||||
@FXML
|
||||
private AddIngredientCtrl addIngredientCtrl;
|
||||
private Scene addIngredient;
|
||||
|
||||
@FXML
|
||||
private AddStepsCtrl addStepsCtrl;
|
||||
private Scene addStep;
|
||||
|
||||
private String addNameTitle = "add.name.title";
|
||||
private String addIngredientTitle = "add.step.title";
|
||||
private String addStepTitle = "add.ingredient.title";
|
||||
|
||||
@Inject
|
||||
public MainCtrl(LocaleManager localeManager) {
|
||||
this.localeManager = localeManager;
|
||||
}
|
||||
|
||||
public void setup(Stage primaryStage,
|
||||
Pair<AddNameCtrl, Parent> addName,
|
||||
Pair<FoodpalApplicationCtrl, Parent> foodpal,
|
||||
Pair<AddIngredientCtrl, Parent> addIngredient,
|
||||
Pair<AddStepsCtrl, Parent> addStep) throws IOException, InterruptedException {
|
||||
|
||||
public void initialize(Stage primaryStage, Pair<QuoteOverviewCtrl, Parent> overview,
|
||||
Pair<AddQuoteCtrl, Parent> add) {
|
||||
this.primaryStage = primaryStage;
|
||||
this.overviewCtrl = overview.getKey();
|
||||
this.overview = new Scene(overview.getValue());
|
||||
|
||||
this.addCtrl = add.getKey();
|
||||
this.add = new Scene(add.getValue());
|
||||
this.addNameCtrl = addName.getKey();
|
||||
this.addName = new Scene(addName.getValue());
|
||||
|
||||
showOverview();
|
||||
this.foodpalCtrl = foodpal.getKey();
|
||||
this.foodpal = new Scene(foodpal.getValue());
|
||||
|
||||
this.addIngredientCtrl = addIngredient.getKey();
|
||||
this.addIngredient = new Scene(addIngredient.getValue());
|
||||
|
||||
this.addStepsCtrl = addStep.getKey();
|
||||
this.addStep = new Scene(addStep.getValue());
|
||||
|
||||
showFoodpal();
|
||||
primaryStage.show();
|
||||
|
||||
initialize(); // Initialize LocaleManager stuff manually since MainCtrl isn't loaded from FXML.
|
||||
}
|
||||
|
||||
public void showOverview() {
|
||||
primaryStage.setTitle("Quotes: Overview");
|
||||
primaryStage.setScene(overview);
|
||||
overviewCtrl.refresh();
|
||||
@Override
|
||||
public void updateText() {
|
||||
addNameTitle = getLocaleString("add.recipe.title");
|
||||
addStepTitle = getLocaleString("add.step.title");
|
||||
addIngredientTitle = getLocaleString("add.ingredient.title");
|
||||
}
|
||||
|
||||
public void showAdd() {
|
||||
primaryStage.setTitle("Quotes: Adding Quote");
|
||||
primaryStage.setScene(add);
|
||||
add.setOnKeyPressed(e -> addCtrl.keyPressed(e));
|
||||
@Override
|
||||
public LocaleManager getLocaleManager() {
|
||||
return localeManager;
|
||||
}
|
||||
|
||||
public void showAddName() {
|
||||
primaryStage.setTitle(addNameTitle);
|
||||
primaryStage.setScene(addName);
|
||||
addName.setOnKeyPressed(e -> {
|
||||
try {
|
||||
addNameCtrl.keyPressed(e);
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void showFoodpal() {
|
||||
primaryStage.setTitle("FoodPal");
|
||||
primaryStage.setScene(foodpal);
|
||||
foodpalCtrl.refresh();
|
||||
}
|
||||
|
||||
public void showAddIngredient() {
|
||||
primaryStage.setTitle(addIngredientTitle);
|
||||
primaryStage.setScene(addIngredient);
|
||||
addIngredient.setOnKeyPressed(e -> {
|
||||
try {
|
||||
addIngredientCtrl.keyPressed(e);
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void showAddSteps() {
|
||||
primaryStage.setTitle(addStepTitle);
|
||||
primaryStage.setScene(addStep);
|
||||
addStep.setOnKeyPressed(e -> {
|
||||
try {
|
||||
addStepsCtrl.keyPressed(e);
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Delft University of Technology
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package client.scenes;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import client.utils.ServerUtils;
|
||||
import commons.Quote;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
|
||||
public class QuoteOverviewCtrl implements Initializable {
|
||||
|
||||
private final ServerUtils server;
|
||||
private final MainCtrl mainCtrl;
|
||||
|
||||
private ObservableList<Quote> data;
|
||||
|
||||
@FXML
|
||||
private TableView<Quote> table;
|
||||
@FXML
|
||||
private TableColumn<Quote, String> colFirstName;
|
||||
@FXML
|
||||
private TableColumn<Quote, String> colLastName;
|
||||
@FXML
|
||||
private TableColumn<Quote, String> colQuote;
|
||||
|
||||
@Inject
|
||||
public QuoteOverviewCtrl(ServerUtils server, MainCtrl mainCtrl) {
|
||||
this.server = server;
|
||||
this.mainCtrl = mainCtrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
colFirstName.setCellValueFactory(q -> new SimpleStringProperty(q.getValue().person.firstName));
|
||||
colLastName.setCellValueFactory(q -> new SimpleStringProperty(q.getValue().person.lastName));
|
||||
colQuote.setCellValueFactory(q -> new SimpleStringProperty(q.getValue().quote));
|
||||
}
|
||||
|
||||
public void addQuote() {
|
||||
mainCtrl.showAdd();
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
var quotes = server.getQuotes();
|
||||
data = FXCollections.observableList(quotes);
|
||||
table.setItems(data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package client.scenes.recipe;
|
||||
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
|
||||
/**
|
||||
* A custom ListCell for displaying and editing ingredients in an
|
||||
* IngredientList. Allows inline editing of ingredient names.
|
||||
*
|
||||
* @see IngredientListCtrl
|
||||
*/
|
||||
public class IngredientListCell extends ListCell<String> {
|
||||
// TODO: change all this to use actual Ingredient objects
|
||||
// and all that :)
|
||||
|
||||
@Override
|
||||
protected void updateItem(String item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
this.setText(null);
|
||||
} else {
|
||||
this.setText(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEdit() {
|
||||
super.startEdit();
|
||||
|
||||
TextField textField = new TextField(this.getItem());
|
||||
|
||||
// Commit edit on Enter key press
|
||||
textField.setOnAction(event -> {
|
||||
this.commitEdit(textField.getText());
|
||||
});
|
||||
|
||||
// Cancel edit on Escape key press
|
||||
textField.setOnKeyReleased(event -> {
|
||||
if (event.getCode() == KeyCode.ESCAPE) {
|
||||
this.cancelEdit();
|
||||
}
|
||||
});
|
||||
|
||||
this.setText(null);
|
||||
this.setGraphic(textField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the input is valid (non-empty).
|
||||
*
|
||||
* @param input The input string to validate.
|
||||
* @return true if valid, false otherwise.
|
||||
*/
|
||||
private boolean isInputValid(String input) {
|
||||
return input != null && !input.trim().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelEdit() {
|
||||
super.cancelEdit();
|
||||
|
||||
this.setText(this.getItem());
|
||||
this.setGraphic(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitEdit(String newValue) {
|
||||
this.setGraphic(null);
|
||||
|
||||
if (!isInputValid(newValue)) {
|
||||
newValue = this.getItem(); // Revert to old value if input is invalid
|
||||
}
|
||||
|
||||
super.commitEdit(newValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
package client.scenes.recipe;
|
||||
|
||||
import commons.Recipe;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Function;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.ListView.EditEvent;
|
||||
|
||||
/**
|
||||
* Controller for the ingredient list view in the recipe detail scene.
|
||||
* Manages displaying, adding, editing, and deleting ingredients.
|
||||
*
|
||||
* @see RecipeStepListCell The custom cell implementation.
|
||||
*/
|
||||
public class IngredientListCtrl implements Initializable {
|
||||
/**
|
||||
* <p>
|
||||
* The list of ingredients currently displayed.
|
||||
* <br>
|
||||
* As the ingredient list in {@link Recipe} is immutable,
|
||||
* this copies that list upon initialization to this mutable list.
|
||||
* <br>
|
||||
* Please use the {@link #setUpdateCallback(Function)} function to listen for
|
||||
* changes and update the recipe accordingly.
|
||||
* </p>
|
||||
*/
|
||||
private ObservableList<String> ingredients;
|
||||
|
||||
/**
|
||||
* A callback function that is called when the ingredient list is updated.
|
||||
*/
|
||||
private Function<List<String>, Void> updateCallback;
|
||||
|
||||
@FXML
|
||||
ListView<String> ingredientListView;
|
||||
|
||||
@FXML
|
||||
Button addIngredientButton;
|
||||
@FXML
|
||||
Button deleteIngredientButton;
|
||||
|
||||
/**
|
||||
* Set the recipe and refetch data from it.
|
||||
* This replaces the current ingredient list.
|
||||
* Only use this once per recipe when initializing the detail view.
|
||||
*
|
||||
* @param recipe The recipe to fetch data from.
|
||||
*/
|
||||
public void refetchFromRecipe(Recipe recipe) {
|
||||
if (recipe == null) {
|
||||
this.ingredients = FXCollections.observableArrayList(new ArrayList<>());
|
||||
} else {
|
||||
List<String> ingredientList = recipe.getIngredients();
|
||||
this.ingredients = FXCollections.observableArrayList(ingredientList);
|
||||
}
|
||||
|
||||
this.ingredientListView.setItems(this.ingredients);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback that's called when the ingredient list changes.
|
||||
*
|
||||
* @param callback The function to call upon each update.
|
||||
*/
|
||||
public void setUpdateCallback(Function<List<String>, Void> callback) {
|
||||
this.updateCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the ingredient list view.
|
||||
*/
|
||||
private void refresh() {
|
||||
ingredientListView.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ingredient addition. Automatically calls update callback.
|
||||
*
|
||||
* @param event The action event.
|
||||
*/
|
||||
private void handleIngredientAdd(ActionEvent event) {
|
||||
this.ingredients.add("Ingredient " + (this.ingredients.size() + 1));
|
||||
this.refresh();
|
||||
this.updateCallback.apply(this.ingredients);
|
||||
|
||||
var select = this.ingredientListView.getSelectionModel();
|
||||
select.select(this.ingredients.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ingredient edits. Automatically calls update callback.
|
||||
*
|
||||
* @param event The edit event.
|
||||
*/
|
||||
private void handleIngredientEdit(EditEvent<String> event) {
|
||||
int index = event.getIndex();
|
||||
String newValue = event.getNewValue();
|
||||
|
||||
this.ingredients.set(index, newValue);
|
||||
this.refresh();
|
||||
this.updateCallback.apply(this.ingredients);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ingredient deletion. Automatically calls update callback.
|
||||
*
|
||||
* @param event The action event.
|
||||
*/
|
||||
private void handleIngredientDelete(ActionEvent event) {
|
||||
var select = this.ingredientListView.getSelectionModel();
|
||||
int selectedIndex = select.getSelectedIndex();
|
||||
// No index is selected, don't do anything
|
||||
if (selectedIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.ingredients.remove(selectedIndex);
|
||||
this.refresh();
|
||||
this.updateCallback.apply(this.ingredients);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
// TODO: set up communication with the server
|
||||
// this would probably be best done with the callback (so this class doesn't
|
||||
// interact with the server at all)
|
||||
|
||||
this.ingredientListView.setEditable(true);
|
||||
this.ingredientListView.setCellFactory(
|
||||
list -> {
|
||||
return new RecipeStepListCell();
|
||||
});
|
||||
|
||||
this.ingredientListView.setOnEditCommit(this::handleIngredientEdit);
|
||||
this.addIngredientButton.setOnAction(this::handleIngredientAdd);
|
||||
this.deleteIngredientButton.setOnAction(this::handleIngredientDelete);
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
package client.scenes.recipe;
|
||||
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
|
||||
/**
|
||||
* A custom ListCell for displaying and editing ingredients in an
|
||||
* RecipeStepList. Allows inline editing of ingredient names.
|
||||
*
|
||||
* @see RecipeStepListCtrl
|
||||
*/
|
||||
public class RecipeStepListCell extends ListCell<String> {
|
||||
/**
|
||||
* Get the display text for the given item, prefixed with its index.
|
||||
* Looks like "1. Step description".
|
||||
*
|
||||
* @param item The step description.
|
||||
* @return The display text.
|
||||
*/
|
||||
private String getDisplayText(String item) {
|
||||
return this.getIndex() + ". " + item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the display text for the current item.
|
||||
* Looks like "1. Step description".
|
||||
*
|
||||
* @return The display text.
|
||||
*/
|
||||
private String getDisplayText() {
|
||||
return this.getDisplayText(this.getItem());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(String item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (empty || item == null) {
|
||||
this.setText(null);
|
||||
} else {
|
||||
this.setText(this.getDisplayText(item));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEdit() {
|
||||
super.startEdit();
|
||||
|
||||
TextField textField = new TextField(this.getItem());
|
||||
|
||||
// Commit edit on Enter key press
|
||||
textField.setOnAction(event -> {
|
||||
this.commitEdit(textField.getText());
|
||||
});
|
||||
|
||||
// Cancel edit on Escape key press
|
||||
textField.setOnKeyReleased(event -> {
|
||||
if (event.getCode() == KeyCode.ESCAPE) {
|
||||
this.cancelEdit();
|
||||
}
|
||||
});
|
||||
|
||||
this.setText(null);
|
||||
this.setGraphic(textField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the input is valid (non-empty).
|
||||
*
|
||||
* @param input The input string to validate.
|
||||
* @return true if valid, false otherwise.
|
||||
*/
|
||||
private boolean isInputValid(String input) {
|
||||
return input != null && !input.trim().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelEdit() {
|
||||
super.cancelEdit();
|
||||
|
||||
this.setText(this.getDisplayText());
|
||||
this.setGraphic(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitEdit(String newValue) {
|
||||
this.setGraphic(null);
|
||||
|
||||
if (!isInputValid(newValue)) {
|
||||
newValue = this.getItem(); // Revert to old value if input is invalid
|
||||
}
|
||||
|
||||
super.commitEdit(newValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
package client.scenes.recipe;
|
||||
|
||||
import commons.Recipe;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.function.Function;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.ListView.EditEvent;
|
||||
|
||||
/**
|
||||
* Controller for the step list view in the recipe detail scene.
|
||||
* Manages displaying, adding, editing, and deleting steps.
|
||||
*
|
||||
* @see RecipeStepListCell The custom cell implementation.
|
||||
*/
|
||||
public class RecipeStepListCtrl implements Initializable {
|
||||
/**
|
||||
* <p>
|
||||
* The list of recipe steps currently displayed.
|
||||
* <br>
|
||||
* As the step list in {@link Recipe} is immutable,
|
||||
* this copies that list upon initialization to this mutable list.
|
||||
* <br>
|
||||
* Please use the {@link #setUpdateCallback(Function)} function to listen for
|
||||
* changes and update the recipe accordingly.
|
||||
* </p>
|
||||
*/
|
||||
private ObservableList<String> steps;
|
||||
|
||||
/**
|
||||
* A callback function that is called when the step list is updated.
|
||||
*/
|
||||
private Function<List<String>, Void> updateCallback;
|
||||
|
||||
@FXML
|
||||
ListView<String> recipeStepListView;
|
||||
|
||||
@FXML
|
||||
Button addStepButton;
|
||||
@FXML
|
||||
Button deleteStepButton;
|
||||
|
||||
/**
|
||||
* Set the recipe and refetch data from it.
|
||||
* This replaces the current step list.
|
||||
* Only use this once per recipe when initializing the detail view.
|
||||
*
|
||||
* @param recipe The recipe to fetch data from.
|
||||
*/
|
||||
public void refetchFromRecipe(Recipe recipe) {
|
||||
if (recipe == null) {
|
||||
this.steps = FXCollections.observableArrayList(new ArrayList<>());
|
||||
} else {
|
||||
List<String> stepList = recipe.getPreparationSteps();
|
||||
this.steps = FXCollections.observableArrayList(stepList);
|
||||
}
|
||||
|
||||
this.recipeStepListView.setItems(this.steps);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a callback that's called when the step list changes.
|
||||
*
|
||||
* @param callback The function to call upon each update.
|
||||
*/
|
||||
public void setUpdateCallback(Function<List<String>, Void> callback) {
|
||||
this.updateCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the step list view.
|
||||
*/
|
||||
private void refresh() {
|
||||
recipeStepListView.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle step addition. Automatically calls update callback.
|
||||
*
|
||||
* @param event The action event.
|
||||
*/
|
||||
private void handleIngredientAdd(ActionEvent event) {
|
||||
this.steps.add("Step " + (this.steps.size() + 1));
|
||||
this.refresh();
|
||||
this.updateCallback.apply(this.steps);
|
||||
|
||||
var select = this.recipeStepListView.getSelectionModel();
|
||||
select.select(this.steps.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle step edits. Automatically calls update callback.
|
||||
*
|
||||
* @param event The edit event.
|
||||
*/
|
||||
private void handleIngredientEdit(EditEvent<String> event) {
|
||||
int index = event.getIndex();
|
||||
String newValue = event.getNewValue();
|
||||
|
||||
this.steps.set(index, newValue);
|
||||
this.refresh();
|
||||
this.updateCallback.apply(this.steps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle step deletion. Automatically calls update callback.
|
||||
*
|
||||
* @param event The action event.
|
||||
*/
|
||||
private void handleIngredientDelete(ActionEvent event) {
|
||||
var select = this.recipeStepListView.getSelectionModel();
|
||||
int selectedIndex = select.getSelectedIndex();
|
||||
// No index is selected, don't do anything
|
||||
if (selectedIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.steps.remove(selectedIndex);
|
||||
this.refresh();
|
||||
this.updateCallback.apply(this.steps);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
// TODO: set up communication with the server
|
||||
// this would probably be best done with the callback (so this class doesn't
|
||||
// interact with the server at all)
|
||||
|
||||
this.recipeStepListView.setEditable(true);
|
||||
this.recipeStepListView.setCellFactory(
|
||||
list -> {
|
||||
return new RecipeStepListCell();
|
||||
});
|
||||
|
||||
this.recipeStepListView.setOnEditCommit(this::handleIngredientEdit);
|
||||
this.addStepButton.setOnAction(this::handleIngredientAdd);
|
||||
this.deleteStepButton.setOnAction(this::handleIngredientDelete);
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
42
client/src/main/java/client/utils/LocaleAware.java
Normal file
42
client/src/main/java/client/utils/LocaleAware.java
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package client.utils;
|
||||
|
||||
import javafx.fxml.Initializable;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public interface LocaleAware extends Initializable {
|
||||
|
||||
/**
|
||||
* Updates all text when locale changes.
|
||||
* Must be implemented by each controller.
|
||||
*/
|
||||
void updateText();
|
||||
|
||||
/**
|
||||
* Returns the injected LocaleManager.
|
||||
* Must be implemented by each controller.
|
||||
*/
|
||||
LocaleManager getLocaleManager();
|
||||
|
||||
@Override
|
||||
default void initialize(URL location, ResourceBundle resources) {
|
||||
updateText();
|
||||
|
||||
getLocaleManager().getBundleProperty().addListener((_, _, _) -> updateText());
|
||||
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
default void initialize() {
|
||||
initialize(null, null);
|
||||
}
|
||||
|
||||
default String getLocaleString(String key) {
|
||||
return getLocaleManager().getBundle().getString(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional function for initialization of components. Runs after localization.
|
||||
*/
|
||||
default void initializeComponents() {}
|
||||
}
|
||||
47
client/src/main/java/client/utils/LocaleManager.java
Normal file
47
client/src/main/java/client/utils/LocaleManager.java
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package client.utils;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class LocaleManager {
|
||||
private final ObjectProperty<Locale> currentLocale = new SimpleObjectProperty<>(Locale.ENGLISH);
|
||||
private final ObjectProperty<ResourceBundle> currentBundle = new SimpleObjectProperty<>();
|
||||
|
||||
private static final String RESOURCE_BUNDLE_PATH = "locale/lang";
|
||||
|
||||
public LocaleManager() {
|
||||
// TODO: Set currentLocale to config value instead of EN default.
|
||||
updateBundle();
|
||||
currentLocale.addListener((_, _, _) -> updateBundle());
|
||||
}
|
||||
|
||||
private void updateBundle() {
|
||||
ResourceBundle bundle = ResourceBundle.getBundle(RESOURCE_BUNDLE_PATH,
|
||||
currentLocale.get(),
|
||||
ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_PROPERTIES)
|
||||
);
|
||||
currentBundle.set(bundle);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void setLocale(Locale locale) {
|
||||
currentLocale.set(locale);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public Locale getLocale() {
|
||||
return currentLocale.get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public ResourceBundle getBundle() {
|
||||
return currentBundle.get();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public ObjectProperty<ResourceBundle> getBundleProperty() {
|
||||
return currentBundle;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +1,147 @@
|
|||
/*
|
||||
* Copyright 2021 Delft University of Technology
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package client.utils;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import commons.Recipe;
|
||||
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.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.ConnectException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
|
||||
import org.glassfish.jersey.client.ClientConfig;
|
||||
|
||||
import commons.Quote;
|
||||
import jakarta.ws.rs.ProcessingException;
|
||||
import jakarta.ws.rs.client.ClientBuilder;
|
||||
import jakarta.ws.rs.client.Entity;
|
||||
import jakarta.ws.rs.core.GenericType;
|
||||
|
||||
public class ServerUtils {
|
||||
private static final String SERVER = "http://localhost:8080/api";
|
||||
private final HttpClient client;
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private final int statusOK = 200;
|
||||
|
||||
private static final String SERVER = "http://localhost:8080/";
|
||||
public ServerUtils() {
|
||||
client = HttpClient.newHttpClient();
|
||||
}
|
||||
|
||||
public void getQuotesTheHardWay() throws IOException, URISyntaxException {
|
||||
var url = new URI("http://localhost:8080/api/quotes").toURL();
|
||||
var is = url.openConnection().getInputStream();
|
||||
var br = new BufferedReader(new InputStreamReader(is));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
System.out.println(line);
|
||||
/**
|
||||
* Gets all the recipes from the backend
|
||||
* @return a JSON string with all the recipes
|
||||
*/
|
||||
public List<Recipe> getRecipes() throws IOException, InterruptedException {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/recipes"))
|
||||
.GET()
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if(response.statusCode() != statusOK){
|
||||
throw new IOException("No recipe to get. Server responds with " + response.body());
|
||||
}
|
||||
|
||||
|
||||
return objectMapper.readValue(response.body(), new TypeReference<List<Recipe>>() {
|
||||
});// JSON string-> List<Recipe> (Jackson)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single recipe based on its id
|
||||
* @param id every recipe has it's unique id
|
||||
* @return a singe recipe with the given id
|
||||
*/
|
||||
public Recipe findId(long id) throws IOException, InterruptedException {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER +"/recipe/" + id))
|
||||
.GET()
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if(response.statusCode() != statusOK){
|
||||
throw new IOException("failed finding recipe with id: "+ id + " body: " + response.body());
|
||||
}
|
||||
|
||||
return objectMapper.readValue(response.body(),Recipe.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a recipe to the backend
|
||||
* @param newRecipe the recipe to be added
|
||||
* @return a recipe
|
||||
*/
|
||||
public Recipe addRecipe(Recipe newRecipe) throws IOException, InterruptedException {
|
||||
//Make sure the name of the newRecipe is unique
|
||||
List<Recipe> allRecipes = getRecipes();
|
||||
|
||||
|
||||
int version = 1;
|
||||
String originalName = newRecipe.getName();
|
||||
while (allRecipes.stream().anyMatch(r -> r.getName().equals(newRecipe.getName()))) {
|
||||
String newName = originalName + "(" + version++ + ")";
|
||||
newRecipe.setName(newName);
|
||||
}
|
||||
|
||||
String json = objectMapper.writeValueAsString(newRecipe);
|
||||
|
||||
//Recipe to backend
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/recipe/new"))
|
||||
.header("Content-Type", "application/json")
|
||||
.PUT(HttpRequest.BodyPublishers.ofString(json))
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if(response.statusCode() != statusOK){
|
||||
throw new IOException("Failed to add Recipe: " + newRecipe.toDetailedString() + "body: " + response.body());
|
||||
}
|
||||
|
||||
return objectMapper.readValue(response.body(),Recipe.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a recipe
|
||||
* @param id the recipe that get deleted
|
||||
*/
|
||||
public void deleteRecipe(long id) throws IOException, InterruptedException {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/recipe/" + id))
|
||||
.DELETE()
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if(response.statusCode() != statusOK){
|
||||
throw new IOException("Failed removing recipe with id: " + id + "body: " + response.body());
|
||||
}
|
||||
}
|
||||
|
||||
public List<Quote> getQuotes() {
|
||||
return ClientBuilder.newClient(new ClientConfig()) //
|
||||
.target(SERVER).path("api/quotes") //
|
||||
.request(APPLICATION_JSON) //
|
||||
.get(new GenericType<List<Quote>>() {});
|
||||
}
|
||||
/**
|
||||
* Clones a recipe
|
||||
* @param id the id of the recipe to be cloned
|
||||
* @return a duplicated recipe of the given recipe
|
||||
*/
|
||||
public Recipe cloneRecipe(long id) throws IOException, InterruptedException {
|
||||
//Get the recipe
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/recipe/" + id))
|
||||
.GET() //Needs to be changed to POST() when api is changed
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
public Quote addQuote(Quote quote) {
|
||||
return ClientBuilder.newClient(new ClientConfig()) //
|
||||
.target(SERVER).path("api/quotes") //
|
||||
.request(APPLICATION_JSON) //
|
||||
.post(Entity.entity(quote, APPLICATION_JSON), Quote.class);
|
||||
//200 is the status code for success, other codes can mean there is no recipe to clone
|
||||
if(response.statusCode() != statusOK){
|
||||
throw new IOException("Failed to get recipe to clone with id: " + id + "body: " + response.body());
|
||||
}
|
||||
// recipe exists so you can make a "new" recipe aka the clone
|
||||
Recipe recipe = objectMapper.readValue(response.body(), Recipe.class);
|
||||
|
||||
recipe.setId(null); // otherwise the id is the same as the original, and that's wrong
|
||||
|
||||
return addRecipe(recipe);
|
||||
}
|
||||
|
||||
public boolean isServerAvailable() {
|
||||
|
|
@ -74,4 +157,44 @@ public class ServerUtils {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addRecipeName(String name) throws IOException, InterruptedException {
|
||||
Recipe newRecipe = new Recipe();
|
||||
newRecipe.setName(name);
|
||||
addRecipe(newRecipe);
|
||||
|
||||
}
|
||||
|
||||
public void addRecipeIngredient(Recipe recipe, String ingredient) throws IOException, InterruptedException {
|
||||
List<String> ingredients = new ArrayList<>(recipe.getIngredients());
|
||||
ingredients.add(ingredient);
|
||||
recipe.setIngredients(ingredients);
|
||||
|
||||
updateRecipe(recipe);
|
||||
}
|
||||
|
||||
public void updateRecipe(Recipe recipe) throws IOException, InterruptedException {
|
||||
String json = objectMapper.writeValueAsString(recipe);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(SERVER + "/recipe/" + recipe.getId()))
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.ofString((json))) // Needs to be changed to PUT() when api changed
|
||||
.build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if(response.statusCode() != statusOK){
|
||||
throw new IOException("Failed to update recipe: " + recipe.toDetailedString() + "body: " + response.body());
|
||||
}
|
||||
|
||||
objectMapper.readValue(response.body(), Recipe.class);
|
||||
}
|
||||
|
||||
public void addRecipeStep(Recipe recipe, String preparationStep) throws IOException, InterruptedException {
|
||||
List<String> preparationSteps = new ArrayList<>(recipe.getPreparationSteps());
|
||||
preparationSteps.add(preparationStep);
|
||||
recipe.setPreparationSteps(preparationSteps);
|
||||
|
||||
updateRecipe(recipe);
|
||||
}
|
||||
}
|
||||
72
client/src/main/java/client/utils/ServerUtilsExample.java
Normal file
72
client/src/main/java/client/utils/ServerUtilsExample.java
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2021 Delft University of Technology
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package client.utils;
|
||||
|
||||
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.ws.rs.core.GenericType;
|
||||
import org.glassfish.jersey.client.ClientConfig;
|
||||
|
||||
import commons.Quote;
|
||||
import jakarta.ws.rs.ProcessingException;
|
||||
import jakarta.ws.rs.client.ClientBuilder;
|
||||
import jakarta.ws.rs.client.Entity;
|
||||
|
||||
public class ServerUtilsExample {
|
||||
|
||||
private static final String SERVER = "http://localhost:8080/";
|
||||
|
||||
// public void getQuotesTheHardWay() throws IOException, URISyntaxException {
|
||||
// var url = new URI("http://localhost:8080/api/quotes").toURL();
|
||||
// var is = url.openConnection().getInputStream();
|
||||
// var br = new BufferedReader(new InputStreamReader(is));
|
||||
// String line;
|
||||
// while ((line = br.readLine()) != null) {
|
||||
// System.out.println(line);
|
||||
// }
|
||||
// }
|
||||
|
||||
public List<Quote> getQuotes() {
|
||||
return ClientBuilder.newClient(new ClientConfig()) //
|
||||
.target(SERVER).path("api/quotes") //
|
||||
.request(APPLICATION_JSON) //
|
||||
.get(new GenericType<List<Quote>>() {});
|
||||
}
|
||||
|
||||
public Quote addQuote(Quote quote) {
|
||||
return ClientBuilder.newClient(new ClientConfig()) //
|
||||
.target(SERVER).path("api/quotes") //
|
||||
.request(APPLICATION_JSON) //
|
||||
.post(Entity.entity(quote, APPLICATION_JSON), Quote.class);
|
||||
}
|
||||
|
||||
public boolean isServerAvailable() {
|
||||
try {
|
||||
ClientBuilder.newClient(new ClientConfig()) //
|
||||
.target(SERVER) //
|
||||
.request(APPLICATION_JSON) //
|
||||
.get();
|
||||
} catch (ProcessingException e) {
|
||||
if (e.getCause() instanceof ConnectException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
15
client/src/main/resources/client/scenes/AddIngredient.fxml
Normal file
15
client/src/main/resources/client/scenes/AddIngredient.fxml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
|
||||
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="94.0" prefWidth="470.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.AddIngredientCtrl">
|
||||
<children>
|
||||
<Button fx:id="okButton" layoutX="417.0" layoutY="54.0" mnemonicParsing="false" onAction="#ok" text="Ok" />
|
||||
<Button fx:id="cancelButton" layoutX="349.0" layoutY="54.0" mnemonicParsing="false" onAction="#cancel" text="Cancel" />
|
||||
<TextField fx:id="ingredient" layoutX="120.0" layoutY="24.0" prefHeight="18.0" prefWidth="292.0" />
|
||||
<Label fx:id="ingredientLabel" layoutX="28.0" layoutY="29.0" text="Ingredient Name" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
15
client/src/main/resources/client/scenes/AddName.fxml
Normal file
15
client/src/main/resources/client/scenes/AddName.fxml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
|
||||
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="94.0" prefWidth="470.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.AddNameCtrl">
|
||||
<children>
|
||||
<Button fx:id="okButton" layoutX="417.0" layoutY="54.0" mnemonicParsing="false" onAction="#ok" text="Ok" />
|
||||
<Button fx:id="cancelButton" layoutX="349.0" layoutY="54.0" mnemonicParsing="false" onAction="#cancel" text="Cancel" />
|
||||
<TextField fx:id="recipeName" layoutX="109.0" layoutY="24.0" prefHeight="18.0" prefWidth="292.0" />
|
||||
<Label fx:id="recipeNameLabel" layoutX="28.0" layoutY="29.0" text="Recipe Name" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
|
||||
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="146.0" prefWidth="470.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.AddQuoteCtrl">
|
||||
<children>
|
||||
<Button layoutX="419.0" layoutY="105.0" mnemonicParsing="false" onAction="#ok" text="Ok" />
|
||||
<Button layoutX="350.0" layoutY="105.0" mnemonicParsing="false" onAction="#cancel" text="Cancel" />
|
||||
<TextField fx:id="firstName" layoutX="109.0" layoutY="24.0" prefHeight="26.0" prefWidth="79.0" />
|
||||
<TextField fx:id="lastName" layoutX="286.0" layoutY="24.0" />
|
||||
<TextField fx:id="quote" layoutX="109.0" layoutY="61.0" prefHeight="26.0" prefWidth="345.0" />
|
||||
<Label layoutX="216.0" layoutY="29.0" text="Last Name" />
|
||||
<Label layoutX="28.0" layoutY="29.0" text="First Name" />
|
||||
<Label layoutX="28.0" layoutY="66.0" text="Quote" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
15
client/src/main/resources/client/scenes/AddSteps.fxml
Normal file
15
client/src/main/resources/client/scenes/AddSteps.fxml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
|
||||
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="94.0" prefWidth="470.0" xmlns="http://javafx.com/javafx/23.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.AddStepsCtrl">
|
||||
<children>
|
||||
<Button fx:id="okButton" layoutX="417.0" layoutY="54.0" mnemonicParsing="false" onAction="#ok" text="Ok" />
|
||||
<Button fx:id="cancelButton" layoutX="349.0" layoutY="54.0" mnemonicParsing="false" onAction="#cancel" text="Cancel" />
|
||||
<TextField fx:id="preparationStep" layoutX="120.0" layoutY="24.0" prefHeight="18.0" prefWidth="292.0" />
|
||||
<Label fx:id="preparationStepLabel" layoutX="28.0" layoutY="29.0" text="Preparation step" />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
153
client/src/main/resources/client/scenes/FoodpalApplication.fxml
Normal file
153
client/src/main/resources/client/scenes/FoodpalApplication.fxml
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.control.ToolBar?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.layout.RowConstraints?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<BorderPane prefHeight="800.0" prefWidth="1200.0" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.FoodpalApplicationCtrl">
|
||||
|
||||
<!-- TOP BAR -->
|
||||
<top>
|
||||
<HBox spacing="10">
|
||||
<padding>
|
||||
<Insets bottom="10" left="10" right="10" top="10" />
|
||||
</padding>
|
||||
<GridPane>
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="198.0" minWidth="10.0" prefWidth="130.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="95.0" minWidth="2.0" prefWidth="70.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints maxHeight="54.0" minHeight="10.0" prefHeight="54.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints maxHeight="25.0" minHeight="6.0" prefHeight="6.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
|
||||
<Label prefHeight="56.0" prefWidth="158.0" text="FoodPal">
|
||||
<font>
|
||||
<Font name="System Bold" size="29.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<ButtonBar prefHeight="40.0" prefWidth="200.0" GridPane.rowIndex="2">
|
||||
<buttons>
|
||||
<ToolBar prefHeight="35.0" prefWidth="123.0">
|
||||
<items>
|
||||
<Button fx:id="flagEnButton" minWidth="33.0" onAction="#switchLocale" userData="en" prefHeight="25.0" prefWidth="33.0" text="EN" />
|
||||
<Button fx:id="flagNlButton" onAction="#switchLocale" userData="nl" text="NL" />
|
||||
<Button fx:id="flagPlButton" onAction="#switchLocale" userData="pl" text="PL" />
|
||||
</items>
|
||||
</ToolBar>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
<Pane HBox.hgrow="ALWAYS" />
|
||||
|
||||
<HBox spacing="5" />
|
||||
<GridPane prefHeight="90.0" prefWidth="114.0">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="315.0" minWidth="10.0" prefWidth="32.1666259765625" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="315.0" minWidth="10.0" prefWidth="24.8333740234375" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="173.0" minWidth="10.0" prefWidth="28.6666259765625" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="95.0" minWidth="10.0" prefWidth="34.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
|
||||
<Button fx:id="refreshButton" onAction="#refresh" prefHeight="25.0" prefWidth="34.0" text="⟳" GridPane.columnIndex="3" GridPane.rowIndex="2" />
|
||||
</children>
|
||||
</GridPane>
|
||||
</HBox>
|
||||
</top>
|
||||
|
||||
<!-- LEFT: RECIPE LIST -->
|
||||
<left>
|
||||
<VBox spacing="10">
|
||||
<padding>
|
||||
<Insets bottom="10" left="10" right="10" top="10" />
|
||||
</padding>
|
||||
|
||||
<Label fx:id="recipesLabel" text="Recipes">
|
||||
<font>
|
||||
<Font name="System Bold" size="15.0" />
|
||||
</font></Label>
|
||||
|
||||
<ListView fx:id="recipeList" />
|
||||
|
||||
<HBox spacing="10">
|
||||
<Button fx:id="addRecipeButton" onAction="#addRecipe" text="Add Recipe" />
|
||||
<Button fx:id="removeRecipeButton" onAction="#removeSelectedRecipe" text="Remove Recipe" />
|
||||
<Button fx:id= "cloneRecipeButton" mnemonicParsing="false" onAction="#cloneRecipe" text="Clone" />
|
||||
</HBox>
|
||||
</VBox>
|
||||
</left>
|
||||
|
||||
<!-- CENTER: RECIPE DETAILS -->
|
||||
<center>
|
||||
<VBox spacing="20">
|
||||
<padding>
|
||||
<Insets bottom="10" left="10" right="10" top="10" />
|
||||
</padding>
|
||||
|
||||
<!-- Recipe title row -->
|
||||
<HBox spacing="10">
|
||||
<Label fx:id="recipeNameLabel" text="Recipe Name">
|
||||
<font>
|
||||
<Font name="System Bold" size="14.0" />
|
||||
</font></Label>
|
||||
<Button fx:id="editRecipeTitleButton" onAction="#editRecipeTitle" text="Edit" />
|
||||
<Button fx:id="removeRecipeButton2" mnemonicParsing="false" onAction="#removeSelectedRecipe" text="Remove Recipe" />
|
||||
<Button fx:id="printRecipeButton" mnemonicParsing="false" onAction="#makePrintable" text="Print Recipe" />
|
||||
</HBox>
|
||||
|
||||
<!-- Ingredients -->
|
||||
<VBox spacing="10">
|
||||
<Label fx:id="ingredientsLabel" text="Ingredients">
|
||||
<font>
|
||||
<Font name="System Bold" size="14.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<ListView fx:id="ingredientsListView" prefHeight="167.0" prefWidth="912.0" />
|
||||
<Button fx:id="addIngredientButton" onAction="#addIngredient" text="Add Ingredient" />
|
||||
</VBox>
|
||||
|
||||
<!-- Preparation -->
|
||||
<Label fx:id="preparationLabel" text="Preparation">
|
||||
<font>
|
||||
<Font name="System Bold" size="14.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<ListView fx:id="preparationListView" prefHeight="200.0" />
|
||||
<Button fx:id="addPreparationStepButton" onAction="#addPreparationStep" text="Add Step" />
|
||||
<GridPane>
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="890.6666870117188" minWidth="10.0" prefWidth="854.6666870117188" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="445.3333740234375" minWidth="10.0" prefWidth="57.33331298828125" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
</GridPane>
|
||||
</VBox>
|
||||
</center>
|
||||
|
||||
</BorderPane>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
|
||||
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.QuoteOverviewCtrl">
|
||||
<children>
|
||||
<Button layoutX="503.0" layoutY="360.0" mnemonicParsing="false" onAction="#addQuote" text="Add Quote" />
|
||||
<Button layoutX="431.0" layoutY="360.0" mnemonicParsing="false" onAction="#refresh" text="Refresh" />
|
||||
<TableView fx:id="table" layoutX="14.0" layoutY="14.0" prefHeight="337.0" prefWidth="572.0">
|
||||
<columns>
|
||||
<TableColumn fx:id="colFirstName" prefWidth="113.0" text="First Name" />
|
||||
<TableColumn fx:id="colLastName" prefWidth="114.0" text="Last Name" />
|
||||
<TableColumn fx:id="colQuote" prefWidth="344.0" text="Quote" />
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<AnchorPane xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.recipe.RecipeStepListCtrl">
|
||||
<children>
|
||||
<VBox minHeight="200.0" minWidth="200.0">
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
||||
<children>
|
||||
<Label text="Ingredients">
|
||||
<padding>
|
||||
<Insets bottom="5.0" top="5.0" />
|
||||
</padding>
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<HBox alignment="CENTER" spacing="10.0">
|
||||
<children>
|
||||
<Button fx:id="addIngredientButton" mnemonicParsing="false" text="Add" />
|
||||
<Button fx:id="deleteIngredientButton" mnemonicParsing="false" text="Delete" />
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets left="10.0" right="10.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
<ListView fx:id="ingredientListView" prefHeight="200.0" prefWidth="200.0" />
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ListView?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Font?>
|
||||
|
||||
<AnchorPane xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="client.scenes.recipe.RecipeStepListCtrl">
|
||||
<children>
|
||||
<VBox minHeight="200.0" minWidth="200.0">
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
||||
<children>
|
||||
<Label text="Steps">
|
||||
<padding>
|
||||
<Insets bottom="5.0" top="5.0" />
|
||||
</padding>
|
||||
<font>
|
||||
<Font name="System Bold" size="16.0" />
|
||||
</font>
|
||||
</Label>
|
||||
<HBox alignment="CENTER" spacing="10.0">
|
||||
<children>
|
||||
<Button fx:id="addStepButton" mnemonicParsing="false" text="Add" />
|
||||
<Button fx:id="deleteStepButton" mnemonicParsing="false" text="Delete" />
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
<padding>
|
||||
<Insets left="10.0" right="10.0" />
|
||||
</padding>
|
||||
</HBox>
|
||||
<ListView fx:id="recipeStepListView" prefHeight="200.0" prefWidth="200.0" />
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
26
client/src/main/resources/locale/lang.properties
Normal file
26
client/src/main/resources/locale/lang.properties
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
add.ingredient.title=Add Ingredient
|
||||
add.recipe.title=Create recipe
|
||||
add.step.title=Add Step
|
||||
|
||||
add.ingredient.label=Ingredient
|
||||
add.recipe.label=Recipe Name
|
||||
add.step.label=Step
|
||||
|
||||
button.ok=Ok
|
||||
button.cancel=Cancel
|
||||
|
||||
menu.label.recipes=Recipes
|
||||
menu.label.ingredients=Ingredients
|
||||
menu.label.preparation=Preparation
|
||||
|
||||
menu.button.add.recipe=Add Recipe
|
||||
menu.button.add.ingredient=Add Ingredient
|
||||
menu.button.add.step=Add Step
|
||||
|
||||
menu.button.remove.recipe=Remove Recipe
|
||||
menu.button.remove.ingredient=Remove Ingredient
|
||||
menu.button.remove.step=Remove Step
|
||||
|
||||
menu.button.edit=Edit
|
||||
menu.button.clone=Clone
|
||||
menu.button.print=Print recipe
|
||||
26
client/src/main/resources/locale/lang_en.properties
Normal file
26
client/src/main/resources/locale/lang_en.properties
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
add.ingredient.title=Add Ingredient
|
||||
add.recipe.title=Create recipe
|
||||
add.step.title=Add Step
|
||||
|
||||
add.ingredient.label=Ingredient
|
||||
add.recipe.label=Recipe Name
|
||||
add.step.label=Step
|
||||
|
||||
button.ok=Ok
|
||||
button.cancel=Cancel
|
||||
|
||||
menu.label.recipes=Recipes
|
||||
menu.label.ingredients=Ingredients
|
||||
menu.label.preparation=Preparation
|
||||
|
||||
menu.button.add.recipe=Add Recipe
|
||||
menu.button.add.ingredient=Add Ingredient
|
||||
menu.button.add.step=Add Step
|
||||
|
||||
menu.button.remove.recipe=Remove Recipe
|
||||
menu.button.remove.ingredient=Remove Ingredient
|
||||
menu.button.remove.step=Remove Step
|
||||
|
||||
menu.button.edit=Edit
|
||||
menu.button.clone=Clone
|
||||
menu.button.print=Print recipe
|
||||
26
client/src/main/resources/locale/lang_nl.properties
Normal file
26
client/src/main/resources/locale/lang_nl.properties
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
add.ingredient.title=Ingredient toevoegen
|
||||
add.recipe.title=Recept aanmaken
|
||||
add.step.title=Stap toevoegen
|
||||
|
||||
add.ingredient.label=Ingredient
|
||||
add.recipe.label=Receptnaam
|
||||
add.step.label=Bereidingsstap
|
||||
|
||||
button.ok=Ok
|
||||
button.cancel=Annuleren
|
||||
|
||||
menu.label.recipes=Recepten
|
||||
menu.label.ingredients=Ingrediënten
|
||||
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.remove.recipe=Recept verwijderen
|
||||
menu.button.remove.ingredient=Ingrediënt verwijderen
|
||||
menu.button.remove.step=Stap verwijderen
|
||||
|
||||
menu.button.edit=Bewerken
|
||||
menu.button.clone=Dupliceren
|
||||
menu.button.print=Recept afdrukken
|
||||
26
client/src/main/resources/locale/lang_pl.properties
Normal file
26
client/src/main/resources/locale/lang_pl.properties
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
add.ingredient.title=Dodaj składnik
|
||||
add.recipe.title=Utwórz przepis
|
||||
add.step.title=Dodaj instrukcję
|
||||
|
||||
add.ingredient.label=Składnik
|
||||
add.recipe.label=Nazwa przepisu
|
||||
add.step.label=Instrukcja
|
||||
|
||||
button.ok=Ok
|
||||
button.cancel=Anuluj
|
||||
|
||||
menu.label.recipes=Przepisy
|
||||
menu.label.ingredients=Składniki
|
||||
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.remove.recipe=Usuń przepis
|
||||
menu.button.remove.ingredient=Usuń składnik
|
||||
menu.button.remove.step=Usuń instrukcję
|
||||
|
||||
menu.button.edit=Edytuj
|
||||
menu.button.clone=Duplikuj
|
||||
menu.button.print=Drukuj przepis
|
||||
106
client/src/test/java/client/ServerUtilsTest.java
Normal file
106
client/src/test/java/client/ServerUtilsTest.java
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package client;
|
||||
|
||||
import client.utils.ServerUtils;
|
||||
import commons.Recipe;
|
||||
import org.junit.jupiter.api.Assumptions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ServerUtilsTest {
|
||||
static ServerUtils dv;
|
||||
static Recipe testRecipe;
|
||||
final int fakeId = -1; // If suppose ID's are only positive
|
||||
|
||||
@BeforeEach
|
||||
void setup() throws IOException, InterruptedException {
|
||||
dv = new ServerUtils();
|
||||
|
||||
Assumptions.assumeTrue(dv.isServerAvailable(), "Server not available");
|
||||
|
||||
//Making sure there is no recipe in the backend yet
|
||||
for (Recipe recipe : dv.getRecipes()) {
|
||||
dv.deleteRecipe(recipe.getId());
|
||||
}
|
||||
|
||||
Recipe r = new Recipe();
|
||||
|
||||
r.setName("Tosti");
|
||||
r.setIngredients(List.of("Bread", "Cheese", "Ham"));
|
||||
r.setPreparationSteps(List.of("Step 1:", "Step 2"));
|
||||
|
||||
testRecipe = dv.addRecipe(r);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addRecipeTest() throws IOException, InterruptedException {
|
||||
Recipe r = new Recipe();
|
||||
r.setName("Eggs on toast");
|
||||
r.setIngredients(List.of("Bread", "egg", "salt"));
|
||||
r.setPreparationSteps(List.of("Step 1:", "Step 2"));
|
||||
|
||||
testRecipe = dv.addRecipe(r);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noRecipeToAdd(){
|
||||
Recipe r = new Recipe();
|
||||
assertThrows(IOException.class, () -> dv.addRecipe(r));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void getAllRecipesTest() throws IOException, InterruptedException {
|
||||
List<Recipe> recipes = dv.getRecipes();
|
||||
|
||||
assertNotNull(recipes, "The list should not be null");
|
||||
assertTrue(recipes.size() >= 0, "The list should be 0 (when no recipes), or more");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void findRecipeWithIDTest() throws IOException, InterruptedException {
|
||||
Recipe r = dv.findId(testRecipe.getId());
|
||||
|
||||
assertEquals(testRecipe.getId(), r.getId());
|
||||
assertEquals("Tosti", r.getName());
|
||||
assertIterableEquals(List.of("Bread", "Cheese", "Ham"), r.getIngredients());
|
||||
assertIterableEquals(List.of("Step 1:", "Step 2"), r.getPreparationSteps());
|
||||
}
|
||||
|
||||
@Test
|
||||
void noRecipeFoundTest() throws IOException, InterruptedException {
|
||||
assertThrows(IOException.class, () -> dv.findId(fakeId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteRecipeTest() throws IOException, InterruptedException {
|
||||
dv.deleteRecipe(testRecipe.getId());
|
||||
|
||||
assertThrows(IOException.class, () ->dv.findId(testRecipe.getId()), "The recipe shouldn't exists anymore" );
|
||||
}
|
||||
|
||||
@Test
|
||||
void noRecipeToDelete() throws IOException, InterruptedException {
|
||||
assertThrows(IOException.class, () -> dv.deleteRecipe(fakeId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void cloneRecipeTest() throws IOException, InterruptedException {
|
||||
Recipe clone = dv.cloneRecipe(testRecipe.getId());
|
||||
|
||||
assertNotEquals(clone.getId(), testRecipe.getId(), "The id's should not be equal to each other");
|
||||
assertNotEquals(clone.getName(),testRecipe.getName(),"The name's should not be the same");
|
||||
assertEquals(clone.getIngredients(), testRecipe.getIngredients(), "the ingredients should be the same");
|
||||
assertEquals(clone.getPreparationSteps(), testRecipe.getPreparationSteps(),"The steps should be the same");
|
||||
}
|
||||
|
||||
@Test
|
||||
void noRecipeToClone(){
|
||||
assertThrows(IOException.class, () -> dv.cloneRecipe(fakeId));
|
||||
}
|
||||
}
|
||||
|
|
@ -15,16 +15,16 @@
|
|||
*/
|
||||
package client.scenes;
|
||||
|
||||
import client.utils.LocaleManager;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class MainCtrlTest {
|
||||
|
||||
private MainCtrl sut;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
sut = new MainCtrl();
|
||||
sut = new MainCtrl(new LocaleManager());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -1,46 +1,62 @@
|
|||
# Code of Conduct CSEP
|
||||
> Last changed: 2025-11-14 04:20 PM
|
||||
## Assignment description
|
||||
- working on our teamwork skills
|
||||
- coding with other people
|
||||
- communication skill in a group setting
|
||||
> Last changed: 2025-11-27 10:30 PM
|
||||
## 1. Assignment description
|
||||
- Working on our teamwork skills by helping and collaborating with each other.
|
||||
- Coding with other people through the use of Git + GitLab tooling.
|
||||
- Communication skill in a group setting working in a project with 6 people
|
||||
|
||||
## Target or ambition level
|
||||
- so we working towards a 10 but a 8 is fine
|
||||
- the most important thing is that everyone does their best
|
||||
## 2. Target or ambition level
|
||||
- Goal is at least an 8/10, aiming for a 10.
|
||||
- The most important thing is that everyone does their best
|
||||
|
||||
## Planning
|
||||
- friday - before the ta meeting checking eachother codes and making a planning for the week after
|
||||
- label the to do lists in gitlab on friday (during planning time)
|
||||
- check up on eachother thoughout the week
|
||||
- **Submitter** - Rithvik
|
||||
## 3. Planning
|
||||
- Meet in person minimally 2 times a week with preference to Thursday and Friday
|
||||
- Meet 1 time a week online on monday
|
||||
- We will adjust expectations and deadlines dynamically depending on what has been actually achieved in the previous week, during one of the aforementioned meetings.
|
||||
- During a pre-meeting session in the CSE1105 labs on Friday, we will:
|
||||
- discuss any development blockages that team members encountered during the week.
|
||||
- go over open MRs.
|
||||
- plan any task items for the next week.
|
||||
- consider potential additions to the week's agenda.
|
||||
- **Submitter of any Brightspace material** - Rithvik Sriram
|
||||
|
||||
## Communication
|
||||
- Discord for our online meeting and sharing info and asking questions
|
||||
## 4. Communication
|
||||
- Discord for our online meeting and sharing info and asking questions.
|
||||
- GitLab to share our code
|
||||
- Friday lab meetings for in person meetings
|
||||
- Thursday and Friday lab meetings for in person meetings
|
||||
|
||||
### Collaboration outside of the mandatory meetings
|
||||
- Friday during lab before ta meeting
|
||||
- Discord
|
||||
## 5. Collaboration outside of the mandatory meetings
|
||||
- Thursdays (to code together and help eachother)
|
||||
- Friday during lab before ta meeting (to make the final changes before merging)
|
||||
- Discord (for our quick questions about the small things)
|
||||
- GitLab
|
||||
|
||||
## Help and assistance
|
||||
- We have discord together, so if there is a question or issue it can be asked there and also during the friday meeting
|
||||
## 6. Help and assistance
|
||||
- Reserving Discord as a method of communication through which anyone who has an issue or question can talk.
|
||||
- A person who is having trouble with implementing a specific part of the software is ensured to have another team member available who can assist in debugging/guiding the implementation of that particular feature.
|
||||
|
||||
## Work quality
|
||||
- By having the other 5 members rate the code on gitlab
|
||||
- Making sure we use the same checkstyle document
|
||||
## 7. Work quality
|
||||
- Each Merge Request is required to:
|
||||
- have at least approval from one person responsible for backend, and one person responsible for frontend.
|
||||
- concern only one feature/issue, to ensure atomic MRs/commits.
|
||||
- have generally atomic commits, aka. frequent and small, such that any issues introduced by one specific commit can be triaged quickly.
|
||||
- Comply to the existing CheckStyle document (enforced automatically via Maven build server).
|
||||
- have clear comments and JavaDoc written wherever non-trivial functionality is involved.
|
||||
|
||||
## Decision-making
|
||||
- Discuss it with arguments so consensus
|
||||
## 8. Decision-making
|
||||
- Important decisions within the group, such as major refactoring or design decisions, will be decided within the group by way of consensus.
|
||||
- Decide on where and where to meet during the week by way of Discord polling.
|
||||
|
||||
## Broken agreements
|
||||
- Discuss it with them
|
||||
- If they dont do anything (work, meetings) at all discuss with the ta
|
||||
## 9. Broken agreements
|
||||
- If someone breaks the agreements we have agreed upon in this code of conduct we will first of all discuss it with them
|
||||
- If someone isn't collaborating at all we will make this clear to the TA and hope they can help
|
||||
- If that doesn't resolve it we will bring it up with the lecturer
|
||||
- Never will we just exclude someone without taking the proper step
|
||||
|
||||
## 10. Problem resolution
|
||||
- Main way of problem resolution is by reasoning and team discussion in good faith.
|
||||
- If a team member is not able to, or will be late to, a specific meeting, this should be communicated at least 1 full day before the meeting's commencement.
|
||||
- For major issues and problems for which the above methods cannot suffice, the responsible chair during the week in which the issue occurred will consult third parties in order of increasing severity:
|
||||
1. discuss the issue with the TA.
|
||||
2. communicate with the CSE1105 teaching team e.g. by form of email.
|
||||
|
||||
## Problem resolution
|
||||
- We are all adults here so we can just talk it out by civil discussion
|
||||
- If your gonna be late, communicate with the rest of the team
|
||||
- Give a reason if you can't show up for a meeting
|
||||
- Contact with the TA will be needed when someone doesn't react to any of our messages and doesnt show up at all
|
||||
|
|
|
|||
42
countlines.bat
Normal file
42
countlines.bat
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
for /f "delims=" %%i in ('git config user.email') do set AUTHOR=%%i
|
||||
echo Checking commit stats for %AUTHOR%...
|
||||
echo.
|
||||
|
||||
echo === Last 7 days (all .java files) ===
|
||||
git log --author="%AUTHOR%" --since="7 days ago" --pretty=tformat: --numstat | findstr "\.java" > temp_java.txt
|
||||
|
||||
set added=0
|
||||
set removed=0
|
||||
|
||||
for /f "tokens=1,2" %%a in (temp_java.txt) do (
|
||||
set /a added+=%%a
|
||||
set /a removed+=%%b
|
||||
)
|
||||
|
||||
set /a net=added-removed
|
||||
echo Lines added in .java: %added%
|
||||
echo Lines removed in .java: %removed%
|
||||
echo Net lines: %net%
|
||||
echo.
|
||||
|
||||
echo === All time (excluding *Test.java) ===
|
||||
git log --author="%AUTHOR%" --pretty=tformat: --numstat -- . ":(exclude)*Test.java" | findstr "\.java" > temp_java.txt
|
||||
|
||||
set added=0
|
||||
set removed=0
|
||||
|
||||
for /f "tokens=1,2" %%a in (temp_java.txt) do (
|
||||
set /a added+=%%a
|
||||
set /a removed+=%%b
|
||||
)
|
||||
|
||||
set /a net=added-removed
|
||||
echo Lines added in .java (excluding *Test.java): %added%
|
||||
echo Lines removed in .java (excluding *Test.java): %removed%
|
||||
echo Net lines: %net%
|
||||
|
||||
del temp_java.txt
|
||||
endlocal
|
||||
28
countlines.sh
Normal file
28
countlines.sh
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/bin/bash
|
||||
|
||||
AUTHOR=$(git config user.email)
|
||||
echo -e "Checking commit stats for \033[38:5:2m$AUTHOR\033[0m..."
|
||||
|
||||
# Get numstat for commits by author, filter only .java lines
|
||||
git log --author="$AUTHOR" --since="7 days ago" --pretty=tformat: --numstat \
|
||||
| awk '
|
||||
$3 ~ /\.java$/ {
|
||||
added += $1
|
||||
removed += $2
|
||||
}
|
||||
END {
|
||||
print "Lines added in .java:", added
|
||||
print "Lines removed in .java:", removed
|
||||
print "Net lines:", added - removed
|
||||
}'
|
||||
git log --author="$AUTHOR" --pretty=tformat: --numstat -- . ':(exclude)*Test.java' \
|
||||
| awk '
|
||||
$3 ~ /\.java$/ {
|
||||
added += $1
|
||||
removed += $2
|
||||
}
|
||||
END {
|
||||
print "Lines added in .java (excluding *Test.java):", added
|
||||
print "Lines removed in .java (excluding *Test.java):", removed
|
||||
print "Net lines:", added - removed
|
||||
}'
|
||||
59
docs/agenda/agenda-03.md
Normal file
59
docs/agenda/agenda-03.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Week 3 meeting agenda
|
||||
|
||||
| Key | Value |
|
||||
| ------------ | -------------------------------------------------------- |
|
||||
| Date | Nov 28th |
|
||||
| Time | 16:45 |
|
||||
| Location | DW PC Hall 2 |
|
||||
| Chair | Steven Liu |
|
||||
| Minute Taker | Oskar Rasieński |
|
||||
| Attendees | Mei Chang van der Werff, Natalia Cholewa, Rithvik Sriram |
|
||||
## Table of contents
|
||||
1. (1 min) Introduction by chair
|
||||
2. (1 min) Any additions to the agenda?
|
||||
3. (1-2 min) TA announcements?
|
||||
4. (1-2 min) New submission of Code of Conduct + additions?
|
||||
5. (30 min) **Meeting content**
|
||||
1. (2 min) Week 3 progress checklist, MRs, and summary
|
||||
- Have we completed all of basic requirements? If not, what to resolve as soon as possible.
|
||||
2. (3 min) Socket modelling - what do we (don't) want to update with WebSockets
|
||||
1. How should we model individual changes, e.g. what happens when the user adds a new recipe? or add an ingredient to a recipe?
|
||||
3. (3 min) Discussion on implementing nutritional value calculations
|
||||
4. (1-2 min) Questions and/or suggestions
|
||||
5. (20 min) **Sprint planning week 4**
|
||||
1. (2-3 min) Set a provisional deadline for completing parts of the backlog
|
||||
2. (2 min) Confirm week 4 backend/frontend team formation
|
||||
3. (5 min) Tasks creation (GitLab) and assignment
|
||||
4. (5 min) Decide on a TODO list that each team should complete before the next meeting (see below for a provisional proposal)
|
||||
5. (5 min) Other issues
|
||||
6. (2 min) Questions
|
||||
|
||||
**Max time:** ~35 minutes
|
||||
## Addendum
|
||||
### Provisional week 3 tasks checklist
|
||||
> May have further additions after the pre-meeting session.
|
||||
|
||||
**Backend**:
|
||||
- [ ] Basic recipe definition
|
||||
- [ ] API endpoints that provide Recipe CRUD functionality
|
||||
- [ ] Unit tests for the endpoints
|
||||
- [ ] ...
|
||||
**Frontend**:
|
||||
- [ ] A list of recipe names to choose from
|
||||
- [ ] An interface to display a recipe when it is chosen from the recipe list
|
||||
- [ ] (Functional) buttons in the interface
|
||||
- [ ] Abstracted requests sending to the backend
|
||||
- [ ] ...
|
||||
## Provisional week 4 list of tasks
|
||||
**Backend**:
|
||||
- 4.2 content
|
||||
- WebSockets service implementation
|
||||
- 4.3 content
|
||||
- implementing actual formal/informal ingredients and appropriate unit class definitions
|
||||
- ... more to add
|
||||
**Frontend**:
|
||||
- Make sure the basic frontend functionality are in place and works as expected
|
||||
- Improving ingredient/steps editing + deletion to work with the new Ingredient and Unit types
|
||||
- Making a new scene/view + buttons for the Nutrition view
|
||||
- Make the interface text ResourceBundle based to implement internationalisation
|
||||
- ... more to add
|
||||
68
docs/agenda/agenda-04.md
Normal file
68
docs/agenda/agenda-04.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Week 3 meeting agenda
|
||||
|
||||
| Key | Value |
|
||||
| ------------ |-----------------------------------------------------------------------------------------|
|
||||
| Date | Dec 5th |
|
||||
| Time | 16:45 |
|
||||
| Location | DW PC Hall 2 |
|
||||
| Chair | Oskar Rasieński |
|
||||
| Minute Taker | Rithvik Sriram |
|
||||
| Attendees | Mei Chang van der Werff, Natalia Cholewa, Rithvik Sriram, Aysegul Aydinlik, Steven Liu |
|
||||
## Table of contents
|
||||
1. (1 min) Introduction by chair
|
||||
2. (1 min) Any additions to the agenda?
|
||||
3. (1-2 min) TA announcements?
|
||||
4. (1 min) Buddy check reminder.
|
||||
5. (1 min) Next meeting thursday after midterms.
|
||||
6. (33 min) **Meeting content**
|
||||
1. (5 min) Week 4 progress checklist, MRs, and summary
|
||||
- Was 4.1 completed?
|
||||
- Was 4.2 completed?
|
||||
- Progress 4.3
|
||||
- Progress 4.6
|
||||
2. (1-2 min) Questions and/or suggestions
|
||||
3. (3 min) Showcase
|
||||
4. (20 min) **Sprint planning week 5-6**
|
||||
- What needs to be done?
|
||||
- How do we do it?
|
||||
- Who does it?
|
||||
5. (2 min) Designate teams.
|
||||
6. (1 min) Who is the next chair and minute taker?
|
||||
7. (2 min) Questions
|
||||
8. (1 min) Summarize everything
|
||||
|
||||
**Max time:** ~40 minutes
|
||||
## Addendum
|
||||
### Provisional week 4 tasks checklist
|
||||
|
||||
**Backend**:
|
||||
- [ ] Ingredient and Unit class definitions
|
||||
- [ ] Integrate new Ingredient class into Recipe
|
||||
- [ ] Websocket backend
|
||||
**Frontend**:
|
||||
- [ ] Integrate everything from last week together
|
||||
- [ ] Remove example code
|
||||
- [ ] Print export
|
||||
- [ ] Edit button
|
||||
- [ ] Nutritional value view
|
||||
- [ ] Make ingredients unit based instead of string
|
||||
- [ ] Websockets client-side
|
||||
- [ ] Resource bundles for localization
|
||||
|
||||
## Provisional week 6 list of tasks
|
||||
**Backend**:
|
||||
- 4.4
|
||||
- Warning when favourite recipes has been deleted by someone else. (Likely through ws)
|
||||
...
|
||||
|
||||
**Frontend**:
|
||||
- 4.2
|
||||
- Finish websocket client side.
|
||||
- 4.3
|
||||
- Finish any uncompleted tasks.
|
||||
- 4.4
|
||||
- Favourite recipes (tied to id, only local)
|
||||
- Warning when favourite recipes has been deleted by someone else.
|
||||
- Search bar with a basic query syntax. (Filters existing List, not a new window)
|
||||
- Favourites category (filter)
|
||||
...
|
||||
67
docs/feedback/week-03.md
Normal file
67
docs/feedback/week-03.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
|
||||
# Meeting feedback
|
||||
|
||||
**Quick Summary Scale**
|
||||
|
||||
Insufficient/Sufficient/Good/Excellent
|
||||
|
||||
|
||||
#### Agenda
|
||||
|
||||
Feedback: **Good - Excellent**
|
||||
|
||||
- The agenda was uploaded on time.
|
||||
- The agenda was formatted according to the template.
|
||||
- The individual points were clear.
|
||||
- I liked that you included an addendum and a provisional list of tasks for everyone.
|
||||
- You can slightly adjust the layout of the agenda to be easier to read. For example, you can have sections like Opening, Updates, Feedback, Closing etc - you can use anything you want.
|
||||
|
||||
#### Performance of the Previous Minute Taker
|
||||
|
||||
Feedback: **Good - Excellent**
|
||||
|
||||
- The notes have not been merged into the agenda. Try to have a single document for agenda + minutes.
|
||||
- The notes cover most of the discussed points.
|
||||
- The notes are clear. I liked how you distributed them based on discussed topics. Try to have a similar approach but merged with the agenda.
|
||||
- The notes contain concrete agreements and evryone was assigned to a task.
|
||||
- All agreements are actionable and realistic.
|
||||
- A table showing task distribution could be useful.
|
||||
|
||||
#### Chair performance
|
||||
|
||||
Feedback: **Excellent**
|
||||
|
||||
- You did a good job with starting the meeting and checking in with everyone.
|
||||
- You covered all points from the agenda.
|
||||
- I liked that you started with a quick summary of how last week went.
|
||||
- You followed and explained all your points and asked for the opinion of the others.
|
||||
- I liked that you took the lead and kept to the agenda.
|
||||
- I also liked that you did a quick summary at the end.
|
||||
|
||||
|
||||
#### Attitude & Relation
|
||||
|
||||
Feedback: **Excellent**
|
||||
|
||||
- Overall the atmosphere was constructive and positive.
|
||||
- All ideas were listened to and considered by all team members.
|
||||
- Everyone was active an dinvolved in the meeting.
|
||||
- Everyone took ownership of the meeting.
|
||||
- I liked that you clarified the topics for everyone whenever they asked.
|
||||
|
||||
|
||||
#### Potentially Shippable Produc
|
||||
Feedback: **Sufficient**
|
||||
|
||||
- The team didn't present the current state of the application, however I understand that you worked on it.
|
||||
- Make sure that next week you have a potentially shippable product.
|
||||
- For the future meetings you can reserve a few minutes for showing the application.
|
||||
- Progress has been done compared to last week.
|
||||
|
||||
#### Work Contribution/Distribution in the Team
|
||||
|
||||
Feedback: **Good**
|
||||
|
||||
- You can start by reviewing the action list of last week to get a clear view of what has been completed.
|
||||
- Most of you reached your goal, make sure that everyone is still on track.
|
||||
- So far it feels like everyone is contributing equally to the team, and I like that you are willing to help when someone doesn't know something.
|
||||
52
docs/minutes/week3-meeting-minutes.md
Normal file
52
docs/minutes/week3-meeting-minutes.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Week 3 meeting minutes
|
||||
|
||||
| Key | Value |
|
||||
| ------------ | -------------------------------------------------------- |
|
||||
| Date | Nov 28th |
|
||||
| Time | 16:45 |
|
||||
| Location | DW PC Hall 2 |
|
||||
| Chair | Steven Liu |
|
||||
| Minute Taker | Oskar Rasieński |
|
||||
| Attendees | Mei Chang van der Werff, Natalia Cholewa, Rithvik Sriram |
|
||||
|
||||
## Announcements
|
||||
- Gitlab will be down on week 5 wednesday until week 6 wednesday.
|
||||
- Do Buddy check (brightspace).
|
||||
|
||||
## Completed tasks:
|
||||
**Backend**:
|
||||
- Basic recipe definition
|
||||
- API endpoints that provide Recipe CRUD functionality
|
||||
- Unit tests for the endpoints
|
||||
|
||||
**Frontend**:
|
||||
- A list of recipe names to choose from (Interface)
|
||||
- An interface to display a recipe when it is chosen from the recipe list
|
||||
- Server utils (Abstracted requests sending to the backend)
|
||||
|
||||
## Unfinished tasks:
|
||||
- Integration of all subsystems.
|
||||
- print export
|
||||
|
||||
## Teams in week 4:
|
||||
- Natalia and Ayse: Backend
|
||||
- Oskar, Steven, Rithvik, Mei: Frontend
|
||||
|
||||
## TODO in week 4:
|
||||
- Integration of all subsystems. (4.1)
|
||||
- print export (4.1)
|
||||
- Websocket (4.2)
|
||||
- Start with 4.3, and try to do as much as possible.
|
||||
|
||||
## Nutritional Value calculations considerations:
|
||||
- Don't include informal units in nutritional value calculations. (ignore them and set their value to 0)
|
||||
- Doesn't really matter whether its server side or client side calculated.
|
||||
|
||||
## Remarks:
|
||||
- When editing recipes we'll replace the whole recipe with the new edited recipe.
|
||||
- In issues and MRs, track time with /spent and /estimate
|
||||
- If MR doesn't close issue, say its related to an issue with #issuenr
|
||||
- Use resource bundles instead of hardcoding text in JFX.
|
||||
- Put server url in config.
|
||||
|
||||
Meeting started: 16:45 and ended: 17:10 (25 mins).
|
||||
Loading…
Add table
Add a link
Reference in a new issue