5 Commits

Author SHA1 Message Date
  GAM c941c19411 Layout 4 years ago
  GAM 3fc033ac7c clean up 4 years ago
  GAM a0438e36c2 NICE new structure with generic Navigation 4 years ago
  GAM eefdc45633 WIP further restructuring 4 years ago
  GAM f703100ee2 PlayerNavigation 4 years ago
67 changed files with 2380 additions and 1309 deletions
Unified View
  1. +10
    -18
      LICENSE.md
  2. +20
    -9
      README.md
  3. +4
    -4
      db/db_increment_001.sql
  4. +3
    -3
      db/db_increment_002.sql
  5. +835
    -456
      db/db_init.sql
  6. +28
    -26
      frontend/app/components/leafletmap/leaflet-map.js
  7. +1
    -1
      frontend/app/views/example/about/about-view.css
  8. +7
    -6
      frontend/app/views/example/addressform/address-form-view.css
  9. +32
    -32
      frontend/app/views/example/cardlist/card-list-view.css
  10. +7
    -6
      frontend/app/views/example/creditcardform/credit-card-form-view.css
  11. +2
    -2
      frontend/app/views/example/helloworld/hello-world-view.css
  12. +3
    -3
      frontend/app/views/example/map/map-view.css
  13. +16
    -16
      frontend/app/views/example/masterdetail/master-detail-view.css
  14. +7
    -6
      frontend/app/views/example/personform/person-form-view.css
  15. +8
    -0
      frontend/app/views/main/main-view.css
  16. +6
    -6
      frontend/app/views/matchday/matchday-view.css
  17. +10
    -0
      frontend/app/views/player/player-view.css
  18. +5
    -5
      frontend/app/views/table/table-view.css
  19. +2
    -1
      src/main/java/app/data/entity/Match.java
  20. +2
    -1
      src/main/java/app/data/entity/Matchday.java
  21. +2
    -1
      src/main/java/app/data/entity/Player.java
  22. +3
    -1
      src/main/java/app/data/entity/Season.java
  23. +7
    -1
      src/main/java/app/data/service/MatchService.java
  24. +16
    -2
      src/main/java/app/data/service/MatchdayService.java
  25. +191
    -0
      src/main/java/app/data/service/PlayerForTableProvider.java
  26. +22
    -172
      src/main/java/app/data/service/PlayerService.java
  27. +15
    -1
      src/main/java/app/data/service/SeasonService.java
  28. +4
    -0
      src/main/java/app/navigation/Navigable.java
  29. +5
    -0
      src/main/java/app/navigation/NavigableService.java
  30. +115
    -241
      src/main/java/app/navigation/Navigation.java
  31. +22
    -0
      src/main/java/app/navigation/NavigationHeader.java
  32. +0
    -8
      src/main/java/app/navigation/NavigationLevel.java
  33. +3
    -23
      src/main/java/app/navigation/NavigationService.java
  34. +5
    -31
      src/main/java/app/navigation/NavigationUtils.java
  35. +0
    -40
      src/main/java/app/navigation/components/NavigationHeader.java
  36. +0
    -14
      src/main/java/app/navigation/components/button/ButtonUtils.java
  37. +0
    -38
      src/main/java/app/navigation/components/button/NextMatchdayButton.java
  38. +0
    -37
      src/main/java/app/navigation/components/button/PrevMatchdayButton.java
  39. +127
    -0
      src/main/java/app/navigation/match/MatchNavigation.java
  40. +37
    -0
      src/main/java/app/navigation/match/MatchNavigationService.java
  41. +20
    -0
      src/main/java/app/navigation/match/components/MatchNavigationHeader.java
  42. +97
    -0
      src/main/java/app/navigation/matchday/MatchdayNavigation.java
  43. +32
    -0
      src/main/java/app/navigation/matchday/MatchdayNavigationService.java
  44. +19
    -0
      src/main/java/app/navigation/matchday/components/MatchdayNavigationHeader.java
  45. +14
    -0
      src/main/java/app/navigation/matchday/components/button/MatchdayButtonUtils.java
  46. +34
    -0
      src/main/java/app/navigation/matchday/components/button/NextMatchdayButton.java
  47. +33
    -0
      src/main/java/app/navigation/matchday/components/button/PrevMatchdayButton.java
  48. +84
    -0
      src/main/java/app/navigation/player/PlayerNavigation.java
  49. +32
    -0
      src/main/java/app/navigation/player/PlayerNavigationService.java
  50. +19
    -0
      src/main/java/app/navigation/player/components/PlayerNavigationHeader.java
  51. +37
    -0
      src/main/java/app/navigation/player/components/button/NextPlayerButton.java
  52. +14
    -0
      src/main/java/app/navigation/player/components/button/PlayerButtonUtils.java
  53. +37
    -0
      src/main/java/app/navigation/player/components/button/PrevPlayerButton.java
  54. +42
    -5
      src/main/java/app/utils/ComponentUtils.java
  55. +9
    -1
      src/main/java/app/utils/EntityStringUtils.java
  56. +2
    -0
      src/main/java/app/views/main/MainView.java
  57. +7
    -13
      src/main/java/app/views/match/MatchView.java
  58. +7
    -8
      src/main/java/app/views/match/components/EditMatchCard.java
  59. +15
    -13
      src/main/java/app/views/match/components/MatchComponent.java
  60. +1
    -1
      src/main/java/app/views/match/components/utils/GameCardUtils.java
  61. +8
    -8
      src/main/java/app/views/matchday/MatchdayView.java
  62. +23
    -22
      src/main/java/app/views/matchday/components/MatchdayCard.java
  63. +6
    -10
      src/main/java/app/views/navigation/NavigationViewBase.java
  64. +45
    -0
      src/main/java/app/views/player/PlayerView.java
  65. +142
    -0
      src/main/java/app/views/player/components/PlayerCard.java
  66. +7
    -7
      src/main/java/app/views/table/TableView.java
  67. +12
    -10
      src/main/java/app/views/table/components/TableCard.java

+ 10
- 18
LICENSE.md View File

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

+ 20
- 9
README.md View File

@ -3,34 +3,42 @@
This is an example project that can be used as a starting point to create your own Vaadin application with Spring Boot. This is an example project that can be used as a starting point to create your own Vaadin application with Spring Boot.
It contains all the necessary configuration and some placeholder files to get you started. It contains all the necessary configuration and some placeholder files to get you started.
The project is a standard Maven project, so you can import it to your IDE of choice. [Read more how to set up a development environment](https://vaadin.com/docs/v14/flow/installing/installing-overview.html) for Vaadin projects (Windows, Linux, macOS).
The project is a standard Maven project, so you can import it to your IDE of
choice. [Read more how to set up a development environment](https://vaadin.com/docs/v14/flow/installing/installing-overview.html)
for Vaadin projects (Windows, Linux, macOS).
This project was created from https://start.vaadin.com. This project was created from https://start.vaadin.com.
## Running and debugging the application ## Running and debugging the application
### Running the application from the command line. ### Running the application from the command line.
To run from the command line, use `mvn` and open http://localhost:8080 in your browser. To run from the command line, use `mvn` and open http://localhost:8080 in your browser.
### Running and debugging the application in Intellij IDEA ### Running and debugging the application in Intellij IDEA
- Locate the app.Application.java class in the Project view. It is in the src folder, under the main package's root. - Locate the app.Application.java class in the Project view. It is in the src folder, under the main package's root.
- Right-click on the app.Application class - Right-click on the app.Application class
- Select "Debug 'app.Application.main()'" from the list - Select "Debug 'app.Application.main()'" from the list
After the application has started, you can view it at http://localhost:8080/ in your browser.
You can now also attach breakpoints in code for debugging purposes, by clicking next to a line number in any source file.
After the application has started, you can view it at http://localhost:8080/ in your browser. You can now also attach
breakpoints in code for debugging purposes, by clicking next to a line number in any source file.
### Running and debugging the application in Eclipse ### Running and debugging the application in Eclipse
- Locate the app.Application.java class in the Package Explorer. It is in `src/main/java`, under the main package. - Locate the app.Application.java class in the Package Explorer. It is in `src/main/java`, under the main package.
- Right-click on the file and select `Debug As` --> `Java app.Application`. - Right-click on the file and select `Debug As` --> `Java app.Application`.
Do not worry if the debugger breaks at a `SilentExitException`. This is a Spring Boot feature and happens on every startup.
Do not worry if the debugger breaks at a `SilentExitException`. This is a Spring Boot feature and happens on every
startup.
After the application has started, you can view it at http://localhost:8080/ in your browser. You can now also attach
breakpoints in code for debugging purposes, by clicking next to a line number in any source file.
After the application has started, you can view it at http://localhost:8080/ in your browser.
You can now also attach breakpoints in code for debugging purposes, by clicking next to a line number in any source file.
## Project structure ## Project structure
- `MainView.java` in `src/main/java` contains the app.navigation setup. It uses [App Layout](https://vaadin.com/components/vaadin-app-layout).
- `MainView.java` in `src/main/java` contains the app.matchNavigation setup. It
uses [App Layout](https://vaadin.com/components/vaadin-app-layout).
- `app.views` package in `src/main/java` contains the server-side Java app.views of your application. - `app.views` package in `src/main/java` contains the server-side Java app.views of your application.
- `app.views` folder in `frontend/` contains the client-side JavaScript app.views of your application. - `app.views` folder in `frontend/` contains the client-side JavaScript app.views of your application.
@ -38,6 +46,9 @@ You can now also attach breakpoints in code for debugging purposes, by clicking
[vaadin.com](https://vaadin.com) has lots of material to help you get you started: [vaadin.com](https://vaadin.com) has lots of material to help you get you started:
- Follow the tutorials in [vaadin.com/tutorials](https://vaadin.com/tutorials). Especially [vaadin.com/tutorials/getting-started-with-flow](https://vaadin.com/tutorials/getting-started-with-flow) is good for getting a grasp of the basic Vaadin concepts.
- Follow the tutorials in [vaadin.com/tutorials](https://vaadin.com/tutorials).
Especially [vaadin.com/tutorials/getting-started-with-flow](https://vaadin.com/tutorials/getting-started-with-flow) is
good for getting a grasp of the basic Vaadin concepts.
- Read the documentation in [vaadin.com/docs](https://vaadin.com/docs). - Read the documentation in [vaadin.com/docs](https://vaadin.com/docs).
- For a bigger Vaadin application example, check out the Full Stack App starter from [vaadin.com/start](https://vaadin.com/start).
- For a bigger Vaadin application example, check out the Full Stack App starter
from [vaadin.com/start](https://vaadin.com/start).

+ 4
- 4
db/db_increment_001.sql View File

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

+ 3
- 3
db/db_increment_002.sql View File

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

+ 835
- 456
db/db_init.sql
File diff suppressed because it is too large
View File


+ 28
- 26
frontend/app/components/leafletmap/leaflet-map.js View File

@ -6,31 +6,33 @@ const openStreetMapLayer = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
const openStreetMapAttribution = `&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors`; const openStreetMapAttribution = `&copy; <a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors`;
export class LeafletMap extends PolymerElement { export class LeafletMap extends PolymerElement {
_attachDom(dom) {
// Do not use a shadow root
this.appendChild(dom);
}
render() {
return html``;
}
ready() {
super.ready();
this.map = L.map(this);
let tileLayer = L.tileLayer(openStreetMapLayer, {
attribution: openStreetMapAttribution,
maxZoom: 13,
});
tileLayer.addTo(this.map);
}
async setView(latitude, longitude, zoomLevel) {
this.map.setView([latitude, longitude], zoomLevel);
}
static get is() {
return 'leaflet-map';
}
_attachDom(dom) {
// Do not use a shadow root
this.appendChild(dom);
}
render() {
return html``;
}
ready() {
super.ready();
this.map = L.map(this);
let tileLayer = L.tileLayer(openStreetMapLayer, {
attribution: openStreetMapAttribution,
maxZoom: 13,
});
tileLayer.addTo(this.map);
}
async setView(latitude, longitude, zoomLevel) {
this.map.setView([latitude, longitude], zoomLevel);
}
static get is() {
return 'leaflet-map';
}
} }
customElements.define(LeafletMap.is, LeafletMap); customElements.define(LeafletMap.is, LeafletMap);

+ 1
- 1
frontend/app/views/example/about/about-view.css View File

@ -1,3 +1,3 @@
.about-view { .about-view {
display: block;
display: block;
} }

+ 7
- 6
frontend/app/views/example/addressform/address-form-view.css View File

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

+ 32
- 32
frontend/app/views/example/cardlist/card-list-view.css View File

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

+ 7
- 6
frontend/app/views/example/creditcardform/credit-card-form-view.css View File

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

+ 2
- 2
frontend/app/views/example/helloworld/hello-world-view.css View File

@ -1,4 +1,4 @@
.hello-world-view { .hello-world-view {
display: block;
padding: 1em;
display: block;
padding: 1em;
} }

+ 3
- 3
frontend/app/views/example/map/map-view.css View File

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

+ 16
- 16
frontend/app/views/example/masterdetail/master-detail-view.css View File

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

+ 7
- 6
frontend/app/views/example/personform/person-form-view.css View File

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

+ 8
- 0
frontend/app/views/main/main-view.css View File

@ -29,6 +29,14 @@
margin-left: var(--lumo-space-xs); margin-left: var(--lumo-space-xs);
} }
.no-margin-left {
margin-left: 0;
}
a {
color: inherit;
}
/*//////////////////*/ /*//////////////////*/
/* APP-LAYOUT-STUFF */ /* APP-LAYOUT-STUFF */
/*//////////////////*/ /*//////////////////*/


+ 6
- 6
frontend/app/views/matchday/matchday-view.css View File

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

+ 10
- 0
frontend/app/views/player/player-view.css View File

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

+ 5
- 5
frontend/app/views/table/table-view.css View File

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

+ 2
- 1
src/main/java/app/data/entity/Match.java View File

@ -1,5 +1,6 @@
package app.data.entity; package app.data.entity;
import app.navigation.Navigable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -12,7 +13,7 @@ import java.util.Collection;
@Table(name = "match", @Table(name = "match",
schema = "public", schema = "public",
catalog = "chessleague") catalog = "chessleague")
public class Match extends AbstractEntity {
public class Match extends AbstractEntity implements Navigable {
@ManyToOne(cascade = CascadeType.DETACH) @ManyToOne(cascade = CascadeType.DETACH)
@JoinColumn(name = "matchday", @JoinColumn(name = "matchday",


+ 2
- 1
src/main/java/app/data/entity/Matchday.java View File

@ -1,5 +1,6 @@
package app.data.entity; package app.data.entity;
import app.navigation.Navigable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -12,7 +13,7 @@ import java.util.Collection;
@Table(name = "matchday", @Table(name = "matchday",
schema = "public", schema = "public",
catalog = "chessleague") catalog = "chessleague")
public class Matchday extends AbstractEntity {
public class Matchday extends AbstractEntity implements Navigable {
@ManyToOne(cascade = CascadeType.DETACH) @ManyToOne(cascade = CascadeType.DETACH)
@JoinColumn(name = "season", @JoinColumn(name = "season",


+ 2
- 1
src/main/java/app/data/entity/Player.java View File

@ -1,5 +1,6 @@
package app.data.entity; package app.data.entity;
import app.navigation.Navigable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -12,7 +13,7 @@ import java.util.Collection;
@Table(name = "player", @Table(name = "player",
schema = "public", schema = "public",
catalog = "chessleague") catalog = "chessleague")
public class Player extends AbstractEntity {
public class Player extends AbstractEntity implements Navigable {
@Basic @Basic
@Column(name = "name", @Column(name = "name",


+ 3
- 1
src/main/java/app/data/entity/Season.java View File

@ -1,5 +1,6 @@
package app.data.entity; package app.data.entity;
import app.navigation.Navigable;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -12,7 +13,7 @@ import java.util.Collection;
@Table(name = "season", @Table(name = "season",
schema = "public", schema = "public",
catalog = "chessleague") catalog = "chessleague")
public class Season extends AbstractEntity {
public class Season extends AbstractEntity implements Navigable {
@Basic @Basic
@Column(name = "year_start", @Column(name = "year_start",
@ -45,6 +46,7 @@ public class Season extends AbstractEntity {
private Integer weekdayOfViewChange; private Integer weekdayOfViewChange;
@OneToMany(mappedBy = "season", @OneToMany(mappedBy = "season",
fetch = FetchType.EAGER,
cascade = CascadeType.ALL, cascade = CascadeType.ALL,
orphanRemoval = true) orphanRemoval = true)
private Collection<Matchday> matchdays; private Collection<Matchday> matchdays;


+ 7
- 1
src/main/java/app/data/service/MatchService.java View File

@ -5,6 +5,7 @@ import app.data.entity.Game;
import app.data.entity.Match; import app.data.entity.Match;
import app.data.entity.Matchday; import app.data.entity.Matchday;
import app.data.repository.MatchRepository; import app.data.repository.MatchRepository;
import app.navigation.NavigableService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.vaadin.artur.helpers.CrudService; import org.vaadin.artur.helpers.CrudService;
@ -14,7 +15,7 @@ import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
public class MatchService extends CrudService<Match, Integer> {
public class MatchService extends CrudService<Match, Integer> implements NavigableService<Match> {
private final MatchRepository repository; private final MatchRepository repository;
@ -66,4 +67,9 @@ public class MatchService extends CrudService<Match, Integer> {
} }
return score; return score;
} }
@Override
public Class<Match> getNavigableClass() {
return Match.class;
}
} }

+ 16
- 2
src/main/java/app/data/service/MatchdayService.java View File

@ -1,8 +1,10 @@
package app.data.service; package app.data.service;
import app.data.bean.CalculatedMatch;
import app.data.entity.Matchday; import app.data.entity.Matchday;
import app.data.entity.Season; import app.data.entity.Season;
import app.data.repository.MatchdayRepository; import app.data.repository.MatchdayRepository;
import app.navigation.NavigableService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -14,12 +16,15 @@ import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
public class MatchdayService extends CrudService<Matchday, Integer> {
public class MatchdayService extends CrudService<Matchday, Integer> implements NavigableService<Matchday> {
private final MatchdayRepository repository; private final MatchdayRepository repository;
public MatchdayService(@Autowired MatchdayRepository repository) {
private final MatchService matchService;
public MatchdayService(@Autowired MatchdayRepository repository, MatchService matchService) {
this.repository = repository; this.repository = repository;
this.matchService = matchService;
} }
@Override @Override
@ -60,4 +65,13 @@ public class MatchdayService extends CrudService<Matchday, Integer> {
public boolean hasActivity(@NonNull Matchday matchday) { public boolean hasActivity(@NonNull Matchday matchday) {
return matchday.getMatches().stream().mapToInt(match -> match.getGames().size()).sum() != 0; return matchday.getMatches().stream().mapToInt(match -> match.getGames().size()).sum() != 0;
} }
@Override
public Class<Matchday> getNavigableClass() {
return Matchday.class;
}
public List<CalculatedMatch> getCalculatedMatches(Matchday matchday) {
return matchService.getCalculatedMatches(matchday);
}
} }

+ 191
- 0
src/main/java/app/data/service/PlayerForTableProvider.java View File

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

+ 22
- 172
src/main/java/app/data/service/PlayerService.java View File

@ -2,20 +2,24 @@ package app.data.service;
import app.data.bean.CalculatedMatch; import app.data.bean.CalculatedMatch;
import app.data.bean.PlayerForTable; import app.data.bean.PlayerForTable;
import app.data.entity.Match;
import app.data.entity.Matchday; import app.data.entity.Matchday;
import app.data.entity.Player; import app.data.entity.Player;
import app.data.entity.Season;
import app.data.repository.PlayerRepository; import app.data.repository.PlayerRepository;
import app.navigation.NavigableService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.vaadin.artur.helpers.CrudService; import org.vaadin.artur.helpers.CrudService;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@Service @Service
public class PlayerService extends CrudService<Player, Integer> {
public class PlayerService extends CrudService<Player, Integer> implements NavigableService<Player> {
private final PlayerRepository repository; private final PlayerRepository repository;
@ -34,181 +38,27 @@ public class PlayerService extends CrudService<Player, Integer> {
} }
public List<PlayerForTable> getPlayersForTable(Matchday matchday) { public List<PlayerForTable> getPlayersForTable(Matchday matchday) {
return new PlayerForTableProvider(matchday).getPlayersForTableSorted(true);
return new PlayerForTableProvider(this, matchdayService, matchday).getPlayersForTableSorted(true);
} }
private class PlayerForTableProvider {
List<Matchday> matchdays = new ArrayList<>();
List<CalculatedMatch> calculatedMatches = new ArrayList<>();
List<Player> players = repository.findAll();
public PlayerForTableProvider(Matchday matchday) {
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 -> matchService.getCalculatedMatches(matchdayToMap).stream())
.collect(Collectors.toList()));
}
private List<PlayerForTable> getPlayersForTableSorted(boolean calcDiffToLastMatchday) {
List<PlayerForTable> playerForTableList = calcSortedPlayerForTableList();
int offset = 0;
PlayerForTable lastPlayer;
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(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);
}
}
public List<Player> getAllPlayersSorted() {
return repository.findAll().stream()
.sorted((player1, player2) -> player1.getNickname().compareToIgnoreCase(player2.getNickname()))
.collect(Collectors.toList());
}
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;
}
@Override
public Class<Player> getNavigableClass() {
return Player.class;
} }
private enum WinState {
WON, DRAWN, LOST
public List<CalculatedMatch> getCalculatedMatchesSorted(Player player, Season season) {
return matchdayService.getMatchdaysSorted(season).stream()
.flatMap(matchday -> matchday.getMatches().stream())
.distinct()
.filter(match -> match.getPlayer1().equals(player) || match.getPlayer2().equals(player))
.map(matchService::getCalculatedMatch)
.collect(Collectors.toList());
} }
} }

+ 15
- 1
src/main/java/app/data/service/SeasonService.java View File

@ -1,7 +1,9 @@
package app.data.service; package app.data.service;
import app.data.entity.Player;
import app.data.entity.Season; import app.data.entity.Season;
import app.data.repository.SeasonRepository; import app.data.repository.SeasonRepository;
import app.navigation.NavigableService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.vaadin.artur.helpers.CrudService; import org.vaadin.artur.helpers.CrudService;
@ -12,7 +14,7 @@ import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Service @Service
public class SeasonService extends CrudService<Season, Integer> {
public class SeasonService extends CrudService<Season, Integer> implements NavigableService<Season> {
private final SeasonRepository repository; private final SeasonRepository repository;
@ -46,4 +48,16 @@ public class SeasonService extends CrudService<Season, Integer> {
.findFirst(); .findFirst();
} }
public List<Season> getAllSeasonsForPlayerSorted(Player player) {
return repository.findAll().stream()
.filter(season -> season.getMatchdays().stream()
.anyMatch(matchday -> matchday.getMatches().stream()
.anyMatch(match -> match.getPlayer1().equals(player) || match.getPlayer2().equals(player))))
.collect(Collectors.toList());
}
@Override
public Class<Season> getNavigableClass() {
return Season.class;
}
} }

+ 4
- 0
src/main/java/app/navigation/Navigable.java View File

@ -0,0 +1,4 @@
package app.navigation;
public interface Navigable {
}

+ 5
- 0
src/main/java/app/navigation/NavigableService.java View File

@ -0,0 +1,5 @@
package app.navigation;
public interface NavigableService<T extends Navigable> {
Class<T> getNavigableClass();
}

+ 115
- 241
src/main/java/app/navigation/Navigation.java View File

@ -1,12 +1,6 @@
package app.navigation; package app.navigation;
import app.components.label.ValidationLabel; import app.components.label.ValidationLabel;
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.utils.EntityStringUtils; import app.utils.EntityStringUtils;
import com.vaadin.flow.component.AbstractField; import com.vaadin.flow.component.AbstractField;
import com.vaadin.flow.component.HasValue; import com.vaadin.flow.component.HasValue;
@ -15,303 +9,183 @@ import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.router.BeforeEvent; import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter; import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.WildcardParameter; import com.vaadin.flow.router.WildcardParameter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull; 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;
import java.util.*;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public class Navigation implements HasUrlParameter<String> {
import static app.navigation.NavigationUtils.EDIT;
private String route;
private boolean onlyMatchdaysWithActivity;
public abstract class Navigation implements HasUrlParameter<String> {
protected String route;
private final List<Runnable> runnablesToBeRunAfterSelection = new ArrayList<>();
protected final List<Class<? extends Navigable>> navigableClasses = new ArrayList<>();
private final SeasonService seasonService;
private final MatchdayService matchdayService;
private final MatchService matchService;
protected final Map<Class<? extends Navigable>, NavigableService<? extends Navigable>> serviceMap = new HashMap<>();
private final List<Season> seasonList = new ArrayList<>();
private final List<Matchday> matchdayList = new ArrayList<>();
private final List<Match> matchList = new ArrayList<>();
protected final Map<Class<? extends Navigable>, List<? extends Navigable>> listMap = new HashMap<>();
private final Select<Season> seasonSelect = new Select<>();
private final Select<Matchday> matchdaySelect = new Select<>();
private final Select<Match> matchSelect = new Select<>();
protected final Map<Class<? extends Navigable>, Select<? extends Navigable>> selectMap = new HashMap<>();
private NavigationLevel navigationLevel = NavigationLevel.SEASON;
protected final List<Runnable> runnablesToBeRunAfterSelection = new ArrayList<>();
private final ValidationLabel validationLabel = new ValidationLabel();
protected final ValidationLabel validationLabel = new ValidationLabel();
private boolean editFlag = false;
protected boolean editFlag = false;
public Navigation(@Autowired SeasonService seasonService,
@Autowired MatchdayService matchdayService,
@Autowired MatchService matchService) {
this.seasonService = seasonService;
this.matchdayService = matchdayService;
this.matchService = matchService;
fillSeasonSelectWithData();
seasonSelect.addValueChangeListener(seasonSelectValueChangeListener());
matchdaySelect.addValueChangeListener(matchdaySelectValueChangeListener());
matchSelect.addValueChangeListener(matchSelectValueChangeListener());
@SafeVarargs
public Navigation(NavigableService<? extends Navigable>... services) {
for (NavigableService<? extends Navigable> service : services) {
initNavigable(service);
}
seasonSelect.setItemLabelGenerator(EntityStringUtils::getSeasonString);
matchdaySelect.setItemLabelGenerator(EntityStringUtils::getMatchdayString);
matchSelect.setItemLabelGenerator(EntityStringUtils::getMatchString);
if (!navigableClasses.isEmpty()) {
fillChildrenSelectWithData(null, navigableClasses.get(0));
}
} }
public void setRoute(String route) {
this.route = route;
}
private <T extends Navigable> void initNavigable(NavigableService<T> service) {
Class<T> clazz = service.getNavigableClass();
public void setOnlyMatchdaysWithActivity(boolean onlyMatchdaysWithActivity) {
this.onlyMatchdaysWithActivity = onlyMatchdaysWithActivity;
}
navigableClasses.add(clazz);
public void setNavigationLevel(@NonNull NavigationLevel navigationLevel) {
this.navigationLevel = navigationLevel;
}
serviceMap.put(clazz, service);
public void addRunnableToBeRunAfterSelection(Runnable runnable) {
runnablesToBeRunAfterSelection.add(runnable);
}
listMap.put(clazz, new ArrayList<>());
public boolean seasonEnabled() {
return this.navigationLevel.compareTo(NavigationLevel.SEASON) >= 0;
Select<T> select = new Select<>();
select.addValueChangeListener(selectValueChangeListener(clazz));
select.setItemLabelGenerator(EntityStringUtils::getObjectString);
selectMap.put(clazz, select);
} }
public boolean matchdayEnabled() {
return this.navigationLevel.compareTo(NavigationLevel.MATCHDAY) >= 0;
public final void setRoute(String route) {
this.route = route;
} }
public boolean matchEnabled() {
return this.navigationLevel.compareTo(NavigationLevel.MATCH) >= 0;
public final ValidationLabel getValidationLabel() {
return validationLabel;
} }
private String getRoute() {
protected final String getRoute() {
if (route != null) return route; if (route != null) return route;
throw new IllegalStateException("Route must be set!"); throw new IllegalStateException("Route must be set!");
} }
private void updateUrl() {
String seasonParam = null;
String matchdayParam = null;
String matchParam = null;
if (seasonEnabled() && seasonSelect.getOptionalValue().isPresent())
seasonParam = EntityStringUtils.getSeasonStringForURL(seasonSelect.getValue());
if (matchdayEnabled() && matchdaySelect.getOptionalValue().isPresent())
matchdayParam = EntityStringUtils.getMatchdayStringForURL(matchdaySelect.getValue());
if (matchEnabled() && matchSelect.getOptionalValue().isPresent())
matchParam = EntityStringUtils.getMatchStringForURL(matchSelect.getValue());
String params = NavigationUtils.getWildcardParam(editFlag, seasonParam, matchdayParam, matchParam);
UI.getCurrent().getPage().getHistory().pushState(null, String.format("%s/%s", getRoute(), params));
}
private HasValue.ValueChangeListener<? super AbstractField.ComponentValueChangeEvent<Select<Season>, Season>> seasonSelectValueChangeListener() {
return event -> {
if (!seasonEnabled()) throw new IllegalStateException("Cannot select season when it is not enabled!");
if (matchdayEnabled()) {
fillMatchdaySelectWithData(event.getValue());
autoselectMatchday();
return;
}
doPostSelectionStuff();
};
}
private HasValue.ValueChangeListener<? super AbstractField.ComponentValueChangeEvent<Select<Matchday>, Matchday>> matchdaySelectValueChangeListener() {
return event -> {
if (!matchdayEnabled()) throw new IllegalStateException("Cannot select matchday when it is not enabled!");
if (matchEnabled()) {
fillMatchSelectWithData(event.getValue());
autoselectMatch();
return;
}
doPostSelectionStuff();
};
}
private HasValue.ValueChangeListener<? super AbstractField.ComponentValueChangeEvent<Select<Match>, Match>> matchSelectValueChangeListener() {
return event -> {
if (!matchEnabled()) throw new IllegalStateException("Cannot select match when it is not enabled!");
doPostSelectionStuff();
};
}
private void doPostSelectionStuff() {
protected void doPostSelectionStuff() {
validationLabel.setValid(true); validationLabel.setValid(true);
updateUrl(); updateUrl();
runnablesToBeRunAfterSelection.forEach(Runnable::run); runnablesToBeRunAfterSelection.forEach(Runnable::run);
} }
private void autoselectSeason() {
if (!seasonEnabled())
throw new IllegalStateException("This method should not be called when season is not enabled!");
if (seasonList.isEmpty()) {
validationLabel.setText("No Seasons in List!");
validationLabel.setValid(false);
return;
}
seasonSelect.setValue(seasonList.get(seasonList.size() - 1));
}
protected abstract <PARENT extends Navigable, CHILD extends Navigable> List<CHILD> getChildren(@Nullable PARENT parent, @NonNull Class<CHILD> childClass);
private void autoselectMatchday() { // TODO: add date stuff and choose depending on date instead!
if (!matchdayEnabled())
throw new IllegalStateException("This method should not be called when matchday is not enabled!");
if (matchdayList.isEmpty()) {
validationLabel.setText("No Matchdays in List!");
validationLabel.setValid(false);
return;
}
Matchday matchdayToSelect = matchdayList.get(0);
for (Matchday matchday : matchdayList) if (matchdayService.hasActivity(matchday)) matchdayToSelect = matchday;
matchdaySelect.setValue(matchdayToSelect);
}
protected abstract <T extends Navigable> T getDefaultValue(Class<T> clazz);
private void autoselectMatch() {
if (!matchEnabled())
throw new IllegalStateException("This method should not be called when match is not enabled!");
if (matchList.isEmpty()) {
validationLabel.setText("No Matches in List!");
validationLabel.setValid(false);
return;
protected void updateUrl() {
String[] params = new String[navigableClasses.size()];
for (int i = 0; i < navigableClasses.size(); i++) {
Select<? extends Navigable> select = selectMap.get(navigableClasses.get(i));
if (select.getOptionalValue().isPresent()) {
params[i] = EntityStringUtils.getObjectStringForURL(select.getValue());
}
} }
matchSelect.setValue(matchList.get(0));
}
private void fillSeasonSelectWithData() {
seasonList.clear();
seasonList.addAll(seasonService.getAllSeasonsSorted());
seasonSelect.setItems(seasonList);
String wildcardParam = NavigationUtils.getWildcardParam(editFlag, params);
UI.getCurrent().getPage().getHistory().pushState(null, String.format("%s/%s", getRoute(), wildcardParam));
} }
private void fillMatchdaySelectWithData(Season season) {
matchdayList.clear();
List<Matchday> matchdaysToAdd = onlyMatchdaysWithActivity ?
matchdayService.getMatchdaysWithActivitySortedOrElseListWithOnlyFirstMatchday(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);
}
@Override
public void setParameter(BeforeEvent event, @WildcardParameter String param) {
Map<NavigationLevel, Optional<String>> map = NavigationUtils.getParameterMap(param);
editFlag = NavigationUtils.editFlag(param);
navigate(map.get(NavigationLevel.SEASON), map.get(NavigationLevel.MATCHDAY), map.get(NavigationLevel.MATCH));
}
private void navigate(Optional<String> seasonParam, Optional<String> matchdayParam, Optional<String> matchParam) {
if (!seasonEnabled()) return;
Optional<Season> season = NavigationUtils.getObjectFromParam(seasonList, seasonParam);
if (season.isPresent()) seasonSelect.setValue(season.get());
else autoselectSeason();
if (!matchdayEnabled()) return;
Optional<Matchday> matchday = NavigationUtils.getObjectFromParam(matchdayList, matchdayParam);
if (matchday.isPresent()) matchdaySelect.setValue(matchday.get());
else autoselectMatchday();
if (!matchEnabled()) return;
Optional<Match> match = NavigationUtils.getObjectFromParam(matchList, matchParam);
if (match.isPresent()) matchSelect.setValue(match.get());
else autoselectMatch();
}
public ValidationLabel getValidationLabel() {
return validationLabel;
}
public List<Season> getSeasonList() {
return seasonList;
}
public List<Matchday> getMatchdayList() {
return matchdayList;
}
public List<Match> getMatchList() {
return matchList;
}
public Select<Season> getSeasonSelect() {
return seasonSelect;
}
public Select<Matchday> getMatchdaySelect() {
return matchdaySelect;
public boolean editFlag() {
return editFlag;
} }
public Select<Match> getMatchSelect() {
return matchSelect;
public void setEditFlag(boolean editFlag) {
if (editFlag != this.editFlag) {
this.editFlag = editFlag;
updateUrl();
runnablesToBeRunAfterSelection.forEach(Runnable::run);
}
} }
public Optional<Matchday> getSelectedMatchday() {
return matchdaySelect.getOptionalValue();
public void addRunnableToBeRunAfterSelection(Runnable runnable) {
runnablesToBeRunAfterSelection.add(runnable);
} }
public Optional<Season> getSelectedSeason() {
return seasonSelect.getOptionalValue();
protected <T extends Navigable> HasValue.ValueChangeListener<AbstractField.ComponentValueChangeEvent<Select<T>, T>> selectValueChangeListener(Class<T> clazz) {
return event -> {
if (event.getValue() != null) {
if (!isLastClass(clazz)) {
Class<? extends Navigable> childClass = getChildClass(clazz);
fillChildrenSelectWithData(event.getValue(), childClass);
autoselect(childClass);
return;
}
doPostSelectionStuff();
}
// TODO: add else?
};
} }
public Optional<Match> getSelectedMatch() {
return matchSelect.getOptionalValue();
private boolean isLastClass(@NonNull Class<? extends Navigable> clazz) {
return navigableClasses.indexOf(clazz) == navigableClasses.size() - 1;
} }
public SeasonService getSeasonService() {
return seasonService;
@Nullable
private Class<? extends Navigable> getChildClass(@NonNull Class<? extends Navigable> clazz) {
if (!navigableClasses.contains(clazz) || isLastClass(clazz)) return null;
return navigableClasses.get(navigableClasses.indexOf(clazz) + 1);
} }
public MatchdayService getMatchdayService() {
return matchdayService;
@SuppressWarnings("unchecked")
protected <CHILD extends Navigable, PARENT extends Navigable> void fillChildrenSelectWithData(PARENT parent, Class<CHILD> childClass) {
List<CHILD> children = getChildren(parent, childClass);
listMap.put(childClass, children);
Select<CHILD> childSelect = (Select<CHILD>) selectMap.get(childClass);
childSelect.setItems(children);
} }
public MatchService getMatchService() {
return matchService;
@SuppressWarnings("unchecked")
protected <T extends Navigable> void autoselect(Class<T> clazz) {
if (listMap.get(clazz).isEmpty()) {
validationLabel.setText(String.format("No %ss in List!", clazz.getSimpleName()));
validationLabel.setValid(false);
return;
}
Select<T> childSelect = (Select<T>) selectMap.get(clazz);
childSelect.setValue(getDefaultValue(clazz));
} }
public boolean editFlag() {
return editFlag;
}
@Override
public void setParameter(BeforeEvent event, @WildcardParameter String param) {
String[] params = param.split("/");
editFlag = params[params.length - 1].equals(EDIT);
public void setEditFlag(boolean editFlag) {
if (editFlag != this.editFlag) {
this.editFlag = editFlag;
updateUrl();
runnablesToBeRunAfterSelection.forEach(Runnable::run);
for (int i = 0; i < Math.min(params.length, navigableClasses.size()); i++) {
navigateClass(navigableClasses.get(i), params[i]);
} }
} }
public String getWildcardParam() {
return NavigationUtils.getWildcardParam(
editFlag,
getSeasonParam().orElse(null),
getMatchdayParam().orElse(null),
getMatchParam().orElse(null));
}
public Optional<String> getSeasonParam() {
return getSelectedSeason().map(EntityStringUtils::getSeasonStringForURL);
@SuppressWarnings("unchecked")
private <T extends Navigable> void navigateClass(Class<T> clazz, String param) {
List<T> list = (List<T>) listMap.get(clazz);
Select<T> select = (Select<T>) selectMap.get(clazz);
Optional<T> navigable = NavigationUtils.getObjectFromParam(list, param);
if (navigable.isPresent())
select.setValue(navigable.get());
else autoselect(clazz);
} }
public Optional<String> getMatchdayParam() {
return getSelectedMatchday().map(EntityStringUtils::getMatchdayStringForURL);
public String getWildcardParam() {
String[] params = new String[navigableClasses.size()];
for (int i = 0; i < navigableClasses.size(); i++) {
params[i] = getParam(navigableClasses.get(i)).orElse(null);
}
return NavigationUtils.getWildcardParam(editFlag, params);
} }
public Optional<String> getMatchParam() {
return getSelectedMatch().map(EntityStringUtils::getMatchStringForURL);
@SuppressWarnings("unchecked")
private <T extends Navigable> Optional<String> getParam(Class<T> clazz) {
Select<T> select = (Select<T>) selectMap.get(clazz);
return select.getOptionalValue().map(EntityStringUtils::getObjectStringForURL);
} }
}
}

+ 22
- 0
src/main/java/app/navigation/NavigationHeader.java View File

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

+ 0
- 8
src/main/java/app/navigation/NavigationLevel.java View File

@ -1,8 +0,0 @@
package app.navigation;
public enum NavigationLevel {
NONE,
SEASON,
MATCHDAY,
MATCH
}

+ 3
- 23
src/main/java/app/navigation/NavigationService.java View File

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

+ 5
- 31
src/main/java/app/navigation/NavigationUtils.java View File

@ -1,42 +1,19 @@
package app.navigation; package app.navigation;
import app.utils.EntityStringUtils; import app.utils.EntityStringUtils;
import com.vaadin.flow.router.WildcardParameter;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
class NavigationUtils {
private static final String EDIT = "edit";
public class NavigationUtils {
public static final String EDIT = "edit";
private NavigationUtils() { private NavigationUtils() {
} }
@NonNull @NonNull
static Map<NavigationLevel, Optional<String>> getParameterMap(@WildcardParameter String param) {
Map<NavigationLevel, Optional<String>> map = new HashMap<>();
String[] params = param.split("/");
if (params.length >= 1 && !params[0].equals(EDIT)) map.put(NavigationLevel.SEASON, Optional.of(params[0]));
if (params.length >= 2 && !params[1].equals(EDIT)) map.put(NavigationLevel.MATCHDAY, Optional.of(params[1]));
if (params.length >= 3 && !params[2].equals(EDIT)) map.put(NavigationLevel.MATCH, Optional.of(params[2]));
map.putIfAbsent(NavigationLevel.MATCH, Optional.empty());
map.putIfAbsent(NavigationLevel.MATCHDAY, Optional.empty());
map.putIfAbsent(NavigationLevel.SEASON, Optional.empty());
return map;
}
static boolean editFlag(@WildcardParameter String param) {
String[] params = param.split("/");
return params[params.length - 1].equals(EDIT);
}
@NonNull
static String getWildcardParam(boolean editFlag, String... params) {
public static String getWildcardParam(boolean editFlag, String... params) {
StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
for (String param : params) { for (String param : params) {
if (param == null || param.equals("")) { if (param == null || param.equals("")) {
@ -48,12 +25,9 @@ class NavigationUtils {
return stringBuilder.toString(); return stringBuilder.toString();
} }
static <T> Optional<T> getObjectFromParam(List<T> objectList, Optional<String> param) {
if (param.isEmpty()) {
return Optional.empty();
}
public static <T extends Navigable> Optional<T> getObjectFromParam(List<T> objectList, String param) {
return objectList.stream() return objectList.stream()
.filter(object -> EntityStringUtils.getObjectStringForURL(object).equals(param.get()))
.filter(object -> EntityStringUtils.getObjectStringForURL(object).equals(param))
.findFirst(); .findFirst();
} }
} }

+ 0
- 40
src/main/java/app/navigation/components/NavigationHeader.java View File

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

+ 0
- 14
src/main/java/app/navigation/components/button/ButtonUtils.java View File

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

+ 0
- 38
src/main/java/app/navigation/components/button/NextMatchdayButton.java View File

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

+ 0
- 37
src/main/java/app/navigation/components/button/PrevMatchdayButton.java View File

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

+ 127
- 0
src/main/java/app/navigation/match/MatchNavigation.java View File

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

+ 37
- 0
src/main/java/app/navigation/match/MatchNavigationService.java View File

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

+ 20
- 0
src/main/java/app/navigation/match/components/MatchNavigationHeader.java View File

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

+ 97
- 0
src/main/java/app/navigation/matchday/MatchdayNavigation.java View File

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

+ 32
- 0
src/main/java/app/navigation/matchday/MatchdayNavigationService.java View File

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

+ 19
- 0
src/main/java/app/navigation/matchday/components/MatchdayNavigationHeader.java View File

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

+ 14
- 0
src/main/java/app/navigation/matchday/components/button/MatchdayButtonUtils.java View File

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

+ 34
- 0
src/main/java/app/navigation/matchday/components/button/NextMatchdayButton.java View File

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

+ 33
- 0
src/main/java/app/navigation/matchday/components/button/PrevMatchdayButton.java View File

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

+ 84
- 0
src/main/java/app/navigation/player/PlayerNavigation.java View File

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

+ 32
- 0
src/main/java/app/navigation/player/PlayerNavigationService.java View File

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

+ 19
- 0
src/main/java/app/navigation/player/components/PlayerNavigationHeader.java View File

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

+ 37
- 0
src/main/java/app/navigation/player/components/button/NextPlayerButton.java View File

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

+ 14
- 0
src/main/java/app/navigation/player/components/button/PlayerButtonUtils.java View File

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

+ 37
- 0
src/main/java/app/navigation/player/components/button/PrevPlayerButton.java View File

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

+ 42
- 5
src/main/java/app/utils/ComponentUtils.java View File

@ -1,15 +1,41 @@
package app.utils; package app.utils;
import app.data.entity.Player; import app.data.entity.Player;
import app.data.entity.Season;
import app.views.player.PlayerView;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.Label; 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.HorizontalLayout;
import com.vaadin.flow.router.RouterLink;
public class ComponentUtils { public class ComponentUtils {
private ComponentUtils() { private ComponentUtils() {
} }
public static HorizontalLayout getPlayerLabel(Player player) {
return getFormattedLabelWithParentheses(player.getName(), player.getNickname());
public static void configurePlayerLabel(HorizontalLayout layout, Player player, Season season) {
String param = String.format("%s/%s/", EntityStringUtils.getPlayerStringForURL(player), EntityStringUtils.getSeasonStringForURL(season));
RouterLink playerLink = new RouterLink(player.getName(), PlayerView.class, param);
playerLink.addClassName("bold-label");
Anchor chessComLink = new Anchor(player.getPlayerInfo().getUrl(), player.getNickname());
chessComLink.setTarget("_blank");
chessComLink.addClassName("no-margin-left");
configureFormattedComponentsWithParentheses(layout, playerLink, chessComLink);
}
public static HorizontalLayout getPlayerLabel(Player player, Season season, boolean center) {
HorizontalLayout layout = new HorizontalLayout();
configurePlayerLabel(layout, player, season);
if (center) {
layout.setWidthFull();
layout.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);
}
return layout;
} }
public static HorizontalLayout getFormattedLabelWithParentheses(String mainText, String textInParentheses) { public static HorizontalLayout getFormattedLabelWithParentheses(String mainText, String textInParentheses) {
@ -21,10 +47,21 @@ public class ComponentUtils {
public static void configureFormattedLabelWithParentheses(HorizontalLayout horizontalLayout, String mainText, String textInParentheses) { public static void configureFormattedLabelWithParentheses(HorizontalLayout horizontalLayout, String mainText, String textInParentheses) {
Label mainLabel = new Label(mainText); Label mainLabel = new Label(mainText);
mainLabel.addClassName("bold-label"); mainLabel.addClassName("bold-label");
Label parenthesesLabel = new Label(String.format("(%s)", textInParentheses));
parenthesesLabel.addClassName("parentheses-label");
Label parentheseslabel = new Label(textInParentheses);
parentheseslabel.addClassName("no-margin-left");
configureFormattedComponentsWithParentheses(horizontalLayout, mainLabel, parentheseslabel);
}
public static void configureFormattedComponentsWithParentheses(HorizontalLayout horizontalLayout, Component mainComponent, Component parenthesesComponent) {
Label leftParenthesis = new Label("(");
leftParenthesis.addClassName("parentheses-label");
Label rightParenthesis = new Label(")");
rightParenthesis.addClassName("no-margin-left");
horizontalLayout.removeAll(); horizontalLayout.removeAll();
horizontalLayout.add(mainLabel, parenthesesLabel);
horizontalLayout.add(mainComponent, leftParenthesis, parenthesesComponent, rightParenthesis);
} }
} }

+ 9
- 1
src/main/java/app/utils/EntityStringUtils.java View File

@ -26,12 +26,20 @@ public class EntityStringUtils {
return string; return string;
} }
public static String getObjectString(Object object) {
if (object instanceof Player) return getPlayerString((Player) object);
if (object instanceof Season) return getSeasonString((Season) object);
if (object instanceof Matchday) return getMatchdayString((Matchday) object);
if (object instanceof Match) return getMatchString((Match) object);
throw new UnsupportedOperationException(String.format("Cannot get Object String for Type %s!", object.getClass().getSimpleName()));
}
public static String getObjectStringForURL(Object object) { public static String getObjectStringForURL(Object object) {
if (object instanceof Player) return getPlayerStringForURL((Player) object); if (object instanceof Player) return getPlayerStringForURL((Player) object);
if (object instanceof Season) return getSeasonStringForURL((Season) object); if (object instanceof Season) return getSeasonStringForURL((Season) object);
if (object instanceof Matchday) return getMatchdayStringForURL((Matchday) object); if (object instanceof Matchday) return getMatchdayStringForURL((Matchday) object);
if (object instanceof Match) return getMatchStringForURL((Match) object); if (object instanceof Match) return getMatchStringForURL((Match) object);
throw new IllegalStateException(String.format("Cannot get Object String for URL for Type %s!", object.getClass().getSimpleName()));
throw new UnsupportedOperationException(String.format("Cannot get Object String for URL for Type %s!", object.getClass().getSimpleName()));
} }
public static String getSeasonStringForURL(Season season) { public static String getSeasonStringForURL(Season season) {


+ 2
- 0
src/main/java/app/views/main/MainView.java View File

@ -1,6 +1,7 @@
package app.views.main; package app.views.main;
import app.views.matchday.MatchdayView; import app.views.matchday.MatchdayView;
import app.views.player.PlayerView;
import app.views.table.TableView; import app.views.table.TableView;
import com.vaadin.flow.component.Component; import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentUtil; import com.vaadin.flow.component.ComponentUtil;
@ -101,6 +102,7 @@ public class MainView extends AppLayout {
// createTab("Map", MapView.class), // createTab("Map", MapView.class),
createTab("Table", TableView.class), createTab("Table", TableView.class),
createTab("Results", MatchdayView.class), createTab("Results", MatchdayView.class),
createTab("Players", PlayerView.class),
// createTab("Match", MatchView.class) // createTab("Match", MatchView.class)
}; };
} }


+ 7
- 13
src/main/java/app/views/match/MatchView.java View File

@ -1,11 +1,9 @@
package app.views.match; package app.views.match;
import app.data.service.ChessComService; import app.data.service.ChessComService;
import app.data.service.GameInfoService;
import app.data.service.GameService;
import app.gameimage.GameImageService; import app.gameimage.GameImageService;
import app.navigation.NavigationLevel;
import app.navigation.NavigationService;
import app.navigation.match.MatchNavigation;
import app.navigation.match.MatchNavigationService;
import app.views.main.MainView; import app.views.main.MainView;
import app.views.match.components.MatchComponent; import app.views.match.components.MatchComponent;
import app.views.navigation.NavigationViewBase; import app.views.navigation.NavigationViewBase;
@ -17,20 +15,16 @@ import org.springframework.beans.factory.annotation.Autowired;
@CssImport("app/views/match/match-view.css") @CssImport("app/views/match/match-view.css")
@Route(value = "match", layout = MainView.class) @Route(value = "match", layout = MainView.class)
@PageTitle("Schachliga DACH - Results - Matches") @PageTitle("Schachliga DACH - Results - Matches")
public class MatchView extends NavigationViewBase {
public class MatchView extends NavigationViewBase<MatchNavigation> {
private final ChessComService chessComService; private final ChessComService chessComService;
private final GameService gameService;
private final GameInfoService gameInfoService;
private final GameImageService gameImageService; private final GameImageService gameImageService;
private MatchComponent matchComponent; private MatchComponent matchComponent;
public MatchView(@Autowired NavigationService navigationService,
@Autowired ChessComService chessComService, @Autowired GameService gameService, GameInfoService gameInfoService, GameImageService gameImageService) {
super(navigationService, "match", NavigationLevel.MATCH);
public MatchView(@Autowired MatchNavigationService matchNavigationService,
@Autowired ChessComService chessComService, GameImageService gameImageService) {
super(matchNavigationService, "match");
this.chessComService = chessComService; this.chessComService = chessComService;
this.gameService = gameService;
this.gameInfoService = gameInfoService;
this.gameImageService = gameImageService; this.gameImageService = gameImageService;
addClassName("match-view"); addClassName("match-view");
@ -43,7 +37,7 @@ public class MatchView extends NavigationViewBase {
//////////// ////////////
private void configureLayout() { private void configureLayout() {
matchComponent = new MatchComponent(navigation, chessComService, gameImageService);
matchComponent = new MatchComponent((MatchNavigation) navigation, chessComService, gameImageService);
add(matchComponent); add(matchComponent);
} }


+ 7
- 8
src/main/java/app/views/match/components/EditMatchCard.java View File

@ -5,7 +5,7 @@ import app.data.entity.Match;
import app.data.service.ChessComService; import app.data.service.ChessComService;
import app.data.service.MatchService; import app.data.service.MatchService;
import app.gameimage.GameImageService; import app.gameimage.GameImageService;
import app.navigation.Navigation;
import app.navigation.match.MatchNavigation;
import app.utils.ChessComUtils; import app.utils.ChessComUtils;
import app.utils.ComponentUtils; import app.utils.ComponentUtils;
import app.utils.MatchUtils; import app.utils.MatchUtils;
@ -14,7 +14,6 @@ import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener; import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.UI; import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button; 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.Icon;
import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
@ -29,7 +28,7 @@ import static com.vaadin.flow.component.button.ButtonVariant.LUMO_PRIMARY;
public class EditMatchCard extends VerticalLayout { public class EditMatchCard extends VerticalLayout {
private final Navigation navigation;
private final MatchNavigation matchNavigation;
private final MatchService matchService; private final MatchService matchService;
private final ChessComService chessComService; private final ChessComService chessComService;
private final GameImageService gameImageService; private final GameImageService gameImageService;
@ -49,10 +48,10 @@ public class EditMatchCard extends VerticalLayout {
private Match match; private Match match;
public EditMatchCard(Navigation navigation, ChessComService chessComService, GameImageService gameImageService) {
this.navigation = navigation;
public EditMatchCard(MatchNavigation matchNavigation, ChessComService chessComService, GameImageService gameImageService) {
this.matchNavigation = matchNavigation;
this.chessComService = chessComService; this.chessComService = chessComService;
this.matchService = navigation.getMatchService();
this.matchService = matchNavigation.getMatchService();
this.gameImageService = gameImageService; this.gameImageService = gameImageService;
defineLayout(); defineLayout();
@ -168,7 +167,7 @@ public class EditMatchCard extends VerticalLayout {
matchService.update(match); matchService.update(match);
match.getGames().forEach(gameImageService::createImageIfNotPresent); match.getGames().forEach(gameImageService::createImageIfNotPresent);
navigation.setEditFlag(false);
matchNavigation.setEditFlag(false);
}; };
} }
@ -193,6 +192,6 @@ public class EditMatchCard extends VerticalLayout {
} }
private ComponentEventListener<ClickEvent<Button>> createEditCancelButtonListener() { private ComponentEventListener<ClickEvent<Button>> createEditCancelButtonListener() {
return event -> UI.getCurrent().navigate(String.format("matchday/%s", navigation.getWildcardParam().replace("edit/", "")));
return event -> UI.getCurrent().navigate(String.format("matchday/%s", matchNavigation.getWildcardParam().replace("edit/", "")));
} }
} }

+ 15
- 13
src/main/java/app/views/match/components/MatchComponent.java View File

@ -3,10 +3,11 @@ package app.views.match.components;
import app.data.bean.CalculatedMatch; import app.data.bean.CalculatedMatch;
import app.data.entity.Game; import app.data.entity.Game;
import app.data.entity.Match; import app.data.entity.Match;
import app.data.entity.Season;
import app.data.service.ChessComService; import app.data.service.ChessComService;
import app.data.service.MatchService; import app.data.service.MatchService;
import app.gameimage.GameImageService; import app.gameimage.GameImageService;
import app.navigation.Navigation;
import app.navigation.match.MatchNavigation;
import app.utils.ComponentUtils; import app.utils.ComponentUtils;
import app.utils.StringUtils; import app.utils.StringUtils;
import app.views.navigation.interfaces.ContentConfigurable; import app.views.navigation.interfaces.ContentConfigurable;
@ -26,7 +27,7 @@ import java.util.NoSuchElementException;
public class MatchComponent extends Div implements ContentConfigurable { public class MatchComponent extends Div implements ContentConfigurable {
private final Navigation navigation;
private final MatchNavigation matchNavigation;
private final MatchService matchService; private final MatchService matchService;
private final GameImageService gameImageService; private final GameImageService gameImageService;
@ -43,14 +44,14 @@ public class MatchComponent extends Div implements ContentConfigurable {
private Match match; private Match match;
private CalculatedMatch calculatedMatch; private CalculatedMatch calculatedMatch;
public MatchComponent(Navigation navigation,
public MatchComponent(MatchNavigation matchNavigation,
@Autowired ChessComService chessComService, @Autowired ChessComService chessComService,
@Autowired GameImageService gameImageService) { @Autowired GameImageService gameImageService) {
this.navigation = navigation;
this.matchService = navigation.getMatchService();
this.matchNavigation = matchNavigation;
this.matchService = matchNavigation.getMatchService();
this.gameImageService = gameImageService; this.gameImageService = gameImageService;
this.editMatchCard = new EditMatchCard(navigation, chessComService, gameImageService);
this.editMatchCard = new EditMatchCard(matchNavigation, chessComService, gameImageService);
defineLayout(); defineLayout();
} }
@ -95,7 +96,7 @@ public class MatchComponent extends Div implements ContentConfigurable {
Button button = new Button("Add games", new Icon(VaadinIcon.PLUS)); Button button = new Button("Add games", new Icon(VaadinIcon.PLUS));
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY); button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
button.addClickListener(event -> { button.addClickListener(event -> {
navigation.setEditFlag(true);
matchNavigation.setEditFlag(true);
remove(noGamesLayout); remove(noGamesLayout);
}); });
return button; return button;
@ -113,13 +114,13 @@ public class MatchComponent extends Div implements ContentConfigurable {
@Override @Override
public void configureContent() { public void configureContent() {
try { try {
match = navigation.getSelectedMatch().orElseThrow();
match = matchNavigation.getSelectedMatch().orElseThrow();
calculatedMatch = matchService.getCalculatedMatch(match); calculatedMatch = matchService.getCalculatedMatch(match);
configureHeaderContent(); configureHeaderContent();
configureMainContent(); configureMainContent();
} catch (NoSuchElementException e) { } catch (NoSuchElementException e) {
gamesLayout.removeAll(); gamesLayout.removeAll();
add(navigation.getValidationLabel());
add(matchNavigation.getValidationLabel());
} }
} }
@ -129,8 +130,9 @@ public class MatchComponent extends Div implements ContentConfigurable {
} }
private void configureHeaderPlayersLayout() { private void configureHeaderPlayersLayout() {
HorizontalLayout player1 = ComponentUtils.getPlayerLabel(calculatedMatch.getPlayer1());
HorizontalLayout player2 = ComponentUtils.getPlayerLabel(calculatedMatch.getPlayer2());
Season season = match.getMatchday().getSeason();
HorizontalLayout player1 = ComponentUtils.getPlayerLabel(calculatedMatch.getPlayer1(), season, false);
HorizontalLayout player2 = ComponentUtils.getPlayerLabel(calculatedMatch.getPlayer2(), season, false);
Label vs = new Label("vs."); Label vs = new Label("vs.");
headerPlayersLayout.removeAll(); headerPlayersLayout.removeAll();
@ -138,7 +140,7 @@ public class MatchComponent extends Div implements ContentConfigurable {
} }
private void configureHeaderResultLabel() { private void configureHeaderResultLabel() {
if (match.getGames().isEmpty() || navigation.editFlag()) {
if (match.getGames().isEmpty() || matchNavigation.editFlag()) {
headerLayout.remove(headerResultLabel); headerLayout.remove(headerResultLabel);
} else { } else {
headerLayout.add(headerResultLabel); headerLayout.add(headerResultLabel);
@ -148,7 +150,7 @@ public class MatchComponent extends Div implements ContentConfigurable {
} }
private void configureMainContent() { private void configureMainContent() {
if (navigation.editFlag()) {
if (matchNavigation.editFlag()) {
remove(gamesLayout); remove(gamesLayout);
add(editLayout); add(editLayout);
editMatchCard.configureContent(match); editMatchCard.configureContent(match);


+ 1
- 1
src/main/java/app/views/match/components/utils/GameCardUtils.java View File

@ -44,7 +44,7 @@ public class GameCardUtils {
div.addClassName("player"); div.addClassName("player");
div.addClassName(white ? "white-player" : "black-player"); div.addClassName(white ? "white-player" : "black-player");
HorizontalLayout playerLabel = ComponentUtils.getPlayerLabel(player);
HorizontalLayout playerLabel = ComponentUtils.getPlayerLabel(player, game.getMatch().getMatchday().getSeason(), true);
playerLabel.setWidthFull(); playerLabel.setWidthFull();
playerLabel.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER); playerLabel.setJustifyContentMode(FlexComponent.JustifyContentMode.CENTER);


+ 8
- 8
src/main/java/app/views/matchday/MatchdayView.java View File

@ -1,7 +1,7 @@
package app.views.matchday; package app.views.matchday;
import app.navigation.NavigationLevel;
import app.navigation.NavigationService;
import app.navigation.matchday.MatchdayNavigation;
import app.navigation.matchday.MatchdayNavigationService;
import app.views.main.MainView; import app.views.main.MainView;
import app.views.matchday.components.MatchdayCard; import app.views.matchday.components.MatchdayCard;
import app.views.navigation.NavigationViewBase; import app.views.navigation.NavigationViewBase;
@ -13,24 +13,24 @@ import org.springframework.beans.factory.annotation.Autowired;
@CssImport("app/views/matchday/matchday-view.css") @CssImport("app/views/matchday/matchday-view.css")
@Route(value = "matchday", layout = MainView.class) @Route(value = "matchday", layout = MainView.class)
@PageTitle("Schachliga DACH - Results - Matchdays") @PageTitle("Schachliga DACH - Results - Matchdays")
public class MatchdayView extends NavigationViewBase {
public class MatchdayView extends NavigationViewBase<MatchdayNavigation> {
private MatchdayCard matchdayCard; private MatchdayCard matchdayCard;
public MatchdayView(@Autowired NavigationService navigationService) {
super(navigationService, "matchday", NavigationLevel.MATCHDAY);
public MatchdayView(@Autowired MatchdayNavigationService matchNavigationService) {
super(matchNavigationService, "matchday");
addClassName("matchday-view"); addClassName("matchday-view");
configureLayout();
defineLayout();
} }
//////////// ////////////
// LAYOUT // // LAYOUT //
//////////// ////////////
private void configureLayout() {
matchdayCard = new MatchdayCard(navigation);
private void defineLayout() {
matchdayCard = new MatchdayCard((MatchdayNavigation) navigation);
add(matchdayCard); add(matchdayCard);
} }


+ 23
- 22
src/main/java/app/views/matchday/components/MatchdayCard.java View File

@ -2,13 +2,13 @@ package app.views.matchday.components;
import app.data.bean.CalculatedMatch; import app.data.bean.CalculatedMatch;
import app.data.entity.Matchday; import app.data.entity.Matchday;
import app.navigation.Navigation;
import app.navigation.components.button.NextMatchdayButton;
import app.navigation.components.button.PrevMatchdayButton;
import app.data.entity.Season;
import app.navigation.matchday.MatchdayNavigation;
import app.navigation.matchday.components.button.NextMatchdayButton;
import app.navigation.matchday.components.button.PrevMatchdayButton;
import app.utils.ComponentUtils; import app.utils.ComponentUtils;
import app.utils.EntityStringUtils; import app.utils.EntityStringUtils;
import app.utils.StringUtils; import app.utils.StringUtils;
import app.utils.VaadinUtils;
import app.views.navigation.interfaces.ContentConfigurable; import app.views.navigation.interfaces.ContentConfigurable;
import com.vaadin.flow.component.UI; import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.Button;
@ -24,24 +24,21 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Locale;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.TimeZone;
public class MatchdayCard extends Div implements ContentConfigurable { public class MatchdayCard extends Div implements ContentConfigurable {
private final Navigation navigation;
private final MatchdayNavigation matchdayNavigation;
private final Calendar calendar = Calendar.getInstance(); private final Calendar calendar = Calendar.getInstance();
SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.");
private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.");
private final HorizontalLayout header = new HorizontalLayout(); private final HorizontalLayout header = new HorizontalLayout();
private final HorizontalLayout headerLabelLayout = new HorizontalLayout(); private final HorizontalLayout headerLabelLayout = new HorizontalLayout();
private final Grid<CalculatedMatch> grid = new Grid<>(); private final Grid<CalculatedMatch> grid = new Grid<>();
public MatchdayCard(Navigation navigation) {
this.navigation = navigation;
public MatchdayCard(MatchdayNavigation matchdayNavigation) {
this.matchdayNavigation = matchdayNavigation;
addClassName("card"); addClassName("card");
add(new VerticalLayout(header, grid)); add(new VerticalLayout(header, grid));
@ -57,22 +54,21 @@ public class MatchdayCard extends Div implements ContentConfigurable {
} }
private void defineHeader() { private void defineHeader() {
header.add(new PrevMatchdayButton(this.navigation), headerLabelLayout, new NextMatchdayButton(this.navigation));
header.add(new PrevMatchdayButton(this.matchdayNavigation), headerLabelLayout, new NextMatchdayButton(this.matchdayNavigation));
header.setWidthFull(); header.setWidthFull();
headerLabelLayout.addClassName("matchday-header-label-layout"); headerLabelLayout.addClassName("matchday-header-label-layout");
} }
private void defineGrid() { private void defineGrid() {
Label headerPlayer1 = new Label("Player 1"); Label headerPlayer1 = new Label("Player 1");
headerPlayer1.addClassName("column_header");
headerPlayer1.addClassName("column-header");
Label headerPlayer2 = new Label("Player 2"); Label headerPlayer2 = new Label("Player 2");
headerPlayer2.addClassName("column_header");
headerPlayer2.addClassName("column-header");
Label headerResult = new Label("Result"); Label headerResult = new Label("Result");
headerResult.addClassName("column_header");
headerResult.addClassName("column-header");
grid.addColumn(VaadinUtils.getPlayerRenderer(CalculatedMatch::getPlayer1))
grid.addComponentColumn(calculatedMatch -> ComponentUtils.getPlayerLabel(calculatedMatch.getPlayer1(), getSeason(calculatedMatch), true))
.setHeader(headerPlayer1) .setHeader(headerPlayer1)
.setTextAlign(ColumnTextAlign.CENTER) .setTextAlign(ColumnTextAlign.CENTER)
.setWidth("13em") .setWidth("13em")
@ -83,7 +79,7 @@ public class MatchdayCard extends Div implements ContentConfigurable {
.setTextAlign(ColumnTextAlign.CENTER) .setTextAlign(ColumnTextAlign.CENTER)
.setWidth("4em"); .setWidth("4em");
grid.addColumn(VaadinUtils.getPlayerRenderer(CalculatedMatch::getPlayer2))
grid.addComponentColumn(calculatedMatch -> ComponentUtils.getPlayerLabel(calculatedMatch.getPlayer2(), getSeason(calculatedMatch), true))
.setHeader(headerPlayer2) .setHeader(headerPlayer2)
.setTextAlign(ColumnTextAlign.CENTER) .setTextAlign(ColumnTextAlign.CENTER)
.setWidth("13em") .setWidth("13em")
@ -105,11 +101,12 @@ public class MatchdayCard extends Div implements ContentConfigurable {
GridVariant.LUMO_NO_ROW_BORDERS, GridVariant.LUMO_ROW_STRIPES); GridVariant.LUMO_NO_ROW_BORDERS, GridVariant.LUMO_ROW_STRIPES);
} }
@SuppressWarnings("DuplicatedCode") // TODO: get rid of duplicates
private Button createButton(CalculatedMatch match) { private Button createButton(CalculatedMatch match) {
Button button = new Button(); Button button = new Button();
button.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE); button.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
String seasonAndMatchdayParam = navigation.getWildcardParam();
String seasonAndMatchdayParam = matchdayNavigation.getWildcardParam();
String matchParam = EntityStringUtils.getMatchStringForURL(match.getMatch()); String matchParam = EntityStringUtils.getMatchStringForURL(match.getMatch());
String targetWildcardParam = String.format("match/%s%s/", seasonAndMatchdayParam, matchParam); String targetWildcardParam = String.format("match/%s%s/", seasonAndMatchdayParam, matchParam);
@ -124,6 +121,10 @@ public class MatchdayCard extends Div implements ContentConfigurable {
return button; return button;
} }
private Season getSeason(CalculatedMatch calculatedMatch) {
return calculatedMatch.getMatch().getMatchday().getSeason();
}
private String getResultString(CalculatedMatch match) { private String getResultString(CalculatedMatch match) {
return StringUtils.getResultString(":", match.getScore1(), match.getScore2()); return StringUtils.getResultString(":", match.getScore1(), match.getScore2());
} }
@ -131,12 +132,12 @@ public class MatchdayCard extends Div implements ContentConfigurable {
@Override @Override
public void configureContent() { public void configureContent() {
try { try {
Matchday matchday = navigation.getSelectedMatchday().orElseThrow();
Matchday matchday = matchdayNavigation.getSelectedMatchday().orElseThrow();
configureHeaderLabels(matchday); configureHeaderLabels(matchday);
grid.setItems(navigation.getMatchService().getCalculatedMatches(matchday));
grid.setItems(matchdayNavigation.getMatchdayService().getCalculatedMatches(matchday));
} catch (NoSuchElementException e) { } catch (NoSuchElementException e) {
removeAll(); removeAll();
add(navigation.getValidationLabel());
add(matchdayNavigation.getValidationLabel());
} }
} }


+ 6
- 10
src/main/java/app/views/navigation/NavigationViewBase.java View File

@ -1,9 +1,8 @@
package app.views.navigation; package app.views.navigation;
import app.navigation.NavigationHeader;
import app.navigation.Navigation; import app.navigation.Navigation;
import app.navigation.NavigationLevel;
import app.navigation.NavigationService; import app.navigation.NavigationService;
import app.navigation.components.NavigationHeader;
import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.BeforeEvent; import com.vaadin.flow.router.BeforeEvent;
@ -11,19 +10,16 @@ import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.WildcardParameter; import com.vaadin.flow.router.WildcardParameter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
public abstract class NavigationViewBase extends VerticalLayout implements HasUrlParameter<String> {
public abstract class NavigationViewBase<T extends Navigation> extends VerticalLayout implements HasUrlParameter<String> {
protected final Navigation navigation; protected final Navigation navigation;
protected final NavigationHeader navigationHeader;
protected final NavigationHeader<T> navigationHeader;
protected NavigationViewBase(@Autowired NavigationService navigationService,
String route,
NavigationLevel navigationLevel) {
@SuppressWarnings("unchecked")
protected NavigationViewBase(@Autowired NavigationService<T> navigationService, String route) {
this.navigation = navigationService.getNewNavigation(); this.navigation = navigationService.getNewNavigation();
navigation.setRoute(route); navigation.setRoute(route);
navigation.setNavigationLevel(navigationLevel);
this.navigationHeader = new NavigationHeader(navigation);
this.navigationHeader = navigationService.getNewNavigationHeader((T) navigation);
define(); define();
} }


+ 45
- 0
src/main/java/app/views/player/PlayerView.java View File

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

+ 142
- 0
src/main/java/app/views/player/components/PlayerCard.java View File

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

+ 7
- 7
src/main/java/app/views/table/TableView.java View File

@ -1,8 +1,8 @@
package app.views.table; package app.views.table;
import app.data.service.PlayerService; import app.data.service.PlayerService;
import app.navigation.NavigationLevel;
import app.navigation.NavigationService;
import app.navigation.matchday.MatchdayNavigation;
import app.navigation.matchday.MatchdayNavigationService;
import app.views.main.MainView; import app.views.main.MainView;
import app.views.navigation.NavigationViewBase; import app.views.navigation.NavigationViewBase;
import app.views.table.components.TableCard; import app.views.table.components.TableCard;
@ -16,18 +16,18 @@ import org.springframework.beans.factory.annotation.Autowired;
@Route(value = "table", layout = MainView.class) @Route(value = "table", layout = MainView.class)
@RouteAlias(value = "", layout = MainView.class) @RouteAlias(value = "", layout = MainView.class)
@PageTitle("Schachliga DACH - Table") @PageTitle("Schachliga DACH - Table")
public class TableView extends NavigationViewBase {
public class TableView extends NavigationViewBase<MatchdayNavigation> {
private final PlayerService playerService; private final PlayerService playerService;
private TableCard tableCard; private TableCard tableCard;
public TableView(@Autowired NavigationService navigationService,
public TableView(@Autowired MatchdayNavigationService matchdayNavigationService,
@Autowired PlayerService playerService) { @Autowired PlayerService playerService) {
super(navigationService, "table", NavigationLevel.MATCHDAY);
super(matchdayNavigationService, "table");
this.playerService = playerService; this.playerService = playerService;
this.navigation.setOnlyMatchdaysWithActivity(true);
((MatchdayNavigation) this.navigation).setOnlyMatchdaysWithActivity(true);
addClassName("table-view"); addClassName("table-view");
@ -39,7 +39,7 @@ public class TableView extends NavigationViewBase {
//////////// ////////////
private void defineLayout() { private void defineLayout() {
tableCard = new TableCard(navigation, playerService);
tableCard = new TableCard((MatchdayNavigation) navigation, playerService);
add(tableCard); add(tableCard);
} }


+ 12
- 10
src/main/java/app/views/table/components/TableCard.java View File

@ -3,14 +3,15 @@ package app.views.table.components;
import app.data.bean.PlayerForTable; import app.data.bean.PlayerForTable;
import app.data.entity.Matchday; import app.data.entity.Matchday;
import app.data.service.PlayerService; import app.data.service.PlayerService;
import app.navigation.Navigation;
import app.navigation.components.button.NextMatchdayButton;
import app.navigation.components.button.PrevMatchdayButton;
import app.navigation.matchday.MatchdayNavigation;
import app.navigation.matchday.components.button.NextMatchdayButton;
import app.navigation.matchday.components.button.PrevMatchdayButton;
import app.utils.ComponentUtils; import app.utils.ComponentUtils;
import app.utils.EntityStringUtils; import app.utils.EntityStringUtils;
import app.utils.StringUtils; import app.utils.StringUtils;
import app.utils.VaadinUtils; import app.utils.VaadinUtils;
import app.views.navigation.interfaces.ContentConfigurable; import app.views.navigation.interfaces.ContentConfigurable;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.grid.ColumnTextAlign; import com.vaadin.flow.component.grid.ColumnTextAlign;
import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridVariant; import com.vaadin.flow.component.grid.GridVariant;
@ -21,13 +22,14 @@ import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.function.ValueProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
public class TableCard extends Div implements ContentConfigurable { public class TableCard extends Div implements ContentConfigurable {
private final Navigation navigation;
private final MatchdayNavigation matchdayNavigation;
private final PlayerService playerService; private final PlayerService playerService;
@ -35,8 +37,8 @@ public class TableCard extends Div implements ContentConfigurable {
private final HorizontalLayout headerLabelLayout = new HorizontalLayout(); private final HorizontalLayout headerLabelLayout = new HorizontalLayout();
private final Grid<PlayerForTable> grid = new Grid<>(); private final Grid<PlayerForTable> grid = new Grid<>();
public TableCard(Navigation navigation, @Autowired PlayerService playerService) {
this.navigation = navigation;
public TableCard(MatchdayNavigation matchdayNavigation, @Autowired PlayerService playerService) {
this.matchdayNavigation = matchdayNavigation;
this.playerService = playerService; this.playerService = playerService;
addClassName("card"); addClassName("card");
@ -47,7 +49,7 @@ public class TableCard extends Div implements ContentConfigurable {
} }
private void defineHeader() { private void defineHeader() {
header.add(new PrevMatchdayButton(this.navigation), headerLabelLayout, new NextMatchdayButton(this.navigation));
header.add(new PrevMatchdayButton(this.matchdayNavigation), headerLabelLayout, new NextMatchdayButton(this.matchdayNavigation));
header.setWidthFull(); header.setWidthFull();
headerLabelLayout.addClassName("table-header-label-layout"); headerLabelLayout.addClassName("table-header-label-layout");
@ -82,7 +84,7 @@ public class TableCard extends Div implements ContentConfigurable {
.setTextAlign(ColumnTextAlign.START) .setTextAlign(ColumnTextAlign.START)
.setWidth("5em"); .setWidth("5em");
grid.addColumn(VaadinUtils.getPlayerRenderer(PlayerForTable::getPlayer))
grid.addComponentColumn(playerForTable -> ComponentUtils.getPlayerLabel(playerForTable.getPlayer(), matchdayNavigation.getSelectedSeason().orElseThrow(), false))
.setHeader(headerPlayer) .setHeader(headerPlayer)
.setTextAlign(ColumnTextAlign.START) .setTextAlign(ColumnTextAlign.START)
.setWidth("13em"); .setWidth("13em");
@ -161,7 +163,7 @@ public class TableCard extends Div implements ContentConfigurable {
@Override @Override
public void configureContent() { public void configureContent() {
try { try {
Matchday matchday = navigation.getSelectedMatchday().orElseThrow();
Matchday matchday = matchdayNavigation.getSelectedMatchday().orElseThrow();
String mainText = "Table"; String mainText = "Table";
String matchdayText = String.format("Matchday %s", EntityStringUtils.getMatchdayString(matchday)); String matchdayText = String.format("Matchday %s", EntityStringUtils.getMatchdayString(matchday));
@ -169,7 +171,7 @@ public class TableCard extends Div implements ContentConfigurable {
grid.setItems(playerService.getPlayersForTable(matchday)); grid.setItems(playerService.getPlayersForTable(matchday));
} catch (NoSuchElementException e) { } catch (NoSuchElementException e) {
removeAll(); removeAll();
add(navigation.getValidationLabel());
add(matchdayNavigation.getValidationLabel());
} }
} }
} }

Loading…
Cancel
Save