From 6279f412f63489964533674cb8fa30a8331ca807 Mon Sep 17 00:00:00 2001 From: GAM Date: Sun, 14 Mar 2021 22:26:28 +0100 Subject: [PATCH] Edit Matches --- db/db_init.sql | 3 +- src/main/java/app/data/entity/Game.java | 2 +- src/main/java/app/data/entity/GameInfo.java | 13 +- .../app/data/service/ChessComService.java | 55 ++++-- .../java/app/gameimage/GameImageService.java | 20 +- src/main/java/app/navigation/Navigation.java | 51 +++++- .../java/app/navigation/NavigationUtils.java | 26 +-- src/main/java/app/utils/ChessComUtils.java | 13 +- .../java/app/utils/EntityStringUtils.java | 11 +- src/main/java/app/views/match/MatchView.java | 20 +- .../views/match/components/GameComponent.java | 10 +- .../match/components/MatchComponent.java | 171 ++++++++++++++++-- .../components/utils/GameComponentUtils.java | 6 +- 13 files changed, 324 insertions(+), 77 deletions(-) diff --git a/db/db_init.sql b/db/db_init.sql index 72e36c9..ad9975b 100644 --- a/db/db_init.sql +++ b/db/db_init.sql @@ -42,7 +42,8 @@ CREATE TABLE "game" ( CREATE TABLE "game_info" ( "id" SERIAL PRIMARY KEY, "time_control" varchar, - "chess_com_id" varchar + "chess_com_id" varchar, + "fen" varchar ); diff --git a/src/main/java/app/data/entity/Game.java b/src/main/java/app/data/entity/Game.java index 6cb322e..71c13fe 100644 --- a/src/main/java/app/data/entity/Game.java +++ b/src/main/java/app/data/entity/Game.java @@ -56,7 +56,7 @@ public class Game { return Objects.hash(id, player1IsWhite, result); } - @ManyToOne(cascade=CascadeType.ALL) + @ManyToOne(cascade=CascadeType.DETACH) @JoinColumn(name = "match", referencedColumnName = "id", nullable = false) public Match getMatch() { return match; diff --git a/src/main/java/app/data/entity/GameInfo.java b/src/main/java/app/data/entity/GameInfo.java index a9e30a3..ad2ab7f 100644 --- a/src/main/java/app/data/entity/GameInfo.java +++ b/src/main/java/app/data/entity/GameInfo.java @@ -10,6 +10,7 @@ public class GameInfo { private String chessComId; private Game game; private String timeControl; + private String fen; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -42,6 +43,16 @@ public class GameInfo { this.timeControl = format; } + @Basic + @Column(name = "fen", nullable = false) + public String getFen() { + return fen; + } + + public void setFen(String fen) { + this.fen = fen; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -55,7 +66,7 @@ public class GameInfo { return Objects.hash(id, chessComId, timeControl); } - @OneToOne(mappedBy = "gameInfo") + @OneToOne(mappedBy = "gameInfo", cascade = CascadeType.ALL) public Game getGame() { return game; } diff --git a/src/main/java/app/data/service/ChessComService.java b/src/main/java/app/data/service/ChessComService.java index ee10ec2..7bbfbbf 100644 --- a/src/main/java/app/data/service/ChessComService.java +++ b/src/main/java/app/data/service/ChessComService.java @@ -23,25 +23,29 @@ public class ChessComService { private static final Gson gson = new Gson(); private static final String ARCHIVES_ENDPOINT = "https://api.chess.com/pub/player/{username}/games/archives"; + private final Map chessComGameCache = new HashMap<>(); + public ChessComService() { } @NonNull public List getLatestGamesBetweenPlayers(@NonNull Match match, int minAmountOfGames, int maxAmountOfMonths) { - List list = new ArrayList<>(); + List games = new ArrayList<>(); for (String archiveUrl : getLatestArchiveUrls(match.getPlayer1(), maxAmountOfMonths)) { - list.addAll(getChessComGames(archiveUrl).stream() + List chessComGames = getChessComGames(archiveUrl).stream() .sorted(Comparator.comparingLong(ChessComGame::getEndTime).reversed()) .filter(chessComGame -> ChessComUtils.isGameBetweenPlayers(chessComGame, match)) .filter(ChessComUtils::hasValidTimeControl) - .map(chessComGame -> createGame(chessComGame, match)) + .collect(Collectors.toList()); + chessComGames.forEach(chessComGame -> chessComGameCache.put(chessComGame.getUrl(), chessComGame)); + games.addAll(chessComGames.stream() + .map(chessComGame -> getGame(chessComGame, match)) .collect(Collectors.toList())); - - if (list.size() >= minAmountOfGames) { + if (games.size() >= minAmountOfGames) { break; } } - return list; + return games; } // TODO: find exactly two games of each time control private List getChessComGames(String archiveUrl) { @@ -75,8 +79,13 @@ public class ChessComService { return archiveUrls; } + public Optional getGame(@NonNull String url, @NonNull Match match) { + Optional chessComGame = getChessComGame(url, match); + return chessComGame.map(game -> getGame(game, match)); + } + @NonNull - private Game createGame(@NonNull ChessComGame chessComGame, @NonNull Match match) { + public Game getGame(@NonNull ChessComGame chessComGame, @NonNull Match match) { Game game = new Game(); GameInfo gameInfo = new GameInfo(); @@ -85,8 +94,9 @@ public class ChessComService { game.setResult(ChessComUtils.getResult(chessComGame)); game.setGameInfo(gameInfo); - gameInfo.setChessComId(chessComGame.getUrl()); + gameInfo.setChessComId(ChessComUtils.getGameId(chessComGame)); gameInfo.setTimeControl(chessComGame.getTimeControl()); + gameInfo.setFen(chessComGame.getFen()); gameInfo.setGame(game); return game; @@ -101,15 +111,26 @@ public class ChessComService { if (!(game.getMatch().getPlayer1().equals(player) || game.getMatch().getPlayer2().equals(player))) { throw new IllegalArgumentException("Player must be participating in Game!"); } - for (String archiveUrl : getArchiveUrls(player)) { - Optional chessComArchive = getChessComArchive(archiveUrl); - if (chessComArchive.isEmpty()) { - continue; - } - List chessComGames = chessComArchive.get().getGames(); - for (ChessComGame chessComGame : chessComGames) { - if (chessComGame.getUrl().equals(game.getGameInfo().getChessComId())) { - return Optional.of(chessComGame); + return getChessComGame(ChessComUtils.getGameURL(game.getGameInfo().getChessComId()), game.getMatch()); + } + + public Optional getChessComGame(@NonNull String url, Match match) { + Optional gameFromCache = Optional.ofNullable(chessComGameCache.get(url)); + if (gameFromCache.isPresent()) { + return gameFromCache; + } + + for (Player player : Set.of(match.getPlayer1(), match.getPlayer2())) { + for (String archiveUrl : getArchiveUrls(player)) { + Optional chessComArchive = getChessComArchive(archiveUrl); + if (chessComArchive.isEmpty()) { + continue; + } + List chessComGames = chessComArchive.get().getGames(); + for (ChessComGame chessComGame : chessComGames) { + if (chessComGame.getUrl().equals(url)) { + return Optional.of(chessComGame); + } } } } diff --git a/src/main/java/app/gameimage/GameImageService.java b/src/main/java/app/gameimage/GameImageService.java index 671e6e8..c8b1fad 100644 --- a/src/main/java/app/gameimage/GameImageService.java +++ b/src/main/java/app/gameimage/GameImageService.java @@ -18,31 +18,31 @@ public class GameImageService { this.chessComService = chessComService; } - public Optional getVaadinImagePath(Game game) { - return getImagePath(game).map(path -> path.replace("src/main/resources/META-INF/resources/", "")); - } - public Optional getImagePath(Game game) { + public boolean hasImage(Game game) { String path = GameImageUtils.IMAGE_DEST_PATH_SERVER + getGameNumber(game) + GameImageUtils.EXTENSION; File f = new File(path); - if(f.isFile()) { - return Optional.of(path); - } + return f.isFile(); + } + + public boolean createImageIfNotPresent(Game game) { + if (hasImage(game)) return true; Optional optionalFen = getFen(game); if (optionalFen.isPresent()) { GameImageUtils.writeImage(GameImageGenerator.generateImage(optionalFen.get()), getGameNumber(game)); - return Optional.of(path); + return true; } - return Optional.empty(); + return false; } private String getGameNumber(Game game) { String[] url = game.getGameInfo().getChessComId().split("/"); - return url[url.length -1]; + return url[url.length - 1]; } private Optional getFen(Game game) { + if (game.getGameInfo().getFen() != null) return Optional.of(game.getGameInfo().getFen()); return chessComService.getChessComGame(game).map(ChessComGame::getFen); } } diff --git a/src/main/java/app/navigation/Navigation.java b/src/main/java/app/navigation/Navigation.java index 56f9b46..d128026 100644 --- a/src/main/java/app/navigation/Navigation.java +++ b/src/main/java/app/navigation/Navigation.java @@ -8,11 +8,10 @@ import app.data.service.MatchService; import app.data.service.MatchdayService; import app.data.service.SeasonService; import app.utils.EntityStringUtils; -import com.vaadin.flow.component.*; -import com.vaadin.flow.component.html.Label; +import com.vaadin.flow.component.AbstractField; +import com.vaadin.flow.component.HasValue; +import com.vaadin.flow.component.UI; import com.vaadin.flow.component.select.Select; -import com.vaadin.flow.data.renderer.ComponentRenderer; -import com.vaadin.flow.function.SerializableFunction; import com.vaadin.flow.router.BeforeEvent; import com.vaadin.flow.router.HasUrlParameter; import com.vaadin.flow.router.WildcardParameter; @@ -48,6 +47,8 @@ public class Navigation implements HasUrlParameter { private final ValidationLabel validationLabel = new ValidationLabel(); + private boolean editFlag = false; + public Navigation(@Autowired SeasonService seasonService, @Autowired MatchdayService matchdayService, @Autowired MatchService matchService) { @@ -110,7 +111,7 @@ public class Navigation implements HasUrlParameter { if (matchEnabled() && matchSelect.getOptionalValue().isPresent()) matchParam = EntityStringUtils.getMatchStringForURL(matchSelect.getValue()); - String params = NavigationUtils.getWildcardParam(seasonParam, matchdayParam, matchParam); + String params = NavigationUtils.getWildcardParam(editFlag, seasonParam, matchdayParam, matchParam); UI.getCurrent().getPage().getHistory().pushState(null, String.format("%s/%s", getRoute(), params)); } @@ -152,7 +153,8 @@ public class Navigation implements HasUrlParameter { } private void autoselectSeason() { - if (!seasonEnabled()) throw new IllegalStateException("This method should not be called when season is not enabled!"); + if (!seasonEnabled()) + throw new IllegalStateException("This method should not be called when season is not enabled!"); if (seasonList.isEmpty()) { validationLabel.setText("No Seasons in List!"); validationLabel.setValid(false); @@ -162,7 +164,8 @@ public class Navigation implements HasUrlParameter { } private void autoselectMatchday() { // TODO: add date stuff and choose depending on date instead! - if (!matchdayEnabled()) throw new IllegalStateException("This method should not be called when matchday is not enabled!"); + if (!matchdayEnabled()) + throw new IllegalStateException("This method should not be called when matchday is not enabled!"); if (matchdayList.isEmpty()) { validationLabel.setText("No Matchdays in List!"); validationLabel.setValid(false); @@ -174,7 +177,8 @@ public class Navigation implements HasUrlParameter { } private void autoselectMatch() { - if (!matchEnabled()) throw new IllegalStateException("This method should not be called when match is not enabled!"); + if (!matchEnabled()) + throw new IllegalStateException("This method should not be called when match is not enabled!"); if (matchList.isEmpty()) { validationLabel.setText("No Matches in List!"); validationLabel.setValid(false); @@ -206,6 +210,7 @@ public class Navigation implements HasUrlParameter { @Override public void setParameter(BeforeEvent event, @WildcardParameter String param) { Map> map = NavigationUtils.getParameterMap(param); + editFlag = NavigationUtils.editFlag(param); navigate(map.get(NavigationLevel.SEASON), map.get(NavigationLevel.MATCHDAY), map.get(NavigationLevel.MATCH)); } @@ -277,4 +282,34 @@ public class Navigation implements HasUrlParameter { public MatchService getMatchService() { return matchService; } + + public boolean editFlag() { + return editFlag; + } + + public void setEditFlag(boolean editFlag) { + if (editFlag != this.editFlag) { + this.editFlag = editFlag; + updateUrl(); + runnablesToBeRunAfterSelection.forEach(Runnable::run); + } + } + + public String getWildcardParam() { + return NavigationUtils.getWildcardParam( + editFlag, + getSeasonParam().orElse(null), + getMatchdayParam().orElse(null), + getMatchParam().orElse(null)); + } + + public Optional getSeasonParam() { + return getSelectedSeason().map(EntityStringUtils::getSeasonStringForURL); + } + public Optional getMatchdayParam() { + return getSelectedMatchday().map(EntityStringUtils::getMatchdayStringForURL); + } + public Optional getMatchParam() { + return getSelectedMatch().map(EntityStringUtils::getMatchStringForURL); + } } diff --git a/src/main/java/app/navigation/NavigationUtils.java b/src/main/java/app/navigation/NavigationUtils.java index c159b55..80069b6 100644 --- a/src/main/java/app/navigation/NavigationUtils.java +++ b/src/main/java/app/navigation/NavigationUtils.java @@ -1,5 +1,6 @@ package app.navigation; +import app.utils.EntityStringUtils; import com.vaadin.flow.router.WildcardParameter; import org.springframework.lang.NonNull; @@ -10,6 +11,7 @@ import java.util.Optional; @SuppressWarnings("OptionalUsedAsFieldOrParameterType") class NavigationUtils { + private static final String EDIT = "edit"; private NavigationUtils() { } @@ -18,24 +20,23 @@ class NavigationUtils { static Map> getParameterMap(@WildcardParameter String param) { Map> map = new HashMap<>(); String[] params = param.split("/"); - switch (params.length) { - case 3: - map.put(NavigationLevel.MATCH, Optional.of(params[2])); - case 2: - map.put(NavigationLevel.MATCHDAY, Optional.of(params[1])); - case 1: - map.put(NavigationLevel.SEASON, Optional.of(params[0])); - default: - break; - } + if (params.length >= 1 && !params[0].equals(EDIT)) map.put(NavigationLevel.SEASON, Optional.of(params[0])); + if (params.length >= 2 && !params[1].equals(EDIT)) map.put(NavigationLevel.MATCHDAY, Optional.of(params[1])); + if (params.length >= 3 && !params[2].equals(EDIT)) map.put(NavigationLevel.MATCH, Optional.of(params[2])); + map.putIfAbsent(NavigationLevel.MATCH, Optional.empty()); map.putIfAbsent(NavigationLevel.MATCHDAY, Optional.empty()); map.putIfAbsent(NavigationLevel.SEASON, Optional.empty()); return map; } + static boolean editFlag(@WildcardParameter String param) { + String[] params = param.split("/"); + return params[params.length - 1].equals(EDIT); + } + @NonNull - static String getWildcardParam(String... params) { + static String getWildcardParam(boolean editFlag, String... params) { StringBuilder stringBuilder = new StringBuilder(); for (String param : params) { if (param == null || param.equals("")) { @@ -43,6 +44,7 @@ class NavigationUtils { } stringBuilder.append(param).append("/"); } + if (editFlag) stringBuilder.append(EDIT).append("/"); return stringBuilder.toString(); } @@ -51,7 +53,7 @@ class NavigationUtils { return Optional.empty(); } return objectList.stream() - .filter(match -> match.toString().equals(param.get())) + .filter(object -> EntityStringUtils.getObjectStringForURL(object).equals(param.get())) .findFirst(); } } diff --git a/src/main/java/app/utils/ChessComUtils.java b/src/main/java/app/utils/ChessComUtils.java index 64854fa..f60c83b 100644 --- a/src/main/java/app/utils/ChessComUtils.java +++ b/src/main/java/app/utils/ChessComUtils.java @@ -8,7 +8,7 @@ import org.springframework.lang.NonNull; public class ChessComUtils { - private static final String GAME_LINK = "https://www.chess.com/live/game/{gameId}"; + private static final String GAME_LINK = "https://www.chess.com/game/live/{gameId}"; private ChessComUtils() { } @@ -51,8 +51,17 @@ public class ChessComUtils { return false; } - public static String getGameLink(Game game) { + public static String getGameURL(Game game) { String gameId = game.getGameInfo().getChessComId(); + return getGameURL(gameId); + } + + public static String getGameURL(String gameId) { return GAME_LINK.replace("{gameId}", gameId); } + + public static String getGameId(ChessComGame game) { + String[] fields = game.getUrl().split("/"); + return fields[fields.length - 1]; + } } diff --git a/src/main/java/app/utils/EntityStringUtils.java b/src/main/java/app/utils/EntityStringUtils.java index ecffa27..4e183d8 100644 --- a/src/main/java/app/utils/EntityStringUtils.java +++ b/src/main/java/app/utils/EntityStringUtils.java @@ -4,6 +4,7 @@ import app.data.entity.Match; import app.data.entity.Matchday; import app.data.entity.Player; import app.data.entity.Season; +import org.springframework.lang.NonNull; public class EntityStringUtils { private EntityStringUtils() { @@ -17,7 +18,7 @@ public class EntityStringUtils { return player.getNickname().toLowerCase(); } - public static String getSeasonString(Season season) { + public static String getSeasonString(@NonNull Season season) { String string = season.getYearStart().toString(); if (!season.getYearEnd().equals(season.getYearStart())) { string += "-" + season.getYearEnd().toString(); @@ -25,6 +26,14 @@ public class EntityStringUtils { return string; } + public static String getObjectStringForURL(Object object) { + if (object instanceof Player) return getPlayerStringForURL((Player) object); + if (object instanceof Season) return getSeasonStringForURL((Season) object); + if (object instanceof Matchday) return getMatchdayStringForURL((Matchday) object); + if (object instanceof Match) return getMatchStringForURL((Match) object); + throw new IllegalStateException(String.format("Cannot get Object String for URL for Type %s!", object.getClass().getSimpleName())); + } + public static String getSeasonStringForURL(Season season) { return getSeasonString(season); } diff --git a/src/main/java/app/views/match/MatchView.java b/src/main/java/app/views/match/MatchView.java index 0c0ca6b..fd53433 100644 --- a/src/main/java/app/views/match/MatchView.java +++ b/src/main/java/app/views/match/MatchView.java @@ -1,6 +1,9 @@ package app.views.match; -import app.data.service.MatchService; +import app.data.service.ChessComService; +import app.data.service.GameInfoService; +import app.data.service.GameService; +import app.gameimage.GameImageService; import app.navigation.NavigationLevel; import app.navigation.NavigationService; import app.views.main.MainView; @@ -16,14 +19,19 @@ import org.springframework.beans.factory.annotation.Autowired; @PageTitle("Schachliga DACH - Results - Matches") public class MatchView extends NavigationViewBase { - private final MatchService matchService; - + private final ChessComService chessComService; + private final GameService gameService; + private final GameInfoService gameInfoService; + private final GameImageService gameImageService; private MatchComponent matchComponent; public MatchView(@Autowired NavigationService navigationService, - @Autowired MatchService matchService) { + @Autowired ChessComService chessComService, @Autowired GameService gameService, GameInfoService gameInfoService, GameImageService gameImageService) { super(navigationService, "match", NavigationLevel.MATCH); - this.matchService = matchService; + this.chessComService = chessComService; + this.gameService = gameService; + this.gameInfoService = gameInfoService; + this.gameImageService = gameImageService; addClassName("match-view"); @@ -35,7 +43,7 @@ public class MatchView extends NavigationViewBase { //////////// private void configureLayout() { - matchComponent = new MatchComponent(navigation); + matchComponent = new MatchComponent(navigation, chessComService, gameService, gameInfoService, gameImageService); add(matchComponent); } diff --git a/src/main/java/app/views/match/components/GameComponent.java b/src/main/java/app/views/match/components/GameComponent.java index 214bb76..5381c7a 100644 --- a/src/main/java/app/views/match/components/GameComponent.java +++ b/src/main/java/app/views/match/components/GameComponent.java @@ -1,6 +1,7 @@ package app.views.match.components; import app.data.entity.Game; +import app.gameimage.GameImageService; import app.views.match.components.utils.GameComponentUtils; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.html.Div; @@ -9,9 +10,12 @@ import com.vaadin.flow.component.html.Label; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import org.springframework.beans.factory.annotation.Autowired; public class GameComponent extends Div { + private final GameImageService gameImageService; + private final Game game; private final HorizontalLayout inner = new HorizontalLayout(); @@ -19,7 +23,8 @@ public class GameComponent extends Div { private final VerticalLayout left = new VerticalLayout(); private final Div right = new Div(); - public GameComponent(Game game) { + public GameComponent(Game game, @Autowired GameImageService gameImageService) { + this.gameImageService = gameImageService; this.game = game; addClassName("wrapper"); @@ -56,7 +61,8 @@ public class GameComponent extends Div { private void configureRight() { right.addClassName("game-image-div"); - Image image = new Image(GameComponentUtils.getImagePath(game), "Could not find game image."); + gameImageService.createImageIfNotPresent(game); + Image image = new Image(GameComponentUtils.getImagePath(game), "Could not load game image."); right.add(image); image.setWidthFull(); image.setHeightFull(); diff --git a/src/main/java/app/views/match/components/MatchComponent.java b/src/main/java/app/views/match/components/MatchComponent.java index 36537e9..60bd496 100644 --- a/src/main/java/app/views/match/components/MatchComponent.java +++ b/src/main/java/app/views/match/components/MatchComponent.java @@ -3,36 +3,91 @@ package app.views.match.components; import app.data.bean.CalculatedMatch; import app.data.entity.Game; import app.data.entity.Match; +import app.data.service.ChessComService; +import app.data.service.GameInfoService; +import app.data.service.GameService; +import app.gameimage.GameImageService; import app.navigation.Navigation; +import app.utils.ChessComUtils; import app.utils.EntityComponentUtils; import app.utils.StringUtils; import app.views.navigation.interfaces.ContentConfigurable; +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Label; +import com.vaadin.flow.component.icon.Icon; +import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.FlexLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; +import com.vaadin.flow.component.textfield.TextField; +import com.vaadin.flow.shared.Registration; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.ArrayList; +import java.util.List; import java.util.NoSuchElementException; +import java.util.Optional; public class MatchComponent extends Div implements ContentConfigurable { private final Navigation navigation; + private final ChessComService chessComService; + private final GameService gameService; + private final GameInfoService gameInfoService; + private final GameImageService gameImageService; private final VerticalLayout headerLayout = new VerticalLayout(); private final HorizontalLayout headerPlayersLayout = new HorizontalLayout(); private final Label headerResultLabel = new Label(); private final FlexLayout gamesLayout = new FlexLayout(); - - public MatchComponent(Navigation navigation) { + private final VerticalLayout noGamesLayout = new VerticalLayout(); + + private final FlexLayout editLayoutWrapper = new FlexLayout(); + private final VerticalLayout editLayout = new VerticalLayout(); + private final ArrayList editTextFields = new ArrayList<>(); + private final Button editSubmitButton = new Button("Submit", new Icon(VaadinIcon.CHECK)); + private Registration editSubmitButtonRegistration; + private final Button editCancelButton = new Button("Cancel", new Icon(VaadinIcon.CLOSE)); + private Registration editCancelButtonRegistration; + private final Button chessComButton = new Button("Autofill with the latest 6 games between the players", new Icon(VaadinIcon.MAGIC)); + private Registration chessComButtonButtonRegistration; + + + public MatchComponent(Navigation navigation, + @Autowired ChessComService chessComService, + @Autowired GameService gameService, + @Autowired GameInfoService gameInfoService, + @Autowired GameImageService gameImageService) { this.navigation = navigation; + this.chessComService = chessComService; + this.gameService = gameService; + this.gameInfoService = gameInfoService; + this.gameImageService = gameImageService; + + add(headerLayout); - add(headerLayout, gamesLayout); configureHeaderLayout(); configureGamesLayout(); + configureNoGamesLayout(); + configureEditLayout(); + } + + private void configureNoGamesLayout() { + Label label = new Label("No games for this match in the database."); + Button button = new Button("Add games", new Icon(VaadinIcon.PLUS)); + button.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + button.addClickListener(event -> { + navigation.setEditFlag(true); + remove(noGamesLayout); + }); + noGamesLayout.add(label, button); + noGamesLayout.setAlignItems(FlexComponent.Alignment.CENTER); } private void configureHeaderLayout() { @@ -49,23 +104,112 @@ public class MatchComponent extends Div implements ContentConfigurable { gamesLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER); } + private void configureEditLayout() { + editLayoutWrapper.add(editLayout); + editLayoutWrapper.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER); + + editLayout.setAlignItems(FlexComponent.Alignment.CENTER); + editLayout.setWidth(""); + + editLayout.addClassName("wrapper"); + + editLayout.add(chessComButton); + + for (int i = 0; i < 6; i++) { + TextField textField = new TextField(); + textField.setWidth("23em"); + HorizontalLayout horizontalLayout = new HorizontalLayout(new Label(String.format("Game %d:", i + 1)), textField); + horizontalLayout.setAlignItems(FlexComponent.Alignment.CENTER); + horizontalLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER); + editLayout.add(horizontalLayout); + editTextFields.add(textField); + } + + editSubmitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + + HorizontalLayout buttonLayout = new HorizontalLayout(); + buttonLayout.setAlignItems(FlexComponent.Alignment.CENTER); + buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER); + buttonLayout.add(editSubmitButton, editCancelButton); + editLayout.add(buttonLayout); + } + @Override public void configureContent() { try { Match match = navigation.getSelectedMatch().orElseThrow(); - configureHeaderContent(match); - - gamesLayout.removeAll(); - for (Game game : match.getGames()) { - gamesLayout.add(new GameComponent(game)); - } + configureMainContent(match); } catch (NoSuchElementException e) { gamesLayout.removeAll(); add(navigation.getValidationLabel()); } } + private void configureMainContent(Match match) { + if (navigation.editFlag()) { + remove(gamesLayout); + add(editLayoutWrapper); + configureEditContent(match); + return; + } + remove(editLayoutWrapper); + configureGamesContent(match); + } + + private void configureEditContent(Match match) { + List games = new ArrayList<>(match.getGames()); + + // clear text fields + for (TextField textField : editTextFields) { + textField.setValue(""); + } + // fill text fields; + for (int i = 0; i < Math.min(games.size(), 6); i++) + editTextFields.get(i).setValue(ChessComUtils.getGameURL(games.get(i))); + + if (chessComButtonButtonRegistration != null) chessComButtonButtonRegistration.remove(); + chessComButtonButtonRegistration = chessComButton.addClickListener(event -> { + List gamesBetweenPlayers = chessComService.getLatestGamesBetweenPlayers(match, 6, 2); + for (int i = 0; i < Math.min(gamesBetweenPlayers.size(), 6); i++) { + editTextFields.get(i).setValue(ChessComUtils.getGameURL(gamesBetweenPlayers.get(i))); + } + }); + + if (editSubmitButtonRegistration != null) editSubmitButtonRegistration.remove(); + editSubmitButtonRegistration = editSubmitButton.addClickListener(event -> { + match.getGames().clear(); // TODO: delete old games from database as well + for (TextField textField : editTextFields) { + Optional game = chessComService.getGame(textField.getValue(), match); // TODO: handle this when Optional is empty! + game.ifPresent(value -> match.getGames().add(value)); + } + match.getGames().forEach(game -> gameInfoService.update(game.getGameInfo())); + match.getGames().forEach(gameImageService::createImageIfNotPresent); + + navigation.setEditFlag(false); + }); + + if (editCancelButtonRegistration != null) editCancelButtonRegistration.remove(); + editCancelButtonRegistration = editCancelButton.addClickListener(event -> + UI.getCurrent().navigate(String.format("matchday/%s", navigation.getWildcardParam().replace("edit/", "")))); + } + + private void configureGamesContent(Match match) { + if (match.getGames().isEmpty()) { + remove(gamesLayout); + add(noGamesLayout); + return; + } + + remove(noGamesLayout); + add(gamesLayout); + + gamesLayout.removeAll(); + for (Game game : match.getGames()) { + gamesLayout.add(new GameComponent(game, gameImageService)); + } + } + private void configureHeaderContent(Match match) { CalculatedMatch calculatedMatch = navigation.getMatchService().getCalculatedMatch(match); @@ -76,7 +220,12 @@ public class MatchComponent extends Div implements ContentConfigurable { headerPlayersLayout.removeAll(); headerPlayersLayout.add(player1, vs, player2); - headerResultLabel.setText(String.format("%s", - StringUtils.getResultString("-", calculatedMatch.getScore1(), calculatedMatch.getScore2()))); + if (match.getGames().isEmpty() || navigation.editFlag()) { + headerLayout.remove(headerResultLabel); + } else { + headerLayout.add(headerResultLabel); + headerResultLabel.setText(String.format("%s", + StringUtils.getResultString("-", calculatedMatch.getScore1(), calculatedMatch.getScore2()))); + } } } diff --git a/src/main/java/app/views/match/components/utils/GameComponentUtils.java b/src/main/java/app/views/match/components/utils/GameComponentUtils.java index b91c3f8..0305d42 100644 --- a/src/main/java/app/views/match/components/utils/GameComponentUtils.java +++ b/src/main/java/app/views/match/components/utils/GameComponentUtils.java @@ -5,9 +5,6 @@ import app.data.entity.Player; import app.gameimage.GameImageUtils; import app.utils.ChessComUtils; import app.utils.EntityComponentUtils; -import com.vaadin.flow.component.ClickEvent; -import com.vaadin.flow.component.ComponentEventListener; -import com.vaadin.flow.component.UI; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.html.Div; @@ -19,7 +16,6 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; public class GameComponentUtils { private GameComponentUtils() { @@ -139,7 +135,7 @@ public class GameComponentUtils { button.addClickListener(event -> button.getUI().ifPresent(ui -> - ui.getPage().open(ChessComUtils.getGameLink(game), "_blank"))); + ui.getPage().open(ChessComUtils.getGameURL(game), "_blank"))); return button; }