✨ Add pagination to MinecraftHelp, move minecraft-extras to it's own package
This commit is contained in:
parent
454ceb318a
commit
674507fea6
7 changed files with 440 additions and 92 deletions
|
|
@ -21,7 +21,7 @@
|
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
package cloud.commandframework;
|
||||
package cloud.commandframework.minecraft.extras;
|
||||
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2020 Alexander Söderberg & Contributors
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of 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 OR COPYRIGHT HOLDERS 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.
|
||||
//
|
||||
package cloud.commandframework.minecraft.extras;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
final class ComponentHelper {
|
||||
|
||||
public static final Pattern SPECIAL_CHARACTERS_PATTERN = Pattern.compile("[^\\s\\w\\-]");
|
||||
|
||||
private ComponentHelper() {
|
||||
}
|
||||
|
||||
public static @NonNull Component highlight(
|
||||
final @NonNull Component component,
|
||||
final @NonNull TextColor highlightColor
|
||||
) {
|
||||
return component.replaceText(
|
||||
SPECIAL_CHARACTERS_PATTERN,
|
||||
match -> match.color(highlightColor)
|
||||
);
|
||||
}
|
||||
|
||||
public static @NonNull Component repeat(
|
||||
final @NonNull Component component,
|
||||
final int repetitions
|
||||
) {
|
||||
final TextComponent.Builder builder = Component.text();
|
||||
for (int i = 0; i < repetitions; i++) {
|
||||
builder.append(component);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static int length(final @NonNull Component component) {
|
||||
int length = 0;
|
||||
if (component instanceof TextComponent) {
|
||||
length += ((TextComponent) component).content().length();
|
||||
}
|
||||
for (final Component child : component.children()) {
|
||||
length += length(child);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -21,8 +21,9 @@
|
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
package cloud.commandframework;
|
||||
package cloud.commandframework.minecraft.extras;
|
||||
|
||||
import cloud.commandframework.CommandManager;
|
||||
import cloud.commandframework.exceptions.ArgumentParseException;
|
||||
import cloud.commandframework.exceptions.InvalidCommandSenderException;
|
||||
import cloud.commandframework.exceptions.InvalidSyntaxException;
|
||||
|
|
@ -35,7 +36,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Exception handler that sends {@link Component} to the sender. All component builders
|
||||
|
|
@ -45,18 +45,19 @@ import java.util.regex.Pattern;
|
|||
*/
|
||||
public final class MinecraftExceptionHandler<C> {
|
||||
|
||||
private static final Pattern SPECIAL_CHARACTERS_PATTERN = Pattern.compile("[^\\s\\w\\-]");
|
||||
|
||||
/**
|
||||
* Default component builder for {@link InvalidSyntaxException}
|
||||
*/
|
||||
public static final Function<Exception, Component> DEFAULT_INVALID_SYNTAX_FUNCTION =
|
||||
e -> Component.text()
|
||||
.append(Component.text("Invalid command syntax. Correct command syntax is: ", NamedTextColor.RED))
|
||||
.append(Component.text(
|
||||
.append(ComponentHelper.highlight(
|
||||
Component.text(
|
||||
String.format("/%s", ((InvalidSyntaxException) e).getCorrectSyntax()),
|
||||
NamedTextColor.GRAY
|
||||
).replaceText(SPECIAL_CHARACTERS_PATTERN, match -> match.color(NamedTextColor.WHITE)))
|
||||
),
|
||||
NamedTextColor.WHITE
|
||||
))
|
||||
.build();
|
||||
/**
|
||||
* Default component builder for {@link InvalidCommandSenderException}
|
||||
|
|
@ -130,6 +131,19 @@ public final class MinecraftExceptionHandler<C> {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use all four of the default exception handlers
|
||||
*
|
||||
* @return {@code this}
|
||||
*/
|
||||
public @NonNull MinecraftExceptionHandler<C> withDefaultHandlers() {
|
||||
return this
|
||||
.withArgumentParsingHandler()
|
||||
.withInvalidSenderHandler()
|
||||
.withInvalidSyntaxHandler()
|
||||
.withNoPermissionHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify an exception handler
|
||||
*
|
||||
|
|
@ -21,8 +21,10 @@
|
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
package cloud.commandframework;
|
||||
package cloud.commandframework.minecraft.extras;
|
||||
|
||||
import cloud.commandframework.CommandHelpHandler;
|
||||
import cloud.commandframework.CommandManager;
|
||||
import cloud.commandframework.arguments.CommandArgument;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
|
@ -33,12 +35,13 @@ import net.kyori.adventure.text.format.TextColor;
|
|||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Opinionated extension of {@link CommandHelpHandler} for Minecraft
|
||||
|
|
@ -48,6 +51,12 @@ import java.util.regex.Pattern;
|
|||
@SuppressWarnings("unused")
|
||||
public final class MinecraftHelp<C> {
|
||||
|
||||
public static final int DEFAULT_HEADER_FOOTER_LENGTH = 46;
|
||||
public static final int DEFAULT_MAX_RESULTS_PER_PAGE = 6;
|
||||
|
||||
/**
|
||||
* The default color scheme for {@link MinecraftHelp}
|
||||
*/
|
||||
public static final HelpColors DEFAULT_HELP_COLORS = HelpColors.of(
|
||||
NamedTextColor.GOLD,
|
||||
NamedTextColor.GREEN,
|
||||
|
|
@ -56,26 +65,29 @@ public final class MinecraftHelp<C> {
|
|||
NamedTextColor.DARK_GRAY
|
||||
);
|
||||
|
||||
public static final String MESSAGE_HELP = "help";
|
||||
public static final String MESSAGE_HELP_TITLE = "help";
|
||||
public static final String MESSAGE_COMMAND = "command";
|
||||
public static final String MESSAGE_DESCRIPTION = "description";
|
||||
public static final String MESSAGE_NO_DESCRIPTION = "no_description";
|
||||
public static final String MESSAGE_ARGUMENTS = "arguments";
|
||||
public static final String MESSAGE_OPTIONAL = "optional";
|
||||
public static final String MESSAGE_UNKNOWN_HELP_TOPIC_TYPE = "unknown_help_topic_type";
|
||||
public static final String MESSAGE_SHOWING_RESULTS_FOR_QUERY = "showing_results_for_query";
|
||||
public static final String MESSAGE_NO_RESULTS_FOR_QUERY = "no_results_for_query";
|
||||
public static final String MESSAGE_AVAILABLE_COMMANDS = "available_commands";
|
||||
public static final String MESSAGE_CLICK_TO_SHOW_HELP = "click_to_show_help";
|
||||
|
||||
private static final Pattern SPECIAL_CHARACTERS_PATTERN = Pattern.compile("[^\\s\\w\\-]");
|
||||
public static final String MESSAGE_PAGE_OUT_OF_RANGE = "page_out_of_range";
|
||||
public static final String MESSAGE_CLICK_FOR_NEXT_PAGE = "click_for_next_page";
|
||||
public static final String MESSAGE_CLICK_FOR_PREVIOUS_PAGE = "click_for_previous_page";
|
||||
|
||||
private final AudienceProvider<C> audienceProvider;
|
||||
private final CommandManager<C> commandManager;
|
||||
private final String commandPrefix;
|
||||
private final Map<String, String> messageMap = new HashMap<>();
|
||||
|
||||
private HelpColors colors = DEFAULT_HELP_COLORS;
|
||||
private BiFunction<C, String, String> messageProvider = (sender, key) -> this.messageMap.get(key);
|
||||
private HelpColors colors = DEFAULT_HELP_COLORS;
|
||||
private int headerFooterLength = DEFAULT_HEADER_FOOTER_LENGTH;
|
||||
private int maxResultsPerPage = DEFAULT_MAX_RESULTS_PER_PAGE;
|
||||
|
||||
/**
|
||||
* Construct a new Minecraft help instance
|
||||
|
|
@ -94,16 +106,19 @@ public final class MinecraftHelp<C> {
|
|||
this.commandManager = commandManager;
|
||||
|
||||
/* Default Messages */
|
||||
this.messageMap.put(MESSAGE_HELP, "Help");
|
||||
this.messageMap.put(MESSAGE_HELP_TITLE, "Help");
|
||||
this.messageMap.put(MESSAGE_COMMAND, "Command");
|
||||
this.messageMap.put(MESSAGE_DESCRIPTION, "Description");
|
||||
this.messageMap.put(MESSAGE_NO_DESCRIPTION, "No description");
|
||||
this.messageMap.put(MESSAGE_ARGUMENTS, "Arguments");
|
||||
this.messageMap.put(MESSAGE_OPTIONAL, "Optional");
|
||||
this.messageMap.put(MESSAGE_UNKNOWN_HELP_TOPIC_TYPE, "Unknown help topic type");
|
||||
this.messageMap.put(MESSAGE_SHOWING_RESULTS_FOR_QUERY, "Showing search results for query");
|
||||
this.messageMap.put(MESSAGE_NO_RESULTS_FOR_QUERY, "No results for query");
|
||||
this.messageMap.put(MESSAGE_AVAILABLE_COMMANDS, "Available Commands");
|
||||
this.messageMap.put(MESSAGE_CLICK_TO_SHOW_HELP, "Click to show help for this command");
|
||||
this.messageMap.put(MESSAGE_PAGE_OUT_OF_RANGE, "Error: Page <page> is not in range. Must be in range [1, <max_pages>]");
|
||||
this.messageMap.put(MESSAGE_CLICK_FOR_NEXT_PAGE, "Click for next page");
|
||||
this.messageMap.put(MESSAGE_CLICK_FOR_PREVIOUS_PAGE, "Click for previous page");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -158,15 +173,6 @@ public final class MinecraftHelp<C> {
|
|||
this.messageProvider = messageProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the colors currently used for help messages.
|
||||
*
|
||||
* @return The current {@link HelpColors} for this {@link MinecraftHelp} instance
|
||||
*/
|
||||
public @NonNull HelpColors getHelpColors() {
|
||||
return this.colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the colors to use for help messages.
|
||||
*
|
||||
|
|
@ -177,108 +183,169 @@ public final class MinecraftHelp<C> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Query commands and send the results to the recipient
|
||||
* Set the length of the header/footer of help menus
|
||||
* <p>
|
||||
* Defaults to {@link MinecraftHelp#DEFAULT_HEADER_FOOTER_LENGTH}
|
||||
*
|
||||
* @param query Command query (without leading '/')
|
||||
* @param headerFooterLength The new length
|
||||
*/
|
||||
public void setHeaderFooterLength(final int headerFooterLength) {
|
||||
this.headerFooterLength = headerFooterLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum number of help results to display on one page
|
||||
* <p>
|
||||
* Defaults to {@link MinecraftHelp#DEFAULT_MAX_RESULTS_PER_PAGE}
|
||||
*
|
||||
* @param maxResultsPerPage The new value
|
||||
*/
|
||||
public void setMaxResultsPerPage(final int maxResultsPerPage) {
|
||||
this.maxResultsPerPage = maxResultsPerPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query commands and send the results to the recipient. Will respect permissions.
|
||||
*
|
||||
* @param rawQuery Command query (without leading '/', including optional page number)
|
||||
* @param recipient Recipient
|
||||
*/
|
||||
public void queryCommands(
|
||||
final @NonNull String query,
|
||||
final @NonNull String rawQuery,
|
||||
final @NonNull C recipient
|
||||
) {
|
||||
final String[] splitQuery = rawQuery.split(" ");
|
||||
int page;
|
||||
String query;
|
||||
try {
|
||||
final String pageText = splitQuery[splitQuery.length - 1];
|
||||
page = Integer.parseInt(pageText);
|
||||
query = rawQuery.substring(0, Math.max(rawQuery.lastIndexOf(pageText) - 1, 0));
|
||||
} catch (NumberFormatException e) {
|
||||
page = 1;
|
||||
query = rawQuery;
|
||||
}
|
||||
final Audience audience = this.getAudience(recipient);
|
||||
audience.sendMessage(this.line(13)
|
||||
.append(Component.text(" " + this.messageProvider.apply(recipient, MESSAGE_HELP) + " ", this.colors.highlight))
|
||||
.append(this.line(13))
|
||||
this.printTopic(
|
||||
recipient,
|
||||
query,
|
||||
page,
|
||||
this.commandManager.getCommandHelpHandler().queryHelp(recipient, query)
|
||||
);
|
||||
this.printTopic(recipient, query, this.commandManager.getCommandHelpHandler().queryHelp(recipient, query));
|
||||
audience.sendMessage(this.line(30));
|
||||
}
|
||||
|
||||
private void printTopic(
|
||||
final @NonNull C sender,
|
||||
final @NonNull String query,
|
||||
final int page,
|
||||
final CommandHelpHandler.@NonNull HelpTopic<C> helpTopic
|
||||
) {
|
||||
this.getAudience(sender).sendMessage(
|
||||
Component.text(this.messageProvider.apply(sender, MESSAGE_SHOWING_RESULTS_FOR_QUERY) + ": \"", this.colors.text)
|
||||
.append(this.highlight(Component.text("/" + query, this.colors.highlight)))
|
||||
.append(Component.text("\"", this.colors.text))
|
||||
);
|
||||
if (helpTopic instanceof CommandHelpHandler.IndexHelpTopic) {
|
||||
this.printIndexHelpTopic(sender, (CommandHelpHandler.IndexHelpTopic<C>) helpTopic);
|
||||
this.printIndexHelpTopic(sender, query, page, (CommandHelpHandler.IndexHelpTopic<C>) helpTopic);
|
||||
} else if (helpTopic instanceof CommandHelpHandler.MultiHelpTopic) {
|
||||
this.printMultiHelpTopic(sender, (CommandHelpHandler.MultiHelpTopic<C>) helpTopic);
|
||||
this.printMultiHelpTopic(sender, query, page, (CommandHelpHandler.MultiHelpTopic<C>) helpTopic);
|
||||
} else if (helpTopic instanceof CommandHelpHandler.VerboseHelpTopic) {
|
||||
this.printVerboseHelpTopic(sender, (CommandHelpHandler.VerboseHelpTopic<C>) helpTopic);
|
||||
this.printVerboseHelpTopic(sender, query, (CommandHelpHandler.VerboseHelpTopic<C>) helpTopic);
|
||||
} else {
|
||||
throw new IllegalArgumentException(this.messageProvider.apply(sender, MESSAGE_UNKNOWN_HELP_TOPIC_TYPE));
|
||||
throw new IllegalArgumentException("Unknown help topic type");
|
||||
}
|
||||
}
|
||||
|
||||
private void printIndexHelpTopic(
|
||||
final @NonNull C sender,
|
||||
final @NonNull String query,
|
||||
final int page,
|
||||
final CommandHelpHandler.@NonNull IndexHelpTopic<C> helpTopic
|
||||
) {
|
||||
final Audience audience = this.getAudience(sender);
|
||||
audience.sendMessage(lastBranch()
|
||||
if (helpTopic.isEmpty()) {
|
||||
audience.sendMessage(this.basicHeader(sender));
|
||||
audience.sendMessage(Component.text(
|
||||
this.messageProvider.apply(sender, MESSAGE_NO_RESULTS_FOR_QUERY) + ": \"",
|
||||
this.colors.text
|
||||
)
|
||||
.append(this.highlight(Component.text("/" + query, this.colors.highlight)))
|
||||
.append(Component.text("\"", this.colors.text)));
|
||||
audience.sendMessage(this.footer(sender));
|
||||
return;
|
||||
}
|
||||
new Pagination<CommandHelpHandler.VerboseHelpEntry<C>>(
|
||||
(currentPage, maxPages) -> {
|
||||
final List<Component> header = new ArrayList<>();
|
||||
header.add(this.paginatedHeader(sender, currentPage, maxPages));
|
||||
header.add(this.showingResults(sender, query));
|
||||
header.add(this.lastBranch()
|
||||
.append(Component.text(
|
||||
String.format(" %s:", this.messageProvider.apply(sender, MESSAGE_AVAILABLE_COMMANDS)),
|
||||
this.colors.text
|
||||
)));
|
||||
final Iterator<CommandHelpHandler.VerboseHelpEntry<C>> iterator = helpTopic.getEntries().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final CommandHelpHandler.VerboseHelpEntry<C> entry = iterator.next();
|
||||
|
||||
final String description = entry.getDescription().isEmpty()
|
||||
return header;
|
||||
},
|
||||
(helpEntry, isLastOfPage) -> {
|
||||
final String description = helpEntry.getDescription().isEmpty()
|
||||
? this.messageProvider.apply(sender, MESSAGE_CLICK_TO_SHOW_HELP)
|
||||
: entry.getDescription();
|
||||
: helpEntry.getDescription();
|
||||
|
||||
Component message = Component.text(" ")
|
||||
.append(iterator.hasNext() ? branch() : lastBranch())
|
||||
.append(this.highlight(Component.text(String.format(" /%s", entry.getSyntaxString()), this.colors.highlight))
|
||||
final boolean lastBranch =
|
||||
isLastOfPage || helpTopic.getEntries().indexOf(helpEntry) == helpTopic.getEntries().size() - 1;
|
||||
|
||||
return Component.text(" ")
|
||||
.append(lastBranch ? this.lastBranch() : this.branch())
|
||||
.append(this.highlight(Component.text(
|
||||
String.format(" /%s", helpEntry.getSyntaxString()),
|
||||
this.colors.highlight
|
||||
))
|
||||
.hoverEvent(Component.text(description, this.colors.text))
|
||||
.clickEvent(ClickEvent.runCommand(this.commandPrefix + ' ' + entry.getSyntaxString())));
|
||||
|
||||
audience.sendMessage(message);
|
||||
}
|
||||
.clickEvent(ClickEvent.runCommand(this.commandPrefix + " " + helpEntry.getSyntaxString())));
|
||||
},
|
||||
(currentPage, maxPages) -> this.paginatedFooter(sender, currentPage, maxPages, query),
|
||||
(attemptedPage, maxPages) -> this.pageOutOfRange(sender, attemptedPage, maxPages)
|
||||
).render(helpTopic.getEntries(), page, this.maxResultsPerPage).forEach(audience::sendMessage);
|
||||
}
|
||||
|
||||
private void printMultiHelpTopic(
|
||||
final @NonNull C sender,
|
||||
final @NonNull String query,
|
||||
final int page,
|
||||
final CommandHelpHandler.@NonNull MultiHelpTopic<C> helpTopic
|
||||
) {
|
||||
final Audience audience = this.getAudience(sender);
|
||||
audience.sendMessage(lastBranch()
|
||||
.append(this.highlight(Component.text(" /" + helpTopic.getLongestPath(), this.colors.highlight))));
|
||||
final int headerIndentation = helpTopic.getLongestPath().length();
|
||||
final Iterator<String> iterator = helpTopic.getChildSuggestions().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final String suggestion = iterator.next();
|
||||
new Pagination<String>(
|
||||
(currentPage, maxPages) -> {
|
||||
final List<Component> header = new ArrayList<>();
|
||||
header.add(this.paginatedHeader(sender, currentPage, maxPages));
|
||||
header.add(this.showingResults(sender, query));
|
||||
header.add(this.lastBranch()
|
||||
.append(this.highlight(Component.text(" /" + helpTopic.getLongestPath(), this.colors.highlight))));
|
||||
return header;
|
||||
},
|
||||
(suggestion, isLastOfPage) -> {
|
||||
final boolean lastBranch = isLastOfPage
|
||||
|| helpTopic.getChildSuggestions().indexOf(suggestion) == helpTopic.getChildSuggestions().size() - 1;
|
||||
|
||||
final StringBuilder indentation = new StringBuilder();
|
||||
for (int i = 0; i < headerIndentation; i++) {
|
||||
indentation.append(" ");
|
||||
}
|
||||
|
||||
audience.sendMessage(
|
||||
Component.text(indentation.toString())
|
||||
.append(iterator.hasNext() ? this.branch() : this.lastBranch())
|
||||
return ComponentHelper.repeat(Component.space(), headerIndentation)
|
||||
.append(lastBranch ? this.lastBranch() : this.branch())
|
||||
.append(this.highlight(Component.text(" /" + suggestion, this.colors.highlight))
|
||||
.hoverEvent(Component.text(
|
||||
this.messageProvider.apply(sender, MESSAGE_CLICK_TO_SHOW_HELP),
|
||||
this.colors.text
|
||||
))
|
||||
.clickEvent(ClickEvent.runCommand(this.commandPrefix + ' ' + suggestion)))
|
||||
);
|
||||
}
|
||||
.clickEvent(ClickEvent.runCommand(this.commandPrefix + " " + suggestion)));
|
||||
},
|
||||
(currentPage, maxPages) -> this.paginatedFooter(sender, currentPage, maxPages, query),
|
||||
(attemptedPage, maxPages) -> this.pageOutOfRange(sender, attemptedPage, maxPages)
|
||||
).render(helpTopic.getChildSuggestions(), page, this.maxResultsPerPage).forEach(audience::sendMessage);
|
||||
}
|
||||
|
||||
private void printVerboseHelpTopic(
|
||||
final @NonNull C sender,
|
||||
final @NonNull String query,
|
||||
final CommandHelpHandler.@NonNull VerboseHelpTopic<C> helpTopic
|
||||
) {
|
||||
final Audience audience = this.getAudience(sender);
|
||||
audience.sendMessage(this.basicHeader(sender));
|
||||
audience.sendMessage(this.showingResults(sender, query));
|
||||
final String command = this.commandManager.getCommandSyntaxFormatter()
|
||||
.apply(helpTopic.getCommand().getArguments(), null);
|
||||
audience.sendMessage(
|
||||
|
|
@ -342,14 +409,118 @@ public final class MinecraftHelp<C> {
|
|||
audience.sendMessage(component);
|
||||
}
|
||||
}
|
||||
audience.sendMessage(this.footer(sender));
|
||||
}
|
||||
|
||||
private @NonNull Component showingResults(
|
||||
final @NonNull C sender,
|
||||
final @NonNull String query
|
||||
) {
|
||||
return Component.text(this.messageProvider.apply(sender, MESSAGE_SHOWING_RESULTS_FOR_QUERY) + ": \"", this.colors.text)
|
||||
.append(this.highlight(Component.text("/" + query, this.colors.highlight)))
|
||||
.append(Component.text("\"", this.colors.text));
|
||||
}
|
||||
|
||||
private @NonNull Component button(
|
||||
final char icon,
|
||||
final @NonNull String command,
|
||||
final @NonNull String hoverText
|
||||
) {
|
||||
return Component.text()
|
||||
.append(Component.space())
|
||||
.append(Component.text('[', this.colors.accent))
|
||||
.append(Component.text(icon, this.colors.alternateHighlight))
|
||||
.append(Component.text(']', this.colors.accent))
|
||||
.append(Component.space())
|
||||
.clickEvent(ClickEvent.runCommand(command))
|
||||
.hoverEvent(Component.text(hoverText, this.colors.text))
|
||||
.build();
|
||||
}
|
||||
|
||||
private @NonNull Component footer(final @NonNull C sender) {
|
||||
return this.paginatedFooter(sender, 1, 1, "");
|
||||
}
|
||||
|
||||
private @NonNull Component paginatedFooter(
|
||||
final @NonNull C sender,
|
||||
final int currentPage,
|
||||
final int maxPages,
|
||||
final @NonNull String query
|
||||
) {
|
||||
final boolean firstPage = currentPage == 1;
|
||||
final boolean lastPage = currentPage == maxPages;
|
||||
|
||||
if (firstPage && lastPage) {
|
||||
return this.line(this.headerFooterLength);
|
||||
}
|
||||
|
||||
final String nextPageCommand = String.format("%s %s %s", this.commandPrefix, query, currentPage + 1);
|
||||
final Component nextPageButton = this.button('→', nextPageCommand,
|
||||
this.messageProvider.apply(sender, MESSAGE_CLICK_FOR_NEXT_PAGE)
|
||||
);
|
||||
if (firstPage) {
|
||||
return this.header(sender, nextPageButton);
|
||||
}
|
||||
|
||||
final String previousPageCommand = String.format("%s %s %s", this.commandPrefix, query, currentPage - 1);
|
||||
final Component previousPageButton = this.button('←', previousPageCommand,
|
||||
this.messageProvider.apply(sender, MESSAGE_CLICK_FOR_PREVIOUS_PAGE)
|
||||
);
|
||||
if (lastPage) {
|
||||
return this.header(sender, previousPageButton);
|
||||
}
|
||||
|
||||
final Component buttons = Component.text()
|
||||
.append(previousPageButton)
|
||||
.append(this.line(3))
|
||||
.append(nextPageButton).build();
|
||||
return this.header(sender, buttons);
|
||||
}
|
||||
|
||||
private @NonNull Component header(
|
||||
final @NonNull C sender,
|
||||
final @NonNull Component title
|
||||
) {
|
||||
final int sideLength = (this.headerFooterLength - ComponentHelper.length(title)) / 2;
|
||||
return Component.text()
|
||||
.append(this.line(sideLength))
|
||||
.append(title)
|
||||
.append(this.line(sideLength))
|
||||
.build();
|
||||
}
|
||||
|
||||
private @NonNull Component basicHeader(final @NonNull C sender) {
|
||||
return this.header(sender, Component.text(
|
||||
" " + this.messageProvider.apply(sender, MESSAGE_HELP_TITLE) + " ",
|
||||
this.colors.highlight
|
||||
));
|
||||
}
|
||||
|
||||
private @NonNull Component paginatedHeader(
|
||||
final @NonNull C sender,
|
||||
final int currentPage,
|
||||
final int pages
|
||||
) {
|
||||
return this.header(sender, Component.text()
|
||||
.append(Component.text(
|
||||
" " + this.messageProvider.apply(sender, MESSAGE_HELP_TITLE) + " ",
|
||||
this.colors.highlight
|
||||
))
|
||||
.append(Component.text("(", this.colors.alternateHighlight))
|
||||
.append(Component.text(currentPage, this.colors.text))
|
||||
.append(Component.text("/", this.colors.alternateHighlight))
|
||||
.append(Component.text(pages, this.colors.text))
|
||||
.append(Component.text(")", this.colors.alternateHighlight))
|
||||
.append(Component.space())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private @NonNull Component line(final int length) {
|
||||
final TextComponent.Builder line = Component.text();
|
||||
for (int i = 0; i < length; i++) {
|
||||
line.append(Component.text("-", this.colors.primary, TextDecoration.STRIKETHROUGH));
|
||||
}
|
||||
return line.build();
|
||||
return ComponentHelper.repeat(
|
||||
Component.text("-", this.colors.primary, TextDecoration.STRIKETHROUGH),
|
||||
length
|
||||
);
|
||||
}
|
||||
|
||||
private @NonNull Component branch() {
|
||||
|
|
@ -361,14 +532,26 @@ public final class MinecraftHelp<C> {
|
|||
}
|
||||
|
||||
private @NonNull Component highlight(final @NonNull Component component) {
|
||||
return component.replaceText(
|
||||
SPECIAL_CHARACTERS_PATTERN,
|
||||
match -> match.color(this.colors.alternateHighlight)
|
||||
return ComponentHelper.highlight(component, this.colors.alternateHighlight);
|
||||
}
|
||||
|
||||
private @NonNull Component pageOutOfRange(
|
||||
final @NonNull C sender,
|
||||
final int attemptedPage,
|
||||
final int maxPages
|
||||
) {
|
||||
return this.highlight(
|
||||
Component.text(
|
||||
this.messageProvider.apply(sender, MESSAGE_PAGE_OUT_OF_RANGE)
|
||||
.replace("<page>", String.valueOf(attemptedPage))
|
||||
.replace("<max_pages>", String.valueOf(maxPages)),
|
||||
this.colors.text
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for holding the {@link TextColor}s used for help menus
|
||||
* Class for holding the {@link TextColor TextColors} used for help menus
|
||||
*/
|
||||
public static final class HelpColors {
|
||||
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// MIT License
|
||||
//
|
||||
// Copyright (c) 2020 Alexander Söderberg & Contributors
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of 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 OR COPYRIGHT HOLDERS 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.
|
||||
//
|
||||
package cloud.commandframework.minecraft.extras;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
final class Pagination<T> {
|
||||
|
||||
private final BiFunction<Integer, Integer, List<Component>> headerRenderer;
|
||||
private final BiFunction<T, Boolean, Component> rowRenderer;
|
||||
private final BiFunction<Integer, Integer, Component> footerRenderer;
|
||||
private final BiFunction<Integer, Integer, Component> outOfRangeRenderer;
|
||||
|
||||
Pagination(
|
||||
final @NonNull BiFunction<Integer, Integer, List<Component>> headerRenderer,
|
||||
final @NonNull BiFunction<T, Boolean, Component> rowRenderer,
|
||||
final @NonNull BiFunction<Integer, Integer, Component> footerRenderer,
|
||||
final @NonNull BiFunction<Integer, Integer, Component> outOfRangeRenderer
|
||||
) {
|
||||
this.headerRenderer = headerRenderer;
|
||||
this.rowRenderer = rowRenderer;
|
||||
this.footerRenderer = footerRenderer;
|
||||
this.outOfRangeRenderer = outOfRangeRenderer;
|
||||
}
|
||||
|
||||
public @NonNull List<Component> render(
|
||||
final @NonNull List<T> content,
|
||||
final int page,
|
||||
final int itemsPerPage
|
||||
) {
|
||||
final int pages = (int) Math.ceil(content.size() / (itemsPerPage * 1.00));
|
||||
if (page < 1 || page > pages) {
|
||||
return Collections.singletonList(outOfRangeRenderer.apply(page, pages));
|
||||
}
|
||||
|
||||
final List<Component> renderedContent = new ArrayList<>(this.headerRenderer.apply(page, pages));
|
||||
|
||||
final int start = itemsPerPage * (page - 1);
|
||||
final int maxIndex = (start + itemsPerPage);
|
||||
for (int index = start; index < maxIndex; index++) {
|
||||
if (index > content.size() - 1) {
|
||||
break;
|
||||
}
|
||||
renderedContent.add(this.rowRenderer.apply(content.get(index), index == maxIndex - 1));
|
||||
}
|
||||
|
||||
renderedContent.add(this.footerRenderer.apply(page, pages));
|
||||
|
||||
return renderedContent;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -25,4 +25,4 @@
|
|||
/**
|
||||
* Minecraft extras
|
||||
*/
|
||||
package cloud.commandframework;
|
||||
package cloud.commandframework.minecraft.extras;
|
||||
|
|
@ -26,8 +26,8 @@ package cloud.commandframework.examples.bukkit;
|
|||
import cloud.commandframework.Command;
|
||||
import cloud.commandframework.CommandTree;
|
||||
import cloud.commandframework.Description;
|
||||
import cloud.commandframework.MinecraftExceptionHandler;
|
||||
import cloud.commandframework.MinecraftHelp;
|
||||
import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler;
|
||||
import cloud.commandframework.minecraft.extras.MinecraftHelp;
|
||||
import cloud.commandframework.annotations.AnnotationParser;
|
||||
import cloud.commandframework.annotations.Argument;
|
||||
import cloud.commandframework.annotations.CommandDescription;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue