Implement feature that allows to sort items in detail panel. (#259)

Apparently, because it is 2 years old request, it got in a state -> implement or drop.

Fixes #192
This commit is contained in:
BONNe 2022-06-16 17:07:09 +03:00 committed by GitHub
parent fcf6e76599
commit 4948689fe8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 304 additions and 15 deletions

View File

@ -63,6 +63,7 @@ public class DetailsPanel
// By default no-filters are active.
this.activeTab = Tab.ALL_BLOCKS;
this.activeFilter = Filter.NAME;
this.materialCountList = new ArrayList<>(Material.values().length);
this.updateFilters();
@ -101,6 +102,8 @@ public class DetailsPanel
panelBuilder.registerTypeBuilder("PREVIOUS", this::createPreviousButton);
panelBuilder.registerTypeBuilder("BLOCK", this::createMaterialButton);
panelBuilder.registerTypeBuilder("FILTER", this::createFilterButton);
// Register tabs
panelBuilder.registerTypeBuilder("TAB", this::createTabButton);
@ -152,6 +155,61 @@ public class DetailsPanel
}
}
Comparator<Pair<Material, Integer>> sorter;
switch (this.activeFilter)
{
case COUNT ->
{
sorter = (o1, o2) ->
{
if (o1.getValue().equals(o2.getValue()))
{
String o1Name = DetailsPanel.prettifyObject(o1.getKey(), this.user);
String o2Name = DetailsPanel.prettifyObject(o2.getKey(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
}
else
{
return Integer.compare(o2.getValue(), o1.getValue());
}
};
}
case VALUE ->
{
sorter = (o1, o2) ->
{
int o1Value = this.addon.getBlockConfig().getBlockValues().getOrDefault(o1.getKey(), 0);
int o2Value = this.addon.getBlockConfig().getBlockValues().getOrDefault(o2.getKey(), 0);
if (o1Value == o2Value)
{
String o1Name = DetailsPanel.prettifyObject(o1.getKey(), this.user);
String o2Name = DetailsPanel.prettifyObject(o2.getKey(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
}
else
{
return Integer.compare(o2Value, o1Value);
}
};
}
default ->
{
sorter = (o1, o2) ->
{
String o1Name = DetailsPanel.prettifyObject(o1.getKey(), this.user);
String o2Name = DetailsPanel.prettifyObject(o2.getKey(), this.user);
return String.CASE_INSENSITIVE_ORDER.compare(o1Name, o2Name);
};
}
}
this.materialCountList.sort(sorter);
this.pageIndex = 0;
}
@ -240,6 +298,117 @@ public class DetailsPanel
}
/**
* Create next button panel item.
*
* @param template the template
* @param slot the slot
* @return the panel item
*/
private PanelItem createFilterButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot)
{
PanelItemBuilder builder = new PanelItemBuilder();
if (template.icon() != null)
{
// Set icon
builder.icon(template.icon().clone());
}
Filter filter;
if (slot.amountMap().getOrDefault("FILTER", 0) > 1)
{
filter = Enums.getIfPresent(Filter.class, String.valueOf(template.dataMap().get("filter"))).or(Filter.NAME);
}
else
{
filter = this.activeFilter;
}
final String reference = "level.gui.buttons.filters.";
if (template.title() != null)
{
// Set title
builder.name(this.user.getTranslation(this.world, template.title().replace("[filter]", filter.name().toLowerCase())));
}
else
{
builder.name(this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".name"));
}
if (template.description() != null)
{
// Set description
builder.description(this.user.getTranslation(this.world, template.description().replace("[filter]", filter.name().toLowerCase())));
}
else
{
builder.name(this.user.getTranslation(this.world, reference + filter.name().toLowerCase() + ".description"));
}
// Get only possible actions, by removing all inactive ones.
List<ItemTemplateRecord.ActionRecords> activeActions = new ArrayList<>(template.actions());
// Add Click handler
builder.clickHandler((panel, user, clickType, i) ->
{
for (ItemTemplateRecord.ActionRecords action : activeActions)
{
if (clickType == action.clickType() || ClickType.UNKNOWN.equals(action.clickType()))
{
if ("UP".equalsIgnoreCase(action.actionType()))
{
this.activeFilter = Utils.getNextValue(Filter.values(), filter);
// Update filters.
this.updateFilters();
this.build();
}
else if ("DOWN".equalsIgnoreCase(action.actionType()))
{
this.activeFilter = Utils.getPreviousValue(Filter.values(), filter);
// Update filters.
this.updateFilters();
this.build();
}
else if ("SELECT".equalsIgnoreCase(action.actionType()))
{
this.activeFilter = filter;
// Update filters.
this.updateFilters();
this.build();
}
}
}
return true;
});
// Collect tooltips.
List<String> tooltips = activeActions.stream().
filter(action -> action.tooltip() != null).
map(action -> this.user.getTranslation(this.world, action.tooltip())).
filter(text -> !text.isBlank()).
collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size())));
// Add tooltips.
if (!tooltips.isEmpty())
{
// Empty line and tooltips.
builder.description("");
builder.description(tooltips);
}
builder.glow(this.activeFilter == filter);
return builder.build();
}
// ---------------------------------------------------------------------
// Section: Create common buttons
// ---------------------------------------------------------------------
@ -646,6 +815,26 @@ public class DetailsPanel
}
/**
* Sorting order of blocks.
*/
private enum Filter
{
/**
* By name
*/
NAME,
/**
* By value
*/
VALUE,
/**
* By number
*/
COUNT
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
@ -689,4 +878,9 @@ public class DetailsPanel
* This variable stores which tab currently is active.
*/
private Tab activeTab;
/**
* This variable stores active filter for items.
*/
private Filter activeFilter;
}

View File

@ -73,4 +73,62 @@ public class Utils
return defaultValue;
}
/**
* This method allows to get next value from array list after given value.
*
* @param values Array that should be searched for given value.
* @param currentValue Value which next element should be found.
* @param <T> Instance of given object.
* @return Next value after currentValue in values array.
*/
public static <T> T getNextValue(T[] values, T currentValue)
{
for (int i = 0; i < values.length; i++)
{
if (values[i].equals(currentValue))
{
if (i + 1 == values.length)
{
return values[0];
}
else
{
return values[i + 1];
}
}
}
return currentValue;
}
/**
* This method allows to get previous value from array list after given value.
*
* @param values Array that should be searched for given value.
* @param currentValue Value which previous element should be found.
* @param <T> Instance of given object.
* @return Previous value before currentValue in values array.
*/
public static <T> T getPreviousValue(T[] values, T currentValue)
{
for (int i = 0; i < values.length; i++)
{
if (values[i].equals(currentValue))
{
if (i > 0)
{
return values[i - 1];
}
else
{
return values[values.length - 1];
}
}
}
return currentValue;
}
}

View File

@ -123,6 +123,19 @@ level:
name: "&f&l Spawners"
description: |-
&7 Display only spawners.
filters:
name:
name: "&f&l Sort by Name"
description: |-
&7 Sort all blocks by name.
value:
name: "&f&l Sort by Value"
description: |-
&7 Sort all blocks by their value.
count:
name: "&f&l Sort by Count"
description: |-
&7 Sort all blocks by their amount.
# Button that is used in multi-page GUIs which allows to return to previous page.
previous:
name: "&f&l Previous Page"
@ -137,6 +150,9 @@ level:
click-to-view: "&e Click &7 to view."
click-to-previous: "&e Click &7 to view previous page."
click-to-next: "&e Click &7 to view next page."
click-to-select: "&e Click &7 to select."
left-click-to-cycle-up: "&e Left Click &7 to cycle up."
right-click-to-cycle-down: "&e Right Click &7 to cycle down."
conversations:
# Prefix for messages that are send from server.
prefix: "&l&6 [BentoBox]: &r"

View File

@ -19,7 +19,7 @@ detail_panel:
tab: ALL_BLOCKS
actions:
view:
click-type: unknwon
click-type: unknown
tooltip: level.gui.tips.click-to-view
3:
icon: GRASS_BLOCK
@ -30,7 +30,7 @@ detail_panel:
tab: ABOVE_SEA_LEVEL
actions:
view:
click-type: unknwon
click-type: unknown
tooltip: level.gui.tips.click-to-view
4:
icon: WATER_BUCKET
@ -41,7 +41,7 @@ detail_panel:
tab: UNDERWATER
actions:
view:
click-type: unknwon
click-type: unknown
tooltip: level.gui.tips.click-to-view
5:
icon: SPAWNER
@ -52,8 +52,29 @@ detail_panel:
tab: SPAWNER
actions:
view:
click-type: unknwon
click-type: unknown
tooltip: level.gui.tips.click-to-view
9:
# You can create multiple buttons. By default it is one.
icon: IRON_TRAPDOOR
# [filter] is placeholder for different filter types. It will be replaced with name, value, count.
title: level.gui.buttons.filters.[filter].name
description: level.gui.buttons.filters.[filter].description
data:
type: FILTER
# the value of filter button. Suggestion is to leave fist value to name if you use single button.
filter: NAME
actions:
up:
click-type: left
tooltip: level.gui.tips.left-click-to-cycle-up
down:
click-type: right
tooltip: level.gui.tips.right-click-to-cycle-down
# There is also select action. With it you can create multiple filter buttons.
# select:
# click-type: unknown
# tooltip: level.gui.tips.click-to-select
2:
2: material_button
3: material_button

View File

@ -11,7 +11,7 @@ top_panel:
content:
2:
5:
icon: PLAYER_HEAD
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
@ -22,7 +22,7 @@ top_panel:
title: level.gui.buttons.island.empty
3:
4:
icon: PLAYER_HEAD
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
@ -32,7 +32,7 @@ top_panel:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
6:
icon: PLAYER_HEAD
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
@ -43,7 +43,7 @@ top_panel:
title: level.gui.buttons.island.empty
4:
2:
icon: PLAYER_HEAD
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
@ -53,7 +53,7 @@ top_panel:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
3:
icon: PLAYER_HEAD
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
@ -63,7 +63,7 @@ top_panel:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
4:
icon: PLAYER_HEAD
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
@ -73,7 +73,7 @@ top_panel:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
5:
icon: PLAYER_HEAD
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
@ -83,7 +83,7 @@ top_panel:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
6:
icon: PLAYER_HEAD
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
@ -93,7 +93,7 @@ top_panel:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
7:
icon: PLAYER_HEAD
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
@ -103,7 +103,7 @@ top_panel:
icon: LIME_STAINED_GLASS_PANE
title: level.gui.buttons.island.empty
8:
icon: PLAYER_HEAD
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data:
@ -114,7 +114,7 @@ top_panel:
title: level.gui.buttons.island.empty
6:
5:
icon: PLAYER_HEAD
#icon: PLAYER_HEAD
title: level.gui.buttons.island.name
description: level.gui.buttons.island.description
data: