Merge remote-tracking branch 'origin/main' into feature/backend-websockets

This commit is contained in:
Natalia Cholewa 2025-12-04 22:52:16 +01:00
commit cfacc37739
37 changed files with 2333 additions and 254 deletions

View file

@ -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);
}
}

View file

@ -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);
}
}

View 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;
}
}
}

View file

@ -0,0 +1,112 @@
/*
* 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 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 AddNameCtrl implements LocaleAware {
private final ServerUtils server;
private final MainCtrl mainCtrl;
private final LocaleManager localeManager;
@FXML
public TextField recipeName;
@FXML
public Label recipeNameLabel;
@FXML
public Button cancelButton;
@FXML
public Button okButton;
@Inject
public AddNameCtrl(ServerUtils server, MainCtrl mainCtrl, LocaleManager localeManager) {
this.mainCtrl = mainCtrl;
this.server = server;
this.localeManager = localeManager;
}
@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.showFoodpal();
}
public void ok() throws IOException, InterruptedException {
try {
server.addRecipeName(recipeName.getText());
} catch (WebApplicationException e) {
var alert = new Alert(Alert.AlertType.ERROR);
alert.initModality(Modality.APPLICATION_MODAL);
alert.setContentText(e.getMessage());
alert.showAndWait();
return;
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
clearFields();
mainCtrl.showFoodpal();
}
private void clearFields() {
recipeName.clear();
}
public void keyPressed(KeyEvent e) throws IOException, InterruptedException {
switch (e.getCode()) {
case ENTER:
ok();
break;
case ESCAPE:
cancel();
break;
default:
break;
}
}
}

View 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;
}
}
}

View 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();
}
}

View file

@ -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);
}
});
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View 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() {}
}

View 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;
}
}

View file

@ -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);
}
}

View 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;
}
}

View 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>

View 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>

View file

@ -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>

View 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>

Some files were not shown because too many files have changed in this diff Show more