Skip to content

Commit e4976dc

Browse files
authored
Pagination / Search Dialog (#216)
2 parents a979ce7 + f482637 commit e4976dc

15 files changed

Lines changed: 758 additions & 1 deletion

File tree

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
77
javaVersion=25
88
mcVersion=1.21.11
99
group=dev.slne.surf
10-
version=1.21.11-2.57.0
10+
version=1.21.11-2.58.0
1111
relocationPrefix=dev.slne.surf.surfapi.libs
1212
snapshot=false

surf-api-bukkit/surf-api-bukkit-api/api/surf-api-bukkit-api.api

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,111 @@ public final class dev/slne/surf/surfapi/bukkit/api/dialog/builder/DialogTypeBui
461461
public static final fun dialogType (Lkotlin/jvm/functions/Function1;)Lio/papermc/paper/registry/data/dialog/type/DialogType;
462462
}
463463

464+
public final class dev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogScope {
465+
public fun <init> (Ldev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogStore;Lkotlinx/coroutines/CoroutineScope;)V
466+
public final fun remember ([Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
467+
public final fun setState (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
468+
public final fun state ()Ldev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState;
469+
}
470+
471+
public final class dev/slne/surf/surfapi/bukkit/api/dialog/composition/DialogStore {
472+
public fun <init> (Ldev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState;Ljava/util/UUID;Lkotlin/jvm/functions/Function2;Lkotlinx/coroutines/CoroutineScope;)V
473+
public final fun getState ()Ldev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState;
474+
public final fun open (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
475+
public final fun recall (Ljava/util/List;)Ljava/lang/Object;
476+
public final fun remember (Ljava/util/List;Ljava/lang/Object;)V
477+
public final fun update (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
478+
}
479+
480+
public final class dev/slne/surf/surfapi/bukkit/api/dialog/composition/DslKt {
481+
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;
482+
}
483+
484+
public final class dev/slne/surf/surfapi/bukkit/api/dialog/pagination/PaginatedDialogKt {
485+
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;
486+
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;
487+
}
488+
489+
public abstract interface class dev/slne/surf/surfapi/bukkit/api/dialog/query/CursorDialogQuery {
490+
public abstract fun execute (Ldev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
491+
}
492+
493+
public final class dev/slne/surf/surfapi/bukkit/api/dialog/query/CursorResult {
494+
public fun <init> (Ljava/util/List;Ljava/lang/String;Z)V
495+
public final fun component1 ()Ljava/util/List;
496+
public final fun component2 ()Ljava/lang/String;
497+
public final fun component3 ()Z
498+
public final fun copy (Ljava/util/List;Ljava/lang/String;Z)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/CursorResult;
499+
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;
500+
public fun equals (Ljava/lang/Object;)Z
501+
public final fun getHasMore ()Z
502+
public final fun getItems ()Ljava/util/List;
503+
public final fun getNextCursor ()Ljava/lang/String;
504+
public fun hashCode ()I
505+
public fun toString ()Ljava/lang/String;
506+
}
507+
508+
public final class dev/slne/surf/surfapi/bukkit/api/dialog/query/CursorState : dev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState {
509+
public fun <init> ()V
510+
public fun <init> (Ljava/lang/String;Ljava/util/List;ILjava/lang/String;)V
511+
public synthetic fun <init> (Ljava/lang/String;Ljava/util/List;ILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
512+
public final fun component1 ()Ljava/lang/String;
513+
public final fun component2 ()Ljava/util/List;
514+
public final fun component3 ()I
515+
public final fun component4 ()Ljava/lang/String;
516+
public final fun copy (Ljava/lang/String;Ljava/util/List;ILjava/lang/String;)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/CursorState;
517+
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;
518+
public fun equals (Ljava/lang/Object;)Z
519+
public final fun getCursor ()Ljava/lang/String;
520+
public final fun getHistory ()Ljava/util/List;
521+
public final fun getLimit ()I
522+
public final fun getSearch ()Ljava/lang/String;
523+
public fun hashCode ()I
524+
public fun toString ()Ljava/lang/String;
525+
}
526+
527+
public abstract interface class dev/slne/surf/surfapi/bukkit/api/dialog/query/DialogQuery {
528+
public abstract fun execute (Ldev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
529+
}
530+
531+
public final class dev/slne/surf/surfapi/bukkit/api/dialog/query/PageResult {
532+
public fun <init> (Ljava/util/List;IILjava/lang/Integer;)V
533+
public synthetic fun <init> (Ljava/util/List;IILjava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
534+
public final fun component1 ()Ljava/util/List;
535+
public final fun component2 ()I
536+
public final fun component3 ()I
537+
public final fun component4 ()Ljava/lang/Integer;
538+
public final fun copy (Ljava/util/List;IILjava/lang/Integer;)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/PageResult;
539+
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;
540+
public fun equals (Ljava/lang/Object;)Z
541+
public final fun getItems ()Ljava/util/List;
542+
public final fun getPage ()I
543+
public final fun getTotalItems ()Ljava/lang/Integer;
544+
public final fun getTotalPages ()I
545+
public fun hashCode ()I
546+
public fun toString ()Ljava/lang/String;
547+
}
548+
549+
public final class dev/slne/surf/surfapi/bukkit/api/dialog/query/PageState : dev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState {
550+
public fun <init> ()V
551+
public fun <init> (IILjava/lang/String;)V
552+
public synthetic fun <init> (IILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
553+
public final fun component1 ()I
554+
public final fun component2 ()I
555+
public final fun component3 ()Ljava/lang/String;
556+
public final fun copy (IILjava/lang/String;)Ldev/slne/surf/surfapi/bukkit/api/dialog/query/PageState;
557+
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;
558+
public fun equals (Ljava/lang/Object;)Z
559+
public final fun getLimit ()I
560+
public final fun getPage ()I
561+
public final fun getSearch ()Ljava/lang/String;
562+
public fun hashCode ()I
563+
public fun toString ()Ljava/lang/String;
564+
}
565+
566+
public abstract interface class dev/slne/surf/surfapi/bukkit/api/dialog/state/DialogState {
567+
}
568+
464569
public final class dev/slne/surf/surfapi/bukkit/api/event/Listener_extensionKt {
465570
public static final fun cancel (Lorg/bukkit/event/Cancellable;)V
466571
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;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@file:Suppress("UnstableApiUsage")
2+
3+
package dev.slne.surf.surfapi.bukkit.api.dialog.composition
4+
5+
import dev.slne.surf.surfapi.bukkit.api.dialog.state.DialogState
6+
import kotlinx.coroutines.CoroutineScope
7+
8+
class DialogScope<S : DialogState>(
9+
private val store: DialogStore<S>,
10+
private val scope: CoroutineScope,
11+
) {
12+
fun state(): S = store.getState()
13+
14+
suspend fun setState(transform: S.() -> S) {
15+
store.update(transform)
16+
}
17+
18+
suspend fun <T> remember(
19+
vararg keys: Any?,
20+
block: suspend CoroutineScope.() -> T
21+
): T {
22+
val key = keys.toList()
23+
val cached = store.recall<T>(key)
24+
25+
if (cached != null) return cached
26+
27+
val value = block(scope)
28+
29+
store.remember<T>(key, value)
30+
31+
return value
32+
}
33+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package dev.slne.surf.surfapi.bukkit.api.dialog.composition
2+
3+
import dev.slne.surf.surfapi.bukkit.api.dialog.state.DialogState
4+
import dev.slne.surf.surfapi.bukkit.api.extensions.server
5+
import io.papermc.paper.dialog.Dialog
6+
import kotlinx.coroutines.CoroutineScope
7+
import java.util.*
8+
import java.util.concurrent.ConcurrentHashMap
9+
10+
class DialogStore<S : DialogState>(
11+
initialState: S,
12+
private val playerUuid: UUID,
13+
private val renderer: suspend DialogScope<S>.() -> Dialog,
14+
private val scope: CoroutineScope,
15+
) {
16+
private var currentState: S = initialState
17+
private var mounted = true
18+
19+
private val memory = ConcurrentHashMap<List<Any?>, Any?>()
20+
21+
suspend fun open() {
22+
rerender()
23+
}
24+
25+
suspend fun update(transform: S.() -> S) {
26+
currentState = currentState.transform()
27+
rerender()
28+
}
29+
30+
fun getState(): S = currentState
31+
32+
internal suspend fun rerender() {
33+
if (!mounted) return
34+
35+
val dialog = DialogScope(this, scope).renderer()
36+
findPlayer()?.showDialog(dialog)
37+
}
38+
39+
@Suppress("UNCHECKED_CAST")
40+
fun <T> remember(key: List<Any?>, value: Any?) {
41+
memory[key] = value
42+
}
43+
44+
@Suppress("UNCHECKED_CAST")
45+
fun <T> recall(key: List<Any?>): T? {
46+
return memory[key] as? T
47+
}
48+
49+
private fun findPlayer() = server.getPlayer(playerUuid)
50+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
@file:Suppress("UnstableApiUsage")
2+
3+
package dev.slne.surf.surfapi.bukkit.api.dialog.composition
4+
5+
import dev.slne.surf.surfapi.bukkit.api.dialog.state.DialogState
6+
import io.papermc.paper.dialog.Dialog
7+
import kotlinx.coroutines.CoroutineScope
8+
import org.bukkit.entity.Player
9+
10+
suspend fun <S : DialogState> composableDialog(
11+
player: Player,
12+
initialState: S,
13+
scope: CoroutineScope,
14+
content: suspend DialogScope<S>.() -> Dialog
15+
) {
16+
val store = DialogStore(
17+
initialState = initialState,
18+
playerUuid = player.uniqueId,
19+
renderer = content,
20+
scope = scope
21+
)
22+
23+
store.open()
24+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
@file:Suppress("UnstableApiUsage")
2+
3+
package dev.slne.surf.surfapi.bukkit.api.dialog.pagination
4+
5+
import dev.slne.surf.surfapi.bukkit.api.dialog.base
6+
import dev.slne.surf.surfapi.bukkit.api.dialog.builder.actionButton
7+
import dev.slne.surf.surfapi.bukkit.api.dialog.composition.composableDialog
8+
import dev.slne.surf.surfapi.bukkit.api.dialog.dialog
9+
import dev.slne.surf.surfapi.bukkit.api.dialog.query.DialogQuery
10+
import dev.slne.surf.surfapi.bukkit.api.dialog.query.PageResult
11+
import dev.slne.surf.surfapi.bukkit.api.dialog.query.PageState
12+
import dev.slne.surf.surfapi.bukkit.api.dialog.type
13+
import dev.slne.surf.surfapi.core.api.messages.builder.SurfComponentBuilder
14+
import io.papermc.paper.dialog.Dialog
15+
import kotlinx.coroutines.CoroutineScope
16+
import kotlinx.coroutines.launch
17+
import net.kyori.adventure.text.Component
18+
import org.bukkit.entity.Player
19+
20+
suspend fun <T> paginatedDialog(
21+
player: Player,
22+
query: DialogQuery<PageState, T>,
23+
titleBuilder: SurfComponentBuilder.(PageState, PageResult<T>) -> Unit = { _, result ->
24+
if (result.totalPages == 0) {
25+
text("Keine Ergebnisse")
26+
} else {
27+
text("Seite ${result.page} von ${result.totalPages}")
28+
}
29+
},
30+
searchable: Boolean = false,
31+
itemBuilder: (T) -> Pair<Component, Dialog>,
32+
scope: CoroutineScope
33+
) = composableDialog(
34+
player = player,
35+
initialState = PageState(),
36+
scope = scope
37+
) {
38+
val state = state()
39+
val page = remember(state.page, state.search) {
40+
query.execute(state)
41+
}
42+
43+
dialog {
44+
base {
45+
title {
46+
titleBuilder(this, state, page)
47+
}
48+
49+
if (searchable) {
50+
input {
51+
text("search") {
52+
label {
53+
text("Suche")
54+
}
55+
56+
initial(state.search ?: "")
57+
}
58+
}
59+
}
60+
}
61+
62+
type {
63+
multiAction {
64+
columns(1)
65+
66+
// Item Buttons
67+
page.items.forEach { item ->
68+
val (dialogTitle, dialog) = itemBuilder(item)
69+
70+
action(actionButton {
71+
label(dialogTitle)
72+
73+
action {
74+
playerCallback {
75+
player.showDialog(dialog)
76+
}
77+
}
78+
})
79+
}
80+
81+
// Pagination Buttons
82+
if (state.page > 1) {
83+
action(actionButton {
84+
label {
85+
text("Zurück")
86+
}
87+
88+
action {
89+
playerCallback {
90+
scope.launch {
91+
setState { copy(page = page.page - 1) }
92+
}
93+
}
94+
}
95+
})
96+
}
97+
98+
if (state.page < page.totalPages) {
99+
action(actionButton {
100+
label {
101+
text("Weiter")
102+
}
103+
104+
action {
105+
playerCallback {
106+
scope.launch {
107+
setState { copy(page = page.page + 1) }
108+
}
109+
}
110+
}
111+
})
112+
}
113+
114+
if (searchable) {
115+
action(actionButton {
116+
label {
117+
text("Suche zurücksetzen")
118+
}
119+
120+
action {
121+
playerCallback {
122+
scope.launch {
123+
setState { copy(search = null, page = 1) }
124+
}
125+
}
126+
}
127+
})
128+
129+
action(actionButton {
130+
label {
131+
text("Suchen")
132+
}
133+
134+
action {
135+
customPlayerClick { response, _ ->
136+
val search = response.getText("search") ?: ""
137+
138+
scope.launch {
139+
setState { copy(search = search, page = 1) }
140+
}
141+
}
142+
}
143+
})
144+
}
145+
}
146+
}
147+
}
148+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package dev.slne.surf.surfapi.bukkit.api.dialog.query
2+
3+
import dev.slne.surf.surfapi.bukkit.api.dialog.state.DialogState
4+
5+
fun interface DialogQuery<S : DialogState, T> {
6+
suspend fun execute(state: S): PageResult<T>
7+
}
8+
9+
fun interface CursorDialogQuery<S : DialogState, T> {
10+
suspend fun execute(state: S): CursorResult<T>
11+
}

0 commit comments

Comments
 (0)