diff --git a/.idea/runConfigurations/Main.xml b/.idea/runConfigurations/Main.xml
index 05bd440c11..488f6cf328 100644
--- a/.idea/runConfigurations/Main.xml
+++ b/.idea/runConfigurations/Main.xml
@@ -7,6 +7,7 @@
+
diff --git a/build.gradle b/build.gradle
index d3ff158fa3..9ccd4d8edd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -230,7 +230,7 @@ dependencies {
implementation("io.projectreactor.addons:reactor-extra")
implementation("io.projectreactor:reactor-tools")
- def commonsVersion = "1013d75fc9"
+ def commonsVersion = "9c1978d235"
implementation("com.github.FAForever.faf-java-commons:faf-commons-data:${commonsVersion}") {
exclude module: 'guava'
diff --git a/src/main/java/com/faforever/client/domain/api/Mod.java b/src/main/java/com/faforever/client/domain/api/Mod.java
index 942c80208a..7ebdfa7d96 100644
--- a/src/main/java/com/faforever/client/domain/api/Mod.java
+++ b/src/main/java/com/faforever/client/domain/api/Mod.java
@@ -2,8 +2,14 @@
import com.faforever.client.domain.server.PlayerInfo;
+import java.net.URL;
+
public record Mod(
Integer id,
String displayName,
- boolean recommended, String author, PlayerInfo uploader, ReviewsSummary reviewsSummary
+ URL repositoryURL,
+ boolean recommended,
+ String author,
+ PlayerInfo uploader,
+ ReviewsSummary reviewsSummary
) {}
diff --git a/src/main/java/com/faforever/client/domain/api/ModVersion.java b/src/main/java/com/faforever/client/domain/api/ModVersion.java
index 74c9af6c8e..4209a74aef 100644
--- a/src/main/java/com/faforever/client/domain/api/ModVersion.java
+++ b/src/main/java/com/faforever/client/domain/api/ModVersion.java
@@ -12,7 +12,10 @@ public record ModVersion(
ComparableVersion version,
URL thumbnailUrl,
URL downloadUrl,
- ModType modType, boolean ranked, boolean hidden, Mod mod,
+ ModType modType,
+ boolean ranked,
+ boolean hidden,
+ Mod mod,
OffsetDateTime createTime,
OffsetDateTime updateTime
) {}
diff --git a/src/main/java/com/faforever/client/mapstruct/ModMapper.java b/src/main/java/com/faforever/client/mapstruct/ModMapper.java
index 483d0e41d5..51fe6b1cc8 100644
--- a/src/main/java/com/faforever/client/mapstruct/ModMapper.java
+++ b/src/main/java/com/faforever/client/mapstruct/ModMapper.java
@@ -18,6 +18,7 @@ public interface ModMapper {
@Mapping(target = "modType", source = "modInfo.uiOnly")
@Mapping(target = "mod", expression = "java(new ModBean())")
@Mapping(target = "mod.displayName", source = "modInfo.name")
+ @Mapping(target = "mod.repositoryURL", source = "modInfo.url")
ModVersion map(com.faforever.commons.mod.Mod modInfo, Path basePath);
default ModType mapModType(boolean isUIOnly) {
@@ -33,6 +34,7 @@ default Path mapImagePath(com.faforever.commons.mod.Mod modInfo, Path basePath)
}
@Mapping(target = "reviewsSummary", source = "modReviewsSummary")
+ @Mapping(target = "repositoryURL", source = "repositoryUrl")
Mod map(com.faforever.commons.api.dto.Mod dto);
@InheritInverseConfiguration
diff --git a/src/main/java/com/faforever/client/mod/ModDetailController.java b/src/main/java/com/faforever/client/mod/ModDetailController.java
index 804b765081..d42dce10ac 100644
--- a/src/main/java/com/faforever/client/mod/ModDetailController.java
+++ b/src/main/java/com/faforever/client/mod/ModDetailController.java
@@ -9,6 +9,7 @@
import com.faforever.client.fx.ImageViewHelper;
import com.faforever.client.fx.JavaFxUtil;
import com.faforever.client.fx.NodeController;
+import com.faforever.client.fx.PlatformService;
import com.faforever.client.fx.SimpleChangeListener;
import com.faforever.client.fx.contextmenu.ContextMenuBuilder;
import com.faforever.client.i18n.I18n;
@@ -29,6 +30,7 @@
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Button;
+import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ScrollPane;
@@ -43,12 +45,16 @@
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
+import java.net.URL;
+import java.util.Objects;
+
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Slf4j
@RequiredArgsConstructor
public class ModDetailController extends NodeController {
+ private final PlatformService platformService;
private final ModService modService;
private final NotificationService notificationService;
private final I18n i18n;
@@ -81,6 +87,7 @@ public class ModDetailController extends NodeController {
public Node modDetailRoot;
public ReviewsController reviewsController;
public Label authorLabel;
+ public Hyperlink urlHyperlink;
@Override
protected void onInitialize() {
@@ -98,6 +105,20 @@ protected void onInitialize() {
bindProperties();
modVersion.addListener((SimpleChangeListener) this::onModVersionChanged);
+ urlHyperlink.setOnAction(event -> {
+ ModVersion modVersionCurrent = modVersion.get();
+ if (modVersionCurrent == null) {
+ return;
+ }
+
+ URL repositoryURL = modVersionCurrent.mod().repositoryURL();
+ if (repositoryURL == null) {
+ return;
+ }
+
+ platformService.showDocument(repositoryURL.toString());
+ });
+
// TODO hidden until dependencies are available
dependenciesTitle.setManaged(false);
dependenciesContainer.setManaged(false);
@@ -109,7 +130,11 @@ private void bindProperties() {
.bind(modVersion.map(modService::loadThumbnail)
.flatMap(imageViewHelper::createPlaceholderImageOnErrorObservable)
.when(showing));
+
nameLabel.textProperty().bind(modObservable.map(Mod::displayName).when(showing));
+
+ urlHyperlink.visibleProperty().bind(modObservable.map(Mod::repositoryURL).map(Objects::nonNull).when(showing));
+
authorLabel.textProperty().bind(modObservable.map(Mod::author)
.map(author -> i18n.get("modVault.details.author", author))
.when(showing));
diff --git a/src/main/java/com/faforever/client/mod/ModUploadTask.java b/src/main/java/com/faforever/client/mod/ModUploadTask.java
index c329bda64b..53935881bb 100644
--- a/src/main/java/com/faforever/client/mod/ModUploadTask.java
+++ b/src/main/java/com/faforever/client/mod/ModUploadTask.java
@@ -1,6 +1,7 @@
package com.faforever.client.mod;
import com.faforever.client.api.FafApiAccessor;
+import com.faforever.client.domain.api.ModVersion;
import com.faforever.client.i18n.I18n;
import com.faforever.client.preferences.DataPrefs;
import com.faforever.client.task.CompletableTask;
@@ -8,6 +9,7 @@
import com.faforever.client.util.Validator;
import com.faforever.commons.io.ByteCountListener;
import com.faforever.commons.io.Zipper;
+import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
@@ -15,8 +17,10 @@
import org.springframework.stereotype.Component;
import java.io.OutputStream;
+import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@@ -29,6 +33,7 @@
@Slf4j
public class ModUploadTask extends CompletableTask {
+ private final ModService modService;
private final FafApiAccessor fafApiAccessor;
private final I18n i18n;
private final DataPrefs dataPrefs;
@@ -36,8 +41,9 @@ public class ModUploadTask extends CompletableTask {
private Path modPath;
@Autowired
- public ModUploadTask(FafApiAccessor fafApiAccessor, I18n i18n, DataPrefs dataPrefs) {
+ public ModUploadTask(ModService modService, FafApiAccessor fafApiAccessor, I18n i18n, DataPrefs dataPrefs) {
super(Priority.HIGH);
+ this.modService = modService;
this.dataPrefs = dataPrefs;
this.fafApiAccessor = fafApiAccessor;
this.i18n = i18n;
@@ -53,6 +59,16 @@ protected Void call() throws Exception {
Path tmpFile = createTempFile(cacheDirectory, "mod", ".zip");
try {
+
+ log.debug("Retrieve information from mod_info.lua at: {}", modPath);
+ ModVersion modVersionInfo = modService.extractModInfo(modPath);
+ URL repositoryURL = modVersionInfo.mod().repositoryURL();
+
+ HashMap parameters = new HashMap<>();
+ if (repositoryURL != null) {
+ parameters.put("repositoryUrl", repositoryURL.toString());
+ }
+
log.debug("Zipping mod `{}` to `{}`", modPath, tmpFile);
updateTitle(i18n.get("modVault.upload.compressing"));
@@ -72,7 +88,7 @@ protected Void call() throws Exception {
log.debug("Uploading mod `{}` as `{}`", modPath, tmpFile);
updateTitle(i18n.get("modVault.upload.uploading"));
- return fafApiAccessor.uploadFile("/mods/upload", tmpFile, byteListener, Map.of()).block();
+ return fafApiAccessor.uploadFile("/mods/upload", tmpFile, byteListener, Map.of("metadata", parameters)).block();
} finally {
Files.delete(tmpFile);
ResourceLocks.freeUploadLock();
@@ -82,4 +98,4 @@ protected Void call() throws Exception {
public void setModPath(Path modPath) {
this.modPath = modPath;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties
index 557e057f88..dcace79337 100644
--- a/src/main/resources/i18n/messages.properties
+++ b/src/main/resources/i18n/messages.properties
@@ -753,6 +753,7 @@ map.versionId = Map version ID
vault.replays.ownReplays = Your replays
modVault.details.uploader = Uploaded by\: {0}
modVault.details.author = Authored by\: {0}
+modVault.details.source = Source
gameSelect.select.invalidPath = Invalid path
gamePath.select.noneChosen = You did not select a game location\!
gamePath.select.noValidExe = The specified location does not contain a valid Supreme Commander.exe and is therefore is no valid Forged Alliance installation.
diff --git a/src/main/resources/theme/vault/mod/mod_detail.fxml b/src/main/resources/theme/vault/mod/mod_detail.fxml
index 5862fb173a..fe7937b20e 100644
--- a/src/main/resources/theme/vault/mod/mod_detail.fxml
+++ b/src/main/resources/theme/vault/mod/mod_detail.fxml
@@ -1,20 +1,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -30,54 +22,42 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
+
+
-
-
+
+
-
-
-
+
+
+
+
-
-
+
+
-
+
-
-
+
+
@@ -108,7 +88,7 @@