From 92418ba9d66759119a79642e217f06200f37151b Mon Sep 17 00:00:00 2001 From: GAM Date: Thu, 11 Mar 2021 02:45:49 +0100 Subject: [PATCH] match view basic --- db/db_init.sql | 2 +- .../SeasonAndMatchdayNavigation.java | 16 + .../application/data/entity/Match.java | 5 + .../data/service/MatchService.java | 16 +- .../data/service/MatchdayService.java | 14 +- .../data/service/PlayerService.java | 2 +- .../application/navigation/Navigation.java | 422 ++++++++++++++++++ .../navigation/NavigationUtils.java | 39 ++ .../navigation/UrlParameterType.java | 7 + .../SeasonAndMatchdayNavigation.java | 258 ----------- .../application/views/results/MatchView.java | 16 +- .../views/results/MatchdayView.java | 57 +-- .../views/results/ResultsView.java | 23 +- .../application/views/table/TableView.java | 50 +-- 14 files changed, 564 insertions(+), 363 deletions(-) create mode 100644 src/main/java/com/example/application/components/navigation/SeasonAndMatchdayNavigation.java create mode 100644 src/main/java/com/example/application/navigation/Navigation.java create mode 100644 src/main/java/com/example/application/navigation/NavigationUtils.java create mode 100644 src/main/java/com/example/application/navigation/UrlParameterType.java delete mode 100644 src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigation.java diff --git a/db/db_init.sql b/db/db_init.sql index 2644c12..867ead1 100644 --- a/db/db_init.sql +++ b/db/db_init.sql @@ -104,7 +104,7 @@ INSERT INTO "player_info" (url) VALUES ('https://www.chess.com/member/maddinglad INSERT INTO "player" (name, nickname, info) VALUES ('Maddin', 'maddingladi', (SELECT id from player_info WHERE url='https://www.chess.com/member/maddingladi') ); INSERT INTO "player_info" (url) VALUES ('https://www.chess.com/member/magnus-brother'); -INSERT INTO "player" (name, nickname, info) VALUES ('Tariq', 'Magnus-brother', (SELECT id from player_info WHERE url='https://www.chess.com/member/magnus_brother') ); +INSERT INTO "player" (name, nickname, info) VALUES ('Tariq', 'Magnus-brother', (SELECT id from player_info WHERE url='https://www.chess.com/member/magnus-brother') ); INSERT INTO "player_info" (url) VALUES ('https://www.chess.com/member/mama-lolo'); INSERT INTO "player" (name, nickname, info) VALUES ('Malte', 'Mama-Lolo', (SELECT id from player_info WHERE url='https://www.chess.com/member/mama-lolo') ); diff --git a/src/main/java/com/example/application/components/navigation/SeasonAndMatchdayNavigation.java b/src/main/java/com/example/application/components/navigation/SeasonAndMatchdayNavigation.java new file mode 100644 index 0000000..d4e433a --- /dev/null +++ b/src/main/java/com/example/application/components/navigation/SeasonAndMatchdayNavigation.java @@ -0,0 +1,16 @@ +package com.example.application.components.navigation; + +import com.example.application.navigation.Navigation; +import com.vaadin.flow.component.html.Label; +import com.vaadin.flow.component.orderedlayout.FlexComponent; +import com.vaadin.flow.component.orderedlayout.HorizontalLayout; + +public class SeasonAndMatchdayNavigation extends HorizontalLayout { + + public SeasonAndMatchdayNavigation(Navigation navigation) { + setWidthFull(); + setAlignItems(FlexComponent.Alignment.CENTER); + setJustifyContentMode(FlexComponent.JustifyContentMode.END); + add(new Label("Season:"), navigation.getSeasonSelect(), new Label("Matchday:"), navigation.getMatchdaySelect()); + } +} diff --git a/src/main/java/com/example/application/data/entity/Match.java b/src/main/java/com/example/application/data/entity/Match.java index bc7d3a1..21d21f7 100644 --- a/src/main/java/com/example/application/data/entity/Match.java +++ b/src/main/java/com/example/application/data/entity/Match.java @@ -75,4 +75,9 @@ public class Match { public void setMatchday(Matchday matchday) { this.matchday = matchday; } + + @Override + public String toString() { + return String.format("%s-vs-%s", player1.getNickname().toLowerCase(), player2.getNickname().toLowerCase()); + } } diff --git a/src/main/java/com/example/application/data/service/MatchService.java b/src/main/java/com/example/application/data/service/MatchService.java index 7ec5b33..a6d6bb7 100644 --- a/src/main/java/com/example/application/data/service/MatchService.java +++ b/src/main/java/com/example/application/data/service/MatchService.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Service; import org.vaadin.artur.helpers.CrudService; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Service @@ -25,9 +26,22 @@ public class MatchService extends CrudService { return repository; } - public List getCalculatedMatches(Matchday matchday) { + public List getMatches(Matchday matchday) { return repository.findAll().stream() .filter(match -> match.getMatchday().equals(matchday)) + .collect(Collectors.toList()); + } + + public Optional getFirstMatch(Matchday matchday) { + List matches = getMatches(matchday); + if (matches.isEmpty()) { + return Optional.empty(); + } + return Optional.of(matches.get(0)); + } + + public List getCalculatedMatches(Matchday matchday) { + return getMatches(matchday).stream() .map(match -> new CalculatedMatch(match, match.getPlayer1(), match.getPlayer2(), getScore1(match), getScore2(match))) .collect(Collectors.toList()); } diff --git a/src/main/java/com/example/application/data/service/MatchdayService.java b/src/main/java/com/example/application/data/service/MatchdayService.java index 9e1ead7..88e1aff 100644 --- a/src/main/java/com/example/application/data/service/MatchdayService.java +++ b/src/main/java/com/example/application/data/service/MatchdayService.java @@ -27,15 +27,15 @@ public class MatchdayService extends CrudService { } @NonNull - public List getMatchdaysForSeasonSorted(@NonNull Season season) { + public List getMatchdaysSorted(@NonNull Season season) { return repository.findAll().stream() .filter(matchday -> matchday.getSeason().equals(season)) .sorted(Comparator.comparingInt(Matchday::getNumber)) .collect(Collectors.toList()); } - public Optional getFirstMatchdayForSeason(@NonNull Season season) { - List matchdays = getMatchdaysForSeasonSorted(season); + public Optional getFirstMatchday(@NonNull Season season) { + List matchdays = getMatchdaysSorted(season); if (matchdays.isEmpty()) { return Optional.empty(); } @@ -43,7 +43,7 @@ public class MatchdayService extends CrudService { } @NonNull - public List getMatchdaysWithActivityForSeasonSorted(@NonNull Season season) { + public List getMatchdaysWithActivitySorted(@NonNull Season season) { return repository.findAll().stream() .filter(matchday -> matchday.getSeason().equals(season)) .filter(this::hasActivity) @@ -51,10 +51,10 @@ public class MatchdayService extends CrudService { .collect(Collectors.toList()); } - public Optional getLastMatchdayWithActivityForSeason(@NonNull Season season) { - List matchdaysWithActivity = getMatchdaysWithActivityForSeasonSorted(season); + public Optional getLastMatchdayWithActivityOrElseFirstMatchday(@NonNull Season season) { + List matchdaysWithActivity = getMatchdaysWithActivitySorted(season); if (matchdaysWithActivity.isEmpty()) { - return Optional.empty(); + return getFirstMatchday(season); } return Optional.of(matchdaysWithActivity.get(matchdaysWithActivity.size()-1)); } diff --git a/src/main/java/com/example/application/data/service/PlayerService.java b/src/main/java/com/example/application/data/service/PlayerService.java index 2fb8724..8626cbb 100644 --- a/src/main/java/com/example/application/data/service/PlayerService.java +++ b/src/main/java/com/example/application/data/service/PlayerService.java @@ -42,7 +42,7 @@ public class PlayerService extends CrudService { List players = repository.findAll(); public PlayerForTableProvider(Matchday matchday) { - matchdays.addAll(matchdayService.getMatchdaysForSeasonSorted(matchday.getSeason()).stream() + matchdays.addAll(matchdayService.getMatchdaysSorted(matchday.getSeason()).stream() .filter(matchdayToFilter -> matchdayToFilter.getNumber() <= matchday.getNumber()) .collect(Collectors.toList())); diff --git a/src/main/java/com/example/application/navigation/Navigation.java b/src/main/java/com/example/application/navigation/Navigation.java new file mode 100644 index 0000000..caecee8 --- /dev/null +++ b/src/main/java/com/example/application/navigation/Navigation.java @@ -0,0 +1,422 @@ +package com.example.application.navigation; + +import com.example.application.data.entity.Match; +import com.example.application.data.entity.Matchday; +import com.example.application.data.entity.Season; +import com.example.application.data.service.MatchService; +import com.example.application.data.service.MatchdayService; +import com.example.application.data.service.SeasonService; +import com.vaadin.flow.component.*; +import com.vaadin.flow.component.button.Button; +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.select.Select; +import com.vaadin.flow.router.BeforeEvent; +import com.vaadin.flow.router.HasUrlParameter; +import com.vaadin.flow.router.WildcardParameter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class Navigation implements HasUrlParameter { + // TODO: show dropdown menus also for invalid URLs (with content that fits the situation) + + private final String route; + private final boolean onlyMatchdaysWithActivity; + private final List runnablesToBeRunAfterSelection = new ArrayList<>(); + + private final SeasonService seasonService; + private final MatchdayService matchdayService; + private final MatchService matchService; + + private final List seasonList = new ArrayList<>(); + private final List matchdayList = new ArrayList<>(); + private final List matchList = new ArrayList<>(); + + private final Select seasonSelect = new Select<>(); + private final Select matchdaySelect = new Select<>(); + private final Select matchSelect = new Select<>(); + + private String seasonParam; + private String matchdayParam; + private String matchParam; + + private boolean autoselectSeason = false; + private boolean autoselectMatchday = false; + private boolean autoselectMatch = false; + + private final Label invalidUrlLabel = new Label(); + private final Button prevMatchdayButton = new Button(new Icon(VaadinIcon.ARROW_LEFT)); + private final Button nextMatchdayButton = new Button(new Icon(VaadinIcon.ARROW_RIGHT)); + + public Navigation(String route, + @Autowired SeasonService seasonService, + @Autowired MatchdayService matchdayService, + @Autowired MatchService matchService) { + this(route, seasonService, matchdayService, matchService, false); + } + + public Navigation(String route, + @Autowired SeasonService seasonService, + @Autowired MatchdayService matchdayService, + @Autowired MatchService matchService, + boolean onlyMatchdaysWithActivity) { + this.route = route; + this.seasonService = seasonService; + this.matchdayService = matchdayService; + this.matchService = matchService; + this.onlyMatchdaysWithActivity = onlyMatchdaysWithActivity; + + fillSeasonSelectWithData(); + seasonSelect.addValueChangeListener(seasonSelectValueChangeListener()); + matchdaySelect.addValueChangeListener(matchdaySelectValueChangeListener()); + matchSelect.addValueChangeListener(matchSelectValueChangeListener()); + } + + public void setAutoselectSeason(boolean autoselectSeason) { + this.autoselectSeason = autoselectSeason; + } + + public void setAutoselectMatchday(boolean autoselectMatchday) { + this.autoselectMatchday = autoselectMatchday; + } + + public void setAutoselectMatch(boolean autoselectMatch) { + this.autoselectMatch = autoselectMatch; + } + + // TODO: run the runnables after each selection (anyhow), then push history state (UI.getCurrent().getPage().getHistory().pushState(null, "http://host.com/person?action=edit&id=1");). + // Navigate as little as possible. + private void updateUrl() { + String params = NavigationUtils.getWildcardParam(seasonParam, matchdayParam, matchParam); + UI.getCurrent().getPage().getHistory().pushState(null, String.format("%s/%s", route, params)); + } + + private HasValue.ValueChangeListener, Matchday>> matchdaySelectValueChangeListener() { + return matchdayChangeEvent -> { + Matchday matchday = matchdayChangeEvent.getValue(); + if (matchday != null) { + matchParam = matchday.toString(); + updateUrl(); + runnablesToBeRunAfterSelection.forEach(Runnable::run); + } + }; + } + + private HasValue.ValueChangeListener, Season>> seasonSelectValueChangeListener() { + return seasonChangeEvent -> { + Season newSeason = seasonChangeEvent.getValue(); + if (newSeason != null) { + String seasonParam = newSeason.toString(); + String matchdayParam = null; + + Matchday matchdayInNewSeason = null; + Matchday matchdayInOldSeason = matchdaySelect.getValue(); + if (matchdayInOldSeason != null) { + matchdayParam = matchdayInOldSeason.toString(); + matchdayInNewSeason = getMatchdayFromParam(matchdayParam, newSeason); + } + matchdayParam = matchdayInNewSeason == null ? "1" : matchdayParam; + + this.seasonParam = seasonParam; + this.matchdayParam = matchdayParam; + this.matchParam = null; + updateUrl(); + runnablesToBeRunAfterSelection.forEach(Runnable::run); + } + }; + } + + + private HasValue.ValueChangeListener, Match>> matchSelectValueChangeListener() { + return matchChangeEvent -> { + Match match = matchChangeEvent.getValue(); + if (match != null) { + matchParam = match.toString(); + updateUrl(); + runnablesToBeRunAfterSelection.forEach(Runnable::run); // TODO: offer different lists for season, matchday, match + } + }; + } + + private void fillSeasonSelectWithData() { + seasonList.clear(); + seasonList.addAll(seasonService.getAllSeasonsSorted()); + seasonSelect.setItems(seasonList); + } + + private void fillMatchdaySelectWithData(Season season) { + matchdayList.clear(); + List matchdaysToAdd = onlyMatchdaysWithActivity ? + matchdayService.getMatchdaysWithActivitySorted(season) : matchdayService.getMatchdaysSorted(season); + matchdayList.addAll(matchdaysToAdd); + matchdaySelect.setItems(matchdayList); + } + + private void fillMatchSelectWithData(Matchday matchday) { + matchList.clear(); + matchList.addAll(matchService.getMatches(matchday)); + matchSelect.setItems(matchList); + } + + private boolean isMatchDayParamValid(@NonNull String matchdayParam) { + return matchdayList.stream().anyMatch(matchday -> matchdayParam.equals(matchday.toString())); + } + + private void navigate(String seasonParam, String matchdayParam, String matchParam) { // TODO: change this to String... -> see where you need which parameters + UI.getCurrent().navigate(String.format("%s/%s", route, NavigationUtils.getWildcardParam(seasonParam, matchdayParam, matchParam))); + } + + @Override + public void setParameter(BeforeEvent event, @WildcardParameter String param) { + Map map = NavigationUtils.getParameterMap(param); + setParameter(map.get(UrlParameterType.SEASON), map.get(UrlParameterType.MATCHDAY), map.get(UrlParameterType.MATCH)); + } + + private boolean paramInvalid(@Nullable String param) { + return param == null || param.equals(""); + } + + private void noMatchFound(Season season, Matchday matchday) { + invalidUrlLabel.setText(String.format("No Match found in Matchday %s in Season %s!", matchday.toString(), season.toString())); + } + + private void matchFound(Season season, Matchday matchday, Match match) { + matchSelect.setValue(match); + + navigate(season.toString(), matchday.toString(), match.toString()); + } + + private void noMatchdayFound(Season season) { + invalidUrlLabel.setText(String.format("No Matchday found in Season %s!", season.toString())); + } + + private void autoselectMatch(Season season, Matchday matchday) { + Optional firstMatch = matchService.getFirstMatch(matchday); + firstMatch.ifPresentOrElse( + match -> matchFound(season, matchday, match), + () -> noMatchFound(season, matchday)); + } + + private void matchdayFound(Season season, Matchday matchday, String matchParam) { + matchdaySelect.setValue(matchday); + fillMatchSelectWithData(matchday); + + if (paramInvalid(matchParam) && autoselectMatch) { + autoselectMatch(season, matchday); + return; + } + navigate(season.toString(), matchday.toString(), matchParam); + } + + private void noSeasonFound() { + invalidUrlLabel.setText("No Season found!"); + } + + private void autoselectMatchday(Season season, String matchParam) { + Optional latestMatchday = matchdayService.getLastMatchdayWithActivityOrElseFirstMatchday(season); + latestMatchday.ifPresentOrElse( + matchday -> matchdayFound(season, matchday, matchParam), + () -> noMatchdayFound(season)); + } + + private void seasonFound(Season season, String matchdayParam, String matchParam) { + seasonSelect.setValue(season); + fillMatchdaySelectWithData(season); + + if (paramInvalid(matchdayParam) && autoselectMatchday) { + autoselectMatchday(season, matchParam); + return; + } + navigate(season.toString(), matchdayParam, matchParam); + } + + private void autoselectSeason(String matchdayParam, String matchParam) { + Optional latestSeason = seasonService.getLatestSeason(); + latestSeason.ifPresentOrElse( + season -> seasonFound(season, matchdayParam, matchParam), + this::noSeasonFound); + } + + private boolean autoselectIfNecessary(String seasonParam, String matchdayParam, String matchParam) { + if (paramInvalid(seasonParam) && autoselectSeason) { + autoselectSeason(matchdayParam, matchParam); + return true; + } + if (paramInvalid(matchdayParam) && autoselectMatchday) { + autoselectMatchday(seasonSelect.getValue(), matchParam); + return true; + } + if (paramInvalid(matchParam) && autoselectMatch) { + autoselectMatch(seasonSelect.getValue(), matchdaySelect.getValue()); + return true; + } + return false; + } + + public void setParameter(String seasonParam, String matchdayParam, String matchParam) { + if (autoselectIfNecessary(seasonParam, matchdayParam, matchParam)) { + for (Runnable runnable : runnablesToBeRunAfterSelection) { + runnable.run(); + } + return; + } + + Season season = getSeasonFromParam(seasonParam); + if (season != null) { + seasonSelect.setValue(season); + this.seasonParam = seasonParam; + fillMatchdaySelectWithData(season); + } else if (autoselectSeason) { + invalidUrlLabel.setText(String.format("Invalid URL! Season \"%s\" does not exist in the database!", seasonParam)); + return; + } + + Matchday matchday = getMatchdayFromParam(matchdayParam); + if (matchday != null) { + matchdaySelect.setValue(matchday); + this.matchdayParam = matchdayParam; + fillMatchSelectWithData(matchday); + configureButtons(); + } else if (autoselectMatchday) { + String messageExtra = onlyMatchdaysWithActivity ? " or has no games played yet" : ""; + invalidUrlLabel.setText(String.format("Invalid URL! Matchday \"%s\" in Season \"%s\" does not exist in the database%s!", matchdayParam, seasonParam, messageExtra)); + return; + } + + Match match = getMatchFromParam(matchParam); + if (match != null) { + matchSelect.setValue(match); + this.matchParam = matchParam; + } else if (autoselectMatch) { + invalidUrlLabel.setText(String.format("Invalid URL: Match \"%s\" in Matchday \"%s\" in Season \"%s\" does not exist in the database!", matchParam, matchdayParam, seasonParam)); + } + + for (Runnable runnable : runnablesToBeRunAfterSelection) { + runnable.run(); + } + } + + @Nullable + private Season getSeasonFromParam(@Nullable String seasonParam) { + if (seasonParam == null) { + return null; + } + for (Season season : seasonList) { + if (seasonParam.equals(season.toString())) { + return season; + } + } + return null; + } + + @Nullable + private Matchday getMatchdayFromParam(@Nullable String matchdayParam) { + return getMatchdayFromParam(matchdayParam, null); + } + + @Nullable + private Matchday getMatchdayFromParam(@Nullable String matchdayParam, @Nullable Season season) { + if (matchdayParam == null) { + return null; + } + List matchdayList = season == null ? this.matchdayList : matchdayService.getMatchdaysSorted(season); + for (Matchday matchday : matchdayList) { + if (matchdayParam.equals(matchday.toString())) { + return matchday; + } + } + return null; + } + + @Nullable + private Match getMatchFromParam(@Nullable String matchParam) { + if (matchParam == null) { + return null; + } + for (Match match : matchList) { + if (matchParam.equals(match.toString())) { + return match; + } + } + return null; + } + + public void addRunnableToBeRunAfterSelection(Runnable runnable) { + runnablesToBeRunAfterSelection.add(runnable); + } + + private void configureButtons() { + prevMatchdayButton.setEnabled(isMatchDayParamValid(getPrevMatchdayParam())); + prevMatchdayButton.addClickListener(getButtonClickListener(getPrevMatchdayParam())); + + nextMatchdayButton.setEnabled(isMatchDayParamValid(getNextMatchdayParam())); + nextMatchdayButton.addClickListener(getButtonClickListener(getNextMatchdayParam())); + } + + private ComponentEventListener> getButtonClickListener(String matchdayParam) { + return buttonClickEvent -> navigate(seasonParam, matchdayParam, matchParam); + } + + private String getPrevMatchdayParam() { + try { + return String.valueOf(Integer.parseInt(matchdayParam) - 1); + } catch (NumberFormatException e) { + return ""; + } + } + + private String getNextMatchdayParam() { + try { + return String.valueOf(Integer.parseInt(matchdayParam) + 1); + } catch (NumberFormatException e) { + return ""; + } + } + + public Optional getSelectedMatchday() { + return matchdaySelect.getOptionalValue(); + } + + public Optional getSelectedSeason() { + return seasonSelect.getOptionalValue(); + } + + public Optional getSelectedMatch() { + return matchSelect.getOptionalValue(); + } + + public Button getPrevMatchdayButton() { + return prevMatchdayButton; + } + + public Button getNextMatchdayButton() { + return nextMatchdayButton; + } + + public Label getInvalidUrlLabel() { + return invalidUrlLabel; + } + + public Select getSeasonSelect() { + return seasonSelect; + } + + public Select getMatchdaySelect() { + return matchdaySelect; + } + + public Select getMatchSelect() { + return matchSelect; + } + + public void selectMatch(Match match) { + navigate(seasonParam, matchdayParam, match.toString()); + } +} diff --git a/src/main/java/com/example/application/navigation/NavigationUtils.java b/src/main/java/com/example/application/navigation/NavigationUtils.java new file mode 100644 index 0000000..980e081 --- /dev/null +++ b/src/main/java/com/example/application/navigation/NavigationUtils.java @@ -0,0 +1,39 @@ +package com.example.application.navigation; + +import com.vaadin.flow.router.WildcardParameter; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class NavigationUtils { + + public static Map getParameterMap(@WildcardParameter String param) { + Map map = new HashMap<>(); + String[] params = param.split("/"); + switch (params.length) { + case 3: + map.put(UrlParameterType.MATCH, params[2]); + case 2: + map.put(UrlParameterType.MATCHDAY, params[1]); + case 1: + map.put(UrlParameterType.SEASON, params[0]); + default: + break; + } + return map; + } + + @NonNull + public static String getWildcardParam(String... params) { + StringBuilder stringBuilder = new StringBuilder(); + for (String param : params) { + if (param == null || param.equals("")) { + break; + } + stringBuilder.append(param).append("/"); + } + return stringBuilder.toString(); + } +} diff --git a/src/main/java/com/example/application/navigation/UrlParameterType.java b/src/main/java/com/example/application/navigation/UrlParameterType.java new file mode 100644 index 0000000..7c9b347 --- /dev/null +++ b/src/main/java/com/example/application/navigation/UrlParameterType.java @@ -0,0 +1,7 @@ +package com.example.application.navigation; + +public enum UrlParameterType { + SEASON, + MATCHDAY, + MATCH +} diff --git a/src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigation.java b/src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigation.java deleted file mode 100644 index 267df6d..0000000 --- a/src/main/java/com/example/application/views/abstractnavigation/SeasonAndMatchdayNavigation.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.example.application.views.abstractnavigation; - -import com.example.application.data.entity.Matchday; -import com.example.application.data.entity.Season; -import com.example.application.data.service.MatchdayService; -import com.example.application.data.service.SeasonService; -import com.vaadin.flow.component.*; -import com.vaadin.flow.component.button.Button; -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.select.Select; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -public class SeasonAndMatchdayNavigation { - // TODO: show dropdown menus also for invalid URLs (with content that fits the situation) - - private final SeasonService seasonService; - private final MatchdayService matchdayService; - - private final Label invalidUrlLabel = new Label(); - - private final HorizontalLayout selectionLayout = new HorizontalLayout(); - - private final List seasonList = new ArrayList<>(); - private final Select seasonSelect = new Select<>(); - - private final List matchdayList = new ArrayList<>(); - private final Select matchdaySelect = new Select<>(); - private final String route; - private final boolean onlyMatchdaysWithActivity; - private Runnable runnableToBeRunAfterSelection; - private final Button prevButton = new Button(new Icon(VaadinIcon.ARROW_LEFT)); - private final Button nextButton = new Button(new Icon(VaadinIcon.ARROW_RIGHT)); - - private String seasonParam; - private String matchdayParam; - - public SeasonAndMatchdayNavigation(String route, - @Autowired SeasonService seasonService, - @Autowired MatchdayService matchdayService) { - this(route, seasonService, matchdayService, false); - } - - public SeasonAndMatchdayNavigation(String route, - @Autowired SeasonService seasonService, - @Autowired MatchdayService matchdayService, - boolean onlyMatchdaysWithActivity) { - this.route = route; - this.seasonService = seasonService; - this.matchdayService = matchdayService; - this.onlyMatchdaysWithActivity = onlyMatchdaysWithActivity; - - configureSelectionLayout(); - } - - private void configureSelectionLayout() { - selectionLayout.setWidthFull(); - selectionLayout.setAlignItems(FlexComponent.Alignment.CENTER); - selectionLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END); - selectionLayout.add(new Label("Season:"), seasonSelect, new Label("Matchday:"), matchdaySelect); - - seasonSelect.addValueChangeListener(seasonSelectValueChangeListener()); - matchdaySelect.addValueChangeListener(matchdaySelectValueChangeListener()); - - // provide data - fillSeasonSelectWithData(); - } - - private HasValue.ValueChangeListener, Matchday>> matchdaySelectValueChangeListener() { - return matchdayChangeEvent -> { - Season season = seasonSelect.getValue(); - Matchday matchday = matchdayChangeEvent.getValue(); - if (season != null && matchday != null) { - String seasonParam = season.toString(); - String matchdayParam = matchday.toString(); - navigate(seasonParam, matchdayParam); - } - }; - } - - private HasValue.ValueChangeListener, Season>> seasonSelectValueChangeListener() { - return seasonChangeEvent -> { - Season newSeason = seasonChangeEvent.getValue(); - if (newSeason != null) { - String seasonParam = newSeason.toString(); - String matchdayParam = null; - - Matchday matchdayInNewSeason = null; - Matchday matchdayInOldSeason = matchdaySelect.getValue(); - if (matchdayInOldSeason != null) { - matchdayParam = matchdayInOldSeason.toString(); - matchdayInNewSeason = getMatchdayFromParam(matchdayParam, newSeason); - } - matchdayParam = matchdayInNewSeason == null ? "1" : matchdayParam; - - navigate(seasonParam, matchdayParam); - } - }; - } - - private void fillSeasonSelectWithData() { - seasonList.clear(); - seasonList.addAll(seasonService.getAllSeasonsSorted()); - seasonSelect.setItems(seasonList); - } - - private void fillMatchdaySelectWithData() { - matchdayList.clear(); - List matchdaysToAdd = onlyMatchdaysWithActivity ? - matchdayService.getMatchdaysWithActivityForSeasonSorted(seasonSelect.getValue()) : matchdayService.getMatchdaysForSeasonSorted(seasonSelect.getValue()); - matchdayList.addAll(matchdaysToAdd); - matchdaySelect.setItems(matchdayList); - } - - private boolean isMatchDayParamValid(@NonNull String matchdayParam) { - return matchdayList.stream().anyMatch(matchday -> matchdayParam.equals(matchday.toString())); - } - - private void navigate(String seasonParam, String matchdayParam) { - UI.getCurrent().navigate(String.format("%s/%s/%s/", route, seasonParam, matchdayParam)); -// setParameter(seasonParam, matchdayParam); - } - - public void setParameter(String seasonParam, String matchdayParam) { - - // TODO: handle corner case where first matchday has no active games yet: still show first matchday - if (seasonParam == null || seasonParam.equals("") || matchdayParam == null || matchdayParam.equals("")) { - Optional latestSeason = seasonService.getLatestSeason(); - if (latestSeason.isPresent()) { - Optional latestMatchday = matchdayService.getLastMatchdayWithActivityForSeason(latestSeason.get()); - if (latestMatchday.isPresent()) { - seasonParam = latestSeason.get().toString(); - matchdayParam = latestMatchday.get().toString(); - navigate(seasonParam, matchdayParam); - setParameter(seasonParam, matchdayParam); - } - } - return; - } - - Season season = getSeasonFromParam(seasonParam); - if (season == null) { - invalidUrlLabel.setText(String.format("Invalid URL! Season \"%s\" does not exist in the database!", seasonParam)); - return; - } - - seasonSelect.setValue(season); - this.seasonParam = seasonParam; - fillMatchdaySelectWithData(); - - Matchday matchday = getMatchdayFromParam(matchdayParam); - if (matchday == null) { - String messageExtra = onlyMatchdaysWithActivity ? " or has no games played yet" : ""; - invalidUrlLabel.setText(String.format("Invalid URL! Matchday \"%s\" in Season \"%s\" does not exist in the database%s!", matchdayParam, seasonParam, messageExtra)); - return; - } - - matchdaySelect.setValue(matchday); - this.matchdayParam = matchdayParam; - configureButtons(); - - if ((runnableToBeRunAfterSelection) != null) { - runnableToBeRunAfterSelection.run(); - } - } - - @Nullable - private Season getSeasonFromParam(@NonNull String seasonParam) { - for (Season season : seasonList) { - if (seasonParam.equals(season.toString())) { - return season; - } - } - return null; - } - - @Nullable - private Matchday getMatchdayFromParam(@NonNull String matchdayParam) { - return getMatchdayFromParam(matchdayParam, null); - } - - @Nullable - private Matchday getMatchdayFromParam(@NonNull String matchdayParam, @Nullable Season season) { - List matchdayList = season == null ? this.matchdayList : matchdayService.getMatchdaysForSeasonSorted(season); - for (Matchday matchday : matchdayList) { - if (matchdayParam.equals(matchday.toString())) { - return matchday; - } - } - return null; - } - - public void setRunnableToBeRunAfterSelection(Runnable runnableToBeRunAfterSelection) { - this.runnableToBeRunAfterSelection = runnableToBeRunAfterSelection; - } - - - private void configureButtons() { - prevButton.setEnabled(isMatchDayParamValid(getPrevMatchdayParam())); - prevButton.addClickListener(getButtonClickListener(getPrevMatchdayParam())); - - nextButton.setEnabled(isMatchDayParamValid(getNextMatchdayParam())); - nextButton.addClickListener(getButtonClickListener(getNextMatchdayParam())); - } - - private ComponentEventListener> getButtonClickListener(String matchdayParam) { - return buttonClickEvent -> navigate(seasonParam, matchdayParam); - } - - private String getPrevMatchdayParam() { - try { - return String.valueOf(Integer.parseInt(matchdayParam) - 1); - } catch (NumberFormatException e) { - return ""; - } - } - - private String getNextMatchdayParam() { - try { - return String.valueOf(Integer.parseInt(matchdayParam) + 1); - } catch (NumberFormatException e) { - return ""; - } - } - - public Optional getSelectedMatchday() { - return matchdaySelect.getOptionalValue(); - } - - public Optional getSelectedSeason() { - return seasonSelect.getOptionalValue(); - } - - public HorizontalLayout getSelectionLayout() { - return selectionLayout; - } - - public Button getPrevButton() { - return prevButton; - } - - public Button getNextButton() { - return nextButton; - } - - public Label getInvalidUrlLabel() { - return invalidUrlLabel; - } -} diff --git a/src/main/java/com/example/application/views/results/MatchView.java b/src/main/java/com/example/application/views/results/MatchView.java index 38418eb..03d41f8 100644 --- a/src/main/java/com/example/application/views/results/MatchView.java +++ b/src/main/java/com/example/application/views/results/MatchView.java @@ -1,22 +1,26 @@ package com.example.application.views.results; +import com.example.application.navigation.Navigation; import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Label; import com.vaadin.flow.router.BeforeEvent; import com.vaadin.flow.router.HasUrlParameter; -public class MatchView extends Div implements HasUrlParameter { +public class MatchView extends Div { + + private final Navigation navigation; + private final Label label = new Label(); // private final ChessComService chessComService; // private final PlayerService playerService; // public ResultsViewRight(@Autowired ChessComService chessComService, @Autowired PlayerService playerService) { - public MatchView() { + public MatchView(Navigation navigation) { + this.navigation = navigation; + navigation.addRunnableToBeRunAfterSelection(()-> label.setText(navigation.getSelectedMatch().toString())); + add(label); // this.chessComService = chessComService; // this.playerService = playerService; } - @Override - public void setParameter(BeforeEvent beforeEvent, String[] SeasonParamAndMatchdayParamAndGameParam) { - - } } diff --git a/src/main/java/com/example/application/views/results/MatchdayView.java b/src/main/java/com/example/application/views/results/MatchdayView.java index 77091a6..b6d8c8c 100644 --- a/src/main/java/com/example/application/views/results/MatchdayView.java +++ b/src/main/java/com/example/application/views/results/MatchdayView.java @@ -1,13 +1,13 @@ package com.example.application.views.results; +import com.example.application.components.navigation.SeasonAndMatchdayNavigation; import com.example.application.data.bean.CalculatedMatch; +import com.example.application.data.entity.Match; import com.example.application.data.entity.Matchday; import com.example.application.data.service.MatchService; -import com.example.application.data.service.MatchdayService; -import com.example.application.data.service.SeasonService; +import com.example.application.navigation.Navigation; import com.example.application.utils.StringUtils; import com.example.application.utils.VaadinUtils; -import com.example.application.views.abstractnavigation.SeasonAndMatchdayNavigation; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.grid.ColumnTextAlign; import com.vaadin.flow.component.grid.Grid; @@ -16,19 +16,18 @@ 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 com.vaadin.flow.data.selection.SelectionEvent; +import com.vaadin.flow.data.selection.SelectionListener; import com.vaadin.flow.function.ValueProvider; -import com.vaadin.flow.router.BeforeEvent; -import com.vaadin.flow.router.HasUrlParameter; -import com.vaadin.flow.router.WildcardParameter; import org.springframework.beans.factory.annotation.Autowired; import java.util.Optional; -public class MatchdayView extends VerticalLayout implements HasUrlParameter { +public class MatchdayView extends VerticalLayout { private final MatchService matchService; - private final SeasonAndMatchdayNavigation seasonAndMatchdayNavigation; + private final Navigation navigation; private final VerticalLayout matchdayLayout = new VerticalLayout(); private final Label matchdayHeader = new Label(); @@ -37,13 +36,13 @@ public class MatchdayView extends VerticalLayout implements HasUrlParameter, CalculatedMatch>) selectionEvent -> { + Optional calculatedMatch = selectionEvent.getFirstSelectedItem(); + calculatedMatch.ifPresent(match -> navigation.selectMatch(match.getMatch())); + }); } private String getResultString(CalculatedMatch match) { @@ -109,32 +112,10 @@ public class MatchdayView extends VerticalLayout implements HasUrlParameter selectedMatchday = seasonAndMatchdayNavigation.getSelectedMatchday(); + Optional selectedMatchday = navigation.getSelectedMatchday(); selectedMatchday.ifPresent(matchday -> { matchdayHeader.setText(String.format("Matchday %s", matchday.toString())); grid.setItems(matchService.getCalculatedMatches(matchday)); }); } - - - @Override - public void setParameter(BeforeEvent event, @WildcardParameter String param) { - String[] params = param.split("/"); - String seasonParam; - String matchdayParam; - switch (params.length) { - case 0: - seasonParam = ""; - matchdayParam = ""; - break; - case 1: - seasonParam = params[0]; - matchdayParam = ""; - break; - default: - seasonParam = params[0]; - matchdayParam = params[1]; - } - seasonAndMatchdayNavigation.setParameter(seasonParam, matchdayParam); - } } diff --git a/src/main/java/com/example/application/views/results/ResultsView.java b/src/main/java/com/example/application/views/results/ResultsView.java index bf3c8fb..fd4936a 100644 --- a/src/main/java/com/example/application/views/results/ResultsView.java +++ b/src/main/java/com/example/application/views/results/ResultsView.java @@ -1,6 +1,7 @@ package com.example.application.views.results; import com.example.application.data.service.*; +import com.example.application.navigation.Navigation; import com.example.application.views.main.MainView; import com.vaadin.flow.component.dependency.CssImport; import com.vaadin.flow.component.html.Div; @@ -15,6 +16,8 @@ public class ResultsView extends Div implements HasUrlParameter { private final SplitLayout splitLayout = new SplitLayout(); + private final Navigation navigation; + private final MatchdayView matchdayView; private final MatchView matchView; @@ -24,8 +27,11 @@ public class ResultsView extends Div implements HasUrlParameter { @Autowired ChessComService chessComService, @Autowired PlayerService playerService) { - this.matchdayView = new MatchdayView(seasonService, matchdayService, matchService); - this.matchView = new MatchView(); + this.navigation = new Navigation("results", seasonService, matchdayService, matchService); + this.navigation.setAutoselectSeason(true); + this.navigation.setAutoselectMatchday(true); + this.matchdayView = new MatchdayView(navigation, matchService); + this.matchView = new MatchView(navigation); addClassName("results-view"); @@ -56,17 +62,6 @@ public class ResultsView extends Div implements HasUrlParameter { @Override public void setParameter(BeforeEvent beforeEvent, @WildcardParameter String param) { -// String[] params = param.split("/"); -// if (params.length < 2) { -// // TODO: handle this case! -// return; -// } -// String[] seasonParamAndMatchdayParam = Arrays.copyOfRange(params, 0, 2); - matchdayView.setParameter(beforeEvent, param); - -// if (params.length < 3) { -// disableSplit(); -// } -// matchView.setParameter(beforeEvent, params); + navigation.setParameter(beforeEvent, param); } } diff --git a/src/main/java/com/example/application/views/table/TableView.java b/src/main/java/com/example/application/views/table/TableView.java index a40fb28..baf1313 100644 --- a/src/main/java/com/example/application/views/table/TableView.java +++ b/src/main/java/com/example/application/views/table/TableView.java @@ -1,12 +1,14 @@ package com.example.application.views.table; +import com.example.application.components.navigation.SeasonAndMatchdayNavigation; import com.example.application.data.bean.PlayerForTable; +import com.example.application.data.service.MatchService; import com.example.application.data.service.MatchdayService; import com.example.application.data.service.PlayerService; import com.example.application.data.service.SeasonService; import com.example.application.utils.StringUtils; import com.example.application.utils.VaadinUtils; -import com.example.application.views.abstractnavigation.SeasonAndMatchdayNavigation; +import com.example.application.navigation.Navigation; import com.example.application.views.main.MainView; import com.vaadin.flow.component.dependency.CssImport; import com.vaadin.flow.component.grid.ColumnTextAlign; @@ -20,7 +22,6 @@ import com.vaadin.flow.function.ValueProvider; import com.vaadin.flow.router.*; import org.springframework.beans.factory.annotation.Autowired; -import java.util.Arrays; import java.util.NoSuchElementException; @CssImport("./views/table/table-view.css") @@ -31,23 +32,22 @@ public class TableView extends VerticalLayout implements HasUrlParameter private final PlayerService playerService; - private final SeasonAndMatchdayNavigation seasonAndMatchdayNavigation; + private final Navigation navigation; private final Label invalidUrlLabel; private final Grid grid = new Grid<>(); - public TableView(@Autowired SeasonService seasonService, @Autowired MatchdayService matchdayService, @Autowired PlayerService playerService) { + public TableView(@Autowired SeasonService seasonService, @Autowired MatchdayService matchdayService, @Autowired MatchService matchService, @Autowired PlayerService playerService) { this.playerService = playerService; - this.seasonAndMatchdayNavigation = new SeasonAndMatchdayNavigation("table", seasonService, matchdayService, true); - this.seasonAndMatchdayNavigation.setRunnableToBeRunAfterSelection(this::configureContent); - this.invalidUrlLabel = seasonAndMatchdayNavigation.getInvalidUrlLabel(); + this.navigation = new Navigation("table", seasonService, matchdayService, matchService, true); + this.navigation.setAutoselectSeason(true); + this.navigation.setAutoselectMatchday(true); + this.navigation.addRunnableToBeRunAfterSelection(this::configureContent); + this.invalidUrlLabel = navigation.getInvalidUrlLabel(); addClassName("table-view"); configureLayout(); - -// seasonAndMatchdayNavigation.addRunnableToBeRunAfterSeasonParamChange(this::configureContent); -// seasonAndMatchdayNavigation.addRunnableToBeRunAfterMatchdayParamChange(this::configureContent); } //////////// @@ -59,7 +59,7 @@ public class TableView extends VerticalLayout implements HasUrlParameter setHeightFull(); setAlignItems(FlexComponent.Alignment.CENTER); - add(seasonAndMatchdayNavigation.getSelectionLayout(), new HorizontalLayout(grid)); + add(new SeasonAndMatchdayNavigation(navigation), new HorizontalLayout(grid)); configureGrid(); } @@ -151,7 +151,7 @@ public class TableView extends VerticalLayout implements HasUrlParameter protected void configureContent() { try { - grid.setItems(playerService.getPlayersForTable(seasonAndMatchdayNavigation.getSelectedMatchday().orElseThrow())); + grid.setItems(playerService.getPlayersForTable(navigation.getSelectedMatchday().orElseThrow())); } catch (NoSuchElementException e) { invalidUrlLabel.setText("No season and/or matchday selected! Please select them above."); } @@ -159,30 +159,6 @@ public class TableView extends VerticalLayout implements HasUrlParameter @Override public void setParameter(BeforeEvent event, @WildcardParameter String param) { - String[] params = param.split("/"); - String seasonParam; - String matchdayParam; - switch (params.length) { - case 0: - seasonParam = ""; - matchdayParam = ""; - break; - case 1: - seasonParam = params[0]; - matchdayParam = ""; - break; - default: - seasonParam = params[0]; - matchdayParam = params[1]; - } - seasonAndMatchdayNavigation.setParameter(seasonParam, matchdayParam); - } - - private String[] getSeasonParamAndMatchdayParam(String[] params) { - if (params.length < 2) { - String seasonParam = params.length == 1 ? params[0] : ""; - params = new String[]{seasonParam, ""}; - } - return Arrays.copyOfRange(params, 0, 2); + navigation.setParameter(event, param); } }