diff --git a/frontend/app/views/main/main-view.css b/frontend/app/views/main/main-view.css index 2202c01..55afd3a 100644 --- a/frontend/app/views/main/main-view.css +++ b/frontend/app/views/main/main-view.css @@ -1,33 +1,32 @@ - /*/////////*/ /* GENERAL */ /*/////////*/ .content { - background-color: var(--lumo-contrast-10pct); - min-height: 100%; + background-color: var(--lumo-contrast-10pct); + min-height: 100%; } .grid-card { - background-color: var(--lumo-base-color); - border-radius: var(--lumo-border-radius); - box-shadow: var(--lumo-box-shadow-xs); - padding: 0 var(--lumo-space-m) var(--lumo-space-m); + background-color: var(--lumo-base-color); + border-radius: var(--lumo-border-radius); + box-shadow: var(--lumo-box-shadow-xs); + padding: 0 var(--lumo-space-m) var(--lumo-space-m); } .card { - background-color: var(--lumo-base-color); - border-radius: var(--lumo-border-radius); - box-shadow: var(--lumo-box-shadow-xs); - padding: var(--lumo-space-m); + background-color: var(--lumo-base-color); + border-radius: var(--lumo-border-radius); + box-shadow: var(--lumo-box-shadow-xs); + padding: var(--lumo-space-m); } .bold-label { - font-weight: bold; + font-weight: bold; } .nickname-label { - margin-left: var(--lumo-space-xs); + margin-left: var(--lumo-space-xs); } /*//////////////////*/ @@ -35,47 +34,53 @@ /*//////////////////*/ #header { - height: var(--lumo-size-xl); - box-shadow: var(--lumo-box-shadow-s); + height: var(--lumo-size-xl); + box-shadow: var(--lumo-box-shadow-s); } + #header vaadin-avatar { - margin-left: auto; - margin-right: var(--lumo-space-m); + margin-left: auto; + margin-right: var(--lumo-space-m); } + vaadin-app-layout[dir='rtl'] #header vaadin-avatar { - margin-left: var(--lumo-space-m); - margin-right: auto; + margin-left: var(--lumo-space-m); + margin-right: auto; } + #header h1 { - font-size: var(--lumo-font-size-l); - margin: 0; + font-size: var(--lumo-font-size-l); + margin: 0; } + #logo { - box-sizing: border-box; - box-shadow: inset 0 -1px var(--lumo-contrast-10pct); - padding: var(--lumo-space-s) var(--lumo-space-m); + box-sizing: border-box; + box-shadow: inset 0 -1px var(--lumo-contrast-10pct); + padding: var(--lumo-space-s) var(--lumo-space-m); } + #logo img { - height: calc(var(--lumo-size-l) * 1.5); + height: calc(var(--lumo-size-l) * 1.5); } + #logo h1 { - font-size: var(--lumo-font-size-xl); - font-weight: 600; - margin: 0 var(--lumo-space-s); + font-size: var(--lumo-font-size-xl); + font-weight: 600; + margin: 0 var(--lumo-space-s); } vaadin-tab { - font-size: var(--lumo-font-size-s); - height: var(--lumo-size-l); - font-weight: 600; - color: var(--lumo-body-text-color); + font-size: var(--lumo-font-size-s); + height: var(--lumo-size-l); + font-weight: 600; + color: var(--lumo-body-text-color); } vaadin-tab:hover { - background-color: var(--lumo-contrast-5pct); + background-color: var(--lumo-contrast-5pct); } vaadin-tab[selected] { - background-color: var(--lumo-primary-color-10pct); - color: var(--lumo-primary-text-color); + background-color: var(--lumo-primary-color-10pct); + color: var(--lumo-primary-text-color); } diff --git a/src/main/java/app/data/service/ChessComService.java b/src/main/java/app/data/service/ChessComService.java index cd51049..0144b27 100644 --- a/src/main/java/app/data/service/ChessComService.java +++ b/src/main/java/app/data/service/ChessComService.java @@ -8,6 +8,8 @@ import app.data.entity.GameInfo; import app.data.entity.Match; import app.data.entity.Player; import app.utils.ChessComUtils; +import app.utils.MatchUtils; +import app.utils.TimeControl; import app.utils.HttpUtils; import com.google.gson.Gson; import org.springframework.lang.NonNull; @@ -16,6 +18,8 @@ import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; +import static app.utils.TimeControl.*; + @Service public class ChessComService { // TODO: make everything nullsafe @@ -29,8 +33,8 @@ public class ChessComService { } @NonNull - public List getLatestGamesBetweenPlayers(@NonNull Match match, int amountOfGames, int maxAmountOfMonths) { - List games = new ArrayList<>(); + public Map> getLatestGamesBetweenPlayers(@NonNull Match match, int maxAmountOfMonths) { + Map> map = MatchUtils.createEmptyGamesMap(); for (String archiveUrl : getLatestArchiveUrls(match.getPlayer1(), maxAmountOfMonths)) { List chessComGames = getChessComGames(archiveUrl).stream() .sorted(Comparator.comparingLong(ChessComGame::getEndTime).reversed()) @@ -38,19 +42,24 @@ public class ChessComService { .filter(ChessComUtils::hasValidTimeControl) .collect(Collectors.toList()); chessComGames.forEach(chessComGame -> chessComGameCache.put(chessComGame.getUrl(), chessComGame)); - games.addAll(chessComGames.stream() + List games = chessComGames.stream() .map(chessComGame -> getGame(chessComGame, match)) - .collect(Collectors.toList())); - if (games.size() >= amountOfGames) { + .collect(Collectors.toList()); + map.get(TEN_MINUTES).addAll(games.stream().filter(game -> game.getGameInfo().getTimeControl().equals("600")).collect(Collectors.toList())); + map.get(FIVE_MINUTES).addAll(games.stream().filter(game -> game.getGameInfo().getTimeControl().equals("300")).collect(Collectors.toList())); + map.get(THREE_MINUTES).addAll(games.stream().filter(game -> game.getGameInfo().getTimeControl().equals("180")).collect(Collectors.toList())); + if (map.get(TEN_MINUTES).size() >= 2 && map.get(FIVE_MINUTES).size() >= 2 && map.get(THREE_MINUTES).size() >= 2) { break; } } - games.sort(Comparator.comparingLong(game -> game.getGameInfo().getEndTime())); - if (games.size() > amountOfGames) { - games = games.subList(games.size() - amountOfGames, games.size()); + + for (TimeControl key : map.keySet()) { + map.get(key).sort(Comparator.comparingLong(game -> game.getGameInfo().getEndTime())); + int size = map.get(key).size(); + if (size > 2) map.put(key, map.get(key).subList(size - 2, size)); } - return games; - } // TODO: find exactly two games of each time control + return map; + } private List getChessComGames(String archiveUrl) { List list = new ArrayList<>(); diff --git a/src/main/java/app/utils/MatchUtils.java b/src/main/java/app/utils/MatchUtils.java new file mode 100644 index 0000000..b13a49a --- /dev/null +++ b/src/main/java/app/utils/MatchUtils.java @@ -0,0 +1,29 @@ +package app.utils; + +import app.data.entity.Game; +import app.data.entity.Match; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MatchUtils { + public static Map> getGamesMap(Match match) { + Map> map = createEmptyGamesMap(); + + for (Game game : match.getGames()) { + TimeControl timeControl = TimeControl.fromSecondsString(game.getGameInfo().getTimeControl()); + map.get(timeControl).add(game); + } + return map; + } + + public static Map> createEmptyGamesMap() { + Map> map = new HashMap<>(); + for (TimeControl key : TimeControl.values()) { + map.put(key, new ArrayList<>()); + } + return map; + } +} diff --git a/src/main/java/app/utils/TimeControl.java b/src/main/java/app/utils/TimeControl.java new file mode 100644 index 0000000..92c59de --- /dev/null +++ b/src/main/java/app/utils/TimeControl.java @@ -0,0 +1,65 @@ +package app.utils; + +import java.sql.Time; + +public enum TimeControl { + TEN_MINUTES, + FIVE_MINUTES, + THREE_MINUTES; + + public static TimeControl fromSecondsString(String secondsString) { + switch (secondsString) { + case "600": + return TEN_MINUTES; + case "300": + return FIVE_MINUTES; + case "180": + return THREE_MINUTES; + default: + throw new UnsupportedOperationException(String.format("%s cannot be converted to a TimeControl!", secondsString)); + } + } + + public String toSecondsString() { + switch (this) { + case TEN_MINUTES: + return "600"; + case FIVE_MINUTES: + return "300"; + case THREE_MINUTES: + return "180"; + default: + throw new UnsupportedOperationException(String.format("Cannot convert %s to a secondsString!", this.toString())); + } + } + + public static String toSecondsString(TimeControl timeControl) { + return timeControl.toSecondsString(); + } + + public static TimeControl fromPresentationString(String presentationString) { + switch (presentationString) { + case "10 min": + return TEN_MINUTES; + case "5 min": + return FIVE_MINUTES; + case "3 min": + return THREE_MINUTES; + default: + throw new UnsupportedOperationException(String.format("%s cannot be converted to a TimeControl!", presentationString)); + } + } + + public String toPresentationString() { + switch (this) { + case TEN_MINUTES: + return "10 min"; + case FIVE_MINUTES: + return "5 min"; + case THREE_MINUTES: + return "3 min"; + default: + throw new UnsupportedOperationException(String.format("Cannot convert %s to a presentationString!", this.toString())); + } + } +} diff --git a/src/main/java/app/views/match/components/EditMatchCard.java b/src/main/java/app/views/match/components/EditMatchCard.java index 2b3544e..e7f498c 100644 --- a/src/main/java/app/views/match/components/EditMatchCard.java +++ b/src/main/java/app/views/match/components/EditMatchCard.java @@ -7,23 +7,27 @@ import app.data.service.MatchService; import app.gameimage.GameImageService; import app.navigation.Navigation; import app.utils.ChessComUtils; +import app.utils.MatchUtils; +import app.utils.TimeControl; 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.formlayout.FormLayout; +import com.vaadin.flow.component.formlayout.FormLayout.ResponsiveStep; 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.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.TextField; import com.vaadin.flow.shared.Registration; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; + +import static app.utils.TimeControl.*; +import static com.vaadin.flow.component.button.ButtonVariant.LUMO_PRIMARY; +import static com.vaadin.flow.component.formlayout.FormLayout.ResponsiveStep.LabelsPosition.ASIDE; public class EditMatchCard extends VerticalLayout { @@ -32,7 +36,10 @@ public class EditMatchCard extends VerticalLayout { private final ChessComService chessComService; private final GameImageService gameImageService; - private final ArrayList textFields = new ArrayList<>(); + private final VerticalLayout formLayout = new VerticalLayout(); + private final List tenMinuteTextFields = new ArrayList<>(); + private final List fiveMinuteTextFields = new ArrayList<>(); + private final List threeMinuteTextFields = new ArrayList<>(); // TODO: autocorrect "/live/game" to "/game/live/" private final Button submitButton = new Button("Submit", new Icon(VaadinIcon.CHECK)); // TODO: disable when no 6 values in form, and make sure you don't freeze forever when there are wrong entries @@ -40,7 +47,7 @@ public class EditMatchCard extends VerticalLayout { 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 final Button chessComButton = new Button("Autofill with the latest games between the players", new Icon(VaadinIcon.MAGIC)); private Registration chessComButtonButtonRegistration; private Match match; @@ -59,7 +66,7 @@ public class EditMatchCard extends VerticalLayout { //////////// private void defineLayout() { - setAlignItems(FlexComponent.Alignment.CENTER); + setAlignItems(Alignment.CENTER); setWidth(""); addClassName("card"); add(chessComButton); @@ -70,25 +77,48 @@ public class EditMatchCard extends VerticalLayout { } private void defineTextFields() { - for (int i = 0; i < 6; i++) { + add(formLayout); +// formLayout.setSpacing(false); + formLayout.setPadding(false); +// List responsiveSteps = new ArrayList<>(); +// for (int i = 0; i < 6; i++) { +// responsiveSteps.add(new ResponsiveStep("40em", 1, ASIDE)); +// } +// formLayout.setResponsiveSteps(responsiveSteps); + defineTextFields(tenMinuteTextFields, TEN_MINUTES, 1); + defineTextFields(fiveMinuteTextFields, FIVE_MINUTES, 3); + defineTextFields(threeMinuteTextFields, THREE_MINUTES, 5); + } + + private void defineTextFields(List list, TimeControl timeControl, int startGameNumber) { + for (int i = 0; i < 2; 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); - add(horizontalLayout); - textFields.add(textField); + textField.setPlaceholder("Please enter URL"); + Label gameLabel = new Label(String.format("Game %d", startGameNumber + i)); + gameLabel.addClassName("bold-label"); + Label timeControlLabel = new Label(String.format("(%s):", timeControl.toPresentationString())); + timeControlLabel.addClassName("nickname-label"); + HorizontalLayout labelLayout = new HorizontalLayout(gameLabel, timeControlLabel); + labelLayout.setSpacing(false); + labelLayout.setAlignItems(Alignment.CENTER); + labelLayout.setJustifyContentMode(JustifyContentMode.START); + HorizontalLayout row = new HorizontalLayout(labelLayout, textField); + row.setWidthFull(); + row.setJustifyContentMode(JustifyContentMode.BETWEEN); + formLayout.add(row); + list.add(textField); } } private void defineSubmitButton() { - submitButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY); + submitButton.addThemeVariants(LUMO_PRIMARY); } private void addSubmitAndCancelButtons() { HorizontalLayout buttonLayout = new HorizontalLayout(); - buttonLayout.setAlignItems(FlexComponent.Alignment.CENTER); - buttonLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER); + buttonLayout.setAlignItems(Alignment.CENTER); + buttonLayout.setJustifyContentMode(JustifyContentMode.CENTER); buttonLayout.add(submitButton, editCancelButton); add(buttonLayout); } @@ -99,10 +129,9 @@ public class EditMatchCard extends VerticalLayout { void configureContent(Match match) { this.match = match; - List games = new ArrayList<>(match.getGames()); clearTextFields(); - fillTextFieldsWithURLs(games); + fillTextFieldsWithURLs(MatchUtils.getGamesMap(match)); configureChessComButton(); configureEditSubmitButton(); @@ -110,12 +139,20 @@ public class EditMatchCard extends VerticalLayout { } private void clearTextFields() { - textFields.forEach(textField -> textField.setValue("")); + for (List list : Set.of(tenMinuteTextFields, fiveMinuteTextFields, threeMinuteTextFields)) { + list.forEach(textField -> textField.setValue("")); + } } - private void fillTextFieldsWithURLs(List games) { - for (int i = 0; i < Math.min(games.size(), 6); i++) - textFields.get(i).setValue(ChessComUtils.getGameURL(games.get(i))); + private void fillTextFieldsWithURLs(Map> games) { + for (int i = 0; i < 2; i++) { + if (games.get(TEN_MINUTES).size() > i) + tenMinuteTextFields.get(i).setValue(ChessComUtils.getGameURL(games.get(TEN_MINUTES).get(i))); + if (games.get(FIVE_MINUTES).size() > i) + fiveMinuteTextFields.get(i).setValue(ChessComUtils.getGameURL(games.get(FIVE_MINUTES).get(i))); + if (games.get(THREE_MINUTES).size() > i) + threeMinuteTextFields.get(i).setValue(ChessComUtils.getGameURL(games.get(THREE_MINUTES).get(i))); + } } private void configureChessComButton() { @@ -125,7 +162,7 @@ public class EditMatchCard extends VerticalLayout { private ComponentEventListener> createChessComButtonClickListener() { return event -> { - List gamesBetweenPlayers = chessComService.getLatestGamesBetweenPlayers(match, 6, 2); + Map> gamesBetweenPlayers = chessComService.getLatestGamesBetweenPlayers(match, 2); fillTextFieldsWithURLs(gamesBetweenPlayers); }; } @@ -147,7 +184,7 @@ public class EditMatchCard extends VerticalLayout { } private void addGamesToMatchFromFieldValues() { - for (TextField textField : textFields) { + for (TextField textField : tenMinuteTextFields) { Optional game = chessComService.getGame(textField.getValue(), match); // TODO: handle this when Optional is empty! game.ifPresent(value -> match.getGames().add(value)); }