mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-17 11:24:56 -04:00
improve eros error dialog, chat: url parsing
This commit is contained in:
parent
5d0d194ed2
commit
407019dadb
@ -43,7 +43,7 @@ class BaseComponent : ChatComponent {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(parent: TextComponent? = null, legacy: String = "") {
|
||||
constructor(parent: TextComponent? = null, legacy: String = "", restrictedMode: Boolean = false) {
|
||||
val currentText = StringBuilder()
|
||||
var currentColor = parent?.color
|
||||
val currentFormatting: MutableSet<ChatFormattingCode> = parent?.formatting?.toMutableSet() ?: mutableSetOf()
|
||||
@ -54,15 +54,34 @@ class BaseComponent : ChatComponent {
|
||||
|
||||
|
||||
fun push() {
|
||||
if (currentText.isNotEmpty()) {
|
||||
parts += TextComponent(message = currentText.toString(), color = currentColor, formatting = currentFormatting.toMutableSet())
|
||||
if (currentText.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val spaceSplit = currentText.split(' ')
|
||||
for ((index, split) in spaceSplit.withIndex()) {
|
||||
var clickEvent: ClickEvent? = null
|
||||
for (protocol in URLProtocols.VALUES) {
|
||||
if (!split.startsWith(protocol.prefix)) {
|
||||
continue
|
||||
}
|
||||
if (protocol.restricted && restrictedMode) {
|
||||
break
|
||||
}
|
||||
clickEvent = ClickEvent(ClickEvent.ClickEventActions.OPEN_URL, split)
|
||||
break
|
||||
}
|
||||
parts += TextComponent(message = split, color = currentColor, formatting = currentFormatting.toMutableSet(), clickEvent = clickEvent)
|
||||
if (index != spaceSplit.size - 1) {
|
||||
parts += TextComponent(message = " ", color = currentColor, formatting = currentFormatting.toMutableSet())
|
||||
}
|
||||
}
|
||||
currentFormatting.clear()
|
||||
currentColor = null
|
||||
currentText.clear()
|
||||
}
|
||||
currentFormatting.clear()
|
||||
}
|
||||
|
||||
while (char != CharacterIterator.DONE) {
|
||||
// ToDo: Parse urls with click event (and respect restrictedMode)
|
||||
if (char != ProtocolDefinition.TEXT_COMPONENT_SPECIAL_PREFIX_CHAR) {
|
||||
currentText.append(char)
|
||||
char = iterator.next()
|
||||
@ -94,7 +113,7 @@ class BaseComponent : ChatComponent {
|
||||
push()
|
||||
}
|
||||
|
||||
constructor(translator: Translator? = null, parent: TextComponent? = null, json: Map<String, Any>) {
|
||||
constructor(translator: Translator? = null, parent: TextComponent? = null, json: Map<String, Any>, restrictedMode: Boolean = false) {
|
||||
val currentParent: TextComponent?
|
||||
var currentText = ""
|
||||
json["text"]?.nullCast<String>()?.let {
|
||||
@ -121,10 +140,10 @@ class BaseComponent : ChatComponent {
|
||||
formatting.addOrRemove(PreChatFormattingCodes.STRIKETHROUGH, json["strikethrough"]?.toBoolean())
|
||||
formatting.addOrRemove(PreChatFormattingCodes.OBFUSCATED, json["obfuscated"]?.toBoolean())
|
||||
|
||||
val clickEvent = json["clickEvent"]?.compoundCast()?.let { click -> ClickEvent(click) }
|
||||
val clickEvent = json["clickEvent"]?.compoundCast()?.let { click -> ClickEvent(click, restrictedMode) }
|
||||
val hoverEvent = json["hoverEvent"]?.compoundCast()?.let { hover -> HoverEvent(hover) }
|
||||
|
||||
val textComponent = MultiChatComponent(
|
||||
val textComponent = TextComponent(
|
||||
message = currentText,
|
||||
color = color,
|
||||
formatting = formatting,
|
||||
|
@ -76,7 +76,7 @@ interface ChatComponent {
|
||||
val EMPTY = ChatComponent.of("")
|
||||
|
||||
@JvmOverloads
|
||||
fun of(raw: Any? = null, translator: Translator? = null, parent: TextComponent? = null, ignoreJson: Boolean = false): ChatComponent {
|
||||
fun of(raw: Any? = null, translator: Translator? = null, parent: TextComponent? = null, ignoreJson: Boolean = false, restrictedMode: Boolean = false): ChatComponent {
|
||||
// ToDo: Remove gson, replace with maps
|
||||
if (raw == null) {
|
||||
return BaseComponent()
|
||||
@ -85,13 +85,13 @@ interface ChatComponent {
|
||||
return raw
|
||||
}
|
||||
if (raw is Map<*, *>) {
|
||||
return BaseComponent(translator, parent, raw.unsafeCast())
|
||||
return BaseComponent(translator, parent, raw.unsafeCast(), restrictedMode)
|
||||
}
|
||||
val string = when (raw) {
|
||||
is List<*> -> {
|
||||
val component = BaseComponent()
|
||||
for (part in raw) {
|
||||
component += of(part, translator, parent)
|
||||
component += of(part, translator, parent, restrictedMode = restrictedMode)
|
||||
}
|
||||
return component
|
||||
}
|
||||
@ -99,7 +99,7 @@ interface ChatComponent {
|
||||
}
|
||||
if (!ignoreJson && string.startsWith('{')) {
|
||||
try {
|
||||
return BaseComponent(translator, parent, JSONSerializer.MAP_ADAPTER.fromJson(string)!!)
|
||||
return BaseComponent(translator, parent, JSONSerializer.MAP_ADAPTER.fromJson(string)!!, restrictedMode)
|
||||
} catch (ignored: JsonEncodingException) {
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,10 @@
|
||||
package de.bixilon.minosoft.data.text
|
||||
|
||||
import de.bixilon.minosoft.Minosoft
|
||||
import de.bixilon.minosoft.data.text.events.ClickEvent
|
||||
import de.bixilon.minosoft.data.text.events.HoverEvent
|
||||
import de.bixilon.minosoft.gui.eros.dialog.ErosErrorReport.Companion.report
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.hyperlink
|
||||
import de.bixilon.minosoft.gui.rendering.RenderConstants
|
||||
import de.bixilon.minosoft.gui.rendering.RenderWindow
|
||||
import de.bixilon.minosoft.gui.rendering.font.Font
|
||||
@ -38,6 +42,8 @@ open class TextComponent(
|
||||
message: Any? = "",
|
||||
var color: RGBColor? = null,
|
||||
var formatting: MutableSet<ChatFormattingCode> = mutableSetOf(),
|
||||
var clickEvent: ClickEvent? = null,
|
||||
var hoverEvent: HoverEvent? = null,
|
||||
) : ChatComponent {
|
||||
override var message: String = message?.toString() ?: "null"
|
||||
|
||||
@ -160,6 +166,26 @@ open class TextComponent(
|
||||
}
|
||||
}
|
||||
nodes.add(text)
|
||||
|
||||
clickEvent?.let { event ->
|
||||
when (event.action) {
|
||||
ClickEvent.ClickEventActions.OPEN_URL -> text.hyperlink(event.value.toString())
|
||||
else -> {
|
||||
NotImplementedError("Unknown action ${event.action}").report()
|
||||
return@let
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hoverEvent?.let {
|
||||
when (it.action) {
|
||||
HoverEvent.HoverEventActions.SHOW_TEXT -> text.accessibleText = it.value.toString() // ToDo
|
||||
else -> {
|
||||
NotImplementedError("Unknown action ${it.action}").report()
|
||||
return@let
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2020 Moritz Zwerger
|
||||
* Copyright (C) 2021 Moritz Zwerger
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
@ -10,15 +10,20 @@
|
||||
*
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.data.text
|
||||
|
||||
import de.bixilon.minosoft.data.text.events.ClickEvent
|
||||
import de.bixilon.minosoft.data.text.events.HoverEvent
|
||||
import de.bixilon.minosoft.util.KUtil
|
||||
import de.bixilon.minosoft.util.enum.ValuesEnum
|
||||
|
||||
class MultiChatComponent(
|
||||
message: String = "",
|
||||
color: RGBColor? = null,
|
||||
formatting: MutableSet<ChatFormattingCode> = mutableSetOf(),
|
||||
var clickEvent: ClickEvent? = null,
|
||||
var hoverEvent: HoverEvent? = null,
|
||||
) : TextComponent(message, color, formatting)
|
||||
enum class URLProtocols(val prefix: String, val restricted: Boolean = false) {
|
||||
HTTP("http://"),
|
||||
HTTPS("https://"),
|
||||
FILE("file:", true),
|
||||
;
|
||||
|
||||
companion object : ValuesEnum<URLProtocols> {
|
||||
override val VALUES: Array<URLProtocols> = values()
|
||||
override val NAME_MAP: Map<String, URLProtocols> = KUtil.getEnumValues(VALUES)
|
||||
}
|
||||
}
|
@ -13,15 +13,23 @@
|
||||
package de.bixilon.minosoft.data.text.events
|
||||
|
||||
import de.bixilon.minosoft.util.KUtil
|
||||
import de.bixilon.minosoft.util.Util
|
||||
import de.bixilon.minosoft.util.enum.ValuesEnum
|
||||
|
||||
class ClickEvent {
|
||||
val action: ClickEventActions
|
||||
val value: Any
|
||||
|
||||
constructor(json: Map<String, Any>) {
|
||||
constructor(json: Map<String, Any>, restrictedMode: Boolean = false) {
|
||||
action = ClickEventActions[json["action"].toString().lowercase()]
|
||||
this.value = json["value"]!!
|
||||
|
||||
if (!restrictedMode) {
|
||||
return
|
||||
}
|
||||
if (action == ClickEventActions.OPEN_URL) {
|
||||
Util.checkURL(value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
constructor(action: ClickEventActions, value: Any) {
|
||||
|
@ -13,19 +13,29 @@
|
||||
|
||||
package de.bixilon.minosoft.gui.eros.dialog
|
||||
|
||||
import de.bixilon.minosoft.Minosoft
|
||||
import de.bixilon.minosoft.gui.eros.controller.JavaFXWindowController
|
||||
import de.bixilon.minosoft.gui.eros.crash.ErosCrashReport.Companion.crash
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.ctext
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.text
|
||||
import de.bixilon.minosoft.terminal.RunConfiguration
|
||||
import de.bixilon.minosoft.util.KUtil.asResourceLocation
|
||||
import de.bixilon.minosoft.util.KUtil.realName
|
||||
import de.bixilon.minosoft.util.KUtil.toStackTrace
|
||||
import javafx.application.Platform
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Button
|
||||
import javafx.scene.control.TextArea
|
||||
import javafx.scene.text.TextFlow
|
||||
|
||||
|
||||
class ErosErrorReport : JavaFXWindowController() {
|
||||
@FXML private lateinit var headerFX: TextFlow
|
||||
@FXML private lateinit var descriptionFX: TextFlow
|
||||
@FXML private lateinit var detailsFX: TextArea
|
||||
@FXML private lateinit var ignoreFX: Button
|
||||
@FXML private lateinit var fatalCrashFX: Button
|
||||
|
||||
var exception: Throwable? = null
|
||||
set(value) {
|
||||
@ -45,9 +55,23 @@ class ErosErrorReport : JavaFXWindowController() {
|
||||
exception?.crash()
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
super.init()
|
||||
headerFX.text = HEADER
|
||||
descriptionFX.text = DESCRIPTION
|
||||
|
||||
ignoreFX.ctext = IGNORE
|
||||
fatalCrashFX.ctext = FATAL_CRASH
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private val LAYOUT = "minosoft:eros/dialog/error.fxml".asResourceLocation()
|
||||
private val TITLE = { exception: Throwable? -> Minosoft.LANGUAGE_MANAGER.translate("minosoft:error.title".asResourceLocation(), null, exception?.let { it::class.java.realName }) }
|
||||
private val HEADER = "minosoft:error.header".asResourceLocation()
|
||||
private val DESCRIPTION = "minosoft:error.description".asResourceLocation()
|
||||
private val IGNORE = "minosoft:error.ignore".asResourceLocation()
|
||||
private val FATAL_CRASH = "minosoft:error.fatal_crash".asResourceLocation()
|
||||
|
||||
fun Throwable?.report() {
|
||||
if (RunConfiguration.DISABLE_EROS) {
|
||||
@ -55,7 +79,7 @@ class ErosErrorReport : JavaFXWindowController() {
|
||||
}
|
||||
|
||||
Platform.runLater {
|
||||
val controller = JavaFXUtil.openModal<ErosErrorReport>("", LAYOUT)
|
||||
val controller = JavaFXUtil.openModal<ErosErrorReport>(TITLE(this), LAYOUT)
|
||||
controller.exception = this
|
||||
controller.stage.show()
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import de.bixilon.minosoft.gui.eros.controller.JavaFXWindowController
|
||||
import de.bixilon.minosoft.gui.eros.modding.invoker.JavaFXEventInvoker
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXAccountUtil.avatar
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.clickable
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.ctext
|
||||
import de.bixilon.minosoft.modding.event.events.account.AccountSelectEvent
|
||||
import de.bixilon.minosoft.modding.event.master.GlobalEventMaster
|
||||
@ -90,6 +91,10 @@ class MainErosController : JavaFXWindowController() {
|
||||
ErosMainActivities.ABOUT to aboutIconFX,
|
||||
)
|
||||
|
||||
for (icon in iconMap) {
|
||||
icon.value.clickable()
|
||||
}
|
||||
|
||||
highlightIcon(playIconFX)
|
||||
|
||||
playIconFX.setOnMouseClicked {
|
||||
@ -104,14 +109,19 @@ class MainErosController : JavaFXWindowController() {
|
||||
aboutIconFX.setOnMouseClicked {
|
||||
activity = ErosMainActivities.ABOUT
|
||||
}
|
||||
exitIconFX.setOnMouseClicked {
|
||||
exitIconFX.apply {
|
||||
clickable()
|
||||
setOnMouseClicked {
|
||||
ShutdownManager.shutdown(reason = ShutdownReasons.REQUESTED_BY_USER)
|
||||
}
|
||||
}
|
||||
|
||||
GlobalEventMaster.registerEvent(JavaFXEventInvoker.of<AccountSelectEvent> {
|
||||
accountImageFX.image = it.account?.avatar
|
||||
accountNameFX.ctext = it.account?.username ?: NO_ACCOUNT_SELECTED
|
||||
})
|
||||
accountImageFX.clickable()
|
||||
accountNameFX.clickable()
|
||||
|
||||
activity = ErosMainActivities.PlAY
|
||||
}
|
||||
|
@ -19,10 +19,11 @@ import de.bixilon.minosoft.gui.eros.controller.EmbeddedJavaFXController
|
||||
import de.bixilon.minosoft.gui.eros.controller.JavaFXController
|
||||
import de.bixilon.minosoft.gui.eros.controller.JavaFXWindowController
|
||||
import de.bixilon.minosoft.util.KUtil.setValue
|
||||
import de.bixilon.minosoft.util.KUtil.unsafeCast
|
||||
import javafx.application.HostServices
|
||||
import javafx.css.StyleableProperty
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.Scene
|
||||
import javafx.scene.*
|
||||
import javafx.scene.control.Labeled
|
||||
import javafx.scene.control.TextField
|
||||
import javafx.scene.image.Image
|
||||
@ -92,4 +93,15 @@ object JavaFXUtil {
|
||||
set(value) {
|
||||
this.text = Minosoft.LANGUAGE_MANAGER.translate(value).message
|
||||
}
|
||||
|
||||
fun Text.hyperlink(link: String) {
|
||||
this.setOnMouseClicked { HOST_SERVICES.showDocument(link) }
|
||||
this.accessibleRole = AccessibleRole.HYPERLINK
|
||||
this.styleClass.setAll("hyperlink")
|
||||
this.clickable()
|
||||
}
|
||||
|
||||
fun Node.clickable() {
|
||||
this.cursorProperty().unsafeCast<StyleableProperty<Cursor>>().applyStyle(null, Cursor.HAND)
|
||||
}
|
||||
}
|
||||
|
@ -255,7 +255,7 @@ open class InByteBuffer {
|
||||
}
|
||||
|
||||
open fun readChatComponent(): ChatComponent {
|
||||
return ChatComponent.of(readString())
|
||||
return ChatComponent.of(readString(), restrictedMode = true)
|
||||
}
|
||||
|
||||
fun readChatComponentArray(length: Int = readVarInt()): Array<ChatComponent> {
|
||||
|
@ -17,12 +17,10 @@
|
||||
<RowConstraints maxHeight="Infinity" vgrow="ALWAYS"/>
|
||||
<RowConstraints maxHeight="Infinity" vgrow="NEVER"/>
|
||||
</rowConstraints>
|
||||
<Text strokeWidth="0.0" text="An error occurred">
|
||||
<font>
|
||||
<Font name="System Bold" size="19.0"/>
|
||||
</font>
|
||||
</Text>
|
||||
<TextFlow GridPane.rowIndex="1">
|
||||
<TextFlow fx:id="headerFX" style="-fx-font-size: 30; -fx-font-weight: bold;">
|
||||
<Text text="An error occurred"/>
|
||||
</TextFlow>
|
||||
<TextFlow fx:id="descriptionFX" GridPane.rowIndex="1">
|
||||
<Label text="An error in minosoft occurred. You can continue like before, but the behavior might not be the expected one. If this error persists, feel free to open an issue here: " wrapText="true"/>
|
||||
<Hyperlink onAction="#openURL" text="https://gitlab.bixilon.de/bixilon/minosoft/-/issues/" wrapText="true"/>
|
||||
<GridPane.margin>
|
||||
@ -51,8 +49,8 @@
|
||||
<padding>
|
||||
<Insets top="5.0"/>
|
||||
</padding>
|
||||
<Button maxWidth="Infinity" onAction="#ignore" text="Ignore" GridPane.columnIndex="2"/>
|
||||
<Button maxWidth="Infinity" onAction="#fatalCrash" text="Fatal crash"/>
|
||||
<Button fx:id="ignoreFX" maxWidth="Infinity" onAction="#ignore" text="Ignore" GridPane.columnIndex="2"/>
|
||||
<Button fx:id="fatalCrashFX" maxWidth="Infinity" onAction="#fatalCrash" text="Fatal crash"/>
|
||||
</GridPane>
|
||||
</GridPane>
|
||||
<padding>
|
||||
|
@ -98,3 +98,9 @@ minosoft:connection.kick.close_button=Close
|
||||
minosoft:connection.login_kick.title=Kicked from server
|
||||
minosoft:connection.login_kick.header=You got kicked
|
||||
minosoft:connection.login_kick.description=You got kicked while logging in from %1$s (connected with: %2$s)
|
||||
|
||||
minosoft:error.title=%1$s - Minosoft
|
||||
minosoft:error.header=An error occurred!
|
||||
minosoft:error.description=An error in minosoft occurred. You can continue like before, but the behavior might not be the expected one. If this error persists, feel free to open an issue here: https://gitlab.bixilon.de/bixilon/minosoft/-/issues/
|
||||
minosoft:error.ignore=Ignore
|
||||
minosoft:error.fatal_crash=Fatal crash
|
||||
|
Loading…
x
Reference in New Issue
Block a user