From 7d4b1bac3454674d3519b1b6162e94c56c628d6c Mon Sep 17 00:00:00 2001 From: Emilia Kond Date: Sat, 6 May 2023 15:25:35 +0300 Subject: [PATCH] feat: downsampling setting This adds a new option to the settings which allows for setting a downsampler, which is run before converting the component to HTML. This allows for "simulating" either legacy, or bedrock mode. Has a TODO to resolve, will probably need better visual clarity (i.e. something on the main page to show that you're currently in downsample mode, so you don't forget...?) 4.14.0-SNAPSHOT is currently required since it's the first version which makes `TextColor.nearestColorTo` public. Closes #153 --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- .../kyori/adventure/webui/websocket/Packet.kt | 6 +- src/commonMain/resources/web/index.html | 16 +++++ .../adventure/webui/js/BytebinStorage.kt | 3 + .../net/kyori/adventure/webui/js/Main.kt | 25 ++++++- .../webui/jvm/minimessage/Downsampler.kt | 67 +++++++++++++++++++ .../webui/jvm/minimessage/MiniMessage.kt | 20 +++--- 8 files changed, 124 insertions(+), 17 deletions(-) create mode 100644 src/jvmMain/kotlin/net/kyori/adventure/webui/jvm/minimessage/Downsampler.kt diff --git a/build.gradle.kts b/build.gradle.kts index f954c18..e6467f9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,7 +12,7 @@ plugins { repositories { mavenCentral() - maven(url = "https://oss.sonatype.org/content/repositories/snapshots/") { + maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/") { name = "sonatype-oss-snapshots" mavenContent { snapshotsOnly() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0bc0de8..59a7d5c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -adventure = "4.13.1" +adventure = "4.14.0-SNAPSHOT" kotlin = "1.8.20" ktlint = "0.48.2" ktor = "2.2.4" diff --git a/src/commonMain/kotlin/net/kyori/adventure/webui/websocket/Packet.kt b/src/commonMain/kotlin/net/kyori/adventure/webui/websocket/Packet.kt index dc796d0..f8cf4e2 100644 --- a/src/commonMain/kotlin/net/kyori/adventure/webui/websocket/Packet.kt +++ b/src/commonMain/kotlin/net/kyori/adventure/webui/websocket/Packet.kt @@ -11,7 +11,8 @@ public sealed interface Packet @SerialName("call") public data class Call( public val miniMessage: String? = null, - public val isolateNewlines: Boolean = false + public val isolateNewlines: Boolean = false, + public val downsampler: String? = null, ) : Packet @Serializable @@ -26,5 +27,6 @@ public data class Combined( public val miniMessage: String? = null, public val placeholders: Placeholders? = null, public val background: String? = null, - public val mode: String? = null + public val mode: String? = null, + public val downsampler: String? = null, ) diff --git a/src/commonMain/resources/web/index.html b/src/commonMain/resources/web/index.html index 803d312..2985c62 100644 --- a/src/commonMain/resources/web/index.html +++ b/src/commonMain/resources/web/index.html @@ -330,6 +330,22 @@

MiniMessage Viewer

+
+
+ +
+
+
+
+ +
+
+
+
diff --git a/src/jsMain/kotlin/net/kyori/adventure/webui/js/BytebinStorage.kt b/src/jsMain/kotlin/net/kyori/adventure/webui/js/BytebinStorage.kt index 2098d9a..1b29bc9 100644 --- a/src/jsMain/kotlin/net/kyori/adventure/webui/js/BytebinStorage.kt +++ b/src/jsMain/kotlin/net/kyori/adventure/webui/js/BytebinStorage.kt @@ -100,6 +100,9 @@ public fun restoreFromShortLink(shortCode: String, inputBox: HTMLTextAreaElement structure.getFromCombinedOrLocalStorage(PARAM_MODE, Combined::mode)?.also { mode -> setMode(Mode.fromString(mode)) } + structure.getFromCombinedOrLocalStorage(PARAM_DOWNSAMPLE, Combined::downsampler)?.also { downsampler -> + currentDownsampler = downsampler + } webSocket.send( Placeholders(stringPlaceholders = stringPlaceholders) ) diff --git a/src/jsMain/kotlin/net/kyori/adventure/webui/js/Main.kt b/src/jsMain/kotlin/net/kyori/adventure/webui/js/Main.kt index 3159b3b..0b1b8aa 100644 --- a/src/jsMain/kotlin/net/kyori/adventure/webui/js/Main.kt +++ b/src/jsMain/kotlin/net/kyori/adventure/webui/js/Main.kt @@ -60,11 +60,13 @@ public const val PARAM_MODE: String = "mode" public const val PARAM_BACKGROUND: String = "bg" public const val PARAM_STRING_PLACEHOLDERS: String = "st" public const val PARAM_SHORT_LINK: String = "x" +public const val PARAM_DOWNSAMPLE: String = "ds" private var isInEditorMode: Boolean = false private lateinit var editorInput: EditorInput public lateinit var currentMode: Mode +public lateinit var currentDownsampler: String private lateinit var webSocket: WebSocket public fun mainLoaded() { @@ -222,6 +224,16 @@ public fun mainLoaded() { currentBackground = settingBackground.value } ) + val settingDownsample = document.element("setting-downsample") + currentDownsampler = settingDownsample.value + settingDownsample.addEventListener( + "change", + { + console.log(settingDownsample) + currentDownsampler = settingDownsample.value + parse() + } + ) // SHARING document.getElementById("full-link-share-button")!!.addEventListener( @@ -234,6 +246,7 @@ public fun mainLoaded() { if (currentMode != Mode.SERVER_LIST) { link += "&$PARAM_BACKGROUND=$currentBackground" } + link += "&$PARAM_DOWNSAMPLE=$currentDownsampler" if (!placeholders.stringPlaceholders.isNullOrEmpty()) { link += "&$PARAM_STRING_PLACEHOLDERS=" link += @@ -254,7 +267,8 @@ public fun mainLoaded() { miniMessage = input.value, placeholders = readPlaceholders(), background = if (currentMode != Mode.SERVER_LIST) currentBackground else null, - mode = currentMode.paramName + mode = currentMode.paramName, + downsampler = currentDownsampler ) ) .then { code -> "$homeUrl?$PARAM_SHORT_LINK=$code" } @@ -297,7 +311,7 @@ public fun mainLoaded() { { window.postPacket( "$URL_API$URL_MINI_TO_JSON", - Combined(miniMessage = input.value, placeholders = readPlaceholders()) + Combined(miniMessage = input.value, placeholders = readPlaceholders(), downsampler = currentDownsampler) ) .then { response -> response.text() } .then { text -> window.navigator.clipboard.writeText(text) } @@ -444,6 +458,10 @@ private fun onWebsocketReady() { urlParams.getFromParamsOrLocalStorage(PARAM_MODE)?.also { mode -> setMode(Mode.fromString(mode)) } + // TODO(rymiel): This does not currently set the dropdown to the correct value + urlParams.getFromParamsOrLocalStorage(PARAM_DOWNSAMPLE)?.also { downsampler -> + currentDownsampler = downsampler + } webSocket.send( Placeholders(stringPlaceholders = stringPlaceholders) ) @@ -594,6 +612,7 @@ private fun parse() { val input = document.element("input").value // Store current input for persistence window.localStorage[PARAM_INPUT] = input + window.localStorage[PARAM_DOWNSAMPLE] = currentDownsampler if (input.isEmpty() && currentMode != Mode.SERVER_LIST) { // we don't want to parse if input is empty (server list mode is an exception!) @@ -619,7 +638,7 @@ private fun parse() { if (line == "") "\u200B" else line } - webSocket.send(Call(combinedLines, isolateNewlines = currentMode == Mode.LORE)) + webSocket.send(Call(combinedLines, isolateNewlines = currentMode == Mode.LORE, downsampler = currentDownsampler)) } } } diff --git a/src/jvmMain/kotlin/net/kyori/adventure/webui/jvm/minimessage/Downsampler.kt b/src/jvmMain/kotlin/net/kyori/adventure/webui/jvm/minimessage/Downsampler.kt new file mode 100644 index 0000000..c32b963 --- /dev/null +++ b/src/jvmMain/kotlin/net/kyori/adventure/webui/jvm/minimessage/Downsampler.kt @@ -0,0 +1,67 @@ +package net.kyori.adventure.webui.jvm.minimessage + +import net.kyori.adventure.text.Component +import net.kyori.adventure.text.format.NamedTextColor +import net.kyori.adventure.text.format.TextColor +import java.util.function.UnaryOperator + +// This could just be thing which serializes to legacy, and then back. But this thing is way more fun +private class LimitedDownsampler(val colors: List) { + fun render(component: Component): Component { + return component + .children(component.children().map(::render)) + .color(component.color()?.let { color -> TextColor.nearestColorTo(colors, color) }) + .hoverEvent(null) + .clickEvent(null) + } +} + +// Won't be necessary after https://github.com/KyoriPowered/adventure/issues/910 is closed +private val bedrockColors = listOf( + NamedTextColor.DARK_BLUE, + NamedTextColor.DARK_GREEN, + NamedTextColor.DARK_AQUA, + NamedTextColor.DARK_RED, + NamedTextColor.DARK_PURPLE, + NamedTextColor.GOLD, + NamedTextColor.GRAY, + NamedTextColor.DARK_GRAY, + NamedTextColor.BLUE, + NamedTextColor.GREEN, + NamedTextColor.AQUA, + NamedTextColor.RED, + NamedTextColor.LIGHT_PURPLE, + NamedTextColor.YELLOW, + NamedTextColor.WHITE, + TextColor.color(0xddd605), // minecoin_gold + TextColor.color(0xe3d4d1), // material_quartz + TextColor.color(0xcecaca), // material_iron + TextColor.color(0x443a3b), // material_netherite + TextColor.color(0x971607), // material_redstone + TextColor.color(0xb4684d), // material_copper + TextColor.color(0xdEB12d), // material_gold + TextColor.color(0x47a036), // material_emerald + TextColor.color(0x2cbaa8), // material_diamond + TextColor.color(0x21497b), // material_lapis + TextColor.color(0x9a5cc6), // material_amethyst +) +private val legacyDownsampler = LimitedDownsampler(NamedTextColor.NAMES.values().toList()) +private val bedrockDownsampler = LimitedDownsampler(bedrockColors) + +public enum class Downsampler(private val function: UnaryOperator) { + NONE(UnaryOperator.identity()), + LEGACY(legacyDownsampler::render), + BEDROCK(bedrockDownsampler::render), + ; + + public fun apply(component: Component): Component { + return function.apply(component).compact() + } + + public companion object { + private val values = Downsampler.values() + public fun of(name: String?): Downsampler { + return values.firstOrNull { i -> i.name.equals(name, ignoreCase = true) } ?: NONE + } + } +} diff --git a/src/jvmMain/kotlin/net/kyori/adventure/webui/jvm/minimessage/MiniMessage.kt b/src/jvmMain/kotlin/net/kyori/adventure/webui/jvm/minimessage/MiniMessage.kt index 0d32b2d..1c468be 100644 --- a/src/jvmMain/kotlin/net/kyori/adventure/webui/jvm/minimessage/MiniMessage.kt +++ b/src/jvmMain/kotlin/net/kyori/adventure/webui/jvm/minimessage/MiniMessage.kt @@ -108,6 +108,7 @@ public fun Application.miniMessage() { webSocket(URL_MINI_TO_HTML) { var tagResolver = TagResolver.empty() var miniMessage: String? = null + var downsampler: Downsampler = Downsampler.NONE var isolateNewlines = false for (frame in incoming) { @@ -116,6 +117,7 @@ public fun Application.miniMessage() { is Call -> { miniMessage = packet.miniMessage isolateNewlines = packet.isolateNewlines + downsampler = Downsampler.of(packet.downsampler) } is Placeholders -> tagResolver = packet.tagResolver null -> continue @@ -131,16 +133,17 @@ public fun Application.miniMessage() { .split("\n") .map { line -> HookManager.render(line) } .map { line -> - MiniMessage.miniMessage() - .deserialize(line, tagResolver) + MiniMessage.miniMessage().deserialize(line, tagResolver) } + .map { component -> downsampler.apply(component) } .map { component -> HookManager.render(component) } .forEach { component -> result.appendComponent(component) result.append("\n") } } else { - val component = MiniMessage.miniMessage().deserialize(HookManager.render(miniMessage), tagResolver) + var component = MiniMessage.miniMessage().deserialize(HookManager.render(miniMessage), tagResolver) + component = downsampler.apply(component) result.appendComponent(HookManager.render(component)) } @@ -161,13 +164,10 @@ public fun Application.miniMessage() { post(URL_MINI_TO_JSON) { val structure = Serializers.json.tryDecodeFromString(call.receiveText()) val input = structure?.miniMessage ?: return@post - call.respondText( - GsonComponentSerializer.gson() - .serialize( - MiniMessage.miniMessage() - .deserialize(input, structure.placeholders.tagResolver) - ) - ) + + var component = MiniMessage.miniMessage().deserialize(input, structure.placeholders.tagResolver) + component = Downsampler.of(structure.downsampler).apply(component) + call.respondText(GsonComponentSerializer.gson().serialize(component)) } post(URL_MINI_TO_TREE) {