chore: basic mod browsing (untested due to GTK4 issues)
This commit is contained in:
parent
f1f2e95dd2
commit
ad033711f9
|
@ -10,5 +10,5 @@ repositories {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2")
|
implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2")
|
||||||
implementation("de.undercouch:gradle-download-task:5.1.2")
|
implementation("de.undercouch:gradle-download-task:5.1.2")
|
||||||
implementation("io.gitlab.jfronny:convention:1.4-SNAPSHOT")
|
implementation("io.gitlab.jfronny:convention:1.5-SNAPSHOT")
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
plugins {
|
plugins {
|
||||||
`java-library`
|
id("jf.java")
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ public class Net {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, String apiKey) throws IOException {
|
public static <T> T downloadObject(String url, ThrowingFunction<String, T, IOException> func, String apiKey) throws IOException {
|
||||||
return downloadObject(url, () -> HttpUtils.get(url).header("x-api-key", apiKey).sendString(), func, true);
|
return downloadObject(url, () -> downloadStringAuthenticated(url, apiKey), func, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T downloadObject(String url, String sha1, ThrowingFunction<String, T, IOException> func) throws IOException {
|
public static <T> T downloadObject(String url, String sha1, ThrowingFunction<String, T, IOException> func) throws IOException {
|
||||||
|
@ -82,6 +82,10 @@ public class Net {
|
||||||
return new String(downloadData(url, sha1), StandardCharsets.UTF_8);
|
return new String(downloadData(url, sha1), StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String downloadStringAuthenticated(String url, String apiKey) throws IOException, URISyntaxException {
|
||||||
|
return HttpUtils.get(url).header("x-api-key", apiKey).sendString();
|
||||||
|
}
|
||||||
|
|
||||||
public static void downloadFile(String url, Path path) throws IOException, URISyntaxException {
|
public static void downloadFile(String url, Path path) throws IOException, URISyntaxException {
|
||||||
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
|
if (!Files.exists(path.getParent())) Files.createDirectories(path.getParent());
|
||||||
Files.write(path, downloadData(url));
|
Files.write(path, downloadData(url));
|
||||||
|
|
|
@ -3,13 +3,18 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
plugins {
|
plugins {
|
||||||
id("inceptum.application")
|
id("inceptum.application")
|
||||||
id("com.github.johnrengelman.shadow")
|
id("com.github.johnrengelman.shadow")
|
||||||
kotlin("jvm") version "1.9.0-RC"
|
kotlin("jvm") version "1.9.0"
|
||||||
|
kotlin("plugin.sam.with.receiver") version "1.9.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass.set("io.gitlab.jfronny.inceptum.gtk.GtkMain")
|
mainClass.set("io.gitlab.jfronny.inceptum.gtk.GtkMain")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
samWithReceiver {
|
||||||
|
annotation("io.gitlab.jfronny.commons.SamWithReceiver")
|
||||||
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven("https://jitpack.io") {
|
maven("https://jitpack.io") {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceNameTool
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceNameTool
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType
|
import io.gitlab.jfronny.inceptum.launcher.system.launch.LaunchType
|
||||||
import org.gnome.adw.ActionRow
|
import org.gnome.adw.ActionRow
|
||||||
|
import org.gnome.gdk.Gdk
|
||||||
import org.gnome.gio.Menu
|
import org.gnome.gio.Menu
|
||||||
import org.gnome.gtk.*
|
import org.gnome.gtk.*
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -32,7 +33,6 @@ class InstanceListEntryFactory(
|
||||||
launch.addCssClass("flat")
|
launch.addCssClass("flat")
|
||||||
launch.name = "inceptum-launch"
|
launch.name = "inceptum-launch"
|
||||||
launch.tooltipText = I18n["instance.launch"]
|
launch.tooltipText = I18n["instance.launch"]
|
||||||
launch.hasTooltip = true
|
|
||||||
|
|
||||||
val menu = MenuButton()
|
val menu = MenuButton()
|
||||||
menu.addCssClass("flat")
|
menu.addCssClass("flat")
|
||||||
|
@ -49,7 +49,7 @@ class InstanceListEntryFactory(
|
||||||
row.fixSubtitle()
|
row.fixSubtitle()
|
||||||
|
|
||||||
val rightClicked = GestureClick()
|
val rightClicked = GestureClick()
|
||||||
rightClicked.button = 3
|
rightClicked.button = Gdk.BUTTON_SECONDARY
|
||||||
rightClicked.onPressed { nPress, _, _ -> if (nPress == 1) menu.emitActivate() }
|
rightClicked.onPressed { nPress, _, _ -> if (nPress == 1) menu.emitActivate() }
|
||||||
row.addController(rightClicked)
|
row.addController(rightClicked)
|
||||||
|
|
||||||
|
@ -59,11 +59,11 @@ class InstanceListEntryFactory(
|
||||||
override fun BindContext.bind(widget: ActionRow, data: Decomposed) {
|
override fun BindContext.bind(widget: ActionRow, data: Decomposed) {
|
||||||
if (data.instance?.isLocked ?: true) {
|
if (data.instance?.isLocked ?: true) {
|
||||||
data.item.activatable = false
|
data.item.activatable = false
|
||||||
data.row.subtitle = if (data.instance?.isRunningLocked ?: false) I18n["instance.launch.locked.running"]
|
widget.subtitle = if (data.instance?.isRunningLocked ?: false) I18n["instance.launch.locked.running"]
|
||||||
else I18n["instance.launch.locked.setup"]
|
else I18n["instance.launch.locked.setup"]
|
||||||
}
|
}
|
||||||
|
|
||||||
data.row.title = data.instance.toString()
|
widget.title = data.instance.toString()
|
||||||
|
|
||||||
data.thumbnail.bind(data.instance!!)
|
data.thumbnail.bind(data.instance!!)
|
||||||
|
|
||||||
|
@ -137,14 +137,13 @@ class InstanceListEntryFactory(
|
||||||
val launch = suffixes.firstChild as Button
|
val launch = suffixes.firstChild as Button
|
||||||
val menuButton = launch.nextSibling as MenuButton
|
val menuButton = launch.nextSibling as MenuButton
|
||||||
val popoverMenu = menuButton.popover as PopoverMenu
|
val popoverMenu = menuButton.popover as PopoverMenu
|
||||||
return Decomposed(listItem, id, instance, widget, thumbnail, launch, popoverMenu)
|
return Decomposed(listItem, id, instance, thumbnail, launch, popoverMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Decomposed(
|
class Decomposed(
|
||||||
val item: ListItem,
|
val item: ListItem,
|
||||||
val instanceId: String,
|
val instanceId: String,
|
||||||
val instance: Instance?,
|
val instance: Instance?,
|
||||||
val row: ActionRow,
|
|
||||||
val thumbnail: InstanceThumbnail,
|
val thumbnail: InstanceThumbnail,
|
||||||
val launch: Button,
|
val launch: Button,
|
||||||
val popoverMenu: PopoverMenu
|
val popoverMenu: PopoverMenu
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.gtk.control
|
||||||
|
|
||||||
|
import org.gnome.gtk.Align
|
||||||
|
import org.gnome.gtk.ProgressBar
|
||||||
|
import org.gnome.gtk.Revealer
|
||||||
|
import org.gnome.gtk.RevealerTransitionType
|
||||||
|
|
||||||
|
class LoadingRevealer: Revealer() {
|
||||||
|
private val progressBar: ProgressBar
|
||||||
|
|
||||||
|
init {
|
||||||
|
transitionType = RevealerTransitionType.CROSSFADE
|
||||||
|
revealChild = false
|
||||||
|
vexpand = false
|
||||||
|
hexpand = false
|
||||||
|
valign = Align.CENTER
|
||||||
|
halign = Align.FILL
|
||||||
|
progressBar = ProgressBar().apply {
|
||||||
|
hexpand = true
|
||||||
|
vexpand = false
|
||||||
|
addCssClass("osd")
|
||||||
|
}
|
||||||
|
child = progressBar
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRunning(state: Boolean) {
|
||||||
|
revealChild = state
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setProgress(progress: Double) {
|
||||||
|
progressBar.fraction = progress.coerceIn(0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pulse() {
|
||||||
|
progressBar.pulse()
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import org.gnome.gtk.*
|
||||||
import org.jetbrains.annotations.PropertyKey
|
import org.jetbrains.annotations.PropertyKey
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
open class SectionedSettingsTab(window: Window?): SettingsTab<Box>(window, Box(Orientation.VERTICAL, 8)) {
|
open class SectionedSettingsTab<W : Window>(window: W?): SettingsTab<Box, W>(window, Box(Orientation.VERTICAL, 8)) {
|
||||||
init {
|
init {
|
||||||
content.marginHorizontal = 24
|
content.marginHorizontal = 24
|
||||||
content.marginTop = 12
|
content.marginTop = 12
|
||||||
|
|
|
@ -5,8 +5,8 @@ import io.gitlab.jfronny.inceptum.gtk.GtkEnvBackend
|
||||||
import org.gnome.gtk.Widget
|
import org.gnome.gtk.Widget
|
||||||
import org.gnome.gtk.Window
|
import org.gnome.gtk.Window
|
||||||
|
|
||||||
open class SettingsTab<T : Widget>(
|
open class SettingsTab<T : Widget, W : Window>(
|
||||||
protected val window: Window?,
|
protected val window: W?,
|
||||||
val content: T
|
val content: T
|
||||||
) {
|
) {
|
||||||
protected fun showError(message: String, t: Throwable) =
|
protected fun showError(message: String, t: Throwable) =
|
||||||
|
|
|
@ -40,7 +40,7 @@ open class SettingsWindow(app: Application?) : Window() {
|
||||||
setDefaultSize(720, 360)
|
setDefaultSize(720, 360)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addTab(tab: SettingsTab<*>, title: @PropertyKey(resourceBundle = I18n.BUNDLE) String, iconName: String) {
|
fun addTab(tab: SettingsTab<*, *>, title: @PropertyKey(resourceBundle = I18n.BUNDLE) String, iconName: String) {
|
||||||
stack.addTitledWithIcon(tab.content, title, I18n[title], iconName)
|
stack.addTitledWithIcon(tab.content, title, I18n[title], iconName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,6 @@ import org.gnome.gtk.StringList
|
||||||
|
|
||||||
fun StringList.clear() = splice(0, size, null)
|
fun StringList.clear() = splice(0, size, null)
|
||||||
fun StringList.addAll(values: Array<String>) = splice(size, 0, values)
|
fun StringList.addAll(values: Array<String>) = splice(size, 0, values)
|
||||||
|
fun StringList.replaceAll(values: Array<String>) = splice(0, size, values)
|
||||||
|
|
||||||
val ListModel.size get() = nItems
|
val ListModel.size get() = nItems
|
|
@ -13,7 +13,8 @@ import org.gnome.gio.Cancellable
|
||||||
import org.gnome.gtk.*
|
import org.gnome.gtk.*
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
class ExportTab(private val instance: Instance, window: InstanceSettingsWindow?) : SectionedSettingsTab(window) {
|
class ExportTab(window: InstanceSettingsWindow) : SectionedSettingsTab<InstanceSettingsWindow>(window) {
|
||||||
|
private val instance: Instance = window.instance
|
||||||
init {
|
init {
|
||||||
section(null) {
|
section(null) {
|
||||||
row("instance.settings.export.version", "instance.settings.export.version.subtitle") {
|
row("instance.settings.export.version", "instance.settings.export.version.subtitle") {
|
||||||
|
|
|
@ -14,7 +14,6 @@ import io.gitlab.jfronny.inceptum.gtk.util.markup
|
||||||
import io.gitlab.jfronny.inceptum.gtk.util.toTypedArray
|
import io.gitlab.jfronny.inceptum.gtk.util.toTypedArray
|
||||||
import io.gitlab.jfronny.inceptum.launcher.api.FabricMetaApi
|
import io.gitlab.jfronny.inceptum.launcher.api.FabricMetaApi
|
||||||
import io.gitlab.jfronny.inceptum.launcher.api.McApi
|
import io.gitlab.jfronny.inceptum.launcher.api.McApi
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceList
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceNameTool
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.InstanceNameTool
|
||||||
import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser
|
import io.gitlab.jfronny.inceptum.launcher.util.GameVersionParser
|
||||||
|
@ -26,12 +25,13 @@ import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class GeneralTab(instance: Instance, window: InstanceSettingsWindow) : SectionedSettingsTab(window) {
|
class GeneralTab(window: InstanceSettingsWindow) : SectionedSettingsTab<InstanceSettingsWindow>(window) {
|
||||||
companion object {
|
companion object {
|
||||||
private val VERSIONS = McApi.getVersions()
|
private val VERSIONS = McApi.getVersions()
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
val instance = window.instance
|
||||||
section(null) {
|
section(null) {
|
||||||
row("instance.settings.general.name", "instance.settings.general.name.placeholder") {
|
row("instance.settings.general.name", "instance.settings.general.name.placeholder") {
|
||||||
val apply = Button.newWithLabel(I18n["instance.settings.apply"])
|
val apply = Button.newWithLabel(I18n["instance.settings.apply"])
|
||||||
|
|
|
@ -4,10 +4,10 @@ import io.gitlab.jfronny.inceptum.gtk.control.settings.SettingsWindow
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
||||||
import org.gnome.gtk.Application
|
import org.gnome.gtk.Application
|
||||||
|
|
||||||
class InstanceSettingsWindow(app: Application?, instance: Instance) : SettingsWindow(app) {
|
class InstanceSettingsWindow(val app: Application?, val instance: Instance) : SettingsWindow(app) {
|
||||||
init {
|
init {
|
||||||
addTab(GeneralTab(instance, this), "instance.settings.general", "preferences-other-symbolic")
|
addTab(GeneralTab(this), "instance.settings.general", "preferences-other-symbolic")
|
||||||
addTab(ModsTab(instance, this), "instance.settings.mods", "package-x-generic-symbolic")
|
addTab(ModsTab(this), "instance.settings.mods", "package-x-generic-symbolic")
|
||||||
addTab(ExportTab(instance, this), "instance.settings.export", "send-to-symbolic")
|
addTab(ExportTab(this), "instance.settings.export", "send-to-symbolic")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,55 @@
|
||||||
package io.gitlab.jfronny.inceptum.gtk.window.settings.instance
|
package io.gitlab.jfronny.inceptum.gtk.window.settings.instance
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.concurrent.AsyncRequest
|
||||||
|
import io.gitlab.jfronny.commons.concurrent.VoidFuture
|
||||||
import io.gitlab.jfronny.inceptum.gtk.control.ILabel
|
import io.gitlab.jfronny.inceptum.gtk.control.ILabel
|
||||||
import io.gitlab.jfronny.inceptum.gtk.control.KDropDown
|
import io.gitlab.jfronny.inceptum.gtk.control.KDropDown
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.KSignalListItemFactory
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.control.LoadingRevealer
|
||||||
import io.gitlab.jfronny.inceptum.gtk.control.settings.SettingsTab
|
import io.gitlab.jfronny.inceptum.gtk.control.settings.SettingsTab
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.I18n
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.clear
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.fixSubtitle
|
||||||
|
import io.gitlab.jfronny.inceptum.gtk.util.replaceAll
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.LauncherEnv
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.CurseforgeApi
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.api.ModrinthApi
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.model.modrinth.ModrinthProjectType
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Instance
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Mod
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.ModManager
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.ModPath
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.mds.ModsDirScanner
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.source.CurseforgeModSource
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.source.ModSource
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.source.ModrinthModSource
|
||||||
|
import org.gnome.adw.ActionRow
|
||||||
import org.gnome.adw.Leaflet
|
import org.gnome.adw.Leaflet
|
||||||
import org.gnome.adw.LeafletTransitionType
|
import org.gnome.adw.LeafletTransitionType
|
||||||
import org.gnome.gtk.Box
|
import org.gnome.glib.GLib
|
||||||
import org.gnome.gtk.Orientation
|
import org.gnome.gtk.*
|
||||||
import org.gnome.gtk.SearchBar
|
import org.jetbrains.annotations.PropertyKey
|
||||||
import org.gnome.gtk.SearchEntry
|
import java.util.concurrent.ForkJoinPool
|
||||||
import org.gnome.gtk.Separator
|
import kotlin.jvm.optionals.getOrNull
|
||||||
|
|
||||||
|
class ModsTab(window: InstanceSettingsWindow) : SettingsTab<Leaflet, InstanceSettingsWindow>(window, Leaflet()) {
|
||||||
|
private val instance: Instance
|
||||||
|
private val mds: ModsDirScanner
|
||||||
|
private val listModel: StringList
|
||||||
|
private val loadingRevealer = LoadingRevealer()
|
||||||
|
private val descriptionLabel: ILabel
|
||||||
|
|
||||||
|
private var page: Page = Page.LOCAL
|
||||||
|
enum class Page {
|
||||||
|
LOCAL, MODRINTH, CURSEFORGE
|
||||||
|
}
|
||||||
|
|
||||||
class ModsTab(instance: Instance?, window: InstanceSettingsWindow?) : SettingsTab<Leaflet>(window, Leaflet()) {
|
|
||||||
init {
|
init {
|
||||||
|
instance = window.instance
|
||||||
|
mds = instance.mds
|
||||||
|
|
||||||
content.apply {
|
content.apply {
|
||||||
|
//TODO consider filter panel via Flap
|
||||||
homogeneous = false
|
homogeneous = false
|
||||||
canNavigateBack = true
|
canNavigateBack = true
|
||||||
transitionType = LeafletTransitionType.OVER
|
transitionType = LeafletTransitionType.OVER
|
||||||
|
@ -23,9 +58,9 @@ class ModsTab(instance: Instance?, window: InstanceSettingsWindow?) : SettingsTa
|
||||||
append(KDropDown("instance.settings.mods.local", "instance.settings.mods.modrinth", "instance.settings.mods.curseforge").apply {
|
append(KDropDown("instance.settings.mods.local", "instance.settings.mods.modrinth", "instance.settings.mods.curseforge").apply {
|
||||||
onChange { newFilter ->
|
onChange { newFilter ->
|
||||||
when (newFilter) {
|
when (newFilter) {
|
||||||
0 -> switchToLocalMods()
|
0 -> switchTo(Page.LOCAL)
|
||||||
1 -> switchToModrinthMods()
|
1 -> switchTo(Page.MODRINTH)
|
||||||
2 -> switchToCurseforgeMods()
|
2 -> switchTo(Page.CURSEFORGE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -39,17 +74,206 @@ class ModsTab(instance: Instance?, window: InstanceSettingsWindow?) : SettingsTa
|
||||||
child = entry
|
child = entry
|
||||||
keyCaptureWidget = entry
|
keyCaptureWidget = entry
|
||||||
})
|
})
|
||||||
|
listModel = StringList(arrayOf())
|
||||||
|
append(Overlay().apply {
|
||||||
|
child = ModsListView(listModel)
|
||||||
|
addOverlay(loadingRevealer)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
append(Separator(Orientation.VERTICAL)).navigatable = false
|
append(Separator(Orientation.VERTICAL)).navigatable = false
|
||||||
append(Box(Orientation.VERTICAL, 6).apply {
|
append(Box(Orientation.VERTICAL, 6).apply {
|
||||||
append(ILabel("instance.settings.mods.unsupported"))
|
descriptionLabel = ILabel("instance.settings.mods.select")
|
||||||
|
append(descriptionLabel)
|
||||||
//TODO content
|
//TODO content
|
||||||
})
|
})
|
||||||
|
|
||||||
|
addTickCallback { _, _ ->
|
||||||
|
val toShow = mutableListOf<String>()
|
||||||
|
if (page == Page.LOCAL) {
|
||||||
|
loadingRevealer.setRunning(!mds.isComplete)
|
||||||
|
val mods = window.instance.mods
|
||||||
|
loadingRevealer.setProgress((mods.filter { mds.hasScanned(it) }.size.toDouble() / mods.size))
|
||||||
|
for (mod in mods) {
|
||||||
|
if (mod.name.contains(currentSearchString)) {
|
||||||
|
//TODO improve this search
|
||||||
|
this@ModsTab.mods[mod.name] = ModState.Installed(mod)
|
||||||
|
toShow.add(mod.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listModel.replaceAll(mods.map { it.name }.toTypedArray())
|
||||||
|
} else {
|
||||||
|
loadingRevealer.setRunning(searchResult == null || !mds.isComplete)
|
||||||
|
if (searchResult != null) {
|
||||||
|
for (mod in searchResult.orEmpty()) {
|
||||||
|
this@ModsTab.mods[mod.name] = mod
|
||||||
|
toShow.add(mod.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listModel.replaceAll(toShow.toTypedArray())
|
||||||
|
GLib.SOURCE_CONTINUE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun switchToLocalMods(): Unit = TODO("set up")
|
private var currentSearchString: String = ""
|
||||||
fun switchToModrinthMods(): Unit = TODO("set up")
|
private var searchResult: List<ModState>? = null
|
||||||
fun switchToCurseforgeMods(): Unit = TODO("set up")
|
//TODO search pagination
|
||||||
fun updateSearch(search: String): Unit = TODO("set up")
|
private val search = AsyncRequest({
|
||||||
|
searchResult = null
|
||||||
|
loadingRevealer.setRunning(true)
|
||||||
|
loadingRevealer.pulse()
|
||||||
|
VoidFuture(ForkJoinPool.commonPool().submit {
|
||||||
|
val sources: List<ModSource> = when (page) {
|
||||||
|
Page.CURSEFORGE -> {
|
||||||
|
val cf = CurseforgeApi.search(instance.gameVersion, currentSearchString, 0, "Popularity") //TODO allow user to choose sort mode
|
||||||
|
cf.mapNotNull {
|
||||||
|
if (isCancelled) return@submit
|
||||||
|
val file = it.latestFilesIndexes.firstOrNull { it.gameVersion == instance.gameVersion } //TODO support compatible minor versions
|
||||||
|
if (file == null) null
|
||||||
|
else CurseforgeModSource(it.id, file.fileId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Page.MODRINTH -> {
|
||||||
|
val mr = ModrinthApi.search(currentSearchString, 0, instance.gameVersion, ModrinthProjectType.mod).hits
|
||||||
|
mr.mapNotNull {
|
||||||
|
if (isCancelled) return@submit
|
||||||
|
val file = ModrinthApi.getLatestVersions(it.project_id, instance.gameVersion).best
|
||||||
|
if (file == null) null
|
||||||
|
else ModrinthModSource(file.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Page.LOCAL -> listOf()
|
||||||
|
}
|
||||||
|
searchResult = sources.map { ModState(it) }
|
||||||
|
})
|
||||||
|
}, {
|
||||||
|
loadingRevealer.setRunning(false)
|
||||||
|
})
|
||||||
|
fun switchTo(page: Page): Unit {
|
||||||
|
listModel.clear()
|
||||||
|
mods.clear()
|
||||||
|
this.page = page
|
||||||
|
updateSearch(currentSearchString)
|
||||||
|
}
|
||||||
|
fun updateSearch(search: String): Unit {
|
||||||
|
descriptionLabel.text = "Searching is currently unsupported"
|
||||||
|
currentSearchString = search
|
||||||
|
this.search.request()
|
||||||
|
}
|
||||||
|
fun selectMod(mod: ModState): Unit {
|
||||||
|
//TODO detailed menu for version selection, ...
|
||||||
|
descriptionLabel.setMarkup(mod.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ModsListView(model: StringList): ListView(ModsListSelectionModel(model), ModsListItemFactory()) {
|
||||||
|
init {
|
||||||
|
vscrollPolicy = ScrollablePolicy.NATURAL
|
||||||
|
addCssClass("navigation-sidebar")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModsListSelectionModel(model: StringList): SingleSelection(model) {
|
||||||
|
init {
|
||||||
|
autoselect = false
|
||||||
|
// onSelectionChanged { position, nItems ->
|
||||||
|
// val v = (selectedItem as? StringObject)?.string
|
||||||
|
// if (v != null)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mods = LinkedHashMap<String, ModState>()
|
||||||
|
|
||||||
|
//TODO https://gitlab.gnome.org/World/gfeeds/-/blob/master/data/ui/sidebar_listbox_row.blp
|
||||||
|
inner class ModsListItemFactory: KSignalListItemFactory<Decomposed, ActionRow>() {
|
||||||
|
override fun setup(): ActionRow {
|
||||||
|
val row = ActionRow()
|
||||||
|
row.activatable = true
|
||||||
|
|
||||||
|
val quickAction = Button.newFromIconName("folder-download-symbolic")
|
||||||
|
quickAction.addCssClass("flat")
|
||||||
|
quickAction.tooltipText = I18n["instance.settings.mods.download"]
|
||||||
|
|
||||||
|
row.fixSubtitle()
|
||||||
|
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun BindContext.bind(widget: ActionRow, data: Decomposed) {
|
||||||
|
widget.title = data.mod!!.name
|
||||||
|
widget.subtitle = data.mod.summary
|
||||||
|
registerForUnbind(widget.onActivated { selectMod(data.mod) })
|
||||||
|
fun setupQuickAction(
|
||||||
|
iconName: String,
|
||||||
|
description: @PropertyKey(resourceBundle = I18n.BUNDLE) String,
|
||||||
|
handler: Button.Clicked
|
||||||
|
) {
|
||||||
|
data.quickAction.iconName = iconName
|
||||||
|
data.quickAction.tooltipText = I18n[description]
|
||||||
|
registerForUnbind(data.quickAction.onClicked(handler))
|
||||||
|
}
|
||||||
|
when (data.mod) {
|
||||||
|
is ModState.Installed -> if (data.mod.outdated) setupQuickAction("software-update-available-symbolic", "instance.settings.mods.update") {
|
||||||
|
data.mod.updates[0]()
|
||||||
|
}
|
||||||
|
else setupQuickAction("edit-delete-symbolic", "instance.settings.mods.delete") {
|
||||||
|
data.mod.remove()
|
||||||
|
}
|
||||||
|
is ModState.Available -> setupQuickAction("folder-download-symbolic", "instance.settings.mods.download") {
|
||||||
|
data.mod.install()
|
||||||
|
}
|
||||||
|
else -> setupQuickAction("dialog-question-symbolic", "instance.settings.mods.unknown") {
|
||||||
|
LauncherEnv.showError(I18n["instance.settings.mods.unknown.description"], I18n["instance.settings.mods.unknown"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun UnbindContext.unbind(widget: ActionRow, data: Decomposed) {
|
||||||
|
// Not needed currently
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ActionContext.castWidget(widget: Widget): ActionRow = widget as ActionRow
|
||||||
|
override fun ActionContext.lookup(id: String, widget: ActionRow): Decomposed {
|
||||||
|
val mod = mods[id]
|
||||||
|
val suffixes = widget.firstChild!!.lastChild as Box
|
||||||
|
val quickAction = suffixes.firstChild as Button
|
||||||
|
return Decomposed(mod, quickAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Decomposed(val mod: ModState?, val quickAction: Button)
|
||||||
|
|
||||||
|
interface ModState {
|
||||||
|
val name: String
|
||||||
|
val summary: String
|
||||||
|
val description: String
|
||||||
|
|
||||||
|
class Installed(private val mod: Mod) : ModState {
|
||||||
|
private val sources = mod.metadata.sources
|
||||||
|
val updates: List<() -> Unit> = sources.mapNotNull { it.value.getOrNull() }.map { { mod.update(it) } }
|
||||||
|
val outdated get() = updates.isEmpty()
|
||||||
|
fun remove() = mod.delete()
|
||||||
|
override val name: String = mod.name
|
||||||
|
override val summary: String by lazy { mod.metadata.sources.bestSummary }
|
||||||
|
override val description: String by lazy { sources.bestDescription ?: mod.description.joinToString(separator = "\n") }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Available(private val mod: ModSource, private val instance: Instance) : ModState {
|
||||||
|
fun install() {
|
||||||
|
//TODO thread and possibly show progress
|
||||||
|
ModManager.download(mod, instance.modsDir.resolve(mod.shortName + ModPath.EXT_IMOD), instance.mds)
|
||||||
|
.write()
|
||||||
|
}
|
||||||
|
override val name: String = mod.name
|
||||||
|
override val summary: String = mod.summary
|
||||||
|
override val description: String = mod.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ModState(mod: ModSource): ModState {
|
||||||
|
val installed = mds.mods.filter { it.metadata.sources.keys.any { mod.projectMatches(it) } }
|
||||||
|
return if (installed.isEmpty()) ModState.Available(mod, instance)
|
||||||
|
else ModState.Installed(installed[0])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import io.gitlab.jfronny.inceptum.gtk.window.dialog.MicrosoftLoginDialog
|
||||||
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager
|
import io.gitlab.jfronny.inceptum.launcher.api.account.AccountManager
|
||||||
import org.gnome.gtk.*
|
import org.gnome.gtk.*
|
||||||
|
|
||||||
class AccountsTab(window: Window?) : SectionedSettingsTab(window) {
|
class AccountsTab(window: Window?) : SectionedSettingsTab<Window>(window) {
|
||||||
init {
|
init {
|
||||||
section(null) {
|
section(null) {
|
||||||
build()
|
build()
|
||||||
|
|
|
@ -5,7 +5,7 @@ import io.gitlab.jfronny.inceptum.common.model.inceptum.UpdateChannel
|
||||||
import io.gitlab.jfronny.inceptum.gtk.control.settings.SectionedSettingsTab
|
import io.gitlab.jfronny.inceptum.gtk.control.settings.SectionedSettingsTab
|
||||||
import org.gnome.gtk.Window
|
import org.gnome.gtk.Window
|
||||||
|
|
||||||
class GeneralTab(window: Window?) : SectionedSettingsTab(window) {
|
class GeneralTab(window: Window?) : SectionedSettingsTab<Window>(window) {
|
||||||
init {
|
init {
|
||||||
section(null) {
|
section(null) {
|
||||||
row("settings.general.snapshots", "settings.general.snapshots.subtitle") {
|
row("settings.general.snapshots", "settings.general.snapshots.subtitle") {
|
||||||
|
|
|
@ -59,8 +59,7 @@ instance.settings.general.args.client.subtitle=Arguments to add to Minecraft Cli
|
||||||
instance.settings.general.args.server.subtitle=Arguments to add to Minecraft Servers
|
instance.settings.general.args.server.subtitle=Arguments to add to Minecraft Servers
|
||||||
instance.settings.export=Export
|
instance.settings.export=Export
|
||||||
instance.settings.mods=Mods
|
instance.settings.mods=Mods
|
||||||
instance.settings.mods.unsupported=Mod management is currently unavailable in the GTK UI.\
|
instance.settings.mods.select=Select a mod to view information about it
|
||||||
Please use the ImGUI or CLI to manage mods for now.
|
|
||||||
instance.settings.export.title=%1$s
|
instance.settings.export.title=%1$s
|
||||||
instance.settings.export.subtitle=Export this Pack as a %1$s %2$s file
|
instance.settings.export.subtitle=Export this Pack as a %1$s %2$s file
|
||||||
instance.settings.export.version=Version
|
instance.settings.export.version=Version
|
||||||
|
@ -112,4 +111,9 @@ menu.file.new.file.error=Could not import Instance
|
||||||
menu.file.new.new=Create
|
menu.file.new.new=Create
|
||||||
instance.settings.mods.local=Local
|
instance.settings.mods.local=Local
|
||||||
instance.settings.mods.modrinth=Modrinth
|
instance.settings.mods.modrinth=Modrinth
|
||||||
instance.settings.mods.curseforge=CurseForge
|
instance.settings.mods.curseforge=CurseForge
|
||||||
|
instance.settings.mods.download=Download
|
||||||
|
instance.settings.mods.delete=Delete
|
||||||
|
instance.settings.mods.update=Update
|
||||||
|
instance.settings.mods.unknown=Unknown
|
||||||
|
instance.settings.mods.unknown.description=Unknown mod state
|
|
@ -59,8 +59,7 @@ instance.settings.general.args.client.subtitle=Argumente f
|
||||||
instance.settings.general.args.server.subtitle=Argumente für Minecraft-Server
|
instance.settings.general.args.server.subtitle=Argumente für Minecraft-Server
|
||||||
instance.settings.export=Export
|
instance.settings.export=Export
|
||||||
instance.settings.mods=Mods
|
instance.settings.mods=Mods
|
||||||
instance.settings.mods.unsupported=Die Verwaltung von Modifikationen ist momentan noch in Entwicklung.\
|
instance.settings.mods.select=Wählen sie eine Mod um Informationen über sie zu sehen
|
||||||
Bitte verwenden Sie vorerst die ImGUI oder CLI, um Mods zu verwalten.
|
|
||||||
instance.settings.export.title=%1$s
|
instance.settings.export.title=%1$s
|
||||||
instance.settings.export.subtitle=Dieses Pack als %2$s für %1$s exportieren
|
instance.settings.export.subtitle=Dieses Pack als %2$s für %1$s exportieren
|
||||||
instance.settings.export.version=Version
|
instance.settings.export.version=Version
|
||||||
|
@ -112,4 +111,9 @@ menu.file.new.file.error=Konnte Instanz nicht importieren
|
||||||
menu.file.new.new=Erstellen
|
menu.file.new.new=Erstellen
|
||||||
instance.settings.mods.local=Lokal
|
instance.settings.mods.local=Lokal
|
||||||
instance.settings.mods.modrinth=Modrinth
|
instance.settings.mods.modrinth=Modrinth
|
||||||
instance.settings.mods.curseforge=CurseForge
|
instance.settings.mods.curseforge=CurseForge
|
||||||
|
instance.settings.mods.download=Herunterladen
|
||||||
|
instance.settings.mods.delete=Löschen
|
||||||
|
instance.settings.mods.update=Aktualisieren
|
||||||
|
instance.settings.mods.unknown=Unbekannt
|
||||||
|
instance.settings.mods.unknown.description=Mod in unbekanntem Zustand
|
|
@ -93,29 +93,13 @@ public class AddModWindow extends Window {
|
||||||
ImGui.text("Installed");
|
ImGui.text("Installed");
|
||||||
} else {
|
} else {
|
||||||
if (ImGui.button("Add##" + projectId)) {
|
if (ImGui.button("Add##" + projectId)) {
|
||||||
ModrinthVersion stable = null;
|
ModrinthVersion latest = ModrinthApi.getLatestVersion(projectId, instance.getGameVersion());
|
||||||
ModrinthVersion beta = null;
|
|
||||||
ModrinthVersion latest = null;
|
|
||||||
for (ModrinthVersion version : ModrinthApi.getVersions(projectId)) {
|
|
||||||
if (version.game_versions().contains(instance.getGameVersion()) && version.loaders().contains("fabric")) {
|
|
||||||
latest = version;
|
|
||||||
if (version.version_type() == ModrinthVersion.VersionType.beta || version.version_type() == ModrinthVersion.VersionType.release) {
|
|
||||||
beta = version;
|
|
||||||
}
|
|
||||||
if (version.version_type() == ModrinthVersion.VersionType.release) {
|
|
||||||
stable = version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stable != null) beta = stable;
|
|
||||||
if (beta != null) latest = beta;
|
|
||||||
if (latest == null) {
|
if (latest == null) {
|
||||||
LauncherEnv.showError("No valid version could be identified for this mod", "No version found");
|
LauncherEnv.showError("No valid version could be identified for this mod", "No version found");
|
||||||
} else {
|
} else {
|
||||||
ModrinthVersion finalLatest = latest;
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
ModManager.download(new ModrinthModSource(finalLatest.id()), instance.getModsDir().resolve((mod.slug() == null ? projectId : mod.slug()) + ModPath.EXT_IMOD), instance.mds()).write();
|
ModManager.download(new ModrinthModSource(latest.id()), instance.getModsDir().resolve((mod.slug() == null ? projectId : mod.slug()) + ModPath.EXT_IMOD), instance.mds()).write();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LauncherEnv.showError("Could not download mod", e);
|
LauncherEnv.showError("Could not download mod", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,10 @@ public class CurseforgeApi {
|
||||||
return checkDistribution(Net.downloadObject(API_URL + "mods/" + id, GC_GetModResponse::read, API_KEY).data());
|
return checkDistribution(Net.downloadObject(API_URL + "mods/" + id, GC_GetModResponse::read, API_KEY).data());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getDescription(int id) throws IOException {
|
||||||
|
return Net.downloadObject(API_URL + "mods/" + id + "/description", GC_GetModDescriptionResponse::read, API_KEY).data();
|
||||||
|
}
|
||||||
|
|
||||||
private static CurseforgeMod checkDistribution(CurseforgeMod mod) {
|
private static CurseforgeMod checkDistribution(CurseforgeMod mod) {
|
||||||
if (!mod.allowModDistribution()) {
|
if (!mod.allowModDistribution()) {
|
||||||
throw new IllegalArgumentException("The author of the mod \"" + mod.slug() + "\" has chosen to deliberately break your ability of downloading it.\n"
|
throw new IllegalArgumentException("The author of the mod \"" + mod.slug() + "\" has chosen to deliberately break your ability of downloading it.\n"
|
||||||
|
|
|
@ -4,6 +4,7 @@ import io.gitlab.jfronny.gson.compile.util.GList;
|
||||||
import io.gitlab.jfronny.gson.stream.JsonReader;
|
import io.gitlab.jfronny.gson.stream.JsonReader;
|
||||||
import io.gitlab.jfronny.inceptum.common.Net;
|
import io.gitlab.jfronny.inceptum.common.Net;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.model.modrinth.*;
|
import io.gitlab.jfronny.inceptum.launcher.model.modrinth.*;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
|
@ -43,6 +44,22 @@ public class ModrinthApi {
|
||||||
return Net.downloadObject(API_HOST + "v2/version/" + id, GC_ModrinthVersion::read);
|
return Net.downloadObject(API_HOST + "v2/version/" + id, GC_ModrinthVersion::read);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ModrinthLatest getLatestVersions(String mod, String gameVersion) throws IOException {
|
||||||
|
ModrinthVersion stable = null;
|
||||||
|
ModrinthVersion beta = null;
|
||||||
|
ModrinthVersion latest = null;
|
||||||
|
for (ModrinthVersion version : ModrinthApi.getVersions(mod)) {
|
||||||
|
if (version.game_versions().contains(gameVersion) && version.loaders().contains("fabric")) {
|
||||||
|
latest = version;
|
||||||
|
if (version.version_type() == ModrinthVersion.VersionType.beta || version.version_type() == ModrinthVersion.VersionType.release)
|
||||||
|
beta = version;
|
||||||
|
if (version.version_type() == ModrinthVersion.VersionType.release)
|
||||||
|
stable = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ModrinthLatest(stable, beta, latest);
|
||||||
|
}
|
||||||
|
|
||||||
public static ModrinthVersion getVersionByHash(String sha1) throws IOException {
|
public static ModrinthVersion getVersionByHash(String sha1) throws IOException {
|
||||||
return Net.downloadObject(API_HOST + "v2/version_file/" + sha1 + "?algorithm=sha1", GC_ModrinthVersion::read);
|
return Net.downloadObject(API_HOST + "v2/version_file/" + sha1 + "?algorithm=sha1", GC_ModrinthVersion::read);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.launcher.model.curseforge.response;
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
||||||
|
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||||
|
|
||||||
|
@GSerializable(configure = GsonPreset.Api.class)
|
||||||
|
public record GetModDescriptionResponse(String data) {
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package io.gitlab.jfronny.inceptum.launcher.model.inceptum;
|
||||||
import io.gitlab.jfronny.commons.HashUtils;
|
import io.gitlab.jfronny.commons.HashUtils;
|
||||||
import io.gitlab.jfronny.commons.data.MutCollection;
|
import io.gitlab.jfronny.commons.data.MutCollection;
|
||||||
import io.gitlab.jfronny.commons.data.delegate.DelegateMap;
|
import io.gitlab.jfronny.commons.data.delegate.DelegateMap;
|
||||||
|
import io.gitlab.jfronny.commons.serialize.gson.api.v1.Ignore;
|
||||||
import io.gitlab.jfronny.gson.compile.annotations.GPrefer;
|
import io.gitlab.jfronny.gson.compile.annotations.GPrefer;
|
||||||
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
||||||
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
import io.gitlab.jfronny.inceptum.common.GsonPreset;
|
||||||
|
@ -11,7 +12,9 @@ import io.gitlab.jfronny.inceptum.launcher.api.CurseforgeApi;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.api.ModrinthApi;
|
import io.gitlab.jfronny.inceptum.launcher.api.ModrinthApi;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.gson.ModMetaSourcesAdapter;
|
import io.gitlab.jfronny.inceptum.launcher.gson.ModMetaSourcesAdapter;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.model.curseforge.response.FingerprintMatchesResponse;
|
import io.gitlab.jfronny.inceptum.launcher.model.curseforge.response.FingerprintMatchesResponse;
|
||||||
|
import io.gitlab.jfronny.inceptum.launcher.system.instance.Mod;
|
||||||
import io.gitlab.jfronny.inceptum.launcher.system.source.*;
|
import io.gitlab.jfronny.inceptum.launcher.system.source.*;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -34,6 +37,29 @@ public record ModMeta(
|
||||||
public Sources() {
|
public Sources() {
|
||||||
super(MutCollection.mapOf());
|
super(MutCollection.mapOf());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<ModSource> getPreferredMetadataSource() {
|
||||||
|
return keySet().stream().max((left, right) -> {
|
||||||
|
if (left.equals(right)) return 0;
|
||||||
|
if (left instanceof ModrinthModSource) return 1;
|
||||||
|
if (right instanceof ModrinthModSource) return -1;
|
||||||
|
if (left instanceof CurseforgeModSource) return 1;
|
||||||
|
if (right instanceof CurseforgeModSource) return -1;
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull String getBestSummary() {
|
||||||
|
return getPreferredMetadataSource()
|
||||||
|
.map(ModSource::getSummary)
|
||||||
|
.orElse("Local Mod");
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getBestDescription() {
|
||||||
|
return getPreferredMetadataSource()
|
||||||
|
.map(ModSource::getDescription)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GPrefer
|
@GPrefer
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package io.gitlab.jfronny.inceptum.launcher.model.modrinth;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public record ModrinthLatest(@Nullable ModrinthVersion stable, @Nullable ModrinthVersion beta, @Nullable ModrinthVersion latest) {
|
||||||
|
public @Nullable ModrinthVersion get(ModrinthVersion.VersionType type) {
|
||||||
|
return switch (type) {
|
||||||
|
case alpha -> latest;
|
||||||
|
case beta -> beta;
|
||||||
|
case release -> stable;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ModrinthVersion getBest() {
|
||||||
|
if (stable != null) return stable;
|
||||||
|
if (beta != null) return beta;
|
||||||
|
if (latest != null) return latest;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package io.gitlab.jfronny.inceptum.launcher.system.source;
|
package io.gitlab.jfronny.inceptum.launcher.system.source;
|
||||||
|
|
||||||
import io.gitlab.jfronny.commons.HashUtils;
|
import io.gitlab.jfronny.commons.HashUtils;
|
||||||
|
import io.gitlab.jfronny.commons.StringFormatter;
|
||||||
import io.gitlab.jfronny.commons.cache.MemoryOperationResultCache;
|
import io.gitlab.jfronny.commons.cache.MemoryOperationResultCache;
|
||||||
import io.gitlab.jfronny.commons.tuple.Triple;
|
import io.gitlab.jfronny.commons.tuple.Triple;
|
||||||
import io.gitlab.jfronny.commons.tuple.Tuple;
|
import io.gitlab.jfronny.commons.tuple.Tuple;
|
||||||
|
@ -102,11 +103,35 @@ public final class CurseforgeModSource implements ModSource {
|
||||||
return current.fileName();
|
return current.fileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
try {
|
||||||
|
return CurseforgeApi.getDescription(mod.id());
|
||||||
|
} catch (IOException e) {
|
||||||
|
return "Could not get description\n\n" + StringFormatter.toString(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSummary() {
|
||||||
|
return mod.summary();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return obj instanceof ModSource ms && equals(ms);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(ModSource other) {
|
public boolean equals(ModSource other) {
|
||||||
return other instanceof CurseforgeModSource cu && cu.projectId == projectId && cu.fileId == fileId;
|
return other instanceof CurseforgeModSource cu && cu.projectId == projectId && cu.fileId == fileId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean projectMatches(ModSource other) {
|
||||||
|
return other instanceof CurseforgeModSource cu && cu.projectId == projectId;
|
||||||
|
}
|
||||||
|
|
||||||
public int getFileId() {
|
public int getFileId() {
|
||||||
return fileId;
|
return fileId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,8 +57,31 @@ public record DirectModSource(String fileName, String url, Set<ModSource> depend
|
||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return "Downloaded directly, no description is available";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSummary() {
|
||||||
|
return "Downloaded directly, no description is available";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return obj instanceof ModSource ms && equals(ms);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(ModSource other) {
|
public boolean equals(ModSource other) {
|
||||||
return false;
|
return other instanceof DirectModSource dm
|
||||||
|
&& dm.url.equals(url)
|
||||||
|
&& dm.fileName.equals(fileName)
|
||||||
|
&& dm.dependencies.equals(dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean projectMatches(ModSource other) {
|
||||||
|
return other instanceof DirectModSource dm && dm.url.equals(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,14 @@ public interface ModSource {
|
||||||
|
|
||||||
String getFileName();
|
String getFileName();
|
||||||
|
|
||||||
|
String getDescription();
|
||||||
|
|
||||||
|
String getSummary();
|
||||||
|
|
||||||
boolean equals(ModSource other);
|
boolean equals(ModSource other);
|
||||||
|
|
||||||
|
boolean projectMatches(ModSource other);
|
||||||
|
|
||||||
default Path getJarPath() {
|
default Path getJarPath() {
|
||||||
return MetaHolder.LIBRARIES_DIR.resolve("com").resolve(getName()).resolve(getFileName());
|
return MetaHolder.LIBRARIES_DIR.resolve("com").resolve(getName()).resolve(getFileName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,23 +56,7 @@ public final class ModrinthModSource implements ModSource {
|
||||||
@Override
|
@Override
|
||||||
public Optional<ModSource> getUpdate(String gameVersion) throws IOException {
|
public Optional<ModSource> getUpdate(String gameVersion) throws IOException {
|
||||||
return UPDATE_CACHE.get(Tuple.of(versionId, gameVersion), () -> {
|
return UPDATE_CACHE.get(Tuple.of(versionId, gameVersion), () -> {
|
||||||
ModrinthVersion stable = null;
|
ModrinthVersion next = ModrinthApi.getLatestVersions(getModId(), gameVersion).get(current.version_type());
|
||||||
ModrinthVersion beta = null;
|
|
||||||
ModrinthVersion latest = null;
|
|
||||||
for (ModrinthVersion version : ModrinthApi.getVersions(getModId())) {
|
|
||||||
if (version.game_versions().contains(gameVersion) && version.loaders().contains("fabric")) {
|
|
||||||
latest = version;
|
|
||||||
if (version.version_type() == ModrinthVersion.VersionType.beta || version.version_type() == ModrinthVersion.VersionType.release)
|
|
||||||
beta = version;
|
|
||||||
if (version.version_type() == ModrinthVersion.VersionType.release)
|
|
||||||
stable = version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ModrinthVersion next = switch (current.version_type()) {
|
|
||||||
case alpha -> latest;
|
|
||||||
case beta -> beta;
|
|
||||||
case release -> stable;
|
|
||||||
};
|
|
||||||
if (next == null) return Optional.empty();
|
if (next == null) return Optional.empty();
|
||||||
if (next.version_number().equals(current.version_number())) return Optional.empty();
|
if (next.version_number().equals(current.version_number())) return Optional.empty();
|
||||||
return Optional.of(new ModrinthModSource(next.id()));
|
return Optional.of(new ModrinthModSource(next.id()));
|
||||||
|
@ -99,11 +83,31 @@ public final class ModrinthModSource implements ModSource {
|
||||||
return current.files().get(0).filename();
|
return current.files().get(0).filename();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return mod.body();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSummary() {
|
||||||
|
return mod.description();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return obj instanceof ModSource ms && equals(ms);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(ModSource other) {
|
public boolean equals(ModSource other) {
|
||||||
return other instanceof ModrinthModSource ms && ms.getModId().equals(getModId()) && ms.versionId.equals(versionId);
|
return other instanceof ModrinthModSource ms && ms.getModId().equals(getModId()) && ms.versionId.equals(versionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean projectMatches(ModSource other) {
|
||||||
|
return other instanceof ModrinthModSource ms && ms.getModId().equals(getModId());
|
||||||
|
}
|
||||||
|
|
||||||
public String getVersionId() {
|
public String getVersionId() {
|
||||||
return versionId;
|
return versionId;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue