| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
c941c19411 | Layout | 4 years ago |
|
|
3fc033ac7c | clean up | 4 years ago |
|
|
a0438e36c2 | NICE new structure with generic Navigation | 4 years ago |
|
|
eefdc45633 | WIP further restructuring | 4 years ago |
|
|
f703100ee2 | PlayerNavigation | 4 years ago |
| @ -1,24 +1,16 @@ | |||||
| This is free and unencumbered software released into the public domain. | This is free and unencumbered software released into the public domain. | ||||
| Anyone is free to copy, modify, publish, use, compile, sell, or | |||||
| distribute this software, either in source code form or as a compiled | |||||
| binary, for any purpose, commercial or non-commercial, and by any | |||||
| means. | |||||
| Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or | |||||
| as a compiled binary, for any purpose, commercial or non-commercial, and by any means. | |||||
| In jurisdictions that recognize copyright laws, the author or authors | |||||
| of this software dedicate any and all copyright interest in the | |||||
| software to the public domain. We make this dedication for the benefit | |||||
| of the public at large and to the detriment of our heirs and | |||||
| successors. We intend this dedication to be an overt act of | |||||
| relinquishment in perpetuity of all present and future rights to this | |||||
| software under copyright law. | |||||
| In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright | |||||
| interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the | |||||
| detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of | |||||
| all present and future rights to this software under copyright law. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |||||
| IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |||||
| OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |||||
| ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |||||
| OTHER DEALINGS IN THE SOFTWARE. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | |||||
| WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE | |||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | |||||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||||
| For more information, please refer to <http://unlicense.org> | For more information, please refer to <http://unlicense.org> | ||||
| @ -1,8 +1,8 @@ | |||||
| ALTER TABLE "game_info" | ALTER TABLE "game_info" | ||||
| ADD COLUMN "pgn" varchar, | |||||
| ADD COLUMN "rated" boolean, | |||||
| ADD COLUMN "time_class" varchar, | |||||
| ADD COLUMN "rules" varchar, | |||||
| ADD COLUMN "pgn" varchar, | |||||
| ADD COLUMN "rated" boolean, | |||||
| ADD COLUMN "time_class" varchar, | |||||
| ADD COLUMN "rules" varchar, | |||||
| ADD COLUMN "white_rating" int, | ADD COLUMN "white_rating" int, | ||||
| ADD COLUMN "black_rating" int, | ADD COLUMN "black_rating" int, | ||||
| ADD COLUMN "white_result" varchar, | ADD COLUMN "white_result" varchar, | ||||
| @ -1,5 +1,5 @@ | |||||
| ALTER TABLE "season" | ALTER TABLE "season" | ||||
| ADD COLUMN "week_of_first_matchday" int not null default 9 CHECK ( week_of_first_matchday > 0 AND week_of_first_matchday <= 53 ), | |||||
| ADD COLUMN "week_of_first_matchday" int not null default 9 CHECK ( week_of_first_matchday > 0 AND week_of_first_matchday <= 53 ), | |||||
| ADD COLUMN "first_weekday_of_matchday" int not null default 2 CHECK ( first_weekday_of_matchday > 0 AND first_weekday_of_matchday <= 7 ), | ADD COLUMN "first_weekday_of_matchday" int not null default 2 CHECK ( first_weekday_of_matchday > 0 AND first_weekday_of_matchday <= 7 ), | ||||
| ADD COLUMN "last_weekday_of_matchday" int not null default 1 CHECK ( last_weekday_of_matchday > 0 AND last_weekday_of_matchday <= 7 ), | |||||
| ADD COLUMN "weekday_of_view_change" int not null default 4 CHECK ( weekday_of_view_change > 0 AND weekday_of_view_change <= 7 ); | |||||
| ADD COLUMN "last_weekday_of_matchday" int not null default 1 CHECK ( last_weekday_of_matchday > 0 AND last_weekday_of_matchday <= 7 ), | |||||
| ADD COLUMN "weekday_of_view_change" int not null default 4 CHECK ( weekday_of_view_change > 0 AND weekday_of_view_change <= 7 ); | |||||
| @ -1,3 +1,3 @@ | |||||
| .about-view { | .about-view { | ||||
| display: block; | |||||
| display: block; | |||||
| } | } | ||||
| @ -1,10 +1,11 @@ | |||||
| .address-form-view { | .address-form-view { | ||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| } | } | ||||
| .address-form-view .button-layout { | .address-form-view .button-layout { | ||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| } | } | ||||
| @ -1,70 +1,70 @@ | |||||
| .card-list-view { | .card-list-view { | ||||
| display: block; | |||||
| height: 100%; | |||||
| display: block; | |||||
| height: 100%; | |||||
| } | } | ||||
| .card-list-view vaadin-grid { | .card-list-view vaadin-grid { | ||||
| height: 100%; | |||||
| line-height: var(--lumo-line-height-m); | |||||
| height: 100%; | |||||
| line-height: var(--lumo-line-height-m); | |||||
| } | } | ||||
| .card-list-view vaadin-grid, | .card-list-view vaadin-grid, | ||||
| .card-list-view vaadin-grid-cell-content { | .card-list-view vaadin-grid-cell-content { | ||||
| background-color: var(--lumo-contrast-10pct); | |||||
| background-color: var(--lumo-contrast-10pct); | |||||
| } | } | ||||
| .card-list-view .card { | .card-list-view .card { | ||||
| background-color: var(--lumo-base-color); | |||||
| border-radius: var(--lumo-border-radius); | |||||
| box-shadow: var(--lumo-box-shadow-xs); | |||||
| padding: calc(var(--lumo-space-s) * 1.5) var(--lumo-space-m); | |||||
| background-color: var(--lumo-base-color); | |||||
| border-radius: var(--lumo-border-radius); | |||||
| box-shadow: var(--lumo-box-shadow-xs); | |||||
| padding: calc(var(--lumo-space-s) * 1.5) var(--lumo-space-m); | |||||
| } | } | ||||
| .card-list-view img { | .card-list-view img { | ||||
| border-radius: 50%; | |||||
| flex-shrink: 0; | |||||
| height: var(--lumo-size-m); | |||||
| margin-right: calc(var(--lumo-space-s) * 1.5); | |||||
| width: var(--lumo-size-m); | |||||
| border-radius: 50%; | |||||
| flex-shrink: 0; | |||||
| height: var(--lumo-size-m); | |||||
| margin-right: calc(var(--lumo-space-s) * 1.5); | |||||
| width: var(--lumo-size-m); | |||||
| } | } | ||||
| .card-list-view .header { | .card-list-view .header { | ||||
| align-items: baseline; | |||||
| align-items: baseline; | |||||
| } | } | ||||
| .card-list-view .name { | .card-list-view .name { | ||||
| font-size: var(--lumo-font-size-s); | |||||
| font-weight: bold; | |||||
| margin-right: var(--lumo-space-s); | |||||
| font-size: var(--lumo-font-size-s); | |||||
| font-weight: bold; | |||||
| margin-right: var(--lumo-space-s); | |||||
| } | } | ||||
| .card-list-view .date { | .card-list-view .date { | ||||
| color: var(--lumo-tertiary-text-color); | |||||
| font-size: var(--lumo-font-size-xs); | |||||
| color: var(--lumo-tertiary-text-color); | |||||
| font-size: var(--lumo-font-size-xs); | |||||
| } | } | ||||
| .card-list-view .post { | .card-list-view .post { | ||||
| color: var(--lumo-secondary-text-color); | |||||
| font-size: var(--lumo-font-size-s); | |||||
| margin-bottom: var(--lumo-space-s); | |||||
| white-space: normal; | |||||
| color: var(--lumo-secondary-text-color); | |||||
| font-size: var(--lumo-font-size-s); | |||||
| margin-bottom: var(--lumo-space-s); | |||||
| white-space: normal; | |||||
| } | } | ||||
| .card-list-view .actions { | .card-list-view .actions { | ||||
| align-items: center; | |||||
| align-items: center; | |||||
| } | } | ||||
| .card-list-view iron-icon { | .card-list-view iron-icon { | ||||
| color: var(--lumo-tertiary-text-color); | |||||
| height: calc(var(--lumo-icon-size-s) * 0.8); | |||||
| margin-right: var(--lumo-space-s); | |||||
| width: calc(var(--lumo-icon-size-s) * 0.8); | |||||
| color: var(--lumo-tertiary-text-color); | |||||
| height: calc(var(--lumo-icon-size-s) * 0.8); | |||||
| margin-right: var(--lumo-space-s); | |||||
| width: calc(var(--lumo-icon-size-s) * 0.8); | |||||
| } | } | ||||
| .card-list-view .likes, | .card-list-view .likes, | ||||
| .card-list-view .comments, | .card-list-view .comments, | ||||
| .card-list-view .shares { | .card-list-view .shares { | ||||
| color: var(--lumo-tertiary-text-color); | |||||
| font-size: var(--lumo-font-size-xs); | |||||
| margin-right: var(--lumo-space-l); | |||||
| color: var(--lumo-tertiary-text-color); | |||||
| font-size: var(--lumo-font-size-xs); | |||||
| margin-right: var(--lumo-space-l); | |||||
| } | } | ||||
| @ -1,10 +1,11 @@ | |||||
| .credit-card-form-view { | .credit-card-form-view { | ||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| } | } | ||||
| .credit-card-form-view .button-layout { | .credit-card-form-view .button-layout { | ||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| } | } | ||||
| @ -1,4 +1,4 @@ | |||||
| .hello-world-view { | .hello-world-view { | ||||
| display: block; | |||||
| padding: 1em; | |||||
| display: block; | |||||
| padding: 1em; | |||||
| } | } | ||||
| @ -1,8 +1,8 @@ | |||||
| .map-view { | .map-view { | ||||
| display: flex; | |||||
| height: 100%; | |||||
| display: flex; | |||||
| height: 100%; | |||||
| } | } | ||||
| .map-view .map { | .map-view .map { | ||||
| flex: 1; | |||||
| flex: 1; | |||||
| } | } | ||||
| @ -1,33 +1,33 @@ | |||||
| .master-detail-view { | .master-detail-view { | ||||
| display: flex; | |||||
| height: 100%; | |||||
| flex-direction: column; | |||||
| display: flex; | |||||
| height: 100%; | |||||
| flex-direction: column; | |||||
| } | } | ||||
| .master-detail-view .grid-wrapper { | .master-detail-view .grid-wrapper { | ||||
| flex-grow: 1; | |||||
| width: 100%; | |||||
| flex-grow: 1; | |||||
| width: 100%; | |||||
| } | } | ||||
| .master-detail-view .full-size { | .master-detail-view .full-size { | ||||
| width: 100%; | |||||
| height: 100%; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | } | ||||
| .master-detail-view #editor-layout { | .master-detail-view #editor-layout { | ||||
| width: 400px; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| width: 400px; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| } | } | ||||
| .master-detail-view #editor { | .master-detail-view #editor { | ||||
| padding: var(--lumo-space-l); | |||||
| flex-grow: 1; | |||||
| padding: var(--lumo-space-l); | |||||
| flex-grow: 1; | |||||
| } | } | ||||
| .master-detail-view #button-layout { | .master-detail-view #button-layout { | ||||
| width: 100%; | |||||
| flex-wrap: wrap; | |||||
| background-color: var(--lumo-contrast-5pct); | |||||
| padding: var(--lumo-space-s) var(--lumo-space-l); | |||||
| width: 100%; | |||||
| flex-wrap: wrap; | |||||
| background-color: var(--lumo-contrast-5pct); | |||||
| padding: var(--lumo-space-s) var(--lumo-space-l); | |||||
| } | } | ||||
| @ -1,10 +1,11 @@ | |||||
| .person-form-view { | .person-form-view { | ||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| } | } | ||||
| .person-form-view .button-layout { | .person-form-view .button-layout { | ||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| } | } | ||||
| @ -1,11 +1,11 @@ | |||||
| .matchday-view .column_header { | |||||
| font-weight: bold; | |||||
| font-size: large; | |||||
| .matchday-view .column-header { | |||||
| font-weight: bold; | |||||
| font-size: large; | |||||
| } | } | ||||
| .matchday-view .matchday-header-label-layout { | .matchday-view .matchday-header-label-layout { | ||||
| width: 100%; | |||||
| justify-content: center; | |||||
| font-size: x-large; | |||||
| width: 100%; | |||||
| justify-content: center; | |||||
| font-size: x-large; | |||||
| } | } | ||||
| @ -0,0 +1,10 @@ | |||||
| .player-view .player-header-label-layout { | |||||
| width: 100%; | |||||
| justify-content: center; | |||||
| font-size: x-large; | |||||
| } | |||||
| .player-view .column-header { | |||||
| font-weight: bold; | |||||
| font-size: large; | |||||
| } | |||||
| @ -1,10 +1,10 @@ | |||||
| .table-view .important-table-column-header { | .table-view .important-table-column-header { | ||||
| font-weight: bold; | |||||
| font-size: large; | |||||
| font-weight: bold; | |||||
| font-size: large; | |||||
| } | } | ||||
| .table-view .table-header-label-layout { | .table-view .table-header-label-layout { | ||||
| width: 100%; | |||||
| justify-content: center; | |||||
| font-size: x-large; | |||||
| width: 100%; | |||||
| justify-content: center; | |||||
| font-size: x-large; | |||||
| } | } | ||||
| @ -0,0 +1,191 @@ | |||||
| package app.data.service; | |||||
| import app.data.bean.CalculatedMatch; | |||||
| import app.data.bean.PlayerForTable; | |||||
| import app.data.entity.Matchday; | |||||
| import app.data.entity.Player; | |||||
| import java.util.*; | |||||
| import java.util.function.Function; | |||||
| import java.util.stream.Collectors; | |||||
| import java.util.stream.Stream; | |||||
| class PlayerForTableProvider { | |||||
| private final PlayerService playerService; | |||||
| private final MatchdayService matchdayService; | |||||
| private final List<Matchday> matchdays = new ArrayList<>(); | |||||
| private final List<CalculatedMatch> calculatedMatches = new ArrayList<>(); | |||||
| private final List<Player> players; | |||||
| public PlayerForTableProvider(PlayerService playerService, MatchdayService matchdayService, Matchday matchday) { | |||||
| this.playerService = playerService; | |||||
| this.matchdayService = matchdayService; | |||||
| this.players = playerService.getRepository().findAll(); | |||||
| matchdays.addAll(matchdayService.getMatchdaysSorted(matchday.getSeason()).stream() | |||||
| .filter(matchdayToFilter -> matchdayToFilter.getNumber() <= matchday.getNumber()) | |||||
| .collect(Collectors.toList())); | |||||
| calculatedMatches.addAll(matchdays.stream() | |||||
| .flatMap((Function<Matchday, Stream<CalculatedMatch>>) matchdayToMap -> matchdayService.getCalculatedMatches(matchdayToMap).stream()) | |||||
| .collect(Collectors.toList())); | |||||
| } | |||||
| List<PlayerForTable> getPlayersForTableSorted(boolean calcDiffToLastMatchday) { | |||||
| List<PlayerForTable> playerForTableList = calcSortedPlayerForTableList(); | |||||
| int offset = 0; | |||||
| PlayerForTable currentPlayer; | |||||
| for (int i = 0; i < playerForTableList.size(); i++) { | |||||
| currentPlayer = playerForTableList.get(i); | |||||
| offset = getOffset(playerForTableList, offset, currentPlayer, i); | |||||
| currentPlayer.setPlace(i + 1 - offset); | |||||
| currentPlayer.setPlaceString(offset == 0 ? String.valueOf(i + 1) : ""); | |||||
| } | |||||
| if (calcDiffToLastMatchday) calcAndSetPlaceDiffToLastMatchday(playerForTableList); | |||||
| return playerForTableList; | |||||
| } | |||||
| private int getOffset(List<PlayerForTable> playerForTableList, int offset, PlayerForTable currentPlayer, int i) { | |||||
| PlayerForTable lastPlayer; | |||||
| if (i > 0) { | |||||
| lastPlayer = playerForTableList.get(i - 1); | |||||
| // TODO: add direct comparison below | |||||
| if (playersHaveCompletelyEqualScores(lastPlayer, currentPlayer)) | |||||
| return offset + 1; | |||||
| return 0; | |||||
| } | |||||
| return offset; | |||||
| } | |||||
| private boolean playersHaveCompletelyEqualScores(PlayerForTable lastPlayer, PlayerForTable currentPlayer) { | |||||
| return Objects.equals(currentPlayer.getMatchPoints(), lastPlayer.getMatchPoints()) | |||||
| && Objects.equals(currentPlayer.getGamePointsForSelf(), lastPlayer.getGamePointsForSelf()) | |||||
| && Objects.equals(currentPlayer.getGamePointsForOpponents(), lastPlayer.getGamePointsForOpponents()); | |||||
| } | |||||
| private List<PlayerForTable> calcSortedPlayerForTableList() { | |||||
| return players.stream() | |||||
| .map(this::getPlayerForTable) | |||||
| .sorted(Comparator.comparingDouble(PlayerForTable::getGamePointsForSelf).reversed()) | |||||
| .sorted(Comparator.comparingDouble(PlayerForTable::getGamePointDiff).reversed()) | |||||
| .sorted(Comparator.comparingInt(PlayerForTable::getMatchPoints).reversed()) | |||||
| .collect(Collectors.toList()); | |||||
| } | |||||
| private void calcAndSetPlaceDiffToLastMatchday(List<PlayerForTable> playerForTableList) { | |||||
| if (matchdays.size() < 2) { | |||||
| playerForTableList.forEach(playerForTable -> playerForTable.setPlaceDiffToLastMatchday(Integer.MAX_VALUE)); | |||||
| return; | |||||
| } | |||||
| List<PlayerForTable> listForLastMatchday = getListForLastMatchday(); | |||||
| playerForTableList.forEach(playerForTable -> calcAndSetPlaceDiffToLastMatchday(playerForTable, listForLastMatchday)); | |||||
| } | |||||
| private void calcAndSetPlaceDiffToLastMatchday(PlayerForTable playerForTable, List<PlayerForTable> listForLastMatchday) { | |||||
| int placeLastMatchday = listForLastMatchday.stream() | |||||
| .filter(playerForTableLastMatchday -> playerForTable.getPlayer().equals(playerForTableLastMatchday.getPlayer())) | |||||
| .findFirst() | |||||
| .map(PlayerForTable::getPlace) | |||||
| .orElseThrow(); | |||||
| playerForTable.setPlaceDiffToLastMatchday(placeLastMatchday - playerForTable.getPlace()); | |||||
| } | |||||
| private List<PlayerForTable> getListForLastMatchday() { | |||||
| return new PlayerForTableProvider(playerService, matchdayService, matchdays.get(matchdays.size() - 2)).getPlayersForTableSorted(false); | |||||
| } | |||||
| private PlayerForTable getPlayerForTable(Player player) { | |||||
| List<CalculatedMatch> matchesAsPlayer1 = getMatchesAsPlayer1(player); | |||||
| List<CalculatedMatch> matchesAsPlayer2 = getMatchesAsPlayer2(player); | |||||
| Map<WinState, List<CalculatedMatch>> map = getWinStateLists(matchesAsPlayer1, matchesAsPlayer2); | |||||
| int amountOfMatchesWon = map.get(WinState.WON).size(); | |||||
| int amountOfMatchesDrawn = map.get(WinState.DRAWN).size(); | |||||
| int amountOfMatchesLost = map.get(WinState.LOST).size(); | |||||
| int amountOfMatches = amountOfMatchesWon + amountOfMatchesDrawn + amountOfMatchesLost; | |||||
| int matchPoints = amountOfMatchesWon * 2 + amountOfMatchesDrawn; | |||||
| double gamePointsForSelf = getGamePointsForSelf(matchesAsPlayer1, matchesAsPlayer2); | |||||
| double gamePointsForOpponents = getGamePointsForOpponents(matchesAsPlayer1, matchesAsPlayer2); | |||||
| double gamePointDiff = gamePointsForSelf - gamePointsForOpponents; | |||||
| return new PlayerForTable(player, | |||||
| matchPoints, | |||||
| amountOfMatches, | |||||
| amountOfMatchesWon, | |||||
| amountOfMatchesDrawn, | |||||
| amountOfMatchesLost, | |||||
| gamePointsForSelf, | |||||
| gamePointsForOpponents, | |||||
| gamePointDiff); | |||||
| } | |||||
| private List<CalculatedMatch> getMatchesAsPlayer1(Player player) { | |||||
| return calculatedMatches.stream() | |||||
| .filter(match -> match.getPlayer1().equals(player)) | |||||
| .collect(Collectors.toList()); | |||||
| } | |||||
| private List<CalculatedMatch> getMatchesAsPlayer2(Player player) { | |||||
| return calculatedMatches.stream() | |||||
| .filter(match -> match.getPlayer2().equals(player)) | |||||
| .collect(Collectors.toList()); | |||||
| } | |||||
| private double getGamePointsForSelf(List<CalculatedMatch> matchesAsPlayer1, List<CalculatedMatch> matchesAsPlayer2) { | |||||
| double gamePointsForSelf = 0; | |||||
| gamePointsForSelf += matchesAsPlayer1.stream().mapToDouble(CalculatedMatch::getScore1).sum(); | |||||
| gamePointsForSelf += matchesAsPlayer2.stream().mapToDouble(CalculatedMatch::getScore2).sum(); | |||||
| return gamePointsForSelf; | |||||
| } | |||||
| private double getGamePointsForOpponents(List<CalculatedMatch> matchesAsPlayer1, List<CalculatedMatch> matchesAsPlayer2) { | |||||
| double gamePointsForOpponents = 0; | |||||
| gamePointsForOpponents += matchesAsPlayer1.stream().mapToDouble(CalculatedMatch::getScore2).sum(); | |||||
| gamePointsForOpponents += matchesAsPlayer2.stream().mapToDouble(CalculatedMatch::getScore1).sum(); | |||||
| return gamePointsForOpponents; | |||||
| } | |||||
| private Map<WinState, List<CalculatedMatch>> getWinStateLists(List<CalculatedMatch> matchesAsPlayer1, List<CalculatedMatch> matchesAsPlayer2) { | |||||
| Map<WinState, List<CalculatedMatch>> map = new HashMap<>(); | |||||
| map.put(WinState.WON, new ArrayList<>()); | |||||
| map.put(WinState.DRAWN, new ArrayList<>()); | |||||
| map.put(WinState.LOST, new ArrayList<>()); | |||||
| for (CalculatedMatch match : matchesAsPlayer1) { | |||||
| if (match.getScore1() > match.getScore2()) { | |||||
| map.get(WinState.WON).add(match); | |||||
| continue; | |||||
| } | |||||
| if (match.getScore1() < match.getScore2()) { | |||||
| map.get(WinState.LOST).add(match); | |||||
| continue; | |||||
| } | |||||
| if (match.getScore1() > 0) { | |||||
| map.get(WinState.DRAWN).add(match); | |||||
| } | |||||
| } | |||||
| for (CalculatedMatch match : matchesAsPlayer2) { | |||||
| if (match.getScore2() > match.getScore1()) { | |||||
| map.get(WinState.WON).add(match); | |||||
| continue; | |||||
| } | |||||
| if (match.getScore2() < match.getScore1()) { | |||||
| map.get(WinState.LOST).add(match); | |||||
| continue; | |||||
| } | |||||
| if (match.getScore2() > 0) { | |||||
| map.get(WinState.DRAWN).add(match); | |||||
| } | |||||
| } | |||||
| return map; | |||||
| } | |||||
| private enum WinState { | |||||
| WON, DRAWN, LOST | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,4 @@ | |||||
| package app.navigation; | |||||
| public interface Navigable { | |||||
| } | |||||
| @ -0,0 +1,5 @@ | |||||
| package app.navigation; | |||||
| public interface NavigableService<T extends Navigable> { | |||||
| Class<T> getNavigableClass(); | |||||
| } | |||||
| @ -0,0 +1,22 @@ | |||||
| package app.navigation; | |||||
| import com.vaadin.flow.component.orderedlayout.FlexComponent; | |||||
| import com.vaadin.flow.component.orderedlayout.HorizontalLayout; | |||||
| public abstract class NavigationHeader<T extends Navigation> extends HorizontalLayout { | |||||
| protected final T navigation; | |||||
| public NavigationHeader(T navigation) { | |||||
| this.navigation = navigation; | |||||
| defineLayout(); | |||||
| defineChildren(); | |||||
| } | |||||
| private void defineLayout() { | |||||
| setWidthFull(); | |||||
| setAlignItems(FlexComponent.Alignment.CENTER); | |||||
| setJustifyContentMode(FlexComponent.JustifyContentMode.END); | |||||
| } | |||||
| protected abstract void defineChildren(); | |||||
| } | |||||
| @ -1,8 +0,0 @@ | |||||
| package app.navigation; | |||||
| public enum NavigationLevel { | |||||
| NONE, | |||||
| SEASON, | |||||
| MATCHDAY, | |||||
| MATCH | |||||
| } | |||||
| @ -1,27 +1,7 @@ | |||||
| package app.navigation; | package app.navigation; | ||||
| import app.data.service.MatchService; | |||||
| import app.data.service.MatchdayService; | |||||
| import app.data.service.SeasonService; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.stereotype.Service; | |||||
| public interface NavigationService<T extends Navigation> { | |||||
| T getNewNavigation(); | |||||
| @Service | |||||
| public class NavigationService { | |||||
| private final SeasonService seasonService; | |||||
| private final MatchdayService matchdayService; | |||||
| private final MatchService matchService; | |||||
| public NavigationService(@Autowired SeasonService seasonService, | |||||
| @Autowired MatchdayService matchdayService, | |||||
| @Autowired MatchService matchService) { | |||||
| this.seasonService = seasonService; | |||||
| this.matchdayService = matchdayService; | |||||
| this.matchService = matchService; | |||||
| } | |||||
| public Navigation getNewNavigation() { | |||||
| return new Navigation(seasonService, matchdayService, matchService); | |||||
| } | |||||
| NavigationHeader<T> getNewNavigationHeader(T navigation); | |||||
| } | } | ||||
| @ -1,40 +0,0 @@ | |||||
| package app.navigation.components; | |||||
| import app.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 NavigationHeader extends HorizontalLayout { | |||||
| private final Navigation navigation; | |||||
| private final Label seasonLabel = new Label("Season:"); | |||||
| private final Label matchdayLabel = new Label("Matchday:"); | |||||
| private final Label matchLabel = new Label("Match:"); | |||||
| public NavigationHeader(Navigation navigation) { | |||||
| this.navigation = navigation; | |||||
| configureLayout(); | |||||
| configureChildren(); | |||||
| } | |||||
| private void configureLayout() { | |||||
| setWidthFull(); | |||||
| setAlignItems(FlexComponent.Alignment.CENTER); | |||||
| setJustifyContentMode(FlexComponent.JustifyContentMode.END); | |||||
| } | |||||
| private void configureChildren() { | |||||
| removeAll(); | |||||
| if (navigation.seasonEnabled()) { | |||||
| add(seasonLabel, navigation.getSeasonSelect()); | |||||
| } | |||||
| if (navigation.matchdayEnabled()) { | |||||
| add(matchdayLabel, navigation.getMatchdaySelect()); | |||||
| } | |||||
| if (navigation.matchEnabled()) { | |||||
| add(matchLabel, navigation.getMatchSelect()); | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -1,14 +0,0 @@ | |||||
| package app.navigation.components.button; | |||||
| import app.navigation.Navigation; | |||||
| import java.util.concurrent.atomic.AtomicInteger; | |||||
| class ButtonUtils { | |||||
| static int getMatchdayIndex(Navigation navigation) { | |||||
| AtomicInteger index = new AtomicInteger(); | |||||
| navigation.getSelectedMatchday().ifPresent(matchday -> index.set(navigation.getMatchdayList().indexOf(matchday))); | |||||
| return index.get(); | |||||
| } | |||||
| } | |||||
| @ -1,38 +0,0 @@ | |||||
| package app.navigation.components.button; | |||||
| import app.data.entity.Matchday; | |||||
| import app.navigation.Navigation; | |||||
| import com.vaadin.flow.component.button.Button; | |||||
| import com.vaadin.flow.component.icon.Icon; | |||||
| import com.vaadin.flow.component.icon.VaadinIcon; | |||||
| import java.util.Optional; | |||||
| public class NextMatchdayButton extends Button { | |||||
| private final Navigation navigation; | |||||
| public NextMatchdayButton(Navigation navigation) { | |||||
| this.navigation = navigation; | |||||
| if (!navigation.matchdayEnabled()) | |||||
| throw new IllegalStateException("Cannot instantiate NextMatchdayButton when Matchdays are not enabled!"); | |||||
| setIcon(new Icon(VaadinIcon.ARROW_RIGHT)); | |||||
| navigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Matchday> nextMatchday = getNextMatchday(); | |||||
| setEnabled(nextMatchday.isPresent()); | |||||
| addClickListener(event -> nextMatchday.ifPresent(matchday -> navigation.getMatchdaySelect().setValue(matchday))); | |||||
| } | |||||
| private Optional<Matchday> getNextMatchday() { | |||||
| int index = ButtonUtils.getMatchdayIndex(navigation); | |||||
| if (index >= 0 && index < navigation.getMatchdayList().size() - 1) | |||||
| return Optional.ofNullable(navigation.getMatchdayList().get(index + 1)); | |||||
| return Optional.empty(); | |||||
| } | |||||
| } | |||||
| @ -1,37 +0,0 @@ | |||||
| package app.navigation.components.button; | |||||
| import app.data.entity.Matchday; | |||||
| import app.navigation.Navigation; | |||||
| import com.vaadin.flow.component.button.Button; | |||||
| import com.vaadin.flow.component.icon.Icon; | |||||
| import com.vaadin.flow.component.icon.VaadinIcon; | |||||
| import java.util.Optional; | |||||
| public class PrevMatchdayButton extends Button { | |||||
| private final Navigation navigation; | |||||
| public PrevMatchdayButton(Navigation navigation) { | |||||
| this.navigation = navigation; | |||||
| if (!navigation.matchdayEnabled()) | |||||
| throw new IllegalStateException("Cannot instantiate PrevMatchdayButton when Matchdays are not enabled!"); | |||||
| setIcon(new Icon(VaadinIcon.ARROW_LEFT)); | |||||
| navigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Matchday> prevMatchday = getPrevMatchday(); | |||||
| setEnabled(prevMatchday.isPresent()); | |||||
| addClickListener(event -> prevMatchday.ifPresent(matchday -> navigation.getMatchdaySelect().setValue(matchday))); | |||||
| } | |||||
| private Optional<Matchday> getPrevMatchday() { | |||||
| int index = ButtonUtils.getMatchdayIndex(navigation); | |||||
| if (index > 0) return Optional.ofNullable(navigation.getMatchdayList().get(index - 1)); | |||||
| return Optional.empty(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,127 @@ | |||||
| package app.navigation.match; | |||||
| import app.data.entity.Match; | |||||
| import app.data.entity.Matchday; | |||||
| import app.data.entity.Season; | |||||
| import app.data.service.MatchService; | |||||
| import app.data.service.MatchdayService; | |||||
| import app.data.service.SeasonService; | |||||
| import app.navigation.Navigable; | |||||
| import app.navigation.Navigation; | |||||
| 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.List; | |||||
| import java.util.Optional; | |||||
| public class MatchNavigation extends Navigation { | |||||
| private boolean onlyMatchdaysWithActivity; | |||||
| public MatchNavigation(@Autowired SeasonService seasonService, | |||||
| @Autowired MatchdayService matchdayService, | |||||
| @Autowired MatchService matchService) { | |||||
| super(seasonService, matchdayService, matchService); | |||||
| } | |||||
| public void setOnlyMatchdaysWithActivity(boolean onlyMatchdaysWithActivity) { | |||||
| this.onlyMatchdaysWithActivity = onlyMatchdaysWithActivity; | |||||
| } | |||||
| @Override | |||||
| @SuppressWarnings({"unchecked", "DuplicatedCode"}) // TODO: get rid of duplications | |||||
| protected <PARENT extends Navigable, CHILD extends Navigable> List<CHILD> getChildren(@Nullable PARENT parent, @NonNull Class<CHILD> childClass) { | |||||
| if (childClass.equals(Season.class)) { | |||||
| return (List<CHILD>) getSeasonService().getAllSeasonsSorted(); | |||||
| } | |||||
| if (childClass.equals(Matchday.class)) { | |||||
| assert parent != null; | |||||
| if (onlyMatchdaysWithActivity) | |||||
| return (List<CHILD>) getMatchdayService().getMatchdaysWithActivitySortedOrElseListWithOnlyFirstMatchday((Season) parent); | |||||
| return (List<CHILD>) getMatchdayService().getMatchdaysSorted((Season) parent); | |||||
| } | |||||
| if (childClass.equals(Match.class)) { | |||||
| assert parent != null; | |||||
| return (List<CHILD>) getMatchService().getMatches((Matchday) parent); | |||||
| } | |||||
| throw new UnsupportedOperationException(String.format("This method is not supported for childClass %s", childClass.getSimpleName())); | |||||
| } | |||||
| @Override | |||||
| @SuppressWarnings({"unchecked", "DuplicatedCode"}) // TODO: get rid of duplications | |||||
| protected <T extends Navigable> T getDefaultValue(Class<T> clazz) { | |||||
| if (clazz.equals(Season.class)) { | |||||
| assert !getSeasonList().isEmpty(); | |||||
| return (T) getSeasonList().get(getSeasonList().size() - 1); | |||||
| } | |||||
| if (clazz.equals(Matchday.class)) { | |||||
| assert !getMatchdayList().isEmpty(); | |||||
| Matchday matchdayToSelect = getMatchdayList().get(0); | |||||
| for (Matchday matchday : getMatchdayList()) | |||||
| if (getMatchdayService().hasActivity(matchday)) matchdayToSelect = matchday; | |||||
| return (T) matchdayToSelect; | |||||
| } | |||||
| if (clazz.equals(Match.class)) { | |||||
| assert !getMatchList().isEmpty(); | |||||
| return (T) getMatchList().get(0); | |||||
| } | |||||
| throw new UnsupportedOperationException(String.format("This method is not supported for clazz %s", clazz.getSimpleName())); | |||||
| } | |||||
| public SeasonService getSeasonService() { | |||||
| return (SeasonService) serviceMap.get(Season.class); | |||||
| } | |||||
| public MatchdayService getMatchdayService() { | |||||
| return (MatchdayService) serviceMap.get(Matchday.class); | |||||
| } | |||||
| public MatchService getMatchService() { | |||||
| return (MatchService) serviceMap.get(Match.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public List<Season> getSeasonList() { | |||||
| return (List<Season>) listMap.get(Season.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public List<Matchday> getMatchdayList() { | |||||
| return (List<Matchday>) listMap.get(Matchday.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public List<Match> getMatchList() { | |||||
| return (List<Match>) listMap.get(Match.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public Select<Season> getSeasonSelect() { | |||||
| return (Select<Season>) selectMap.get(Season.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public Select<Matchday> getMatchdaySelect() { | |||||
| return (Select<Matchday>) selectMap.get(Matchday.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public Select<Match> getMatchSelect() { | |||||
| return (Select<Match>) selectMap.get(Match.class); | |||||
| } | |||||
| public Optional<Matchday> getSelectedMatchday() { | |||||
| return getMatchdaySelect().getOptionalValue(); | |||||
| } | |||||
| public Optional<Season> getSelectedSeason() { | |||||
| return getSeasonSelect().getOptionalValue(); | |||||
| } | |||||
| public Optional<Match> getSelectedMatch() { | |||||
| return getMatchSelect().getOptionalValue(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,37 @@ | |||||
| package app.navigation.match; | |||||
| import app.data.service.MatchService; | |||||
| import app.data.service.MatchdayService; | |||||
| import app.data.service.SeasonService; | |||||
| import app.navigation.NavigationHeader; | |||||
| import app.navigation.NavigationService; | |||||
| import app.navigation.match.components.MatchNavigationHeader; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.stereotype.Service; | |||||
| @Service | |||||
| public class MatchNavigationService implements NavigationService<MatchNavigation> { | |||||
| private final SeasonService seasonService; | |||||
| private final MatchdayService matchdayService; | |||||
| private final MatchService matchService; | |||||
| public MatchNavigationService(@Autowired SeasonService seasonService, | |||||
| @Autowired MatchdayService matchdayService, | |||||
| @Autowired MatchService matchService) { | |||||
| this.seasonService = seasonService; | |||||
| this.matchdayService = matchdayService; | |||||
| this.matchService = matchService; | |||||
| } | |||||
| @Override | |||||
| public MatchNavigation getNewNavigation() { | |||||
| return new MatchNavigation(seasonService, matchdayService, matchService); | |||||
| } | |||||
| @Override | |||||
| public NavigationHeader<MatchNavigation> getNewNavigationHeader(MatchNavigation navigation) { | |||||
| return new MatchNavigationHeader(navigation); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,20 @@ | |||||
| package app.navigation.match.components; | |||||
| import app.navigation.NavigationHeader; | |||||
| import app.navigation.match.MatchNavigation; | |||||
| import com.vaadin.flow.component.html.Label; | |||||
| public class MatchNavigationHeader extends NavigationHeader<MatchNavigation> { | |||||
| public MatchNavigationHeader(MatchNavigation navigation) { | |||||
| super(navigation); | |||||
| } | |||||
| @Override | |||||
| protected void defineChildren() { | |||||
| removeAll(); | |||||
| add(new Label("Season:"), navigation.getSeasonSelect()); | |||||
| add(new Label("Matchday:"), navigation.getMatchdaySelect()); | |||||
| add(new Label("Match:"), navigation.getMatchSelect()); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,97 @@ | |||||
| package app.navigation.matchday; | |||||
| import app.data.entity.Matchday; | |||||
| import app.data.entity.Season; | |||||
| import app.data.service.MatchdayService; | |||||
| import app.data.service.SeasonService; | |||||
| import app.navigation.Navigable; | |||||
| import app.navigation.Navigation; | |||||
| 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.List; | |||||
| import java.util.Optional; | |||||
| public class MatchdayNavigation extends Navigation { | |||||
| private boolean onlyMatchdaysWithActivity; | |||||
| MatchdayNavigation(@Autowired SeasonService seasonService, | |||||
| @Autowired MatchdayService matchdayService) { | |||||
| super(seasonService, matchdayService); | |||||
| } | |||||
| public void setOnlyMatchdaysWithActivity(boolean onlyMatchdaysWithActivity) { | |||||
| this.onlyMatchdaysWithActivity = onlyMatchdaysWithActivity; | |||||
| } | |||||
| @Override | |||||
| @SuppressWarnings({"unchecked", "DuplicatedCode"}) // TODO: get rid of duplications | |||||
| protected <PARENT extends Navigable, CHILD extends Navigable> List<CHILD> getChildren(@Nullable PARENT parent, @NonNull Class<CHILD> childClass) { | |||||
| if (childClass.equals(Season.class)) { | |||||
| return (List<CHILD>) getSeasonService().getAllSeasonsSorted(); | |||||
| } | |||||
| if (childClass.equals(Matchday.class)) { | |||||
| assert parent != null; | |||||
| if (onlyMatchdaysWithActivity) | |||||
| return (List<CHILD>) getMatchdayService().getMatchdaysWithActivitySortedOrElseListWithOnlyFirstMatchday((Season) parent); | |||||
| return (List<CHILD>) getMatchdayService().getMatchdaysSorted((Season) parent); | |||||
| } | |||||
| throw new UnsupportedOperationException(String.format("This method is not supported for childClass %s", childClass.getSimpleName())); | |||||
| } | |||||
| @Override | |||||
| @SuppressWarnings({"unchecked", "DuplicatedCode"}) // TODO: get rid of duplications | |||||
| protected <T extends Navigable> T getDefaultValue(Class<T> clazz) { | |||||
| if (clazz.equals(Season.class)) { | |||||
| assert !getSeasonList().isEmpty(); | |||||
| return (T) getSeasonList().get(getSeasonList().size() - 1); | |||||
| } | |||||
| if (clazz.equals(Matchday.class)) { | |||||
| assert !getMatchdayList().isEmpty(); | |||||
| Matchday matchdayToSelect = getMatchdayList().get(0); | |||||
| for (Matchday matchday : getMatchdayList()) | |||||
| if (getMatchdayService().hasActivity(matchday)) matchdayToSelect = matchday; | |||||
| return (T) matchdayToSelect; | |||||
| } | |||||
| throw new UnsupportedOperationException(String.format("This method is not supported for clazz %s", clazz.getSimpleName())); | |||||
| } | |||||
| public SeasonService getSeasonService() { | |||||
| return (SeasonService) serviceMap.get(Season.class); | |||||
| } | |||||
| public MatchdayService getMatchdayService() { | |||||
| return (MatchdayService) serviceMap.get(Matchday.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public List<Season> getSeasonList() { | |||||
| return (List<Season>) listMap.get(Season.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public List<Matchday> getMatchdayList() { | |||||
| return (List<Matchday>) listMap.get(Matchday.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public Select<Season> getSeasonSelect() { | |||||
| return (Select<Season>) selectMap.get(Season.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public Select<Matchday> getMatchdaySelect() { | |||||
| return (Select<Matchday>) selectMap.get(Matchday.class); | |||||
| } | |||||
| public Optional<Matchday> getSelectedMatchday() { | |||||
| return getMatchdaySelect().getOptionalValue(); | |||||
| } | |||||
| public Optional<Season> getSelectedSeason() { | |||||
| return getSeasonSelect().getOptionalValue(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,32 @@ | |||||
| package app.navigation.matchday; | |||||
| import app.data.service.MatchdayService; | |||||
| import app.data.service.SeasonService; | |||||
| import app.navigation.NavigationHeader; | |||||
| import app.navigation.NavigationService; | |||||
| import app.navigation.matchday.components.MatchdayNavigationHeader; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.stereotype.Service; | |||||
| @Service | |||||
| public class MatchdayNavigationService implements NavigationService<MatchdayNavigation> { | |||||
| private final SeasonService seasonService; | |||||
| private final MatchdayService matchdayService; | |||||
| public MatchdayNavigationService(@Autowired SeasonService seasonService, | |||||
| @Autowired MatchdayService matchdayService) { | |||||
| this.seasonService = seasonService; | |||||
| this.matchdayService = matchdayService; | |||||
| } | |||||
| @Override | |||||
| public MatchdayNavigation getNewNavigation() { | |||||
| return new MatchdayNavigation(seasonService, matchdayService); | |||||
| } | |||||
| @Override | |||||
| public NavigationHeader<MatchdayNavigation> getNewNavigationHeader(MatchdayNavigation navigation) { | |||||
| return new MatchdayNavigationHeader(navigation); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,19 @@ | |||||
| package app.navigation.matchday.components; | |||||
| import app.navigation.NavigationHeader; | |||||
| import app.navigation.matchday.MatchdayNavigation; | |||||
| import com.vaadin.flow.component.html.Label; | |||||
| public class MatchdayNavigationHeader extends NavigationHeader<MatchdayNavigation> { | |||||
| public MatchdayNavigationHeader(MatchdayNavigation navigation) { | |||||
| super(navigation); | |||||
| } | |||||
| @Override | |||||
| protected void defineChildren() { | |||||
| removeAll(); | |||||
| add(new Label("Season:"), navigation.getSeasonSelect()); | |||||
| add(new Label("Matchday:"), navigation.getMatchdaySelect()); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,14 @@ | |||||
| package app.navigation.matchday.components.button; | |||||
| import app.navigation.matchday.MatchdayNavigation; | |||||
| import java.util.concurrent.atomic.AtomicInteger; | |||||
| class MatchdayButtonUtils { | |||||
| static int getMatchdayIndex(MatchdayNavigation matchdayNavigation) { | |||||
| AtomicInteger index = new AtomicInteger(-1); | |||||
| matchdayNavigation.getSelectedMatchday().ifPresent(matchday -> index.set(matchdayNavigation.getMatchdayList().indexOf(matchday))); | |||||
| return index.get(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,34 @@ | |||||
| package app.navigation.matchday.components.button; | |||||
| import app.data.entity.Matchday; | |||||
| import app.navigation.matchday.MatchdayNavigation; | |||||
| import com.vaadin.flow.component.button.Button; | |||||
| import com.vaadin.flow.component.icon.VaadinIcon; | |||||
| import java.util.Optional; | |||||
| public class NextMatchdayButton extends Button { | |||||
| private final MatchdayNavigation matchdayNavigation; | |||||
| public NextMatchdayButton(MatchdayNavigation matchdayNavigation) { | |||||
| this.matchdayNavigation = matchdayNavigation; | |||||
| setIcon(VaadinIcon.ARROW_RIGHT.create()); | |||||
| matchdayNavigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Matchday> nextMatchday = getNextMatchday(); | |||||
| setEnabled(nextMatchday.isPresent()); | |||||
| addClickListener(event -> nextMatchday.ifPresent(matchday -> matchdayNavigation.getMatchdaySelect().setValue(matchday))); | |||||
| } | |||||
| private Optional<Matchday> getNextMatchday() { | |||||
| int index = MatchdayButtonUtils.getMatchdayIndex(matchdayNavigation); | |||||
| if (index >= 0 && index < matchdayNavigation.getMatchdayList().size() - 1) | |||||
| return Optional.ofNullable(matchdayNavigation.getMatchdayList().get(index + 1)); | |||||
| return Optional.empty(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,33 @@ | |||||
| package app.navigation.matchday.components.button; | |||||
| import app.data.entity.Matchday; | |||||
| import app.navigation.matchday.MatchdayNavigation; | |||||
| import com.vaadin.flow.component.button.Button; | |||||
| import com.vaadin.flow.component.icon.VaadinIcon; | |||||
| import java.util.Optional; | |||||
| public class PrevMatchdayButton extends Button { | |||||
| private final MatchdayNavigation matchdayNavigation; | |||||
| public PrevMatchdayButton(MatchdayNavigation matchdayNavigation) { | |||||
| this.matchdayNavigation = matchdayNavigation; | |||||
| setIcon(VaadinIcon.ARROW_LEFT.create()); | |||||
| matchdayNavigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Matchday> prevMatchday = getPrevMatchday(); | |||||
| setEnabled(prevMatchday.isPresent()); | |||||
| addClickListener(event -> prevMatchday.ifPresent(matchday -> matchdayNavigation.getMatchdaySelect().setValue(matchday))); | |||||
| } | |||||
| private Optional<Matchday> getPrevMatchday() { | |||||
| int index = MatchdayButtonUtils.getMatchdayIndex(matchdayNavigation); | |||||
| if (index > 0) return Optional.ofNullable(matchdayNavigation.getMatchdayList().get(index - 1)); | |||||
| return Optional.empty(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,84 @@ | |||||
| package app.navigation.player; | |||||
| import app.data.entity.Player; | |||||
| import app.data.entity.Season; | |||||
| import app.data.service.PlayerService; | |||||
| import app.data.service.SeasonService; | |||||
| import app.navigation.Navigable; | |||||
| import app.navigation.Navigation; | |||||
| 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.List; | |||||
| import java.util.Optional; | |||||
| public class PlayerNavigation extends Navigation { | |||||
| PlayerNavigation(@Autowired PlayerService playerService, | |||||
| @Autowired SeasonService seasonService) { | |||||
| super(playerService, seasonService); | |||||
| } | |||||
| @Override | |||||
| @SuppressWarnings("unchecked") | |||||
| protected <PARENT extends Navigable, CHILD extends Navigable> List<CHILD> getChildren(@Nullable PARENT parent, @NonNull Class<CHILD> childClass) { | |||||
| if (childClass.equals(Player.class)) { | |||||
| return (List<CHILD>) getPlayerService().getAllPlayersSorted(); | |||||
| } | |||||
| if (childClass.equals(Season.class)) { | |||||
| return (List<CHILD>) getSeasonService().getAllSeasonsForPlayerSorted((Player) parent); | |||||
| } | |||||
| throw new UnsupportedOperationException(String.format("This method is not supported for childClass %s", childClass.getSimpleName())); | |||||
| } | |||||
| @Override | |||||
| @SuppressWarnings("unchecked") | |||||
| protected <T extends Navigable> T getDefaultValue(Class<T> clazz) { | |||||
| if(clazz.equals(Player.class)) { | |||||
| assert !getPlayerList().isEmpty(); | |||||
| return (T) getPlayerList().get(0); | |||||
| } | |||||
| if (clazz.equals(Season.class)) { | |||||
| assert !getSeasonList().isEmpty(); | |||||
| return (T) getSeasonList().get(getSeasonList().size() - 1); | |||||
| } | |||||
| throw new UnsupportedOperationException(String.format("This method is not supported for clazz %s", clazz.getSimpleName())); | |||||
| } | |||||
| public PlayerService getPlayerService() { | |||||
| return (PlayerService) serviceMap.get(Player.class); | |||||
| } | |||||
| public SeasonService getSeasonService() { | |||||
| return (SeasonService) serviceMap.get(Season.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public List<Season> getSeasonList() { | |||||
| return (List<Season>) listMap.get(Season.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public List<Player> getPlayerList() { | |||||
| return (List<Player>) listMap.get(Player.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public Select<Season> getSeasonSelect() { | |||||
| return (Select<Season>) selectMap.get(Season.class); | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public Select<Player> getPlayerSelect() { | |||||
| return (Select<Player>) selectMap.get(Player.class); | |||||
| } | |||||
| public Optional<Player> getSelectedPlayer() { | |||||
| return getPlayerSelect().getOptionalValue(); | |||||
| } | |||||
| public Optional<Season> getSelectedSeason() { | |||||
| return getSeasonSelect().getOptionalValue(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,32 @@ | |||||
| package app.navigation.player; | |||||
| import app.data.service.PlayerService; | |||||
| import app.data.service.SeasonService; | |||||
| import app.navigation.NavigationHeader; | |||||
| import app.navigation.NavigationService; | |||||
| import app.navigation.player.components.PlayerNavigationHeader; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.stereotype.Service; | |||||
| @Service | |||||
| public class PlayerNavigationService implements NavigationService<PlayerNavigation> { | |||||
| private final PlayerService playerService; | |||||
| private final SeasonService seasonService; | |||||
| public PlayerNavigationService(@Autowired PlayerService playerService, | |||||
| @Autowired SeasonService seasonService) { | |||||
| this.playerService = playerService; | |||||
| this.seasonService = seasonService; | |||||
| } | |||||
| @Override | |||||
| public PlayerNavigation getNewNavigation() { | |||||
| return new PlayerNavigation(playerService, seasonService); | |||||
| } | |||||
| @Override | |||||
| public NavigationHeader<PlayerNavigation> getNewNavigationHeader(PlayerNavigation navigation) { | |||||
| return new PlayerNavigationHeader(navigation); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,19 @@ | |||||
| package app.navigation.player.components; | |||||
| import app.navigation.NavigationHeader; | |||||
| import app.navigation.player.PlayerNavigation; | |||||
| import com.vaadin.flow.component.html.Label; | |||||
| public class PlayerNavigationHeader extends NavigationHeader<PlayerNavigation> { | |||||
| public PlayerNavigationHeader(PlayerNavigation navigation) { | |||||
| super(navigation); | |||||
| } | |||||
| @Override | |||||
| protected void defineChildren() { | |||||
| removeAll(); | |||||
| add(new Label("Player:"), navigation.getPlayerSelect()); | |||||
| add(new Label("Season:"), navigation.getSeasonSelect()); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,37 @@ | |||||
| package app.navigation.player.components.button; | |||||
| import app.data.entity.Player; | |||||
| import app.navigation.player.PlayerNavigation; | |||||
| import com.vaadin.flow.component.button.Button; | |||||
| import com.vaadin.flow.component.icon.VaadinIcon; | |||||
| import java.util.List; | |||||
| import java.util.Optional; | |||||
| public class NextPlayerButton extends Button { | |||||
| private final PlayerNavigation playerNavigation; | |||||
| public NextPlayerButton(PlayerNavigation playerNavigation) { | |||||
| this.playerNavigation = playerNavigation; | |||||
| setIcon(VaadinIcon.ARROW_RIGHT.create()); | |||||
| playerNavigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Player> nextPlayer = getNextPlayer(); | |||||
| setEnabled(nextPlayer.isPresent()); | |||||
| addClickListener(event -> nextPlayer.ifPresent(player -> playerNavigation.getPlayerSelect().setValue(player))); | |||||
| } | |||||
| private Optional<Player> getNextPlayer() { | |||||
| int index = PlayerButtonUtils.getPlayerIndex(playerNavigation); | |||||
| if (index >= 0) { | |||||
| List<Player> playerList = playerNavigation.getPlayerList(); | |||||
| return Optional.of(playerList.get(Math.floorMod(index + 1, playerList.size()))); | |||||
| } | |||||
| return Optional.empty(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,14 @@ | |||||
| package app.navigation.player.components.button; | |||||
| import app.navigation.player.PlayerNavigation; | |||||
| import java.util.concurrent.atomic.AtomicInteger; | |||||
| class PlayerButtonUtils { | |||||
| static int getPlayerIndex(PlayerNavigation playerNavigation) { | |||||
| AtomicInteger index = new AtomicInteger(-1); | |||||
| playerNavigation.getSelectedPlayer().ifPresent(player -> index.set(playerNavigation.getPlayerList().indexOf(player))); | |||||
| return index.get(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,37 @@ | |||||
| package app.navigation.player.components.button; | |||||
| import app.data.entity.Player; | |||||
| import app.navigation.player.PlayerNavigation; | |||||
| import com.vaadin.flow.component.button.Button; | |||||
| import com.vaadin.flow.component.icon.VaadinIcon; | |||||
| import java.util.List; | |||||
| import java.util.Optional; | |||||
| public class PrevPlayerButton extends Button { | |||||
| private final PlayerNavigation playerNavigation; | |||||
| public PrevPlayerButton(PlayerNavigation playerNavigation) { | |||||
| this.playerNavigation = playerNavigation; | |||||
| setIcon(VaadinIcon.ARROW_LEFT.create()); | |||||
| playerNavigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Player> prevPlayer = getPrevPlayer(); | |||||
| setEnabled(prevPlayer.isPresent()); | |||||
| addClickListener(event -> prevPlayer.ifPresent(player -> playerNavigation.getPlayerSelect().setValue(player))); | |||||
| } | |||||
| private Optional<Player> getPrevPlayer() { | |||||
| int index = PlayerButtonUtils.getPlayerIndex(playerNavigation); | |||||
| if (index >= 0) { | |||||
| List<Player> playerList = playerNavigation.getPlayerList(); | |||||
| return Optional.of(playerList.get(Math.floorMod(index - 1, playerList.size()))); | |||||
| } | |||||
| return Optional.empty(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,45 @@ | |||||
| package app.views.player; | |||||
| import app.navigation.player.PlayerNavigation; | |||||
| import app.navigation.player.PlayerNavigationService; | |||||
| import app.views.main.MainView; | |||||
| import app.views.navigation.NavigationViewBase; | |||||
| import app.views.player.components.PlayerCard; | |||||
| import com.vaadin.flow.component.dependency.CssImport; | |||||
| import com.vaadin.flow.router.PageTitle; | |||||
| import com.vaadin.flow.router.Route; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| @CssImport("app/views/player/player-view.css") | |||||
| @Route(value = "player", layout = MainView.class) | |||||
| @PageTitle("Schachliga DACH - Players") | |||||
| public class PlayerView extends NavigationViewBase<PlayerNavigation>{ | |||||
| private PlayerCard playerCard; | |||||
| protected PlayerView(@Autowired PlayerNavigationService playerNavigationService) { | |||||
| super(playerNavigationService, "player"); | |||||
| addClassName("player-view"); | |||||
| defineLayout(); | |||||
| } | |||||
| //////////// | |||||
| // LAYOUT // | |||||
| //////////// | |||||
| private void defineLayout() { | |||||
| playerCard = new PlayerCard((PlayerNavigation) navigation); | |||||
| add(playerCard); | |||||
| } | |||||
| ///////////// | |||||
| // CONTENT // | |||||
| ///////////// | |||||
| @Override | |||||
| protected void configureContent() { | |||||
| playerCard.configureContent(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,142 @@ | |||||
| package app.views.player.components; | |||||
| import app.data.bean.CalculatedMatch; | |||||
| import app.data.entity.Player; | |||||
| import app.data.entity.Season; | |||||
| import app.navigation.player.PlayerNavigation; | |||||
| import app.navigation.player.components.button.NextPlayerButton; | |||||
| import app.navigation.player.components.button.PrevPlayerButton; | |||||
| import app.utils.ComponentUtils; | |||||
| import app.utils.EntityStringUtils; | |||||
| 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.grid.ColumnTextAlign; | |||||
| import com.vaadin.flow.component.grid.Grid; | |||||
| import com.vaadin.flow.component.grid.GridVariant; | |||||
| import com.vaadin.flow.component.html.Div; | |||||
| import com.vaadin.flow.component.html.Label; | |||||
| import com.vaadin.flow.component.icon.VaadinIcon; | |||||
| import com.vaadin.flow.component.orderedlayout.HorizontalLayout; | |||||
| import com.vaadin.flow.component.orderedlayout.VerticalLayout; | |||||
| import java.util.NoSuchElementException; | |||||
| public class PlayerCard extends Div implements ContentConfigurable { | |||||
| private final PlayerNavigation playerNavigation; | |||||
| private final HorizontalLayout header = new HorizontalLayout(); | |||||
| private final HorizontalLayout headerLabelLayout = new HorizontalLayout(); | |||||
| private final Grid<CalculatedMatch> grid = new Grid<>(); | |||||
| private Player player; | |||||
| public PlayerCard(PlayerNavigation playerNavigation) { | |||||
| this.playerNavigation = playerNavigation; | |||||
| addClassName("card"); | |||||
| add(new VerticalLayout(header, grid)); | |||||
| defineHeader(); | |||||
| defineGrid(); | |||||
| } | |||||
| private void defineHeader() { | |||||
| header.add(new PrevPlayerButton(this.playerNavigation), headerLabelLayout, new NextPlayerButton(this.playerNavigation)); | |||||
| header.setWidthFull(); | |||||
| headerLabelLayout.addClassName("player-header-label-layout"); | |||||
| } | |||||
| private void defineGrid() { | |||||
| Label headerMatchday = new Label("Matchday"); | |||||
| headerMatchday.addClassName("column-header"); | |||||
| Label headerOpponent = new Label("Opponent"); | |||||
| headerOpponent.addClassName("column-header"); | |||||
| Label headerResult = new Label("Result"); | |||||
| headerResult.addClassName("column-header"); | |||||
| grid.addColumn(calculatedMatch -> calculatedMatch.getMatch().getMatchday().getNumber()) | |||||
| .setHeader(headerMatchday) | |||||
| .setTextAlign(ColumnTextAlign.CENTER) | |||||
| .setWidth("7em"); | |||||
| grid.addComponentColumn(calculatedMatch -> ComponentUtils.getPlayerLabel(getOpponent(calculatedMatch), getSeason(calculatedMatch), true)) | |||||
| .setHeader(headerOpponent) | |||||
| .setTextAlign(ColumnTextAlign.CENTER) | |||||
| .setWidth("13em"); | |||||
| grid.addColumn(this::getResultString) | |||||
| .setHeader(headerResult) | |||||
| .setTextAlign(ColumnTextAlign.CENTER) | |||||
| .setWidth("6em"); | |||||
| grid.addComponentColumn(this::createButton) | |||||
| .setTextAlign(ColumnTextAlign.CENTER) | |||||
| .setWidth("4em"); | |||||
| grid.setWidth("31em"); // TODO: find a way to set this dynamically based on column widths | |||||
| grid.setHeightByRows(true); | |||||
| grid.addThemeVariants(GridVariant.LUMO_NO_BORDER, | |||||
| GridVariant.LUMO_NO_ROW_BORDERS, GridVariant.LUMO_ROW_STRIPES); | |||||
| } | |||||
| @SuppressWarnings("DuplicatedCode") // TODO; get rid of duplicates | |||||
| private Button createButton(CalculatedMatch match) { | |||||
| Button button = new Button(); | |||||
| button.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE); | |||||
| String seasonParam = EntityStringUtils.getSeasonStringForURL(playerNavigation.getSelectedSeason().orElseThrow()); | |||||
| String matchdayParam = EntityStringUtils.getMatchdayStringForURL(match.getMatch().getMatchday()); | |||||
| String matchParam = EntityStringUtils.getMatchStringForURL(match.getMatch()); | |||||
| String targetWildcardParam = String.format("match/%s/%s/%s/", seasonParam, matchdayParam, matchParam); | |||||
| if (match.getScore1() == 0 && match.getScore2() == 0) { | |||||
| button.setIcon(VaadinIcon.PENCIL.create()); | |||||
| button.addClickListener(event -> UI.getCurrent().navigate(targetWildcardParam + "edit")); | |||||
| return button; | |||||
| } | |||||
| button.setIcon(VaadinIcon.EYE.create()); | |||||
| button.addClickListener(event -> UI.getCurrent().navigate(targetWildcardParam)); | |||||
| return button; | |||||
| } | |||||
| private Player getOpponent(CalculatedMatch match) { | |||||
| return match.getPlayer1().equals(player) ? match.getPlayer2() : match.getPlayer1(); | |||||
| } | |||||
| private Season getSeason(CalculatedMatch calculatedMatch) { | |||||
| return calculatedMatch.getMatch().getMatchday().getSeason(); | |||||
| } | |||||
| private String getResultString(CalculatedMatch match) { | |||||
| boolean isPlayer1 = match.getPlayer1().equals(player); | |||||
| double ownScore = isPlayer1 ? match.getScore1() : match.getScore2(); | |||||
| double opponentScore = isPlayer1 ? match.getScore2() : match.getScore1(); | |||||
| return StringUtils.getResultString(":", ownScore, opponentScore); | |||||
| } | |||||
| @Override | |||||
| public void configureContent() { | |||||
| try { | |||||
| player = playerNavigation.getSelectedPlayer().orElseThrow(); | |||||
| Season season = playerNavigation.getSelectedSeason().orElseThrow(); | |||||
| configureHeaderLabels(player, season); | |||||
| grid.setItems(playerNavigation.getPlayerService().getCalculatedMatchesSorted(player, season)); | |||||
| } catch (NoSuchElementException e) { | |||||
| removeAll(); | |||||
| add(playerNavigation.getValidationLabel()); | |||||
| } | |||||
| } | |||||
| private void configureHeaderLabels(Player player, Season season) { | |||||
| ComponentUtils.configurePlayerLabel(headerLabelLayout, player, season); | |||||
| } | |||||
| } | |||||