mirror of
https://github.com/Sheldan/abstracto.git
synced 2026-01-01 15:28:35 +00:00
[AB-xxx] adding feature to focus on a particular user in both leaderboard command and leaderboard UI
adding button to view the specified user on loaderboard at the rank command
This commit is contained in:
@@ -1,15 +1,26 @@
|
||||
package dev.sheldan.abstracto.experience.api;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.ServerUser;
|
||||
import dev.sheldan.abstracto.core.models.database.AServer;
|
||||
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
|
||||
import dev.sheldan.abstracto.core.models.frontend.RoleDisplay;
|
||||
import dev.sheldan.abstracto.core.models.frontend.UserDisplay;
|
||||
import dev.sheldan.abstracto.core.service.GuildService;
|
||||
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
|
||||
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
|
||||
import dev.sheldan.abstracto.experience.model.api.UserExperienceDisplay;
|
||||
import dev.sheldan.abstracto.experience.model.database.AExperienceLevel;
|
||||
import dev.sheldan.abstracto.experience.model.database.AExperienceRole;
|
||||
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
|
||||
import dev.sheldan.abstracto.experience.model.database.LeaderBoardEntryResult;
|
||||
import dev.sheldan.abstracto.experience.service.ExperienceLevelService;
|
||||
import dev.sheldan.abstracto.experience.service.management.ExperienceLevelManagementService;
|
||||
import dev.sheldan.abstracto.experience.service.management.ExperienceRoleManagementService;
|
||||
import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import net.dv8tion.jda.api.entities.Guild;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.entities.Role;
|
||||
@@ -23,6 +34,7 @@ import org.springframework.data.web.SortDefault;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@@ -41,9 +53,18 @@ public class LeaderboardController {
|
||||
@Autowired
|
||||
private ExperienceLevelService experienceLevelService;
|
||||
|
||||
@Autowired
|
||||
private UserInServerManagementService userInServerManagementService;
|
||||
|
||||
@Autowired
|
||||
private ExperienceRoleManagementService experienceRoleManagementService;
|
||||
|
||||
@Autowired
|
||||
private ExperienceLevelManagementService experienceLevelManagementService;
|
||||
|
||||
@GetMapping(value = "/leaderboards/{serverId}", produces = "application/json")
|
||||
public Page<UserExperienceDisplay> getLeaderboard(@PathVariable("serverId") Long serverId,
|
||||
@PageableDefault(value = 25, page = 0)
|
||||
@PageableDefault(value = 50, page = 0)
|
||||
@SortDefault(sort = "experience", direction = Sort.Direction.DESC)
|
||||
Pageable pageable) {
|
||||
AServer server = serverManagementService.loadServer(serverId);
|
||||
@@ -53,6 +74,23 @@ public class LeaderboardController {
|
||||
.map(userExperience -> convertFromUser(guild, userExperience, pageable, allElements));
|
||||
}
|
||||
|
||||
@GetMapping(value = "/leaderboards/{serverId}/{userId}", produces = "application/json")
|
||||
public List<UserExperienceDisplay> getLeaderboardForUser(@PathVariable("serverId") Long serverId, @PathVariable("userId") Long userId,
|
||||
@RequestParam("windowSize") Integer windowSize) {
|
||||
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(ServerUser.fromId(serverId, userId));
|
||||
Map<Long, AExperienceRole> experienceRolesForServer = experienceRoleManagementService.getExperienceRolesForServer(aUserInAServer.getServerReference())
|
||||
.stream()
|
||||
.collect(Collectors.toMap(AExperienceRole::getId, Function.identity()));
|
||||
|
||||
Map<Integer, AExperienceLevel> levels =
|
||||
experienceLevelManagementService.getLevelConfig().stream().collect(Collectors.toMap(AExperienceLevel::getLevel, Function.identity()));
|
||||
Guild guild = guildService.getGuildById(serverId);
|
||||
List<LeaderBoardEntryResult> allElements = userExperienceManagementService.getWindowedLeaderboardEntriesForUser(aUserInAServer, windowSize);
|
||||
return allElements.stream()
|
||||
.map(leaderboardEntry -> convertFromLeaderboardEntry(guild, leaderboardEntry, experienceRolesForServer, levels))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private UserExperienceDisplay convertFromUser(Guild guild, AUserExperience aUserExperience, Pageable pageable, Page<AUserExperience> page) {
|
||||
Long userId = aUserExperience.getUser().getUserReference().getId();
|
||||
Member member = guild.getMember(UserSnowflake.fromId(userId));
|
||||
@@ -92,4 +130,46 @@ public class LeaderboardController {
|
||||
.build();
|
||||
}
|
||||
|
||||
private UserExperienceDisplay convertFromLeaderboardEntry(Guild guild, LeaderBoardEntryResult leaderBoardEntryResult,
|
||||
Map<Long, AExperienceRole> experienceRolesForServer, Map<Integer, AExperienceLevel> levels) {
|
||||
Long userId = leaderBoardEntryResult.getUserId();
|
||||
Member member = guild.getMember(UserSnowflake.fromId(userId));
|
||||
UserDisplay userDisplay = null;
|
||||
RoleDisplay roleDisplay = null;
|
||||
Long experienceNeededToNextLevel = experienceLevelService.calculateExperienceToNextLevel(leaderBoardEntryResult.getLevel(),
|
||||
leaderBoardEntryResult.getExperience());
|
||||
AExperienceLevel currentExperienceLevel = levels.get(leaderBoardEntryResult.getLevel());
|
||||
Long nextLevelExperience = experienceLevelService.calculateNextLevel(leaderBoardEntryResult.getLevel()).getExperienceNeeded();
|
||||
if(experienceRolesForServer.containsKey(leaderBoardEntryResult.getRoleId())) {
|
||||
AExperienceRole experienceRole = experienceRolesForServer.get(leaderBoardEntryResult.getRoleId());
|
||||
Role role = guild.getRoleById(experienceRole.getRole().getId());
|
||||
if(role != null) {
|
||||
roleDisplay = RoleDisplay.fromRole(role);
|
||||
} else {
|
||||
roleDisplay = RoleDisplay.fromARole(experienceRole.getRole());
|
||||
}
|
||||
}
|
||||
if(member != null) {
|
||||
userDisplay = UserDisplay.fromMember(member);
|
||||
}
|
||||
Long currentExpNeeded = currentExperienceLevel.getExperienceNeeded();
|
||||
Long experienceWithinLevel = leaderBoardEntryResult.getExperience() - currentExpNeeded;
|
||||
Long experienceNeededForCurrentLevel = nextLevelExperience - currentExpNeeded;
|
||||
return UserExperienceDisplay
|
||||
.builder()
|
||||
.id(String.valueOf(userId))
|
||||
.messages(leaderBoardEntryResult.getMessageCount())
|
||||
.level(leaderBoardEntryResult.getLevel())
|
||||
.rank(leaderBoardEntryResult.getRank())
|
||||
.experience(leaderBoardEntryResult.getExperience())
|
||||
.experienceToNextLevel(experienceNeededToNextLevel)
|
||||
.currentLevelExperienceNeeded(experienceNeededForCurrentLevel)
|
||||
.experienceOnCurrentLevel(experienceWithinLevel)
|
||||
.percentage(((float) experienceWithinLevel / experienceNeededForCurrentLevel) * 100)
|
||||
.nextLevelExperienceNeeded(nextLevelExperience)
|
||||
.role(roleDisplay)
|
||||
.member(userDisplay)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
package dev.sheldan.abstracto.experience.command;
|
||||
|
||||
import dev.sheldan.abstracto.core.command.condition.AbstractConditionableCommand;
|
||||
import dev.sheldan.abstracto.core.command.config.*;
|
||||
import dev.sheldan.abstracto.core.command.config.CommandConfiguration;
|
||||
import dev.sheldan.abstracto.core.command.config.HelpInfo;
|
||||
import dev.sheldan.abstracto.core.command.config.Parameter;
|
||||
import dev.sheldan.abstracto.core.command.config.ParameterValidator;
|
||||
import dev.sheldan.abstracto.core.command.config.validator.MinIntegerValueValidator;
|
||||
import dev.sheldan.abstracto.core.command.execution.CommandContext;
|
||||
import dev.sheldan.abstracto.core.command.execution.CommandResult;
|
||||
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
|
||||
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
|
||||
import dev.sheldan.abstracto.core.config.FeatureDefinition;
|
||||
import dev.sheldan.abstracto.core.interaction.InteractionService;
|
||||
import dev.sheldan.abstracto.core.interaction.slash.SlashCommandConfig;
|
||||
import dev.sheldan.abstracto.core.interaction.slash.parameter.SlashCommandParameterService;
|
||||
import dev.sheldan.abstracto.core.models.database.AServer;
|
||||
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
|
||||
import dev.sheldan.abstracto.core.service.ChannelService;
|
||||
import dev.sheldan.abstracto.core.service.management.ServerManagementService;
|
||||
import dev.sheldan.abstracto.core.service.management.UserInServerManagementService;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import dev.sheldan.abstracto.core.utils.FutureUtils;
|
||||
import dev.sheldan.abstracto.experience.config.ExperienceFeatureDefinition;
|
||||
import dev.sheldan.abstracto.experience.config.ExperienceSlashCommandNames;
|
||||
@@ -23,8 +28,10 @@ import dev.sheldan.abstracto.experience.model.LeaderBoardEntry;
|
||||
import dev.sheldan.abstracto.experience.model.template.LeaderBoardEntryModel;
|
||||
import dev.sheldan.abstracto.experience.model.template.LeaderBoardModel;
|
||||
import dev.sheldan.abstracto.experience.service.AUserExperienceService;
|
||||
import dev.sheldan.abstracto.core.templating.model.MessageToSend;
|
||||
import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
@@ -33,11 +40,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Shows the experience gain information of the top 10 users in the server, or if a page number is provided as a parameter, only the members which are on this page.
|
||||
*/
|
||||
@@ -48,6 +50,7 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
|
||||
public static final String LEADER_BOARD_POST_EMBED_TEMPLATE = "leaderboard_post";
|
||||
private static final String LEDERBOARD_COMMAND_NAME = "leaderboard";
|
||||
private static final String PAGE_PARAMETER = "page";
|
||||
private static final String FOCUS_PARAMETER = "focus";
|
||||
@Autowired
|
||||
private AUserExperienceService userExperienceService;
|
||||
|
||||
@@ -80,21 +83,26 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
|
||||
List<Object> parameters = commandContext.getParameters().getParameters();
|
||||
// parameter is optional, in case its not present, we default to the 0th page
|
||||
Integer page = !parameters.isEmpty() ? (Integer) parameters.get(0) : 1;
|
||||
return getMessageToSend(commandContext.getAuthor(), page)
|
||||
return getMessageToSend(commandContext.getAuthor(), page, false)
|
||||
.thenCompose(messageToSend -> FutureUtils.toSingleFutureGeneric(channelService.sendMessageToSendToChannel(messageToSend, commandContext.getChannel())))
|
||||
.thenApply(aVoid -> CommandResult.fromIgnored());
|
||||
}
|
||||
|
||||
private CompletableFuture<MessageToSend> getMessageToSend(Member actorUser, Integer page) {
|
||||
private CompletableFuture<MessageToSend> getMessageToSend(Member actorUser, Integer page, boolean focusMe) {
|
||||
AServer server = serverManagementService.loadServer(actorUser.getGuild());
|
||||
LeaderBoard leaderBoard = userExperienceService.findLeaderBoardData(server, page);
|
||||
LeaderBoard leaderBoard;
|
||||
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(actorUser);
|
||||
if (focusMe) {
|
||||
leaderBoard = userExperienceService.findLeaderBoardDataForUserFocus(aUserInAServer);
|
||||
} else {
|
||||
leaderBoard = userExperienceService.findLeaderBoardData(server, page);
|
||||
}
|
||||
List<CompletableFuture> futures = new ArrayList<>();
|
||||
CompletableFuture<List<LeaderBoardEntryModel>> completableFutures = converter.fromLeaderBoard(leaderBoard);
|
||||
CompletableFuture<List<LeaderBoardEntryModel>> completableFutures = converter.fromLeaderBoard(leaderBoard, actorUser.getGuild().getIdLong());
|
||||
futures.add(completableFutures);
|
||||
log.info("Rendering leaderboard for page {} in server {} for user {}.", page, actorUser.getId(), actorUser.getGuild().getId());
|
||||
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(actorUser);
|
||||
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
|
||||
CompletableFuture<List<LeaderBoardEntryModel>> userRankFuture = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
|
||||
CompletableFuture<List<LeaderBoardEntryModel>> userRankFuture = converter.fromLeaderBoardEntry(Arrays.asList(userRank), actorUser.getGuild().getIdLong());
|
||||
futures.add(userRankFuture);
|
||||
String leaderboardUrl;
|
||||
if(!StringUtils.isBlank(leaderboardExternalURL)) {
|
||||
@@ -108,6 +116,7 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
|
||||
.builder()
|
||||
.userExperiences(finalModels)
|
||||
.leaderboardUrl(leaderboardUrl)
|
||||
.showPlacement(!focusMe)
|
||||
.userExecuting(userRankFuture.join().get(0))
|
||||
.build();
|
||||
return CompletableFuture.completedFuture(templateService.renderEmbedTemplate(LEADER_BOARD_POST_EMBED_TEMPLATE, leaderBoardModel, actorUser.getGuild().getIdLong()));
|
||||
@@ -118,12 +127,18 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
|
||||
@Override
|
||||
public CompletableFuture<CommandResult> executeSlash(SlashCommandInteractionEvent event) {
|
||||
Integer page;
|
||||
boolean focusMe;
|
||||
if (slashCommandParameterService.hasCommandOption(FOCUS_PARAMETER, event)) {
|
||||
focusMe = slashCommandParameterService.getCommandOption(FOCUS_PARAMETER, event, Boolean.class);
|
||||
} else {
|
||||
focusMe = false;
|
||||
}
|
||||
if(slashCommandParameterService.hasCommandOption(PAGE_PARAMETER, event)) {
|
||||
page = slashCommandParameterService.getCommandOption(PAGE_PARAMETER, event, Integer.class);
|
||||
} else {
|
||||
page = 1;
|
||||
}
|
||||
return getMessageToSend(event.getMember(), page)
|
||||
return getMessageToSend(event.getMember(), page, focusMe)
|
||||
.thenCompose(messageToSend -> interactionService.replyMessageToSend(messageToSend, event))
|
||||
.thenApply(aVoid -> CommandResult.fromIgnored());
|
||||
}
|
||||
@@ -139,7 +154,17 @@ public class LeaderBoardCommand extends AbstractConditionableCommand {
|
||||
.templated(true)
|
||||
.type(Integer.class)
|
||||
.build();
|
||||
List<Parameter> parameters = Arrays.asList(pageParameter);
|
||||
|
||||
Parameter focusMe = Parameter
|
||||
.builder()
|
||||
.name(FOCUS_PARAMETER)
|
||||
.validators(leaderBoardPageValidators)
|
||||
.optional(true)
|
||||
.slashCommandOnly(true)
|
||||
.templated(true)
|
||||
.type(Boolean.class)
|
||||
.build();
|
||||
List<Parameter> parameters = Arrays.asList(pageParameter, focusMe);
|
||||
HelpInfo helpInfo = HelpInfo
|
||||
.builder()
|
||||
.templated(true)
|
||||
|
||||
@@ -30,7 +30,9 @@ import dev.sheldan.abstracto.core.templating.service.TemplateService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -78,6 +80,9 @@ public class Rank extends AbstractConditionableCommand {
|
||||
@Autowired
|
||||
private InteractionService interactionService;
|
||||
|
||||
@Value("${abstracto.experience.leaderboard.externalUrl}")
|
||||
private String leaderboardExternalURL;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CommandResult> executeAsync(CommandContext commandContext) {
|
||||
List<Object> parameters = commandContext.getParameters().getParameters();
|
||||
@@ -87,7 +92,7 @@ public class Rank extends AbstractConditionableCommand {
|
||||
}
|
||||
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(targetMember);
|
||||
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
|
||||
CompletableFuture<List<LeaderBoardEntryModel>> future = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
|
||||
CompletableFuture<List<LeaderBoardEntryModel>> future = converter.fromLeaderBoardEntry(Arrays.asList(userRank), commandContext.getGuild().getIdLong());
|
||||
RankModel rankModel = RankModel
|
||||
.builder()
|
||||
.member(targetMember)
|
||||
@@ -115,6 +120,13 @@ public class Rank extends AbstractConditionableCommand {
|
||||
rankModel.setExperienceToNextLevel(experienceNeededToNextLevel);
|
||||
rankModel.setInLevelExperience(experienceWithinLevel);
|
||||
rankModel.setNextLevelExperience(nextLevelExperience);
|
||||
String leaderboardUrl;
|
||||
if(!StringUtils.isBlank(leaderboardExternalURL)) {
|
||||
leaderboardUrl = String.format("%s/experience/leaderboards/%s/%s", leaderboardExternalURL, toRender.getGuild().getIdLong(), toRender.getId());
|
||||
} else {
|
||||
leaderboardUrl = null;
|
||||
}
|
||||
rankModel.setLeaderboardUrl(leaderboardUrl);
|
||||
return templateService.renderEmbedTemplate(RANK_POST_EMBED_TEMPLATE, rankModel, toRender.getGuild().getIdLong());
|
||||
}
|
||||
|
||||
@@ -128,7 +140,7 @@ public class Rank extends AbstractConditionableCommand {
|
||||
}
|
||||
AUserInAServer aUserInAServer = userInServerManagementService.loadOrCreateUser(targetMember);
|
||||
LeaderBoardEntry userRank = userExperienceService.getRankOfUserInServer(aUserInAServer);
|
||||
CompletableFuture<List<LeaderBoardEntryModel>> future = converter.fromLeaderBoardEntry(Arrays.asList(userRank));
|
||||
CompletableFuture<List<LeaderBoardEntryModel>> future = converter.fromLeaderBoardEntry(Arrays.asList(userRank), event.getGuild().getIdLong());
|
||||
RankModel rankModel = RankModel
|
||||
.builder()
|
||||
.member(targetMember)
|
||||
|
||||
@@ -3,7 +3,6 @@ package dev.sheldan.abstracto.experience.converter;
|
||||
import dev.sheldan.abstracto.core.service.MemberService;
|
||||
import dev.sheldan.abstracto.experience.model.LeaderBoard;
|
||||
import dev.sheldan.abstracto.experience.model.LeaderBoardEntry;
|
||||
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
|
||||
import dev.sheldan.abstracto.experience.model.template.LeaderBoardEntryModel;
|
||||
import dev.sheldan.abstracto.experience.service.management.UserExperienceManagementService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -42,26 +41,23 @@ public class LeaderBoardModelConverter {
|
||||
* @return The list of {@link LeaderBoardEntryModel leaderboarEntryModels} which contain the fully fledged information provided to the
|
||||
* leader board template
|
||||
*/
|
||||
public CompletableFuture<List<LeaderBoardEntryModel>> fromLeaderBoard(LeaderBoard leaderBoard) {
|
||||
public CompletableFuture<List<LeaderBoardEntryModel>> fromLeaderBoard(LeaderBoard leaderBoard, Long serverId) {
|
||||
log.debug("Converting {} entries to a list of leaderboard entries.", leaderBoard.getEntries().size());
|
||||
return fromLeaderBoardEntry(leaderBoard.getEntries());
|
||||
return fromLeaderBoardEntry(leaderBoard.getEntries(), serverId);
|
||||
}
|
||||
|
||||
public CompletableFuture<List<LeaderBoardEntryModel>> fromLeaderBoardEntry(List<LeaderBoardEntry> leaderBoardEntries) {
|
||||
public CompletableFuture<List<LeaderBoardEntryModel>> fromLeaderBoardEntry(List<LeaderBoardEntry> leaderBoardEntries, Long serverId) {
|
||||
List<Long> userIds = new ArrayList<>();
|
||||
Long serverId = leaderBoardEntries.get(0).getExperience().getServer().getId();
|
||||
Map<Long, LeaderBoardEntryModel> models = leaderBoardEntries
|
||||
.stream()
|
||||
.map(leaderBoardEntry -> {
|
||||
AUserExperience experience = leaderBoardEntry.getExperience();
|
||||
Long userId = experience.getUser().getUserReference().getId();
|
||||
userIds.add(userId);
|
||||
userIds.add(leaderBoardEntry.getUserId());
|
||||
return LeaderBoardEntryModel
|
||||
.builder()
|
||||
.userId(userId)
|
||||
.experience(experience.getExperience())
|
||||
.messageCount(experience.getMessageCount())
|
||||
.level(experience.getLevelOrDefault())
|
||||
.userId(leaderBoardEntry.getUserId())
|
||||
.experience(leaderBoardEntry.getExperience())
|
||||
.messageCount(leaderBoardEntry.getMessageCount())
|
||||
.level(leaderBoardEntry.getLevel())
|
||||
.rank(leaderBoardEntry.getRank())
|
||||
.build();
|
||||
})
|
||||
|
||||
@@ -55,6 +55,33 @@ public interface UserExperienceRepository extends JpaRepository<AUserExperience
|
||||
"WHERE rank.id = :userInServerId", nativeQuery = true)
|
||||
LeaderBoardEntryResult getRankOfUserInServer(@Param("userInServerId") Long id, @Param("serverId") Long serverId);
|
||||
|
||||
@Query(value = "WITH user_experience_ranked AS" +
|
||||
" ( " +
|
||||
" SELECT us.id, uis.user_id, us.experience, us.role_id, us.level_id, us.message_count, ROW_NUMBER() OVER ( ORDER BY experience DESC ) as rank" +
|
||||
" FROM user_experience us INNER JOIN user_in_server uis ON us.id = uis.user_in_server_id " +
|
||||
" INNER JOIN server s ON s.id = uis.server_id WHERE s.id = :serverId" +
|
||||
" )," +
|
||||
" user_experience_target as (" +
|
||||
" SELECT ranking.id as id, ranking.user_id, ranking.experience as experience, ranking.message_count, ranking.level_id as level, ranking.rank " +
|
||||
" FROM user_experience_ranked ranking" +
|
||||
" WHERE ranking.id = :userInServerId" +
|
||||
" )" +
|
||||
" (SELECT r.id, r.user_id as userId, r.experience, r.level_id as level, r.role_id as roleId, r.message_count as messageCount, r.rank as rank" +
|
||||
" FROM user_experience_ranked r " +
|
||||
" CROSS JOIN user_experience_target t" +
|
||||
" WHERE r.rank <= t.rank " +
|
||||
" ORDER BY r.rank DESC " +
|
||||
" LIMIT :before) " +
|
||||
"UNION ALL " +
|
||||
" (SELECT r.id, r.user_id as userId, r.experience, r.level_id as level, r.role_id as roleId, r.message_count as messageCount, r.rank as rank" +
|
||||
" FROM user_experience_ranked r" +
|
||||
" CROSS JOIN user_experience_target t" +
|
||||
" WHERE r.rank > t.rank " +
|
||||
" ORDER BY r.rank " +
|
||||
" LIMIT :after) " +
|
||||
"ORDER BY rank", nativeQuery = true)
|
||||
List<LeaderBoardEntryResult> getRankOfUserWithWindow(@Param("userInServerId") Long id, @Param("serverId") Long serverId, @Param("before") Long beforeInclusive, @Param("after") Long after);
|
||||
|
||||
@Modifying(clearAutomatically = true)
|
||||
@Query("update AUserExperience u set u.currentExperienceRole = null where u.currentExperienceRole.id = :roleId")
|
||||
void removeExperienceRoleFromUsers(@Param("roleId") Long experienceRoleId);
|
||||
|
||||
@@ -491,11 +491,32 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
|
||||
int pageOffset = page * pageSize;
|
||||
for (int i = 0; i < experiences.size(); i++) {
|
||||
AUserExperience userExperience = experiences.get(i);
|
||||
entries.add(LeaderBoardEntry.builder().experience(userExperience).rank(pageOffset + i + 1).build());
|
||||
LeaderBoardEntry entry = LeaderBoardEntry.fromAUserExperience(userExperience);
|
||||
entry.setRank(pageOffset + i + 1);
|
||||
entries.add(entry);
|
||||
}
|
||||
return LeaderBoard.builder().entries(entries).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeaderBoard findLeaderBoardDataForUserFocus(AUserInAServer aUserInAServer) {
|
||||
List<LeaderBoardEntry> allEntries =
|
||||
userExperienceManagementService.getWindowedLeaderboardEntriesForUser(aUserInAServer, 10)
|
||||
.stream().map(leaderBoardEntryResult -> LeaderBoardEntry
|
||||
.builder()
|
||||
.experience(leaderBoardEntryResult.getExperience())
|
||||
.level(leaderBoardEntryResult.getLevel())
|
||||
.userId(leaderBoardEntryResult.getUserId())
|
||||
.messageCount(leaderBoardEntryResult.getMessageCount())
|
||||
.rank(leaderBoardEntryResult.getRank())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
return LeaderBoard
|
||||
.builder()
|
||||
.entries(allEntries)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeaderBoardEntry getRankOfUserInServer(AUserInAServer userInAServer) {
|
||||
log.debug("Retrieving rank for {}", userInAServer.getUserReference().getId());
|
||||
@@ -509,7 +530,9 @@ public class AUserExperienceServiceBean implements AUserExperienceService {
|
||||
if(rankOfUserInServer != null) {
|
||||
rank = rankOfUserInServer.getRank();
|
||||
}
|
||||
return LeaderBoardEntry.builder().experience(aUserExperience).rank(rank).build();
|
||||
LeaderBoardEntry entry = LeaderBoardEntry.fromAUserExperience(aUserExperience);
|
||||
entry.setRank(rank);
|
||||
return entry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -82,6 +82,12 @@ public class UserExperienceManagementServiceBean implements UserExperienceManage
|
||||
return repository.findTop10ByUser_ServerReferenceOrderByExperienceDesc(aServer, PageRequest.of(page, size));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LeaderBoardEntryResult> getWindowedLeaderboardEntriesForUser(AUserInAServer aUserInAServer, Integer windowSize) {
|
||||
return repository.getRankOfUserWithWindow(aUserInAServer.getUserInServerId(), aUserInAServer.getServerReference().getId(), windowSize.longValue() / 2 + 1,
|
||||
windowSize.longValue() / 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LeaderBoardEntryResult getRankOfUserInServer(AUserExperience userExperience) {
|
||||
return repository.getRankOfUserInServer(userExperience.getId(), userExperience.getServer().getId());
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package dev.sheldan.abstracto.experience.converter;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.database.AServer;
|
||||
import dev.sheldan.abstracto.core.models.database.AUser;
|
||||
import dev.sheldan.abstracto.core.models.database.AUserInAServer;
|
||||
import dev.sheldan.abstracto.core.service.MemberService;
|
||||
import dev.sheldan.abstracto.experience.model.LeaderBoard;
|
||||
import dev.sheldan.abstracto.experience.model.LeaderBoardEntry;
|
||||
import dev.sheldan.abstracto.experience.model.database.AUserExperience;
|
||||
import dev.sheldan.abstracto.experience.model.template.LeaderBoardEntryModel;
|
||||
import net.dv8tion.jda.api.entities.Member;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class LeaderBoardModelConverterTest {
|
||||
|
||||
@InjectMocks
|
||||
public LeaderBoardModelConverter testUnit;
|
||||
|
||||
@Mock
|
||||
private MemberService memberService;
|
||||
|
||||
@Mock
|
||||
private LeaderBoardModelConverter self;
|
||||
|
||||
private static final Long SERVER_ID = 4L;
|
||||
private static final Long USER_ID = 5L;
|
||||
private static final Long USER_ID_2 = 6L;
|
||||
private static final Long USER_IN_SERVER_ID = 7L;
|
||||
private static final Long USER_IN_SERVER_ID_2 = 8L;
|
||||
private static final Long EXPERIENCE = 9L;
|
||||
private static final Long MESSAGES = 10L;
|
||||
private static final Integer LEVEL = 54;
|
||||
|
||||
@Test
|
||||
public void testFromLeaderBoard() {
|
||||
Integer firstRank = 1;
|
||||
|
||||
LeaderBoardEntry entry = getEntry(firstRank, USER_ID, USER_IN_SERVER_ID);
|
||||
Integer secondRank = 2;
|
||||
LeaderBoardEntry entry2 = getEntry(secondRank, USER_ID_2, USER_IN_SERVER_ID_2);
|
||||
List<LeaderBoardEntry> entries = Arrays.asList(entry, entry2);
|
||||
LeaderBoard leaderBoard = Mockito.mock(LeaderBoard.class);
|
||||
when(leaderBoard.getEntries()).thenReturn(entries);
|
||||
Member member = Mockito.mock(Member.class);
|
||||
when(member.getIdLong()).thenReturn(USER_ID);
|
||||
when(memberService.getMembersInServerAsync(SERVER_ID, Arrays.asList(USER_ID, USER_ID_2))).thenReturn(CompletableFuture.completedFuture(Arrays.asList(member)));
|
||||
CompletableFuture<List<LeaderBoardEntryModel>> leaderBoardEntryModels = testUnit.fromLeaderBoard(leaderBoard);
|
||||
LeaderBoardEntryModel firstEntry = leaderBoardEntryModels.join().get(0);
|
||||
Assert.assertEquals(USER_ID, firstEntry.getUserId());
|
||||
LeaderBoardEntryModel secondEntry = leaderBoardEntryModels.join().get(1);
|
||||
Assert.assertEquals(USER_ID_2, secondEntry.getUserId());
|
||||
Assert.assertEquals(entries.size(), leaderBoardEntryModels.join().size());
|
||||
}
|
||||
|
||||
private LeaderBoardEntry getEntry(Integer rank, Long userId, Long userInServerId) {
|
||||
AUserExperience experience = Mockito.mock(AUserExperience.class);
|
||||
when(experience.getMessageCount()).thenReturn(MESSAGES);
|
||||
when(experience.getExperience()).thenReturn(EXPERIENCE);
|
||||
AUserInAServer userInAServer = Mockito.mock(AUserInAServer.class);
|
||||
when(experience.getUser()).thenReturn(userInAServer);
|
||||
AUser user = Mockito.mock(AUser.class);
|
||||
when(userInAServer.getUserReference()).thenReturn(user);
|
||||
when(user.getId()).thenReturn(userId);
|
||||
AServer server = Mockito.mock(AServer.class);
|
||||
when(experience.getServer()).thenReturn(server);
|
||||
when(server.getId()).thenReturn(SERVER_ID);
|
||||
LeaderBoardEntry entry = Mockito.mock(LeaderBoardEntry.class);
|
||||
when(entry.getRank()).thenReturn(rank);
|
||||
when(entry.getExperience()).thenReturn(experience);
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -12,12 +12,19 @@ import lombok.Setter;
|
||||
@Setter
|
||||
@Builder
|
||||
public class LeaderBoardEntry {
|
||||
/**
|
||||
* Object representing the current experience status of a user in a guild.
|
||||
*/
|
||||
private AUserExperience experience;
|
||||
/**
|
||||
* The rank this user has in the respective guild.
|
||||
*/
|
||||
private Long userId;
|
||||
private Integer level;
|
||||
private Long experience;
|
||||
private Long messageCount;
|
||||
private Integer rank;
|
||||
|
||||
public static LeaderBoardEntry fromAUserExperience(AUserExperience aUserExperience) {
|
||||
return LeaderBoardEntry
|
||||
.builder()
|
||||
.experience(aUserExperience.getExperience())
|
||||
.userId(aUserExperience.getUser().getUserReference().getId())
|
||||
.messageCount(aUserExperience.getMessageCount())
|
||||
.level(aUserExperience.getLevelOrDefault())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,8 @@ public interface LeaderBoardEntryResult {
|
||||
|
||||
Long getId();
|
||||
|
||||
/**
|
||||
* The {@link dev.sheldan.abstracto.core.models.database.AUserInAServer} id of the user
|
||||
* @return The ID of the user in a server
|
||||
*/
|
||||
Long getUserInServerId();
|
||||
Long getUserId();
|
||||
Long getRoleId();
|
||||
|
||||
/**
|
||||
* The experience of the {@link dev.sheldan.abstracto.core.models.database.AUserInAServer}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package dev.sheldan.abstracto.experience.model.template;
|
||||
|
||||
import dev.sheldan.abstracto.core.models.context.SlimUserInitiatedServerContext;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
@@ -25,4 +26,9 @@ public class LeaderBoardModel extends SlimUserInitiatedServerContext {
|
||||
*/
|
||||
private LeaderBoardEntryModel userExecuting;
|
||||
private String leaderboardUrl;
|
||||
/**
|
||||
* Whether to show the users own placement
|
||||
*/
|
||||
@Builder.Default
|
||||
private boolean showPlacement = true;
|
||||
}
|
||||
|
||||
@@ -47,4 +47,5 @@ public class RankModel {
|
||||
* The member to show the rank for
|
||||
*/
|
||||
private Member member;
|
||||
private String leaderboardUrl;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ public interface AUserExperienceService {
|
||||
* from the desired page
|
||||
*/
|
||||
LeaderBoard findLeaderBoardData(AServer server, Integer page);
|
||||
LeaderBoard findLeaderBoardDataForUserFocus(AUserInAServer aUserInAServer);
|
||||
|
||||
/**
|
||||
* Retrieves the {@link LeaderBoardEntry} from a specific {@link AUserInAServer} containing information about the
|
||||
|
||||
@@ -62,6 +62,7 @@ public interface UserExperienceManagementService {
|
||||
* @return A list desc ordered by {@link AUserExperience} experience only containing the elements between {@code start} and @{code end}
|
||||
*/
|
||||
List<AUserExperience> findLeaderBoardUsersPaginated(AServer server, Integer page, Integer size);
|
||||
List<LeaderBoardEntryResult> getWindowedLeaderboardEntriesForUser(AUserInAServer aUserInAServer, Integer windowSize);
|
||||
|
||||
/**
|
||||
* Returns the {@link LeaderBoardEntryResult} of the given {@link AUserExperience}.
|
||||
|
||||
@@ -13,9 +13,16 @@ leaderboard_url = f'http://{backend_host}:{backend_port}/experience/v1/leaderboa
|
||||
@app.route('/experience/v1/leaderboards/<serverId>')
|
||||
def get_leaderboard(serverId):
|
||||
page = int(request.args.get('page', 0, type=int))
|
||||
size = int(request.args.get('size', 25, type=int))
|
||||
size = int(request.args.get('size', 50, type=int))
|
||||
leaderboard = requests.get(f'{leaderboard_url}/{serverId}?page={page}&size={size}')
|
||||
logging.info(f'returning leaderboard for server')
|
||||
logging.info(f'returning leaderboard for server {serverId}')
|
||||
return leaderboard.text, leaderboard.status_code
|
||||
|
||||
@app.route('/experience/v1/leaderboards/<serverId>/<userId>')
|
||||
def get_leaderboard_for_user(serverId, userId):
|
||||
windowSize = int(request.args.get('windowSize', 50, type=int))
|
||||
leaderboard = requests.get(f'{leaderboard_url}/{serverId}/{userId}?windowSize={windowSize}')
|
||||
logging.info(f'returning leaderboard for server {serverId} for user {userId}')
|
||||
return leaderboard.text, leaderboard.status_code
|
||||
|
||||
@app.route('/experience/v1/leaderboards/<serverId>/config')
|
||||
@@ -28,3 +35,7 @@ def get_experience_config(serverId):
|
||||
@app.route('/experience/leaderboards/<serverId>')
|
||||
def render_index(serverId):
|
||||
return render_template('experience/leaderboards/index.html', serverId=serverId)
|
||||
|
||||
@app.route('/experience/leaderboards/<serverId>/<userId>')
|
||||
def render_index_for_user(serverId, userId):
|
||||
return render_template('experience/leaderboards/index.html', serverId=serverId, userId=userId)
|
||||
@@ -8,7 +8,7 @@
|
||||
name="description"
|
||||
content="Leaderboard for experience"
|
||||
/>
|
||||
<script>window.serverId={{ serverId }}n</script>
|
||||
<script>window.serverId={{ serverId }}n; window.userId = {{ userId }}</script>
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<title>Experience leaderboard</title>
|
||||
</head>
|
||||
|
||||
@@ -5,10 +5,12 @@ import {Leaderboard} from "./components/Leaderboard";
|
||||
function App() {
|
||||
// @ts-ignore
|
||||
const serverId: bigint = window.serverId
|
||||
// @ts-ignore
|
||||
const userId: bigint = window.userId
|
||||
return (
|
||||
<>
|
||||
<div className="bg-slate-700 bg-cover min-h-screen">
|
||||
<Leaderboard serverId={serverId}/>
|
||||
<Leaderboard serverId={serverId} userId={userId}/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -4,26 +4,87 @@ import {ExperienceMember, GuildInfo} from "../data/leaderboard";
|
||||
import {ExperienceConfigDisplay} from "./ExperienceConfigDisplay";
|
||||
import {ErrorDisplay} from "./ErrorDisplay";
|
||||
|
||||
export function Leaderboard({serverId}: { serverId: bigint }) {
|
||||
export function Leaderboard({serverId, userId}: { serverId: bigint, userId: bigint }) {
|
||||
|
||||
const pageSize = 25;
|
||||
const pageSize = 50;
|
||||
const windowSize = 10;
|
||||
|
||||
const [members, setMembers] = useState<ExperienceMember[]>([])
|
||||
const [memberCount, setMemberCount] = useState(0)
|
||||
const [pageCount, setPageCount] = useState(0)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [pageCountEnd, setPageCountEnd] = useState(0)
|
||||
const [pageCountStart, setPageCountStart] = useState(0)
|
||||
const [pageOffsetEnd, setPageOffsetEnd] = useState(0)
|
||||
const [pageOffsetStart, setPageOffsetStart] = useState(0)
|
||||
const [hasMoreAfterwards, setHasMoreAfterwards] = useState(true)
|
||||
const [hasMoreBefore, setHasMoreBefore] = useState(true)
|
||||
const [userSpecific, setUserSpecific] = useState(false)
|
||||
const [hasError, setError] = useState(false)
|
||||
const [guildInfo, setGuildInfo] = useState<GuildInfo>({} as GuildInfo)
|
||||
|
||||
async function loadLeaderboard(page: number, size: number) {
|
||||
async function loadLeaderboardForGuild(page: number, size: number, takeStart: number, skipStart: number, addStart: boolean) {
|
||||
try {
|
||||
const leaderboardResponse = await fetch(`/experience/v1/leaderboards/${serverId}?page=${page}&size=${size}`)
|
||||
const leaderboardJson = await leaderboardResponse.json()
|
||||
const loadedMembers: Array<ExperienceMember> = leaderboardJson.content;
|
||||
let loadedMembers: ExperienceMember[] = leaderboardJson.content;
|
||||
if(takeStart !== 0) {
|
||||
loadedMembers = loadedMembers.slice(0, takeStart)
|
||||
}
|
||||
if(skipStart !== 0) {
|
||||
loadedMembers = loadedMembers.slice(skipStart, loadedMembers.length)
|
||||
}
|
||||
setMemberCount(memberCount + loadedMembers.length)
|
||||
setHasMore(!leaderboardJson.last)
|
||||
setPageCount(page)
|
||||
if(hasMoreBefore) {
|
||||
setHasMoreBefore(!leaderboardJson.first)
|
||||
}
|
||||
if(hasMoreAfterwards) {
|
||||
setHasMoreAfterwards(!leaderboardJson.last)
|
||||
}
|
||||
if(addStart) {
|
||||
members.unshift(... loadedMembers)
|
||||
setMembers(members)
|
||||
} else {
|
||||
setMembers(members.concat(loadedMembers))
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
setError(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function loadLeaderboardForUser(userId: bigint, windowSize: number) {
|
||||
try {
|
||||
const leaderboardResponse = await fetch(`/experience/v1/leaderboards/${serverId}/${userId}?windowSize=${windowSize}`)
|
||||
const loadedMembers: Array<ExperienceMember> = await leaderboardResponse.json();
|
||||
setMemberCount(memberCount + loadedMembers.length)
|
||||
if(windowSize === loadedMembers.length) { // simple case, we got back the full package
|
||||
setHasMoreBefore(true)
|
||||
setHasMoreAfterwards(true)
|
||||
} else {
|
||||
const indexOfUser = loadedMembers.findIndex(value => value.id === userId.toString())
|
||||
if(indexOfUser < (windowSize / 2)) { // the user is in the upper half
|
||||
setHasMoreBefore(false)
|
||||
} else {
|
||||
setHasMoreBefore(true)
|
||||
}
|
||||
if((windowSize - indexOfUser) < (windowSize / 2)) { // not the full window was reached
|
||||
setHasMoreAfterwards(false)
|
||||
} else {
|
||||
setHasMoreAfterwards(true)
|
||||
}
|
||||
}
|
||||
setMembers(members.concat(loadedMembers))
|
||||
const lastRank = loadedMembers[loadedMembers.length -1].rank;
|
||||
let lastPage = Math.floor(lastRank / pageSize)
|
||||
const pageOffsetEnd = lastRank % pageSize
|
||||
setPageOffsetEnd(pageOffsetEnd) // this is how far we got in the last page, take everything starting here
|
||||
setPageCountEnd(lastPage) // this is the page the last entry is on, the next page we need to load
|
||||
|
||||
const firstRank = loadedMembers[0].rank;
|
||||
const firstPage = Math.floor(firstRank / pageSize)
|
||||
const pageOffsetStart = firstRank % pageSize - firstPage * pageSize - 1
|
||||
setPageOffsetStart(pageOffsetStart) // this is how many we want to use, starting from the top
|
||||
setPageCountStart(firstPage) // this the page we want to load
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
setError(true)
|
||||
@@ -42,16 +103,36 @@ export function Leaderboard({serverId}: { serverId: bigint }) {
|
||||
|
||||
useEffect(()=> {
|
||||
if(memberCount === 0) {
|
||||
loadLeaderboard(0, pageSize)
|
||||
if(userId === 0n) {
|
||||
loadLeaderboardForGuild(0, pageSize, pageSize, 0, false)
|
||||
} else {
|
||||
setUserSpecific(true)
|
||||
loadLeaderboardForUser(userId, windowSize)
|
||||
}
|
||||
}
|
||||
loadGuildInfo()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
},[])
|
||||
|
||||
function loadMore() {
|
||||
loadLeaderboard(pageCount + 1, pageSize)
|
||||
async function loadMore() {
|
||||
await loadLeaderboardForGuild(pageCountEnd + 1, pageSize, pageSize, 0, false)
|
||||
setPageCountEnd(pageCountEnd + 1)
|
||||
}
|
||||
|
||||
async function loadBefore() {
|
||||
await loadLeaderboardForGuild(pageCountStart, pageSize, pageOffsetStart != 0 ? pageOffsetStart : 0, 0, true)
|
||||
setPageOffsetStart(0)
|
||||
setPageCountStart(pageCountStart - 1)
|
||||
}
|
||||
|
||||
async function loadAfter() {
|
||||
await loadLeaderboardForGuild(pageCountEnd, pageSize, pageOffsetEnd != 0 ? 0 : pageSize, pageOffsetEnd, false)
|
||||
setPageOffsetEnd(0)
|
||||
setPageCountEnd(pageCountEnd + 1)
|
||||
}
|
||||
let loadMoreButton = <button className="w-full h-10 bg-gray-500 hover:bg-gray-700 text-white mt-4" onClick={loadMore}>Load more</button>;
|
||||
let loadBeforeButton = <button className="w-full h-10 bg-gray-500 hover:bg-gray-700 text-white mt-4" onClick={loadBefore}>Load before</button>;
|
||||
let loadAfterButton = <button className="w-full h-10 bg-gray-500 hover:bg-gray-700 text-white mt-4" onClick={loadAfter}>Load after</button>;
|
||||
return (
|
||||
<>
|
||||
{!hasError ?
|
||||
@@ -76,6 +157,7 @@ export function Leaderboard({serverId}: { serverId: bigint }) {
|
||||
<ExperienceConfigDisplay serverId={serverId}/>
|
||||
</div>
|
||||
<div className="text-sm text-left w-full mt-4">
|
||||
{hasMoreBefore && userSpecific ? loadBeforeButton : ''}
|
||||
<table className="w-full text-gray-400">
|
||||
<thead
|
||||
className="text-xs uppercase bg-gray-700 text-gray-400">
|
||||
@@ -101,7 +183,10 @@ export function Leaderboard({serverId}: { serverId: bigint }) {
|
||||
{members.map((member, index) => <LeaderboardEntry key={member.id} index={index} member={member}/>)}
|
||||
</tbody>
|
||||
</table>
|
||||
{hasMore ? loadMoreButton : ''}
|
||||
<div>
|
||||
{hasMoreAfterwards && !userSpecific ? loadMoreButton : ''}
|
||||
{hasMoreAfterwards && userSpecific ? loadAfterButton : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
|
||||
Reference in New Issue
Block a user