diff --git a/gradle.properties b/gradle.properties index 1d392cd53..0b3c9747d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled javaVersion=25 mcVersion=1.21.11 group=dev.slne.surf -version=1.21.11-2.57.0 +version=1.21.11-2.58.0 relocationPrefix=dev.slne.surf.surfapi.libs snapshot=false diff --git a/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api b/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api index 4949f3c65..a35605a16 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api +++ b/surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api @@ -461,6 +461,111 @@ public final class dev/slne/surf/surfapi/bukkit/api/dialog/builder/DialogTypeBui public static final fun dialogType (Lkotlin/jvm/functions/Function1;)Lio/papermc/paper/registry/data/dialog/type/DialogType; } +public final class dev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogScope { + public fun (Ldev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogStore;Lkotlinx/coroutines/CoroutineScope;)V + public final fun remember ([Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun setState (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun state ()Ldev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState; +} + +public final class dev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogStore { + public fun (Ldev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState;Ljava/util/UUID;Lkotlin/jvm/functions/Function2;Lkotlinx/coroutines/CoroutineScope;)V + public final fun getState ()Ldev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState; + public final fun open (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun recall (Ljava/util/List;)Ljava/lang/Object; + public final fun remember (Ljava/util/List;Ljava/lang/Object;)V + public final fun update (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/slne/surf/surfapi/bukkit/api/dialog/composition/DslKt { + public static final fun composableDialog (Lorg/bukkit/entity/Player;Ldev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/slne/surf/surfapi/bukkit/api/dialog/pagination/PaginatedDialogKt { + public static final fun paginatedDialog (Lorg/bukkit/entity/Player;Ldev/slne/surf/surfapi/bukkit/api/dialog/query/DialogQuery;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun paginatedDialog$default (Lorg/bukkit/entity/Player;Ldev/slne/surf/surfapi/bukkit/api/dialog/query/DialogQuery;Lkotlin/jvm/functions/Function3;ZLkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; +} + +public abstract interface class dev/slne/surf/surfapi/bukkit/api/dialog/query/CursorDialogQuery { + public abstract fun execute (Ldev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/slne/surf/surfapi/bukkit/api/dialog/query/CursorResult { + public fun (Ljava/util/List;Ljava/lang/String;Z)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Z + public final fun copy (Ljava/util/List;Ljava/lang/String;Z)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/CursorResult; + public static synthetic fun copy$default (Ldev/slne/surf/surfapi/bukkit/api/dialog/query/CursorResult;Ljava/util/List;Ljava/lang/String;ZILjava/lang/Object;)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/CursorResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getHasMore ()Z + public final fun getItems ()Ljava/util/List; + public final fun getNextCursor ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/slne/surf/surfapi/bukkit/api/dialog/query/CursorState : dev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState { + public fun ()V + public fun (Ljava/lang/String;Ljava/util/List;ILjava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/util/List;ILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/util/List; + public final fun component3 ()I + public final fun component4 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/util/List;ILjava/lang/String;)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/CursorState; + public static synthetic fun copy$default (Ldev/slne/surf/surfapi/bukkit/api/dialog/query/CursorState;Ljava/lang/String;Ljava/util/List;ILjava/lang/String;ILjava/lang/Object;)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/CursorState; + public fun equals (Ljava/lang/Object;)Z + public final fun getCursor ()Ljava/lang/String; + public final fun getHistory ()Ljava/util/List; + public final fun getLimit ()I + public final fun getSearch ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class dev/slne/surf/surfapi/bukkit/api/dialog/query/DialogQuery { + public abstract fun execute (Ldev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class dev/slne/surf/surfapi/bukkit/api/dialog/query/PageResult { + public fun (Ljava/util/List;IILjava/lang/Integer;)V + public synthetic fun (Ljava/util/List;IILjava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()I + public final fun component3 ()I + public final fun component4 ()Ljava/lang/Integer; + public final fun copy (Ljava/util/List;IILjava/lang/Integer;)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/PageResult; + public static synthetic fun copy$default (Ldev/slne/surf/surfapi/bukkit/api/dialog/query/PageResult;Ljava/util/List;IILjava/lang/Integer;ILjava/lang/Object;)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/PageResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getItems ()Ljava/util/List; + public final fun getPage ()I + public final fun getTotalItems ()Ljava/lang/Integer; + public final fun getTotalPages ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class dev/slne/surf/surfapi/bukkit/api/dialog/query/PageState : dev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState { + public fun ()V + public fun (IILjava/lang/String;)V + public synthetic fun (IILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()I + public final fun component2 ()I + public final fun component3 ()Ljava/lang/String; + public final fun copy (IILjava/lang/String;)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/PageState; + public static synthetic fun copy$default (Ldev/slne/surf/surfapi/bukkit/api/dialog/query/PageState;IILjava/lang/String;ILjava/lang/Object;)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/PageState; + public fun equals (Ljava/lang/Object;)Z + public final fun getLimit ()I + public final fun getPage ()I + public final fun getSearch ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class dev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState { +} + public final class dev/slne/surf/surfapi/bukkit/api/event/Listener_extensionKt { public static final fun cancel (Lorg/bukkit/event/Cancellable;)V public static final fun listen (Lorg/bukkit/plugin/Plugin;Lkotlin/reflect/KClass;Lorg/bukkit/event/EventPriority;ZZLkotlin/jvm/functions/Function1;)Ldev/slne/surf/surfapi/bukkit/api/event/SingleListener; diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogScope.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogScope.kt new file mode 100644 index 000000000..c81fdeab9 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogScope.kt @@ -0,0 +1,33 @@ +@file:Suppress("UnstableApiUsage") + +package dev.slne.surf.surfapi.bukkit.api.dialog.composition + +import dev.slne.surf.surfapi.bukkit.api.dialog.state.DialogState +import kotlinx.coroutines.CoroutineScope + +class DialogScope( + private val store: DialogStore, + private val scope: CoroutineScope, +) { + fun state(): S = store.getState() + + suspend fun setState(transform: S.() -> S) { + store.update(transform) + } + + suspend fun remember( + vararg keys: Any?, + block: suspend CoroutineScope.() -> T + ): T { + val key = keys.toList() + val cached = store.recall(key) + + if (cached != null) return cached + + val value = block(scope) + + store.remember(key, value) + + return value + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogStore.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogStore.kt new file mode 100644 index 000000000..a778ead61 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogStore.kt @@ -0,0 +1,50 @@ +package dev.slne.surf.surfapi.bukkit.api.dialog.composition + +import dev.slne.surf.surfapi.bukkit.api.dialog.state.DialogState +import dev.slne.surf.surfapi.bukkit.api.extensions.server +import io.papermc.paper.dialog.Dialog +import kotlinx.coroutines.CoroutineScope +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +class DialogStore( + initialState: S, + private val playerUuid: UUID, + private val renderer: suspend DialogScope.() -> Dialog, + private val scope: CoroutineScope, +) { + private var currentState: S = initialState + private var mounted = true + + private val memory = ConcurrentHashMap, Any?>() + + suspend fun open() { + rerender() + } + + suspend fun update(transform: S.() -> S) { + currentState = currentState.transform() + rerender() + } + + fun getState(): S = currentState + + internal suspend fun rerender() { + if (!mounted) return + + val dialog = DialogScope(this, scope).renderer() + findPlayer()?.showDialog(dialog) + } + + @Suppress("UNCHECKED_CAST") + fun remember(key: List, value: Any?) { + memory[key] = value + } + + @Suppress("UNCHECKED_CAST") + fun recall(key: List): T? { + return memory[key] as? T + } + + private fun findPlayer() = server.getPlayer(playerUuid) +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/composition/dsl.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/composition/dsl.kt new file mode 100644 index 000000000..515b13c4e --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/composition/dsl.kt @@ -0,0 +1,24 @@ +@file:Suppress("UnstableApiUsage") + +package dev.slne.surf.surfapi.bukkit.api.dialog.composition + +import dev.slne.surf.surfapi.bukkit.api.dialog.state.DialogState +import io.papermc.paper.dialog.Dialog +import kotlinx.coroutines.CoroutineScope +import org.bukkit.entity.Player + +suspend fun composableDialog( + player: Player, + initialState: S, + scope: CoroutineScope, + content: suspend DialogScope.() -> Dialog +) { + val store = DialogStore( + initialState = initialState, + playerUuid = player.uniqueId, + renderer = content, + scope = scope + ) + + store.open() +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/pagination/PaginatedDialog.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/pagination/PaginatedDialog.kt new file mode 100644 index 000000000..c9531c482 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/pagination/PaginatedDialog.kt @@ -0,0 +1,148 @@ +@file:Suppress("UnstableApiUsage") + +package dev.slne.surf.surfapi.bukkit.api.dialog.pagination + +import dev.slne.surf.surfapi.bukkit.api.dialog.base +import dev.slne.surf.surfapi.bukkit.api.dialog.builder.actionButton +import dev.slne.surf.surfapi.bukkit.api.dialog.composition.composableDialog +import dev.slne.surf.surfapi.bukkit.api.dialog.dialog +import dev.slne.surf.surfapi.bukkit.api.dialog.query.DialogQuery +import dev.slne.surf.surfapi.bukkit.api.dialog.query.PageResult +import dev.slne.surf.surfapi.bukkit.api.dialog.query.PageState +import dev.slne.surf.surfapi.bukkit.api.dialog.type +import dev.slne.surf.surfapi.core.api.messages.builder.SurfComponentBuilder +import io.papermc.paper.dialog.Dialog +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import net.kyori.adventure.text.Component +import org.bukkit.entity.Player + +suspend fun paginatedDialog( + player: Player, + query: DialogQuery, + titleBuilder: SurfComponentBuilder.(PageState, PageResult) -> Unit = { _, result -> + if (result.totalPages == 0) { + text("Keine Ergebnisse") + } else { + text("Seite ${result.page} von ${result.totalPages}") + } + }, + searchable: Boolean = false, + itemBuilder: (T) -> Pair, + scope: CoroutineScope +) = composableDialog( + player = player, + initialState = PageState(), + scope = scope +) { + val state = state() + val page = remember(state.page, state.search) { + query.execute(state) + } + + dialog { + base { + title { + titleBuilder(this, state, page) + } + + if (searchable) { + input { + text("search") { + label { + text("Suche") + } + + initial(state.search ?: "") + } + } + } + } + + type { + multiAction { + columns(1) + + // Item Buttons + page.items.forEach { item -> + val (dialogTitle, dialog) = itemBuilder(item) + + action(actionButton { + label(dialogTitle) + + action { + playerCallback { + player.showDialog(dialog) + } + } + }) + } + + // Pagination Buttons + if (state.page > 1) { + action(actionButton { + label { + text("Zurück") + } + + action { + playerCallback { + scope.launch { + setState { copy(page = page.page - 1) } + } + } + } + }) + } + + if (state.page < page.totalPages) { + action(actionButton { + label { + text("Weiter") + } + + action { + playerCallback { + scope.launch { + setState { copy(page = page.page + 1) } + } + } + } + }) + } + + if (searchable) { + action(actionButton { + label { + text("Suche zurücksetzen") + } + + action { + playerCallback { + scope.launch { + setState { copy(search = null, page = 1) } + } + } + } + }) + + action(actionButton { + label { + text("Suchen") + } + + action { + customPlayerClick { response, _ -> + val search = response.getText("search") ?: "" + + scope.launch { + setState { copy(search = search, page = 1) } + } + } + } + }) + } + } + } + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/query/queries.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/query/queries.kt new file mode 100644 index 000000000..5a4a4438c --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/query/queries.kt @@ -0,0 +1,11 @@ +package dev.slne.surf.surfapi.bukkit.api.dialog.query + +import dev.slne.surf.surfapi.bukkit.api.dialog.state.DialogState + +fun interface DialogQuery { + suspend fun execute(state: S): PageResult +} + +fun interface CursorDialogQuery { + suspend fun execute(state: S): CursorResult +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/query/results.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/query/results.kt new file mode 100644 index 000000000..b757ee5b6 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/query/results.kt @@ -0,0 +1,14 @@ +package dev.slne.surf.surfapi.bukkit.api.dialog.query + +data class PageResult( + val items: List, + val page: Int, + val totalPages: Int, + val totalItems: Int? = null, +) + +data class CursorResult( + val items: List, + val nextCursor: String?, + val hasMore: Boolean +) \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/query/states.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/query/states.kt new file mode 100644 index 000000000..8e473e8ed --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/query/states.kt @@ -0,0 +1,21 @@ +package dev.slne.surf.surfapi.bukkit.api.dialog.query + +import dev.slne.surf.surfapi.bukkit.api.dialog.state.DialogState + +data class PageState( + val page: Int = 1, + val limit: Int = 10, + val search: String? = null +) : DialogState { + + init { + require(page >= 1) { "page must be >= 1, but was $page" } + require(limit > 0) { "limit must be > 0, but was $limit" } + } +} +data class CursorState( + val cursor: String? = null, + val history: List = emptyList(), + val limit: Int = 10, + val search: String? = null +) : DialogState \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/search/SearchDialog.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/search/SearchDialog.kt new file mode 100644 index 000000000..bfb0dfab6 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/search/SearchDialog.kt @@ -0,0 +1,121 @@ +@file:Suppress("UnstableApiUsage") +@file:OptIn(NmsUseWithCaution::class) + +package dev.slne.surf.surfapi.bukkit.api.dialog.search + +import dev.slne.surf.surfapi.bukkit.api.dialog.base +import dev.slne.surf.surfapi.bukkit.api.dialog.builder.DialogBodyBuilder +import dev.slne.surf.surfapi.bukkit.api.dialog.builder.DialogInputBuilder +import dev.slne.surf.surfapi.bukkit.api.dialog.clearDialogs +import dev.slne.surf.surfapi.bukkit.api.dialog.dialog +import dev.slne.surf.surfapi.bukkit.api.dialog.type +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.core.api.messages.builder.SurfComponentBuilder +import io.papermc.paper.dialog.Dialog +import io.papermc.paper.registry.data.dialog.DialogBase +import org.bukkit.entity.Player + +data class SearchDialogAction( + var label: SurfComponentBuilder.() -> Unit, + var tooltip: SurfComponentBuilder.() -> Unit +) + +data class SearchInput( + var key: String = "search", + var label: SurfComponentBuilder.() -> Unit = { + text("Suche") + }, + var initialValue: String = "", + var inputModifier: DialogInputBuilder.TextDialogInput.() -> Unit = {} +) + +fun searchDialog( + title: SurfComponentBuilder.() -> Unit, + externalTitle: SurfComponentBuilder.() -> Unit = title, + onSearch: (player: Player, query: String) -> Unit, + onClose: (player: Player, query: String) -> Unit, + body: DialogBodyBuilder.() -> Unit = { }, + searchButton: SearchDialogAction.() -> Unit = { + label = { + text("Suchen") + } + tooltip = { + text("Klicke hier, um die Suche zu starten.") + } + }, + cancelButton: SearchDialogAction.() -> Unit = { + label = { + text("Abbrechen") + } + tooltip = { + text("Klicke hier, um die Suche abzubrechen.") + } + }, + searchInput: SearchInput.() -> Unit, + canCloseWithEscape: Boolean = false, + afterAction: DialogBase.DialogAfterAction = DialogBase.DialogAfterAction.NONE +): Dialog = dialog { + val searchInput = SearchInput().apply(searchInput) + + val searchButton = SearchDialogAction( + label = { }, + tooltip = { } + ).apply(searchButton) + + val cancelButton = SearchDialogAction( + label = { }, + tooltip = { } + ).apply(cancelButton) + + base { + title(title) + externalTitle(externalTitle) + this.canCloseWithEscape = canCloseWithEscape + this.afterAction = afterAction + + body { + body() + + input { + text(searchInput.key) { + label(searchInput.label) + initial(searchInput.initialValue) + + searchInput.inputModifier(this) + } + } + } + + type { + confirmation { + yes { + label(searchButton.label) + tooltip(searchButton.tooltip) + + action { + customPlayerClick { response, player -> + player.clearDialogs(false) + + val query = response.getText(searchInput.key) ?: "" + onSearch(player, query) + } + } + } + + no { + label(cancelButton.label) + tooltip(cancelButton.tooltip) + + action { + customPlayerClick { response, player -> + player.clearDialogs(false) + + val query = response.getText(searchInput.key) ?: "" + onClose(player, query) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState.kt new file mode 100644 index 000000000..7cdd8ba1d --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState.kt @@ -0,0 +1,3 @@ +package dev.slne.surf.surfapi.bukkit.api.dialog.state + +interface DialogState \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.kt b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.kt index e54569ad9..9ae06d889 100644 --- a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.kt +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.kt @@ -6,6 +6,7 @@ import dev.slne.surf.surfapi.bukkit.api.inventory.framework.register import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution import dev.slne.surf.surfapi.bukkit.api.packet.listener.packetListenerApi import dev.slne.surf.surfapi.bukkit.test.command.SurfApiTestCommand +import dev.slne.surf.surfapi.bukkit.test.command.dialog.dialogTestCommand import dev.slne.surf.surfapi.bukkit.test.command.subcommands.inventory.TestInventoryView import dev.slne.surf.surfapi.bukkit.test.command.subcommands.reflection.Reflection import dev.slne.surf.surfapi.bukkit.test.config.ModernTestConfig @@ -25,6 +26,7 @@ class BukkitPluginMain : SuspendingJavaPlugin() { override suspend fun onEnableAsync() { SurfApiTestCommand().register() + dialogTestCommand() Reflection::class.java.getClassLoader() // initialize Reflection surfComponentApi.enable(this) diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/dialog/DialogTestCommand.kt b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/dialog/DialogTestCommand.kt new file mode 100644 index 000000000..1ef60c479 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/dialog/DialogTestCommand.kt @@ -0,0 +1,10 @@ +package dev.slne.surf.surfapi.bukkit.test.command.dialog + +import dev.jorel.commandapi.kotlindsl.commandAPICommand +import dev.slne.surf.surfapi.bukkit.test.command.dialog.subcommands.paginationDialogTestCommand +import dev.slne.surf.surfapi.bukkit.test.command.dialog.subcommands.searchDialogTestCommand + +fun dialogTestCommand() = commandAPICommand("dialogtest") { + paginationDialogTestCommand() + searchDialogTestCommand() +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/dialog/subcommands/PaginatedDialogTest.kt b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/dialog/subcommands/PaginatedDialogTest.kt new file mode 100644 index 000000000..f031d604b --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/dialog/subcommands/PaginatedDialogTest.kt @@ -0,0 +1,168 @@ +@file:OptIn(NmsUseWithCaution::class) + +package dev.slne.surf.surfapi.bukkit.test.command.dialog.subcommands + +import com.github.shynixn.mccoroutine.folia.launch +import com.github.shynixn.mccoroutine.folia.scope +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.kotlindsl.playerExecutor +import dev.jorel.commandapi.kotlindsl.subcommand +import dev.slne.surf.surfapi.bukkit.api.dialog.base +import dev.slne.surf.surfapi.bukkit.api.dialog.builder.actionButton +import dev.slne.surf.surfapi.bukkit.api.dialog.clearDialogs +import dev.slne.surf.surfapi.bukkit.api.dialog.dialog +import dev.slne.surf.surfapi.bukkit.api.dialog.pagination.paginatedDialog +import dev.slne.surf.surfapi.bukkit.api.dialog.query.DialogQuery +import dev.slne.surf.surfapi.bukkit.api.dialog.query.PageResult +import dev.slne.surf.surfapi.bukkit.api.dialog.query.PageState +import dev.slne.surf.surfapi.bukkit.api.dialog.type +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.bukkit.test.plugin +import dev.slne.surf.surfapi.core.api.messages.adventure.buildText +import io.papermc.paper.dialog.Dialog +import net.kyori.adventure.text.Component + +object PaginatedDialogTest { + val paginationData = mutableListOf() + + init { + val amount = 10000 + + for (i in 1..amount) { + paginationData.add( + PaginationEntry( + id = i, + name = "Eintrag $i", + displayName = Component.text("Eintrag $i") + ) + ) + } + } + + object PaginatedEntryQuery : DialogQuery { + enum class SearchType { + EQUALS, + CONTAINS, + STARTS_WITH, + ENDS_WITH + } + + override suspend fun execute(state: PageState): PageResult { + val currentPage = state.page + val pageSize = state.limit + val search = state.search + + val searchType = when { + search == null -> null + search.startsWith("*") && search.endsWith("*") -> SearchType.CONTAINS + search.startsWith("*") -> SearchType.ENDS_WITH + search.endsWith("*") -> SearchType.STARTS_WITH + else -> SearchType.EQUALS + } + + val normalizedSearch = search?.trim('*')?.lowercase() + + val filteredData = if (normalizedSearch != null) { + paginationData.filter { entry -> + val entryName = entry.name.lowercase() + + when (searchType) { + SearchType.EQUALS -> entryName.equals( + normalizedSearch, + ignoreCase = true + ) + + SearchType.CONTAINS -> entryName.contains( + normalizedSearch, + ignoreCase = true + ) + + SearchType.STARTS_WITH -> entryName.startsWith( + normalizedSearch, + ignoreCase = true + ) + + SearchType.ENDS_WITH -> entryName.endsWith( + normalizedSearch, + ignoreCase = true + ) + + null -> true + } + } + } else { + paginationData + } + + val totalEntries = filteredData.size + val totalPages = (totalEntries + pageSize - 1) / pageSize + val fromIndex = (currentPage - 1) * pageSize + val toIndex = minOf(fromIndex + pageSize, totalEntries) + + val pageEntries = if (fromIndex in 0 until totalEntries) { + filteredData.subList(fromIndex, toIndex) + } else { + emptyList() + } + + return PageResult( + items = pageEntries, + page = currentPage, + totalPages = totalPages, + ) + } + } +} + +fun CommandAPICommand.paginationDialogTestCommand() = subcommand("paginated") { + playerExecutor { player, _ -> + plugin.launch { + paginatedDialog( + player = player, + query = PaginatedDialogTest.PaginatedEntryQuery, + searchable = true, + itemBuilder = { entry -> + buildText { + text(entry.name) + } to entry.dialog + }, + scope = plugin.scope + ) + } + } +} + +data class PaginationEntry( + val id: Int, + val name: String, + val displayName: Component, +) { + val dialog: Dialog + get() = dialog { + base { + title { + text(name) + } + + externalTitle { + text(name) + } + } + + type { + multiAction { + action(actionButton { + label { + text("Schließen") + } + + action { + playerCallback { + it.clearDialogs() + } + } + }) + } + } + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/dialog/subcommands/SearchDialogTest.kt b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/dialog/subcommands/SearchDialogTest.kt new file mode 100644 index 000000000..d56a0d942 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/dialog/subcommands/SearchDialogTest.kt @@ -0,0 +1,47 @@ +package dev.slne.surf.surfapi.bukkit.test.command.dialog.subcommands + +import dev.jorel.commandapi.CommandAPICommand +import dev.jorel.commandapi.kotlindsl.getValue +import dev.jorel.commandapi.kotlindsl.greedyStringArgument +import dev.jorel.commandapi.kotlindsl.playerExecutor +import dev.jorel.commandapi.kotlindsl.subcommand +import dev.slne.surf.surfapi.bukkit.api.dialog.search.searchDialog +import dev.slne.surf.surfapi.core.api.messages.adventure.sendText + +fun CommandAPICommand.searchDialogTestCommand() = subcommand("search") { + greedyStringArgument("initial") + + playerExecutor { player, args -> + val initial: String by args + + player.showDialog( + searchDialog( + title = { + text("Suche") + }, + searchInput = { + initialValue = initial + }, + body = { + plainMessage { + info("Gib einen Suchbegriff ein und klicke auf den Suchen-Button, um die Suche zu starten.") + } + }, + onSearch = { p, query -> + p.sendText { + info("Du hast nach ") + variableValue(query) + info(" gesucht.") + } + }, + onClose = { p, query -> + p.sendText { + info("Du hast die Suche mit dem Suchbegriff ") + variableValue(query) + info(" geschlossen.") + } + } + ) + ) + } +} \ No newline at end of file