| @ -1,24 +1,16 @@ | |||||
| This is free and unencumbered software released into the public domain. | This is free and unencumbered software released into the public domain. | ||||
| Anyone is free to copy, modify, publish, use, compile, sell, or | |||||
| distribute this software, either in source code form or as a compiled | |||||
| binary, for any purpose, commercial or non-commercial, and by any | |||||
| means. | |||||
| Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or | |||||
| as a compiled binary, for any purpose, commercial or non-commercial, and by any means. | |||||
| In jurisdictions that recognize copyright laws, the author or authors | |||||
| of this software dedicate any and all copyright interest in the | |||||
| software to the public domain. We make this dedication for the benefit | |||||
| of the public at large and to the detriment of our heirs and | |||||
| successors. We intend this dedication to be an overt act of | |||||
| relinquishment in perpetuity of all present and future rights to this | |||||
| software under copyright law. | |||||
| In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright | |||||
| interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the | |||||
| detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of | |||||
| all present and future rights to this software under copyright law. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |||||
| IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |||||
| OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |||||
| ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |||||
| OTHER DEALINGS IN THE SOFTWARE. | |||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE | |||||
| WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE | |||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT | |||||
| OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |||||
| For more information, please refer to <http://unlicense.org> | For more information, please refer to <http://unlicense.org> | ||||
| @ -1,8 +1,8 @@ | |||||
| ALTER TABLE "game_info" | ALTER TABLE "game_info" | ||||
| ADD COLUMN "pgn" varchar, | |||||
| ADD COLUMN "rated" boolean, | |||||
| ADD COLUMN "time_class" varchar, | |||||
| ADD COLUMN "rules" varchar, | |||||
| ADD COLUMN "pgn" varchar, | |||||
| ADD COLUMN "rated" boolean, | |||||
| ADD COLUMN "time_class" varchar, | |||||
| ADD COLUMN "rules" varchar, | |||||
| ADD COLUMN "white_rating" int, | ADD COLUMN "white_rating" int, | ||||
| ADD COLUMN "black_rating" int, | ADD COLUMN "black_rating" int, | ||||
| ADD COLUMN "white_result" varchar, | ADD COLUMN "white_result" varchar, | ||||
| @ -1,5 +1,5 @@ | |||||
| ALTER TABLE "season" | ALTER TABLE "season" | ||||
| ADD COLUMN "week_of_first_matchday" int not null default 9 CHECK ( week_of_first_matchday > 0 AND week_of_first_matchday <= 53 ), | |||||
| ADD COLUMN "week_of_first_matchday" int not null default 9 CHECK ( week_of_first_matchday > 0 AND week_of_first_matchday <= 53 ), | |||||
| ADD COLUMN "first_weekday_of_matchday" int not null default 2 CHECK ( first_weekday_of_matchday > 0 AND first_weekday_of_matchday <= 7 ), | ADD COLUMN "first_weekday_of_matchday" int not null default 2 CHECK ( first_weekday_of_matchday > 0 AND first_weekday_of_matchday <= 7 ), | ||||
| ADD COLUMN "last_weekday_of_matchday" int not null default 1 CHECK ( last_weekday_of_matchday > 0 AND last_weekday_of_matchday <= 7 ), | |||||
| ADD COLUMN "weekday_of_view_change" int not null default 4 CHECK ( weekday_of_view_change > 0 AND weekday_of_view_change <= 7 ); | |||||
| ADD COLUMN "last_weekday_of_matchday" int not null default 1 CHECK ( last_weekday_of_matchday > 0 AND last_weekday_of_matchday <= 7 ), | |||||
| ADD COLUMN "weekday_of_view_change" int not null default 4 CHECK ( weekday_of_view_change > 0 AND weekday_of_view_change <= 7 ); | |||||
| @ -1,3 +1,3 @@ | |||||
| .about-view { | .about-view { | ||||
| display: block; | |||||
| display: block; | |||||
| } | } | ||||
| @ -1,10 +1,11 @@ | |||||
| .address-form-view { | .address-form-view { | ||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| } | } | ||||
| .address-form-view .button-layout { | .address-form-view .button-layout { | ||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| } | } | ||||
| @ -1,70 +1,70 @@ | |||||
| .card-list-view { | .card-list-view { | ||||
| display: block; | |||||
| height: 100%; | |||||
| display: block; | |||||
| height: 100%; | |||||
| } | } | ||||
| .card-list-view vaadin-grid { | .card-list-view vaadin-grid { | ||||
| height: 100%; | |||||
| line-height: var(--lumo-line-height-m); | |||||
| height: 100%; | |||||
| line-height: var(--lumo-line-height-m); | |||||
| } | } | ||||
| .card-list-view vaadin-grid, | .card-list-view vaadin-grid, | ||||
| .card-list-view vaadin-grid-cell-content { | .card-list-view vaadin-grid-cell-content { | ||||
| background-color: var(--lumo-contrast-10pct); | |||||
| background-color: var(--lumo-contrast-10pct); | |||||
| } | } | ||||
| .card-list-view .card { | .card-list-view .card { | ||||
| background-color: var(--lumo-base-color); | |||||
| border-radius: var(--lumo-border-radius); | |||||
| box-shadow: var(--lumo-box-shadow-xs); | |||||
| padding: calc(var(--lumo-space-s) * 1.5) var(--lumo-space-m); | |||||
| background-color: var(--lumo-base-color); | |||||
| border-radius: var(--lumo-border-radius); | |||||
| box-shadow: var(--lumo-box-shadow-xs); | |||||
| padding: calc(var(--lumo-space-s) * 1.5) var(--lumo-space-m); | |||||
| } | } | ||||
| .card-list-view img { | .card-list-view img { | ||||
| border-radius: 50%; | |||||
| flex-shrink: 0; | |||||
| height: var(--lumo-size-m); | |||||
| margin-right: calc(var(--lumo-space-s) * 1.5); | |||||
| width: var(--lumo-size-m); | |||||
| border-radius: 50%; | |||||
| flex-shrink: 0; | |||||
| height: var(--lumo-size-m); | |||||
| margin-right: calc(var(--lumo-space-s) * 1.5); | |||||
| width: var(--lumo-size-m); | |||||
| } | } | ||||
| .card-list-view .header { | .card-list-view .header { | ||||
| align-items: baseline; | |||||
| align-items: baseline; | |||||
| } | } | ||||
| .card-list-view .name { | .card-list-view .name { | ||||
| font-size: var(--lumo-font-size-s); | |||||
| font-weight: bold; | |||||
| margin-right: var(--lumo-space-s); | |||||
| font-size: var(--lumo-font-size-s); | |||||
| font-weight: bold; | |||||
| margin-right: var(--lumo-space-s); | |||||
| } | } | ||||
| .card-list-view .date { | .card-list-view .date { | ||||
| color: var(--lumo-tertiary-text-color); | |||||
| font-size: var(--lumo-font-size-xs); | |||||
| color: var(--lumo-tertiary-text-color); | |||||
| font-size: var(--lumo-font-size-xs); | |||||
| } | } | ||||
| .card-list-view .post { | .card-list-view .post { | ||||
| color: var(--lumo-secondary-text-color); | |||||
| font-size: var(--lumo-font-size-s); | |||||
| margin-bottom: var(--lumo-space-s); | |||||
| white-space: normal; | |||||
| color: var(--lumo-secondary-text-color); | |||||
| font-size: var(--lumo-font-size-s); | |||||
| margin-bottom: var(--lumo-space-s); | |||||
| white-space: normal; | |||||
| } | } | ||||
| .card-list-view .actions { | .card-list-view .actions { | ||||
| align-items: center; | |||||
| align-items: center; | |||||
| } | } | ||||
| .card-list-view iron-icon { | .card-list-view iron-icon { | ||||
| color: var(--lumo-tertiary-text-color); | |||||
| height: calc(var(--lumo-icon-size-s) * 0.8); | |||||
| margin-right: var(--lumo-space-s); | |||||
| width: calc(var(--lumo-icon-size-s) * 0.8); | |||||
| color: var(--lumo-tertiary-text-color); | |||||
| height: calc(var(--lumo-icon-size-s) * 0.8); | |||||
| margin-right: var(--lumo-space-s); | |||||
| width: calc(var(--lumo-icon-size-s) * 0.8); | |||||
| } | } | ||||
| .card-list-view .likes, | .card-list-view .likes, | ||||
| .card-list-view .comments, | .card-list-view .comments, | ||||
| .card-list-view .shares { | .card-list-view .shares { | ||||
| color: var(--lumo-tertiary-text-color); | |||||
| font-size: var(--lumo-font-size-xs); | |||||
| margin-right: var(--lumo-space-l); | |||||
| color: var(--lumo-tertiary-text-color); | |||||
| font-size: var(--lumo-font-size-xs); | |||||
| margin-right: var(--lumo-space-l); | |||||
| } | } | ||||
| @ -1,10 +1,11 @@ | |||||
| .credit-card-form-view { | .credit-card-form-view { | ||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| } | } | ||||
| .credit-card-form-view .button-layout { | .credit-card-form-view .button-layout { | ||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| } | } | ||||
| @ -1,4 +1,4 @@ | |||||
| .hello-world-view { | .hello-world-view { | ||||
| display: block; | |||||
| padding: 1em; | |||||
| display: block; | |||||
| padding: 1em; | |||||
| } | } | ||||
| @ -1,8 +1,8 @@ | |||||
| .map-view { | .map-view { | ||||
| display: flex; | |||||
| height: 100%; | |||||
| display: flex; | |||||
| height: 100%; | |||||
| } | } | ||||
| .map-view .map { | .map-view .map { | ||||
| flex: 1; | |||||
| flex: 1; | |||||
| } | } | ||||
| @ -1,33 +1,33 @@ | |||||
| .master-detail-view { | .master-detail-view { | ||||
| display: flex; | |||||
| height: 100%; | |||||
| flex-direction: column; | |||||
| display: flex; | |||||
| height: 100%; | |||||
| flex-direction: column; | |||||
| } | } | ||||
| .master-detail-view .grid-wrapper { | .master-detail-view .grid-wrapper { | ||||
| flex-grow: 1; | |||||
| width: 100%; | |||||
| flex-grow: 1; | |||||
| width: 100%; | |||||
| } | } | ||||
| .master-detail-view .full-size { | .master-detail-view .full-size { | ||||
| width: 100%; | |||||
| height: 100%; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | } | ||||
| .master-detail-view #editor-layout { | .master-detail-view #editor-layout { | ||||
| width: 400px; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| width: 400px; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| } | } | ||||
| .master-detail-view #editor { | .master-detail-view #editor { | ||||
| padding: var(--lumo-space-l); | |||||
| flex-grow: 1; | |||||
| padding: var(--lumo-space-l); | |||||
| flex-grow: 1; | |||||
| } | } | ||||
| .master-detail-view #button-layout { | .master-detail-view #button-layout { | ||||
| width: 100%; | |||||
| flex-wrap: wrap; | |||||
| background-color: var(--lumo-contrast-5pct); | |||||
| padding: var(--lumo-space-s) var(--lumo-space-l); | |||||
| width: 100%; | |||||
| flex-wrap: wrap; | |||||
| background-color: var(--lumo-contrast-5pct); | |||||
| padding: var(--lumo-space-s) var(--lumo-space-l); | |||||
| } | } | ||||
| @ -1,10 +1,11 @@ | |||||
| .person-form-view { | .person-form-view { | ||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| display: block; | |||||
| margin: 0 auto; | |||||
| max-width: 1024px; | |||||
| padding: 0 var(--lumo-space-l); | |||||
| } | } | ||||
| .person-form-view .button-layout { | .person-form-view .button-layout { | ||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| margin-bottom: var(--lumo-space-l); | |||||
| margin-top: var(--lumo-space-m); | |||||
| } | } | ||||
| @ -1,11 +1,11 @@ | |||||
| .matchday-view .column_header { | .matchday-view .column_header { | ||||
| font-weight: bold; | |||||
| font-size: large; | |||||
| 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; | |||||
| } | } | ||||
| @ -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; | |||||
| } | } | ||||
| @ -1,8 +0,0 @@ | |||||
| package app.navigation; | |||||
| public enum NavigationLevel { | |||||
| NONE, | |||||
| SEASON, | |||||
| MATCHDAY, | |||||
| MATCH | |||||
| } | |||||
| @ -1,40 +0,0 @@ | |||||
| package app.navigation.components; | |||||
| import app.navigation.Navigation; | |||||
| import com.vaadin.flow.component.html.Label; | |||||
| import com.vaadin.flow.component.orderedlayout.FlexComponent; | |||||
| import com.vaadin.flow.component.orderedlayout.HorizontalLayout; | |||||
| public class NavigationHeader extends HorizontalLayout { | |||||
| private final Navigation navigation; | |||||
| private final Label seasonLabel = new Label("Season:"); | |||||
| private final Label matchdayLabel = new Label("Matchday:"); | |||||
| private final Label matchLabel = new Label("Match:"); | |||||
| public NavigationHeader(Navigation navigation) { | |||||
| this.navigation = navigation; | |||||
| configureLayout(); | |||||
| configureChildren(); | |||||
| } | |||||
| private void configureLayout() { | |||||
| setWidthFull(); | |||||
| setAlignItems(FlexComponent.Alignment.CENTER); | |||||
| setJustifyContentMode(FlexComponent.JustifyContentMode.END); | |||||
| } | |||||
| private void configureChildren() { | |||||
| removeAll(); | |||||
| if (navigation.seasonEnabled()) { | |||||
| add(seasonLabel, navigation.getSeasonSelect()); | |||||
| } | |||||
| if (navigation.matchdayEnabled()) { | |||||
| add(matchdayLabel, navigation.getMatchdaySelect()); | |||||
| } | |||||
| if (navigation.matchEnabled()) { | |||||
| add(matchLabel, navigation.getMatchSelect()); | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -1,14 +0,0 @@ | |||||
| package app.navigation.components.button; | |||||
| import app.navigation.Navigation; | |||||
| import java.util.concurrent.atomic.AtomicInteger; | |||||
| class ButtonUtils { | |||||
| static int getMatchdayIndex(Navigation navigation) { | |||||
| AtomicInteger index = new AtomicInteger(); | |||||
| navigation.getSelectedMatchday().ifPresent(matchday -> index.set(navigation.getMatchdayList().indexOf(matchday))); | |||||
| return index.get(); | |||||
| } | |||||
| } | |||||
| @ -1,38 +0,0 @@ | |||||
| package app.navigation.components.button; | |||||
| import app.data.entity.Matchday; | |||||
| import app.navigation.Navigation; | |||||
| import com.vaadin.flow.component.button.Button; | |||||
| import com.vaadin.flow.component.icon.Icon; | |||||
| import com.vaadin.flow.component.icon.VaadinIcon; | |||||
| import java.util.Optional; | |||||
| public class NextMatchdayButton extends Button { | |||||
| private final Navigation navigation; | |||||
| public NextMatchdayButton(Navigation navigation) { | |||||
| this.navigation = navigation; | |||||
| if (!navigation.matchdayEnabled()) | |||||
| throw new IllegalStateException("Cannot instantiate NextMatchdayButton when Matchdays are not enabled!"); | |||||
| setIcon(new Icon(VaadinIcon.ARROW_RIGHT)); | |||||
| navigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Matchday> nextMatchday = getNextMatchday(); | |||||
| setEnabled(nextMatchday.isPresent()); | |||||
| addClickListener(event -> nextMatchday.ifPresent(matchday -> navigation.getMatchdaySelect().setValue(matchday))); | |||||
| } | |||||
| private Optional<Matchday> getNextMatchday() { | |||||
| int index = ButtonUtils.getMatchdayIndex(navigation); | |||||
| if (index >= 0 && index < navigation.getMatchdayList().size() - 1) | |||||
| return Optional.ofNullable(navigation.getMatchdayList().get(index + 1)); | |||||
| return Optional.empty(); | |||||
| } | |||||
| } | |||||
| @ -1,37 +0,0 @@ | |||||
| package app.navigation.components.button; | |||||
| import app.data.entity.Matchday; | |||||
| import app.navigation.Navigation; | |||||
| import com.vaadin.flow.component.button.Button; | |||||
| import com.vaadin.flow.component.icon.Icon; | |||||
| import com.vaadin.flow.component.icon.VaadinIcon; | |||||
| import java.util.Optional; | |||||
| public class PrevMatchdayButton extends Button { | |||||
| private final Navigation navigation; | |||||
| public PrevMatchdayButton(Navigation navigation) { | |||||
| this.navigation = navigation; | |||||
| if (!navigation.matchdayEnabled()) | |||||
| throw new IllegalStateException("Cannot instantiate PrevMatchdayButton when Matchdays are not enabled!"); | |||||
| setIcon(new Icon(VaadinIcon.ARROW_LEFT)); | |||||
| navigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Matchday> prevMatchday = getPrevMatchday(); | |||||
| setEnabled(prevMatchday.isPresent()); | |||||
| addClickListener(event -> prevMatchday.ifPresent(matchday -> navigation.getMatchdaySelect().setValue(matchday))); | |||||
| } | |||||
| private Optional<Matchday> getPrevMatchday() { | |||||
| int index = ButtonUtils.getMatchdayIndex(navigation); | |||||
| if (index > 0) return Optional.ofNullable(navigation.getMatchdayList().get(index - 1)); | |||||
| return Optional.empty(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,194 @@ | |||||
| package app.navigation.player; | |||||
| import app.components.label.ValidationLabel; | |||||
| import app.data.entity.Player; | |||||
| import app.data.entity.Season; | |||||
| import app.data.service.PlayerService; | |||||
| import app.data.service.SeasonService; | |||||
| import app.navigation.NavigationUtils; | |||||
| import app.utils.EntityStringUtils; | |||||
| import com.vaadin.flow.component.AbstractField.ComponentValueChangeEvent; | |||||
| import com.vaadin.flow.component.HasValue.ValueChangeListener; | |||||
| import com.vaadin.flow.component.UI; | |||||
| import com.vaadin.flow.component.select.Select; | |||||
| import com.vaadin.flow.router.BeforeEvent; | |||||
| import com.vaadin.flow.router.HasUrlParameter; | |||||
| import com.vaadin.flow.router.WildcardParameter; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| import java.util.Optional; | |||||
| @SuppressWarnings("OptionalUsedAsFieldOrParameterType") | |||||
| public class PlayerNavigation implements HasUrlParameter<String> { | |||||
| private String route; | |||||
| private final List<Runnable> runnablesToBeRunAfterSelection = new ArrayList<>(); | |||||
| private final PlayerService playerService; | |||||
| private final SeasonService seasonService; | |||||
| private final List<Player> playerList = new ArrayList<>(); | |||||
| private final List<Season> seasonList = new ArrayList<>(); | |||||
| private final Select<Player> playerSelect = new Select<>(); | |||||
| private final Select<Season> seasonSelect = new Select<>(); | |||||
| private final ValidationLabel validationLabel = new ValidationLabel(); | |||||
| public PlayerNavigation(@Autowired PlayerService playerService, | |||||
| @Autowired SeasonService seasonService) { | |||||
| this.playerService = playerService; | |||||
| this.seasonService = seasonService; | |||||
| fillPlayerSelectWithData(); | |||||
| playerSelect.addValueChangeListener(playerSelectValueChangeListener()); | |||||
| seasonSelect.addValueChangeListener(seasonSelectValueChangeListener()); | |||||
| playerSelect.setItemLabelGenerator(EntityStringUtils::getPlayerString); | |||||
| seasonSelect.setItemLabelGenerator(EntityStringUtils::getSeasonString); | |||||
| } | |||||
| public void setRoute(String route) { | |||||
| this.route = route; | |||||
| } | |||||
| public void addRunnableToBeRunAfterSelection(Runnable runnable) { | |||||
| runnablesToBeRunAfterSelection.add(runnable); | |||||
| } | |||||
| private String getRoute() { | |||||
| if (route != null) return route; | |||||
| throw new IllegalStateException("Route must be set!"); | |||||
| } | |||||
| private void updateUrl() { | |||||
| String playerParam = null; | |||||
| String seasonParam = null; | |||||
| if (playerSelect.getOptionalValue().isPresent()) | |||||
| playerParam = EntityStringUtils.getPlayerStringForURL(playerSelect.getValue()); | |||||
| if (seasonSelect.getOptionalValue().isPresent()) | |||||
| seasonParam = EntityStringUtils.getSeasonStringForURL(seasonSelect.getValue()); | |||||
| String params = NavigationUtils.getWildcardParam(false, playerParam, seasonParam); | |||||
| UI.getCurrent().getPage().getHistory().pushState(null, String.format("%s/%s", getRoute(), params)); | |||||
| } | |||||
| private ValueChangeListener<ComponentValueChangeEvent<Select<Player>, Player>> playerSelectValueChangeListener() { | |||||
| return event -> { | |||||
| fillSeasonSelectWithData(event.getValue()); | |||||
| autoselectSeason(); | |||||
| }; | |||||
| } | |||||
| private ValueChangeListener<ComponentValueChangeEvent<Select<Season>, Season>> seasonSelectValueChangeListener() { | |||||
| return event -> doPostSelectionStuff(); | |||||
| } | |||||
| private void doPostSelectionStuff() { | |||||
| validationLabel.setValid(true); | |||||
| updateUrl(); | |||||
| runnablesToBeRunAfterSelection.forEach(Runnable::run); | |||||
| } | |||||
| private void autoselectPlayer() { | |||||
| if (playerList.isEmpty()) { | |||||
| validationLabel.setText("No Players in List!"); | |||||
| validationLabel.setValid(false); | |||||
| return; | |||||
| } | |||||
| playerSelect.setValue(playerList.get(0)); | |||||
| } | |||||
| private void autoselectSeason() { | |||||
| if (seasonList.isEmpty()) { | |||||
| validationLabel.setText("No Season in List!"); | |||||
| validationLabel.setValid(false); | |||||
| return; | |||||
| } | |||||
| seasonSelect.setValue(seasonList.get(seasonList.size() - 1)); | |||||
| } | |||||
| private void fillPlayerSelectWithData() { | |||||
| playerList.clear(); | |||||
| playerList.addAll(playerService.getAllPlayersSorted()); | |||||
| playerSelect.setItems(playerList); | |||||
| } | |||||
| private void fillSeasonSelectWithData(Player player) { | |||||
| seasonList.clear(); | |||||
| seasonList.addAll(seasonService.getAllSeasonsForPlayerSorted(player)); | |||||
| seasonSelect.setItems(seasonList); | |||||
| } | |||||
| @Override | |||||
| public void setParameter(BeforeEvent event, @WildcardParameter String param) { | |||||
| Map<PlayerNavigationLevel, Optional<String>> map = PlayerNavigationUtils.getParameterMap(param); | |||||
| navigate(map.get(PlayerNavigationLevel.PLAYER), map.get(PlayerNavigationLevel.SEASON)); | |||||
| } | |||||
| private void navigate(Optional<String> playerParam, Optional<String> seasonParam) { | |||||
| Optional<Player> player = NavigationUtils.getObjectFromParam(playerList, playerParam); | |||||
| if (player.isPresent()) playerSelect.setValue(player.get()); | |||||
| else autoselectPlayer(); | |||||
| Optional<Season> season = NavigationUtils.getObjectFromParam(seasonList, seasonParam); | |||||
| if (season.isPresent()) seasonSelect.setValue(season.get()); | |||||
| else autoselectSeason(); | |||||
| } | |||||
| public ValidationLabel getValidationLabel() { | |||||
| return validationLabel; | |||||
| } | |||||
| public List<Player> getPlayerList() { | |||||
| return playerList; | |||||
| } | |||||
| public List<Season> getSeasonList() { | |||||
| return seasonList; | |||||
| } | |||||
| public Select<Player> getPlayerSelect() { | |||||
| return playerSelect; | |||||
| } | |||||
| public Select<Season> getSeasonSelect() { | |||||
| return seasonSelect; | |||||
| } | |||||
| public Optional<Player> getSelectedPlayer() { | |||||
| return playerSelect.getOptionalValue(); | |||||
| } | |||||
| public Optional<Season> getSelectedSeason() { | |||||
| return seasonSelect.getOptionalValue(); | |||||
| } | |||||
| public PlayerService getPlayerService() { | |||||
| return playerService; | |||||
| } | |||||
| public SeasonService getSeasonService() { | |||||
| return seasonService; | |||||
| } | |||||
| public String getWildcardParam() { | |||||
| return NavigationUtils.getWildcardParam( | |||||
| false, | |||||
| getPlayerParam().orElse(null), | |||||
| getSeasonParam().orElse(null)); | |||||
| } | |||||
| public Optional<String> getPlayerParam() { | |||||
| return getSelectedPlayer().map(EntityStringUtils::getPlayerStringForURL); | |||||
| } | |||||
| public Optional<String> getSeasonParam() { | |||||
| return getSelectedSeason().map(EntityStringUtils::getSeasonStringForURL); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,7 @@ | |||||
| package app.navigation.player; | |||||
| public enum PlayerNavigationLevel { | |||||
| NONE, | |||||
| PLAYER, | |||||
| SEASON, | |||||
| } | |||||
| @ -0,0 +1,23 @@ | |||||
| package app.navigation.player; | |||||
| import app.data.service.PlayerService; | |||||
| import app.data.service.SeasonService; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.stereotype.Service; | |||||
| @Service | |||||
| public class PlayerNavigationService { | |||||
| private final SeasonService seasonService; | |||||
| private final PlayerService playerService; | |||||
| public PlayerNavigationService(@Autowired PlayerService playerService, | |||||
| @Autowired SeasonService seasonService) { | |||||
| this.playerService = playerService; | |||||
| this.seasonService = seasonService; | |||||
| } | |||||
| public PlayerNavigation getNewNavigation() { | |||||
| return new PlayerNavigation(playerService, seasonService); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,25 @@ | |||||
| package app.navigation.player; | |||||
| import com.vaadin.flow.router.WildcardParameter; | |||||
| import org.springframework.lang.NonNull; | |||||
| import java.util.HashMap; | |||||
| import java.util.Map; | |||||
| import java.util.Optional; | |||||
| class PlayerNavigationUtils { | |||||
| private PlayerNavigationUtils() { | |||||
| } | |||||
| @NonNull | |||||
| static Map<PlayerNavigationLevel, Optional<String>> getParameterMap(@WildcardParameter String param) { | |||||
| Map<PlayerNavigationLevel, Optional<String>> map = new HashMap<>(); | |||||
| String[] params = param.split("/"); | |||||
| if (params.length >= 1) map.put(PlayerNavigationLevel.PLAYER, Optional.of(params[0])); | |||||
| if (params.length >= 2) map.put(PlayerNavigationLevel.SEASON, Optional.of(params[1])); | |||||
| map.putIfAbsent(PlayerNavigationLevel.PLAYER, Optional.empty()); | |||||
| map.putIfAbsent(PlayerNavigationLevel.SEASON, Optional.empty()); | |||||
| return map; | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,31 @@ | |||||
| package app.navigation.player.components; | |||||
| import app.navigation.player.PlayerNavigation; | |||||
| import com.vaadin.flow.component.html.Label; | |||||
| import com.vaadin.flow.component.orderedlayout.HorizontalLayout; | |||||
| public class PlayerNavigationHeader extends HorizontalLayout { | |||||
| private final PlayerNavigation playerNavigation; | |||||
| private final Label seasonLabel = new Label("Season:"); | |||||
| private final Label playerLabel = new Label("Player:"); | |||||
| public PlayerNavigationHeader(PlayerNavigation playerNavigation) { | |||||
| this.playerNavigation = playerNavigation; | |||||
| defineLayout(); | |||||
| configureChildren(); | |||||
| } | |||||
| private void defineLayout() { | |||||
| setWidthFull(); | |||||
| setAlignItems(Alignment.CENTER); | |||||
| setJustifyContentMode(JustifyContentMode.END); | |||||
| } | |||||
| private void configureChildren() { | |||||
| removeAll(); | |||||
| add(playerLabel, playerNavigation.getPlayerSelect()); | |||||
| add(seasonLabel, playerNavigation.getSeasonSelect()); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,37 @@ | |||||
| package app.navigation.player.components.button; | |||||
| import app.data.entity.Player; | |||||
| import app.navigation.player.PlayerNavigation; | |||||
| import com.vaadin.flow.component.button.Button; | |||||
| import com.vaadin.flow.component.icon.VaadinIcon; | |||||
| import java.util.List; | |||||
| import java.util.Optional; | |||||
| public class NextPlayerButton extends Button { | |||||
| private final PlayerNavigation playerNavigation; | |||||
| public NextPlayerButton(PlayerNavigation playerNavigation) { | |||||
| this.playerNavigation = playerNavigation; | |||||
| setIcon(VaadinIcon.ARROW_RIGHT.create()); | |||||
| playerNavigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Player> nextPlayer = getNextPlayer(); | |||||
| setEnabled(nextPlayer.isPresent()); | |||||
| addClickListener(event -> nextPlayer.ifPresent(player -> playerNavigation.getPlayerSelect().setValue(player))); | |||||
| } | |||||
| private Optional<Player> getNextPlayer() { | |||||
| int index = PlayerButtonUtils.getPlayerIndex(playerNavigation); | |||||
| if (index < 0) return Optional.empty(); | |||||
| List<Player> playerList = playerNavigation.getPlayerList(); | |||||
| return Optional.ofNullable(playerList.get((index + 1) % playerList.size())); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,14 @@ | |||||
| package app.navigation.player.components.button; | |||||
| import app.navigation.player.PlayerNavigation; | |||||
| import java.util.concurrent.atomic.AtomicInteger; | |||||
| class PlayerButtonUtils { | |||||
| static int getPlayerIndex(PlayerNavigation playerNavigation) { | |||||
| AtomicInteger index = new AtomicInteger(-1); | |||||
| playerNavigation.getSelectedPlayer().ifPresent(player -> index.set(playerNavigation.getPlayerList().indexOf(player))); | |||||
| return index.get(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,37 @@ | |||||
| package app.navigation.player.components.button; | |||||
| import app.data.entity.Player; | |||||
| import app.navigation.player.PlayerNavigation; | |||||
| import com.vaadin.flow.component.button.Button; | |||||
| import com.vaadin.flow.component.icon.VaadinIcon; | |||||
| import java.util.List; | |||||
| import java.util.Optional; | |||||
| public class PrevPlayerButton extends Button { | |||||
| private final PlayerNavigation playerNavigation; | |||||
| public PrevPlayerButton(PlayerNavigation playerNavigation) { | |||||
| this.playerNavigation = playerNavigation; | |||||
| setIcon(VaadinIcon.ARROW_LEFT.create()); | |||||
| playerNavigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Player> prevPlayer = getPrevPlayer(); | |||||
| setEnabled(prevPlayer.isPresent()); | |||||
| addClickListener(event -> prevPlayer.ifPresent(player -> playerNavigation.getPlayerSelect().setValue(player))); | |||||
| } | |||||
| private Optional<Player> getPrevPlayer() { | |||||
| int index = PlayerButtonUtils.getPlayerIndex(playerNavigation); | |||||
| if (index < 0) return Optional.empty(); | |||||
| List<Player> playerList = playerNavigation.getPlayerList(); | |||||
| return Optional.ofNullable(playerList.get((index - 1) % playerList.size())); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,8 @@ | |||||
| package app.navigation.regular; | |||||
| public enum RegularNavigationLevel { | |||||
| NONE, | |||||
| SEASON, | |||||
| MATCHDAY, | |||||
| MATCH | |||||
| } | |||||
| @ -0,0 +1,31 @@ | |||||
| package app.navigation.regular; | |||||
| import app.navigation.NavigationUtils; | |||||
| import com.vaadin.flow.router.WildcardParameter; | |||||
| import org.springframework.lang.NonNull; | |||||
| import java.util.HashMap; | |||||
| import java.util.Map; | |||||
| import java.util.Optional; | |||||
| class RegularNavigationUtils { | |||||
| private RegularNavigationUtils() { | |||||
| } | |||||
| @NonNull | |||||
| static Map<RegularNavigationLevel, Optional<String>> getParameterMap(@WildcardParameter String param) { | |||||
| Map<RegularNavigationLevel, Optional<String>> map = new HashMap<>(); | |||||
| String[] params = param.split("/"); | |||||
| if (params.length >= 1 && !params[0].equals(NavigationUtils.EDIT)) | |||||
| map.put(RegularNavigationLevel.SEASON, Optional.of(params[0])); | |||||
| if (params.length >= 2 && !params[1].equals(NavigationUtils.EDIT)) | |||||
| map.put(RegularNavigationLevel.MATCHDAY, Optional.of(params[1])); | |||||
| if (params.length >= 3 && !params[2].equals(NavigationUtils.EDIT)) | |||||
| map.put(RegularNavigationLevel.MATCH, Optional.of(params[2])); | |||||
| map.putIfAbsent(RegularNavigationLevel.MATCH, Optional.empty()); | |||||
| map.putIfAbsent(RegularNavigationLevel.MATCHDAY, Optional.empty()); | |||||
| map.putIfAbsent(RegularNavigationLevel.SEASON, Optional.empty()); | |||||
| return map; | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,40 @@ | |||||
| package app.navigation.regular.components; | |||||
| import app.navigation.regular.RegularNavigation; | |||||
| import com.vaadin.flow.component.html.Label; | |||||
| import com.vaadin.flow.component.orderedlayout.FlexComponent; | |||||
| import com.vaadin.flow.component.orderedlayout.HorizontalLayout; | |||||
| public class RegularNavigationHeader extends HorizontalLayout { | |||||
| private final RegularNavigation regularNavigation; | |||||
| private final Label seasonLabel = new Label("Season:"); | |||||
| private final Label matchdayLabel = new Label("Matchday:"); | |||||
| private final Label matchLabel = new Label("Match:"); | |||||
| public RegularNavigationHeader(RegularNavigation regularNavigation) { | |||||
| this.regularNavigation = regularNavigation; | |||||
| defineLayout(); | |||||
| configureChildren(); | |||||
| } | |||||
| private void defineLayout() { | |||||
| setWidthFull(); | |||||
| setAlignItems(FlexComponent.Alignment.CENTER); | |||||
| setJustifyContentMode(FlexComponent.JustifyContentMode.END); | |||||
| } | |||||
| private void configureChildren() { | |||||
| removeAll(); | |||||
| if (regularNavigation.seasonEnabled()) { | |||||
| add(seasonLabel, regularNavigation.getSeasonSelect()); | |||||
| } | |||||
| if (regularNavigation.matchdayEnabled()) { | |||||
| add(matchdayLabel, regularNavigation.getMatchdaySelect()); | |||||
| } | |||||
| if (regularNavigation.matchEnabled()) { | |||||
| add(matchLabel, regularNavigation.getMatchSelect()); | |||||
| } | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,14 @@ | |||||
| package app.navigation.regular.components.button; | |||||
| import app.navigation.regular.RegularNavigation; | |||||
| import java.util.concurrent.atomic.AtomicInteger; | |||||
| class MatchdayButtonUtils { | |||||
| static int getMatchdayIndex(RegularNavigation regularNavigation) { | |||||
| AtomicInteger index = new AtomicInteger(-1); | |||||
| regularNavigation.getSelectedMatchday().ifPresent(matchday -> index.set(regularNavigation.getMatchdayList().indexOf(matchday))); | |||||
| return index.get(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,37 @@ | |||||
| package app.navigation.regular.components.button; | |||||
| import app.data.entity.Matchday; | |||||
| import app.navigation.regular.RegularNavigation; | |||||
| 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 RegularNavigation regularNavigation; | |||||
| public NextMatchdayButton(RegularNavigation regularNavigation) { | |||||
| this.regularNavigation = regularNavigation; | |||||
| if (!regularNavigation.matchdayEnabled()) | |||||
| throw new IllegalStateException("Cannot instantiate NextMatchdayButton when Matchdays are not enabled!"); | |||||
| setIcon(VaadinIcon.ARROW_RIGHT.create()); | |||||
| regularNavigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Matchday> nextMatchday = getNextMatchday(); | |||||
| setEnabled(nextMatchday.isPresent()); | |||||
| addClickListener(event -> nextMatchday.ifPresent(matchday -> regularNavigation.getMatchdaySelect().setValue(matchday))); | |||||
| } | |||||
| private Optional<Matchday> getNextMatchday() { | |||||
| int index = MatchdayButtonUtils.getMatchdayIndex(regularNavigation); | |||||
| if (index >= 0 && index < regularNavigation.getMatchdayList().size() - 1) | |||||
| return Optional.ofNullable(regularNavigation.getMatchdayList().get(index + 1)); | |||||
| return Optional.empty(); | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,36 @@ | |||||
| package app.navigation.regular.components.button; | |||||
| import app.data.entity.Matchday; | |||||
| import app.navigation.regular.RegularNavigation; | |||||
| 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 RegularNavigation regularNavigation; | |||||
| public PrevMatchdayButton(RegularNavigation regularNavigation) { | |||||
| this.regularNavigation = regularNavigation; | |||||
| if (!regularNavigation.matchdayEnabled()) | |||||
| throw new IllegalStateException("Cannot instantiate PrevMatchdayButton when Matchdays are not enabled!"); | |||||
| setIcon(VaadinIcon.ARROW_LEFT.create()); | |||||
| regularNavigation.addRunnableToBeRunAfterSelection(this::configure); | |||||
| } | |||||
| private void configure() { | |||||
| Optional<Matchday> prevMatchday = getPrevMatchday(); | |||||
| setEnabled(prevMatchday.isPresent()); | |||||
| addClickListener(event -> prevMatchday.ifPresent(matchday -> regularNavigation.getMatchdaySelect().setValue(matchday))); | |||||
| } | |||||
| private Optional<Matchday> getPrevMatchday() { | |||||
| int index = MatchdayButtonUtils.getMatchdayIndex(regularNavigation); | |||||
| if (index > 0) return Optional.ofNullable(regularNavigation.getMatchdayList().get(index - 1)); | |||||
| return Optional.empty(); | |||||
| } | |||||
| } | |||||