mirror of
https://github.com/alexcrea/CustomAnvil.git
synced 2026-08-28 08:25:56 +02:00
enchant conflict working
This commit is contained in:
parent
4942f3c0d2
commit
0fb8d33f6a
6 changed files with 157 additions and 26 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package xyz.alexcrea.cuanvil.gui.config.global;
|
package xyz.alexcrea.cuanvil.gui.config.global;
|
||||||
|
|
||||||
import com.github.stefvanschie.inventoryframework.gui.GuiItem;
|
import com.github.stefvanschie.inventoryframework.gui.GuiItem;
|
||||||
|
import com.github.stefvanschie.inventoryframework.gui.type.util.Gui;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
import org.bukkit.inventory.ItemFlag;
|
import org.bukkit.inventory.ItemFlag;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
|
@ -24,26 +25,30 @@ public class EnchantConflictGui extends MappedGuiListConfigGui<EnchantConflictGr
|
||||||
private static EnchantConflictGui INSTANCE;
|
private static EnchantConflictGui INSTANCE;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static EnchantConflictGui getCurrentInstance(){
|
public static EnchantConflictGui getCurrentInstance() {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static EnchantConflictGui getInstance(){
|
public static EnchantConflictGui getInstance() {
|
||||||
if(INSTANCE == null) INSTANCE = new EnchantConflictGui();
|
if (INSTANCE == null) INSTANCE = new EnchantConflictGui();
|
||||||
|
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Need to init myself
|
||||||
|
public EnchantConflictGui(Gui parent) {
|
||||||
|
super("Conflict Config", parent);
|
||||||
|
}
|
||||||
|
|
||||||
private EnchantConflictGui() {
|
private EnchantConflictGui() {
|
||||||
super( "Conflict Config");
|
super("Conflict Config");
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected EnchantConflictGroup createAndSaveNewEmptyGeneric(String name){
|
protected EnchantConflictGroup createAndSaveNewEmptyGeneric(String name) {
|
||||||
// Create new empty conflict and display it to the admin
|
// Create new empty conflict and display it to the admin
|
||||||
EnchantConflictGroup conflict = new EnchantConflictGroup(
|
EnchantConflictGroup conflict = new EnchantConflictGroup(
|
||||||
name,
|
name,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
package xyz.alexcrea.cuanvil.gui.config.global;
|
||||||
|
|
||||||
|
import com.github.stefvanschie.inventoryframework.gui.GuiItem;
|
||||||
|
import com.github.stefvanschie.inventoryframework.gui.type.ChestGui;
|
||||||
|
import com.github.stefvanschie.inventoryframework.gui.type.util.Gui;
|
||||||
|
import com.github.stefvanschie.inventoryframework.pane.PatternPane;
|
||||||
|
import com.github.stefvanschie.inventoryframework.pane.util.Pattern;
|
||||||
|
import io.delilaheve.CustomAnvil;
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions;
|
||||||
|
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class ItemConfigGui extends ChestGui {
|
||||||
|
|
||||||
|
public EnchantConflictGui enchantConflictGui;
|
||||||
|
|
||||||
|
|
||||||
|
public ItemConfigGui(Material display, NamespacedKey material) {
|
||||||
|
super(3, material.getKey() + " Config", CustomAnvil.instance);
|
||||||
|
|
||||||
|
Pattern pattern = new Pattern(
|
||||||
|
"0000D0000",
|
||||||
|
"056000780",
|
||||||
|
"000000000"
|
||||||
|
);
|
||||||
|
PatternPane pane = new PatternPane(0, 0, 9, 3, pattern);
|
||||||
|
addPane(pane);
|
||||||
|
|
||||||
|
GuiGlobalItems.addBackgroundItem(pane);
|
||||||
|
|
||||||
|
ItemStack displayItemstack = new ItemStack(display);
|
||||||
|
ItemMeta displayMeta = displayItemstack.getItemMeta();
|
||||||
|
assert displayMeta != null;
|
||||||
|
|
||||||
|
displayMeta.setDisplayName("§aConfiguring " + material);
|
||||||
|
displayItemstack.setItemMeta(displayMeta);
|
||||||
|
pane.bindItem('D', new GuiItem(displayItemstack, GuiGlobalActions.stayInPlace, CustomAnvil.instance));
|
||||||
|
|
||||||
|
// Enchantment Conflicts item
|
||||||
|
ItemStack enchantConflictItemstack = new ItemStack(Material.OAK_FENCE);
|
||||||
|
ItemMeta enchantConflictMeta = enchantConflictItemstack.getItemMeta();
|
||||||
|
assert enchantConflictMeta != null;
|
||||||
|
|
||||||
|
enchantConflictMeta.setDisplayName("§aEnchantment Conflict");
|
||||||
|
enchantConflictMeta.setLore(Collections.singletonList("§7Click here to open enchantment conflict menu"));
|
||||||
|
enchantConflictItemstack.setItemMeta(enchantConflictMeta);
|
||||||
|
|
||||||
|
GuiItem enchantConflictItem = GuiGlobalItems.goToGuiItem(enchantConflictItemstack, getEnchantConflictGui(material));
|
||||||
|
pane.bindItem('5', enchantConflictItem);
|
||||||
|
|
||||||
|
// Group config items
|
||||||
|
ItemStack groupItemstack = new ItemStack(Material.CHEST);
|
||||||
|
ItemMeta groupMeta = groupItemstack.getItemMeta();
|
||||||
|
assert groupMeta != null;
|
||||||
|
|
||||||
|
groupMeta.setDisplayName("§aItem Groups");
|
||||||
|
groupMeta.setLore(Collections.singletonList("§7Click here to open item group menu"));
|
||||||
|
groupItemstack.setItemMeta(groupMeta);
|
||||||
|
|
||||||
|
GuiItem groupConfigItem = GuiGlobalItems.goToGuiItem(groupItemstack, GroupConfigGui.getInstance());
|
||||||
|
|
||||||
|
pane.bindItem('6', groupConfigItem);
|
||||||
|
|
||||||
|
// Unit repair item
|
||||||
|
ItemStack unirRepairItemstack = new ItemStack(Material.DIAMOND);
|
||||||
|
ItemMeta unitRepairMeta = unirRepairItemstack.getItemMeta();
|
||||||
|
assert unitRepairMeta != null;
|
||||||
|
|
||||||
|
unitRepairMeta.setDisplayName("§aUnit Repair");
|
||||||
|
unitRepairMeta.setLore(Collections.singletonList("§7Click here to open anvil unit repair menu"));
|
||||||
|
unirRepairItemstack.setItemMeta(unitRepairMeta);
|
||||||
|
|
||||||
|
GuiItem unitRepairItem = GuiGlobalItems.goToGuiItem(unirRepairItemstack, UnitRepairConfigGui.getInstance());
|
||||||
|
pane.bindItem('7', unitRepairItem);
|
||||||
|
|
||||||
|
// Custom recipe item
|
||||||
|
ItemStack customRecipeItemstack = new ItemStack(Material.CRAFTING_TABLE);
|
||||||
|
ItemMeta customRecipeMeta = customRecipeItemstack.getItemMeta();
|
||||||
|
assert customRecipeMeta != null;
|
||||||
|
|
||||||
|
customRecipeMeta.setDisplayName("§aCustom recipes");
|
||||||
|
customRecipeMeta.setLore(Collections.singletonList("§7Click here to open anvil custom recipe menu"));
|
||||||
|
customRecipeItemstack.setItemMeta(customRecipeMeta);
|
||||||
|
|
||||||
|
GuiItem customRecipeItem = GuiGlobalItems.goToGuiItem(customRecipeItemstack, CustomRecipeConfigGui.getInstance());
|
||||||
|
pane.bindItem('8', customRecipeItem);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Gui getEnchantConflictGui(NamespacedKey material) {
|
||||||
|
if (enchantConflictGui == null) {
|
||||||
|
enchantConflictGui = new EnchantConflictGui(this);
|
||||||
|
enchantConflictGui.setFilter(group ->
|
||||||
|
group.getCantConflictGroup().contain(material)
|
||||||
|
);
|
||||||
|
enchantConflictGui.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
return enchantConflictGui;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -68,7 +68,7 @@ public abstract class ElementListConfigGui<T> extends ChestGui implements ValueU
|
||||||
protected ArrayList<OutlinePane> pages;
|
protected ArrayList<OutlinePane> pages;
|
||||||
protected HashMap<UUID, Integer> pageMap;
|
protected HashMap<UUID, Integer> pageMap;
|
||||||
|
|
||||||
public void init() { // Why I'm using an init function ? //TODO determine why is it using a init function and not used on constructor.
|
public void init() {
|
||||||
GuiGlobalItems.addBackgroundItem(this.backgroundPane);
|
GuiGlobalItems.addBackgroundItem(this.backgroundPane);
|
||||||
this.backgroundPane.bindItem('1', GuiSharedConstant.SECONDARY_BACKGROUND_ITEM);
|
this.backgroundPane.bindItem('1', GuiSharedConstant.SECONDARY_BACKGROUND_ITEM);
|
||||||
addPane(this.backgroundPane);
|
addPane(this.backgroundPane);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package xyz.alexcrea.cuanvil.gui.config.list;
|
package xyz.alexcrea.cuanvil.gui.config.list;
|
||||||
|
|
||||||
import com.github.stefvanschie.inventoryframework.gui.GuiItem;
|
import com.github.stefvanschie.inventoryframework.gui.GuiItem;
|
||||||
|
import com.github.stefvanschie.inventoryframework.gui.type.util.Gui;
|
||||||
import io.delilaheve.CustomAnvil;
|
import io.delilaheve.CustomAnvil;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.entity.HumanEntity;
|
import org.bukkit.entity.HumanEntity;
|
||||||
|
|
@ -14,27 +15,32 @@ import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public abstract class MappedElementListConfigGui< T, S > extends ElementListConfigGui< T > {
|
public abstract class MappedElementListConfigGui<T, S> extends ElementListConfigGui<T> {
|
||||||
|
|
||||||
protected final HashMap<T, S> elementGuiMap;
|
protected final HashMap<T, S> elementGuiMap;
|
||||||
protected MappedElementListConfigGui(@NotNull String title) {
|
|
||||||
super(title, MainConfigGui.getInstance());
|
protected MappedElementListConfigGui(@NotNull String title, @NotNull Gui parent) {
|
||||||
|
super(title, parent);
|
||||||
this.elementGuiMap = new HashMap<>();
|
this.elementGuiMap = new HashMap<>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected MappedElementListConfigGui(@NotNull String title) {
|
||||||
|
this(title, MainConfigGui.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected GuiItem prepareCreateNewItem(){
|
protected GuiItem prepareCreateNewItem() {
|
||||||
// Create new conflict item
|
// Create new conflict item
|
||||||
ItemStack createItem = new ItemStack(Material.PAPER);
|
ItemStack createItem = new ItemStack(Material.PAPER);
|
||||||
ItemMeta createMeta = createItem.getItemMeta();
|
ItemMeta createMeta = createItem.getItemMeta();
|
||||||
assert createMeta != null;
|
assert createMeta != null;
|
||||||
|
|
||||||
createMeta.setDisplayName("§aCreate new "+genericDisplayedName());
|
createMeta.setDisplayName("§aCreate new " + genericDisplayedName());
|
||||||
createMeta.setLore(Arrays.asList(
|
createMeta.setLore(Arrays.asList(
|
||||||
"§7Create a new "+genericDisplayedName()+".",
|
"§7Create a new " + genericDisplayedName() + ".",
|
||||||
"§7You will be asked to name the "+genericDisplayedName()+" in chat.",
|
"§7You will be asked to name the " + genericDisplayedName() + " in chat.",
|
||||||
"§7Then, you should edit the "+genericDisplayedName()+" config as you need"
|
"§7Then, you should edit the " + genericDisplayedName() + " config as you need"
|
||||||
));
|
));
|
||||||
|
|
||||||
createItem.setItemMeta(createMeta);
|
createItem.setItemMeta(createMeta);
|
||||||
|
|
@ -51,8 +57,8 @@ public abstract class MappedElementListConfigGui< T, S > extends ElementListConf
|
||||||
}
|
}
|
||||||
player.closeInventory();
|
player.closeInventory();
|
||||||
|
|
||||||
player.sendMessage("§eWrite the "+genericDisplayedName()+" name you want to create in the chat.\n" +
|
player.sendMessage("§eWrite the " + genericDisplayedName() + " name you want to create in the chat.\n" +
|
||||||
"§eOr write §ccancel §eto go back to "+genericDisplayedName()+" config menu");
|
"§eOr write §ccancel §eto go back to " + genericDisplayedName() + " config menu");
|
||||||
|
|
||||||
CustomAnvil.Companion.getChatListener().setListenedCallback(player, prepareCreateItemConsumer(player));
|
CustomAnvil.Companion.getChatListener().setListenedCallback(player, prepareCreateItemConsumer(player));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package xyz.alexcrea.cuanvil.gui.config.list;
|
package xyz.alexcrea.cuanvil.gui.config.list;
|
||||||
|
|
||||||
import com.github.stefvanschie.inventoryframework.gui.GuiItem;
|
import com.github.stefvanschie.inventoryframework.gui.GuiItem;
|
||||||
|
import com.github.stefvanschie.inventoryframework.gui.type.util.Gui;
|
||||||
import io.delilaheve.CustomAnvil;
|
import io.delilaheve.CustomAnvil;
|
||||||
import org.bukkit.entity.HumanEntity;
|
import org.bukkit.entity.HumanEntity;
|
||||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
|
|
@ -14,19 +15,22 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public abstract class MappedGuiListConfigGui< T, S extends MappedGuiListConfigGui.LazyElement<?>>
|
public abstract class MappedGuiListConfigGui<T, S extends MappedGuiListConfigGui.LazyElement<?>>
|
||||||
extends MappedElementListConfigGui< T, S > {
|
extends MappedElementListConfigGui<T, S> {
|
||||||
|
|
||||||
protected MappedGuiListConfigGui(@NotNull String title) {
|
protected MappedGuiListConfigGui(@NotNull String title) {
|
||||||
super(title);
|
super(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MappedGuiListConfigGui(@NotNull String title, @NotNull Gui parent) {
|
||||||
|
super(title, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reloadValues() {
|
public void reloadValues() {
|
||||||
this.elementGuiMap.forEach((conflict, element) -> {
|
this.elementGuiMap.forEach((conflict, element) -> {
|
||||||
ElementMappedToListGui gui = element.getStored();
|
ElementMappedToListGui gui = element.getStored();
|
||||||
if(gui != null) gui.cleanAndBeUnusable();
|
if (gui != null) gui.cleanAndBeUnusable();
|
||||||
});
|
});
|
||||||
this.elementGuiMap.clear();
|
this.elementGuiMap.clear();
|
||||||
|
|
||||||
|
|
@ -49,7 +53,7 @@ public abstract class MappedGuiListConfigGui< T, S extends MappedGuiListConfigGu
|
||||||
@Override
|
@Override
|
||||||
protected void updateElement(T generic, S element) {
|
protected void updateElement(T generic, S element) {
|
||||||
ElementMappedToListGui gui = element.getStored();
|
ElementMappedToListGui gui = element.getStored();
|
||||||
if(gui != null) gui.updateLocal();
|
if (gui != null) gui.updateLocal();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -58,7 +62,7 @@ public abstract class MappedGuiListConfigGui< T, S extends MappedGuiListConfigGu
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Consumer<String> prepareCreateItemConsumer(HumanEntity player){
|
protected Consumer<String> prepareCreateItemConsumer(HumanEntity player) {
|
||||||
AtomicReference<Consumer<String>> selfRef = new AtomicReference<>();
|
AtomicReference<Consumer<String>> selfRef = new AtomicReference<>();
|
||||||
Consumer<String> selfCallback = (message) -> {
|
Consumer<String> selfCallback = (message) -> {
|
||||||
if (message == null) return;
|
if (message == null) return;
|
||||||
|
|
@ -71,7 +75,7 @@ public abstract class MappedGuiListConfigGui< T, S extends MappedGuiListConfigGu
|
||||||
|
|
||||||
message = message.toLowerCase(Locale.ROOT);
|
message = message.toLowerCase(Locale.ROOT);
|
||||||
if ("cancel".equalsIgnoreCase(message)) {
|
if ("cancel".equalsIgnoreCase(message)) {
|
||||||
player.sendMessage(genericDisplayedName()+" creation cancelled...");
|
player.sendMessage(genericDisplayedName() + " creation cancelled...");
|
||||||
show(player);
|
show(player);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +86,7 @@ public abstract class MappedGuiListConfigGui< T, S extends MappedGuiListConfigGu
|
||||||
// Not the most efficient on large number of conflict, but it should not run often.
|
// Not the most efficient on large number of conflict, but it should not run often.
|
||||||
for (T generic : getDisplayableInstanceOfGeneric()) {
|
for (T generic : getDisplayableInstanceOfGeneric()) {
|
||||||
if (generic.toString().equalsIgnoreCase(message)) {
|
if (generic.toString().equalsIgnoreCase(message)) {
|
||||||
player.sendMessage("§cPlease enter a "+genericDisplayedName()+" name that do not already exist...");
|
player.sendMessage("§cPlease enter a " + genericDisplayedName() + " name that do not already exist...");
|
||||||
// wait next message.
|
// wait next message.
|
||||||
CustomAnvil.Companion.getChatListener().setListenedCallback(player, selfRef.get());
|
CustomAnvil.Companion.getChatListener().setListenedCallback(player, selfRef.get());
|
||||||
return;
|
return;
|
||||||
|
|
@ -90,7 +94,7 @@ public abstract class MappedGuiListConfigGui< T, S extends MappedGuiListConfigGu
|
||||||
}
|
}
|
||||||
|
|
||||||
T generic = createAndSaveNewEmptyGeneric(message);
|
T generic = createAndSaveNewEmptyGeneric(message);
|
||||||
if(generic == null) {// we don't know what to do. so we back up by opening this gui.
|
if (generic == null) {// we don't know what to do. so we back up by opening this gui.
|
||||||
this.show(player);
|
this.show(player);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -117,6 +121,7 @@ public abstract class MappedGuiListConfigGui< T, S extends MappedGuiListConfigGu
|
||||||
|
|
||||||
private final GuiItem parentItem;
|
private final GuiItem parentItem;
|
||||||
private final LazyValue<Consumer<InventoryClickEvent>> lazyOpenConsumer;
|
private final LazyValue<Consumer<InventoryClickEvent>> lazyOpenConsumer;
|
||||||
|
|
||||||
public LazyElement(GuiItem parentItem, Supplier<T> valueSupplier) {
|
public LazyElement(GuiItem parentItem, Supplier<T> valueSupplier) {
|
||||||
super(valueSupplier);
|
super(valueSupplier);
|
||||||
this.parentItem = parentItem;
|
this.parentItem = parentItem;
|
||||||
|
|
@ -131,7 +136,7 @@ public abstract class MappedGuiListConfigGui< T, S extends MappedGuiListConfigGu
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public Consumer<InventoryClickEvent> openAction(){
|
public Consumer<InventoryClickEvent> openAction() {
|
||||||
return event -> lazyOpenConsumer.get().accept(event);
|
return event -> lazyOpenConsumer.get().accept(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil
|
||||||
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
|
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
|
||||||
import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry
|
import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry
|
||||||
import xyz.alexcrea.cuanvil.gui.config.MainConfigGui
|
import xyz.alexcrea.cuanvil.gui.config.MainConfigGui
|
||||||
|
import xyz.alexcrea.cuanvil.gui.config.global.ItemConfigGui
|
||||||
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions
|
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions
|
||||||
|
import xyz.alexcrea.cuanvil.util.MaterialUtil.customType
|
||||||
|
import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir
|
||||||
|
|
||||||
class EditConfigExecutor : CASubCommand() {
|
class EditConfigExecutor : CASubCommand() {
|
||||||
|
|
||||||
|
|
@ -82,7 +85,13 @@ class EditConfigExecutor : CASubCommand() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processItem(sender: HumanEntity) {
|
private fun processItem(sender: HumanEntity) {
|
||||||
// TODO open item edit gui
|
val item = sender.inventory.itemInMainHand
|
||||||
|
if(item.isAir) {
|
||||||
|
sender.sendMessage("Cannot configure the item in hand")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemConfigGui(item.type, item.customType).show(sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processOpen(sender: HumanEntity) {
|
private fun processOpen(sender: HumanEntity) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue