From 8d36cbb6ac8b8b3362bf26b786ce6e3022939d98 Mon Sep 17 00:00:00 2001 From: BlackYps <52536103+BlackYps@users.noreply.github.com> Date: Fri, 31 May 2024 23:28:19 +0200 Subject: [PATCH] Leaderboard improvements (#3181) * Sort leaderboards by player size * Make season selectable * Make UI functional * Improve visuals * Fix test * Fix NPE in rendering thread --- .../leaderboard/LeaderboardController.java | 26 +++++++----- .../LeaderboardDistributionController.java | 1 + .../leaderboard/LeaderboardService.java | 15 ++----- .../leaderboard/LeaderboardsController.java | 15 ++++--- src/main/resources/i18n/messages.properties | 11 +---- .../theme/leaderboard/leaderboard.fxml | 5 ++- src/main/resources/theme/style.css | 41 +++++++++++++++---- .../LeaderboardControllerTest.java | 6 +-- .../leaderboard/LeaderboardServiceTest.java | 4 +- .../LeaderboardsControllerTest.java | 5 +-- 10 files changed, 74 insertions(+), 55 deletions(-) diff --git a/src/main/java/com/faforever/client/leaderboard/LeaderboardController.java b/src/main/java/com/faforever/client/leaderboard/LeaderboardController.java index 7b6dfbf067..428910897a 100644 --- a/src/main/java/com/faforever/client/leaderboard/LeaderboardController.java +++ b/src/main/java/com/faforever/client/leaderboard/LeaderboardController.java @@ -6,12 +6,15 @@ import com.faforever.client.fx.FxApplicationThreadExecutor; import com.faforever.client.fx.JavaFxUtil; import com.faforever.client.fx.NodeController; +import com.faforever.client.fx.ToStringOnlyConverter; import com.faforever.client.i18n.I18n; import com.faforever.client.notification.NotificationService; import com.faforever.client.player.PlayerService; import com.faforever.client.util.TimeService; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.layout.Pane; import javafx.scene.layout.StackPane; @@ -42,26 +45,29 @@ public class LeaderboardController extends NodeController { public StackPane leaderboardRoot; public Pane connectionProgressPane; public Pane contentPane; - public Label seasonLabel; public Label seasonDateLabel; + public ComboBox seasonPicker; public LeaderboardRankingsController leaderboardRankingsController; public LeaderboardPlayerDetailsController leaderboardPlayerDetailsController; public LeaderboardDistributionController leaderboardDistributionController; private final ObjectProperty leagueSeason = new SimpleObjectProperty<>(); + private final ObjectProperty> leagueSeasons = new SimpleObjectProperty<>(List.of()); @Override protected void onInitialize() { JavaFxUtil.bindManagedToVisible(contentPane, connectionProgressPane); connectionProgressPane.visibleProperty().bind(contentPane.visibleProperty().not()); - seasonLabel.textProperty() - .bind(leagueSeason.map(seasonBean -> i18n.getOrDefault(seasonBean.nameKey(), - "leaderboard.season.%s".formatted( - seasonBean.nameKey()), - seasonBean.seasonNumber())) - .map(String::toUpperCase) - .when(showing)); + seasonPicker.setConverter(new ToStringOnlyConverter<>( + seasonBean -> i18n.get("leaderboard.season", seasonBean.seasonNumber()))); + + leagueSeason.bind(seasonPicker.getSelectionModel().selectedItemProperty()); + + leagueSeasons.map(FXCollections::observableList).when(showing).subscribe(seasons -> { + seasonPicker.getItems().setAll(seasons); + seasonPicker.getSelectionModel().selectFirst(); + }); seasonDateLabel.textProperty().bind(leagueSeason.map(seasonBean -> { String startDate = timeService.asDate(seasonBean.startDate(), FormatStyle.MEDIUM); @@ -135,8 +141,8 @@ protected void onInitialize() { }); } - public void setLeagueSeason(LeagueSeason leagueSeason) { - this.leagueSeason.set(leagueSeason); + public void setLeagueSeasons(List leagueSeasons) { + this.leagueSeasons.set(leagueSeasons); } @Override diff --git a/src/main/java/com/faforever/client/leaderboard/LeaderboardDistributionController.java b/src/main/java/com/faforever/client/leaderboard/LeaderboardDistributionController.java index f6acb0004d..cd22db4e4b 100644 --- a/src/main/java/com/faforever/client/leaderboard/LeaderboardDistributionController.java +++ b/src/main/java/com/faforever/client/leaderboard/LeaderboardDistributionController.java @@ -122,6 +122,7 @@ private void updateSubdivisions(List subdivisions) { updateHighlightedSubdivision(); + ratingDistributionChart.setAnimated(false); ratingDistributionChart.setData(FXCollections.observableArrayList(series)); updateChartData(leagueEntries.getValue()); } diff --git a/src/main/java/com/faforever/client/leaderboard/LeaderboardService.java b/src/main/java/com/faforever/client/leaderboard/LeaderboardService.java index b66c5c1b0e..34fb91b064 100644 --- a/src/main/java/com/faforever/client/leaderboard/LeaderboardService.java +++ b/src/main/java/com/faforever/client/leaderboard/LeaderboardService.java @@ -102,26 +102,19 @@ public Flux getActiveSeasons() { } @Cacheable(value = CacheNames.LEAGUE, sync = true) - public Mono getLatestSeason(League league) { + public Flux getSeasons(League league) { ElideNavigatorOnCollection navigator = ElideNavigator.of( com.faforever.commons.api.dto.LeagueSeason.class) .collection() .setFilter( qBuilder().intNum( "league.id") - .eq(league.id()) - .and() - .instant( - "startDate") - .before( - OffsetDateTime.now() - .toInstant(), - false)) + .eq(league.id())) .addSortingRule( "startDate", false); - return fafApiAccessor.getMany(navigator) - .next().map(leaderboardMapper::map); + return fafApiAccessor.getMany(navigator).map(leaderboardMapper::map) + .cache(); } @Cacheable(value = CacheNames.LEAGUE_ENTRIES, sync = true) diff --git a/src/main/java/com/faforever/client/leaderboard/LeaderboardsController.java b/src/main/java/com/faforever/client/leaderboard/LeaderboardsController.java index 21cbb8c19d..f5bdf3dfd8 100644 --- a/src/main/java/com/faforever/client/leaderboard/LeaderboardsController.java +++ b/src/main/java/com/faforever/client/leaderboard/LeaderboardsController.java @@ -20,10 +20,12 @@ import org.springframework.stereotype.Component; import reactor.core.publisher.Mono; +import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.stream.Collectors; @Slf4j @@ -61,17 +63,17 @@ protected void onInitialize() { }); leaderboardService.getLeagues() - .map(league -> { + .collectSortedList(Comparator.comparing(League::technicalName)) + .map(leagues -> leagues.stream().map(league -> { String buttonText = i18n.getOrDefault(league.technicalName(), String.format("leaderboard.%s", - league.technicalName())); + league.technicalName())); ToggleButton toggleButton = new ToggleButton(buttonText); toggleButton.setToggleGroup(navigation); toggleButton.getStyleClass().add("main-navigation-button"); toggleLeagueMap.put(toggleButton, league); return toggleButton; - }) + }).collect(Collectors.toList())) .switchIfEmpty(Mono.error(new IllegalStateException("No leagues loaded"))) - .collectList() .doOnNext(leagueButtons -> { League lastLeagueTab = navigationHandler.getLastLeagueTab(); Toggle startingToggle = toggleLeagueMap.entrySet() @@ -93,9 +95,10 @@ protected void onInitialize() { private void setLeague(League league) { navigationHandler.setLastLeagueTab(league); - leaderboardService.getLatestSeason(league) + leaderboardService.getSeasons(league) + .collectList() .publishOn(fxApplicationThreadExecutor.asScheduler()) - .subscribe(leaderboardController::setLeagueSeason, throwable -> { + .subscribe(leaderboardController::setLeagueSeasons, throwable -> { log.error("Error while loading seasons", throwable); notificationService.addImmediateErrorNotification(throwable, "leaderboard.failedToLoadLeaderboards"); diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index de6d674f2d..557e057f88 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -1156,10 +1156,7 @@ leaderboard.1v1_league = 1v1 League leaderboard.2v2_league = 2v2 League leaderboard.4v4_full_share_league = 4v4 League leaderboard.4v4_share_until_death_league = 4v4 No Share League -leaderboard.season.1v1_league = 1v1 Season {0} -leaderboard.season.2v2_league = 2v2 Season {0} -leaderboard.season.4v4_fs_league = 4v4 Season {0} -leaderboard.season.4v4_sud_league = 4v4 Season {0} +leaderboard.season = Season {0} leaderboard.seasonDate = {0} – {1} leaderboard.searchPrompt = Search player leaderboard.score = Score @@ -1173,10 +1170,6 @@ leagues.divisionName.diamond = Diamond leagues.divisionName.master = Master leagues.divisionName.grandmaster = Grandmaster teammatchmaking.inPlacement = Unlisted -leaderboard.season.1v1_season = 1v1 Season {0} -leaderboard.season.2v2_season = 2v2 Season {0} -leaderboard.season.4v4_fs_season = 4v4 Season {0} -leaderboard.season.4v4_sud_season = 4v4 Season {0} chat.category.self = Me settings.data.mirrorURLs = Download Mirrors settings.data.mirrorURLs.description = Add any mirrors here that you wish to use for downloading game patches instead of downloading from the main FAF server. This can help prevent extremely long download times. @@ -1293,8 +1286,6 @@ ignoreWarning = Ignore warning replay.replayRunning = Replay could not be started because another replay is already running. teammatchmaking.queue.tmm3v3 = 3v3 leaderboard.3v3_league = 3v3 League -leaderboard.season.3v3_league = 3v3 Season {0} -leaderboard.season.3v3_season = 3v3 Season {0} teammatchmaking.couldNotStart = Could not join queues. See error details below. teammatchmaking.couldNotJoinQueue = Could not join queue {0}. See error details below. teammatchmaking.match.searching = Searching... diff --git a/src/main/resources/theme/leaderboard/leaderboard.fxml b/src/main/resources/theme/leaderboard/leaderboard.fxml index 3519777988..b760ad6be1 100644 --- a/src/main/resources/theme/leaderboard/leaderboard.fxml +++ b/src/main/resources/theme/leaderboard/leaderboard.fxml @@ -1,6 +1,7 @@ + @@ -14,10 +15,10 @@ -