feat: search modrinth

This commit is contained in:
huanghongxun
2021-09-12 20:14:39 +08:00
parent f4c25003a0
commit f088bfa114
18 changed files with 1099 additions and 107 deletions

View File

@@ -19,9 +19,15 @@ package org.jackhuang.hmcl.setting;
import javafx.beans.InvalidationListener;
import org.jackhuang.hmcl.download.*;
import org.jackhuang.hmcl.task.DownloadException;
import org.jackhuang.hmcl.task.FetchTask;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.ResponseCodeException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
@@ -32,6 +38,7 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.task.FetchTask.DEFAULT_CONCURRENCY;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class DownloadProviders {
private DownloadProviders() {}
@@ -127,4 +134,23 @@ public final class DownloadProviders {
public static DownloadProvider getDownloadProvider() {
return config().isAutoChooseDownloadType() ? currentDownloadProvider : fileDownloadProvider;
}
public static String localizeErrorMessage(Exception exception) {
if (exception instanceof DownloadException) {
URL url = ((DownloadException) exception).getUrl();
if (exception.getCause() instanceof SocketTimeoutException) {
return i18n("install.failed.downloading.timeout", url);
} else if (exception.getCause() instanceof ResponseCodeException) {
ResponseCodeException responseCodeException = (ResponseCodeException) exception.getCause();
if (I18n.hasKey("download.code." + responseCodeException.getResponseCode())) {
return i18n("download.code." + responseCodeException.getResponseCode(), url);
} else {
return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause());
}
} else {
return i18n("install.failed.downloading.detail", url) + "\n" + StringUtils.getStackTrace(exception.getCause());
}
}
return StringUtils.getStackTrace(exception);
}
}

View File

@@ -23,7 +23,7 @@ import javafx.scene.Node;
import javafx.scene.layout.BorderPane;
import org.jackhuang.hmcl.download.*;
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
import org.jackhuang.hmcl.mod.curse.CurseAddon;
import org.jackhuang.hmcl.mod.DownloadManager;
import org.jackhuang.hmcl.mod.curse.CurseModManager;
import org.jackhuang.hmcl.setting.DownloadProviders;
import org.jackhuang.hmcl.setting.Profile;
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.WeakListenerHolder;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.TabHeader;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
@@ -143,19 +144,25 @@ public class DownloadPage extends BorderPane implements DecoratorPage {
setCenter(transitionPane);
}
private void download(Profile profile, @Nullable String version, CurseAddon.LatestFile file, String subdirectoryName) {
private void download(Profile profile, @Nullable String version, DownloadManager.Version file, String subdirectoryName) {
if (version == null) version = profile.getSelectedVersion();
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version).toPath() : profile.getRepository().getBaseDirectory().toPath();
Path dest = runDirectory.resolve(subdirectoryName).resolve(file.getFileName());
Path dest = runDirectory.resolve(subdirectoryName).resolve(file.getFile().getFilename());
TaskExecutorDialogPane downloadingPane = new TaskExecutorDialogPane(it -> {
});
TaskExecutor executor = Task.composeAsync(() -> {
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getDownloadUrl()), dest.toFile());
task.setName(file.getDisplayName());
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile());
task.setName(file.getName());
return task;
}).whenComplete(exception -> {
if (exception != null) {
Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR);
} else {
Controllers.showToast(i18n("install.success"));
}
}).executor(false);
downloadingPane.setExecutor(executor, true);

View File

@@ -25,6 +25,7 @@ import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
@@ -37,27 +38,34 @@ import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.mod.DownloadManager;
import org.jackhuang.hmcl.mod.curse.CurseAddon;
import org.jackhuang.hmcl.mod.curse.CurseModManager;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.WeakListenerHolder;
import org.jackhuang.hmcl.ui.construct.FloatListCell;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
public class DownloadListPage extends Control implements DecoratorPage, VersionPage.VersionLoadable {
protected final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
@@ -65,10 +73,16 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
private final BooleanProperty failed = new SimpleBooleanProperty(false);
private final boolean versionSelection;
private final ObjectProperty<Profile.ProfileVersion> version = new SimpleObjectProperty<>();
private final ListProperty<CurseAddon> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
private final ListProperty<DownloadManager.Mod> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
private final ObservableList<String> versions = FXCollections.observableArrayList();
private final StringProperty selectedVersion = new SimpleStringProperty();
private final DownloadPage.DownloadCallback callback;
private boolean searchInitialized = false;
protected final BooleanProperty supportChinese = new SimpleBooleanProperty();
protected final ListProperty<String> downloadSources = new SimpleListProperty<>(this, "downloadSources", FXCollections.observableArrayList());
protected final StringProperty downloadSource = new SimpleStringProperty();
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
private TaskExecutor executor;
/**
* @see org.jackhuang.hmcl.mod.curse.CurseModManager#SECTION_MODPACK
@@ -101,6 +115,16 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
searchInitialized = true;
search("", 0, 0, "", 0);
}
if (versionSelection) {
versions.setAll(profile.getRepository().getVersions().stream()
.filter(v -> !v.isHidden())
.sorted(Comparator.comparing((Version v) -> v.getReleaseTime() == null ? new Date(0L) : v.getReleaseTime())
.thenComparing(v -> VersionNumber.asVersion(v.getId())))
.map(Version::getId)
.collect(Collectors.toList()));
selectedVersion.set(profile.getSelectedVersion());
}
}
public boolean isFailed() {
@@ -133,7 +157,11 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
? version.get().getProfile().getRepository().getVersionJar(version.get().getVersion())
: null;
Task.supplyAsync(() -> {
if (executor != null && !executor.isCancelled()) {
executor.cancel();
}
executor = Task.supplyAsync(() -> {
String gameVersion;
if (StringUtils.isBlank(version.get().getVersion())) {
gameVersion = userGameVersion;
@@ -146,16 +174,32 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
setLoading(false);
if (exception == null) {
items.setAll(result);
items.setAll(result.collect(Collectors.toList()));
failed.set(false);
} else {
failed.set(true);
}
}).start();
}).executor(true);
}
protected List<CurseAddon> searchImpl(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws Exception {
return CurseModManager.searchPaginated(gameVersion, category, section, pageOffset, searchFilter, sort);
protected Stream<DownloadManager.Mod> searchImpl(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws Exception {
return CurseModManager.searchPaginated(gameVersion, category, section, pageOffset, searchFilter, sort).stream().map(CurseAddon::toMod);
}
protected String getLocalizedCategory(String category) {
return i18n("curse.category." + category);
}
protected String getLocalizedOfficialPage() {
return i18n("mods.curseforge");
}
protected Profile.ProfileVersion getProfileVersion() {
if (versionSelection) {
return new Profile.ProfileVersion(version.get().getProfile(), selectedVersion.get());
} else {
return version.get();
}
}
@Override
@@ -194,18 +238,28 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
{
int rowIndex = 0;
if (control.versionSelection) {
if (control.versionSelection && control.downloadSources.getSize() > 1) {
JFXComboBox<String> versionsComboBox = new JFXComboBox<>();
GridPane.setColumnSpan(versionsComboBox, 3);
versionsComboBox.setMaxWidth(Double.MAX_VALUE);
Bindings.bindContent(versionsComboBox.getItems(), control.versions);
selectedItemPropertyFor(versionsComboBox).bindBidirectional(control.selectedVersion);
searchPane.addRow(rowIndex++, new Label(i18n("version")), versionsComboBox);
JFXComboBox<String> downloadSourceComboBox = new JFXComboBox<>();
downloadSourceComboBox.setMaxWidth(Double.MAX_VALUE);
downloadSourceComboBox.getItems().setAll(control.downloadSources.get());
downloadSourceComboBox.setConverter(stringConverter(I18n::i18n));
selectedItemPropertyFor(downloadSourceComboBox).bindBidirectional(control.downloadSource);
searchPane.addRow(rowIndex++, new Label(i18n("version")), versionsComboBox, new Label(i18n("settings.launcher.download_source")), downloadSourceComboBox);
}
JFXTextField nameField = new JFXTextField();
nameField.setPromptText(getSkinnable().supportChinese.get() ? i18n("search.hint.chinese") : i18n("search.hint.english"));
JFXTextField gameVersionField = new JFXTextField();
JFXComboBox<String> gameVersionField = new JFXComboBox<>();
gameVersionField.setMaxWidth(Double.MAX_VALUE);
gameVersionField.setEditable(true);
gameVersionField.getItems().setAll(DownloadManager.DEFAULT_GAME_VERSIONS);
Label lblGameVersion = new Label(i18n("world.game_version"));
searchPane.addRow(rowIndex++, new Label(i18n("mods.name")), nameField, lblGameVersion, gameVersionField);
@@ -267,7 +321,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
searchPane.addRow(rowIndex++, searchBox);
EventHandler<ActionEvent> searchAction = e -> getSkinnable()
.search(gameVersionField.getText(),
.search(gameVersionField.getSelectionModel().getSelectedItem(),
Optional.ofNullable(categoryComboBox.getSelectionModel().getSelectedItem())
.map(CategoryIndented::getCategoryId)
.orElse(0),
@@ -293,16 +347,16 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
}
}, getSkinnable().failedProperty()));
JFXListView<CurseAddon> listView = new JFXListView<>();
JFXListView<DownloadManager.Mod> listView = new JFXListView<>();
spinnerPane.setContent(listView);
Bindings.bindContent(listView.getItems(), getSkinnable().items);
listView.setOnMouseClicked(e -> {
if (listView.getSelectionModel().getSelectedIndex() < 0)
return;
CurseAddon selectedItem = listView.getSelectionModel().getSelectedItem();
Controllers.navigate(new DownloadPage(selectedItem, getSkinnable().version.get(), getSkinnable().callback));
DownloadManager.Mod selectedItem = listView.getSelectionModel().getSelectedItem();
Controllers.navigate(new DownloadPage(getSkinnable(), selectedItem, getSkinnable().getProfileVersion(), getSkinnable().callback));
});
listView.setCellFactory(x -> new FloatListCell<CurseAddon>(listView) {
listView.setCellFactory(x -> new FloatListCell<DownloadManager.Mod>(listView) {
TwoLineListItem content = new TwoLineListItem();
ImageView imageView = new ImageView();
@@ -315,19 +369,17 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
}
@Override
protected void updateControl(CurseAddon dataItem, boolean empty) {
protected void updateControl(DownloadManager.Mod dataItem, boolean empty) {
if (empty) return;
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(dataItem.getSlug());
content.setTitle(mod != null ? mod.getDisplayName() : dataItem.getName());
content.setSubtitle(dataItem.getSummary());
content.setTitle(mod != null ? mod.getDisplayName() : dataItem.getTitle());
content.setSubtitle(dataItem.getDescription());
content.getTags().setAll(dataItem.getCategories().stream()
.map(category -> i18n("curse.category." + category.getCategoryId()))
.map(category -> getSkinnable().getLocalizedCategory(category))
.collect(Collectors.toList()));
for (CurseAddon.Attachment attachment : dataItem.getAttachments()) {
if (attachment.isDefault()) {
imageView.setImage(new Image(attachment.getThumbnailUrl(), 40, 40, true, true, true));
}
if (StringUtils.isNotBlank(dataItem.getIconUrl())) {
imageView.setImage(new Image(dataItem.getIconUrl(), 40, 40, true, true, true));
}
}
});

View File

@@ -35,12 +35,12 @@ import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.mod.DownloadManager;
import org.jackhuang.hmcl.mod.ModManager;
import org.jackhuang.hmcl.mod.curse.CurseAddon;
import org.jackhuang.hmcl.mod.curse.CurseModManager;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
@@ -59,7 +59,6 @@ import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -68,15 +67,17 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class DownloadPage extends Control implements DecoratorPage {
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>();
private final ListProperty<CurseAddon.LatestFile> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
private final ListProperty<DownloadManager.Version> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
private final BooleanProperty loading = new SimpleBooleanProperty(false);
private final BooleanProperty failed = new SimpleBooleanProperty(false);
private final CurseAddon addon;
private final DownloadManager.Mod addon;
private final ModTranslations.Mod mod;
private final Profile.ProfileVersion version;
private final DownloadCallback callback;
private final DownloadListPage page;
public DownloadPage(CurseAddon addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {
public DownloadPage(DownloadListPage page, DownloadManager.Mod addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {
this.page = page;
this.addon = addon;
this.mod = ModTranslations.getModByCurseForgeId(addon.getSlug());
this.version = version;
@@ -86,27 +87,33 @@ public class DownloadPage extends Control implements DecoratorPage {
? version.getProfile().getRepository().getVersionJar(version.getVersion())
: null;
Task.runAsync(() -> {
setLoading(true);
setFailed(false);
Task.supplyAsync(() -> {
if (StringUtils.isNotBlank(version.getVersion())) {
Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
if (gameVersion.isPresent()) {
List<CurseAddon.LatestFile> files = CurseModManager.getFiles(addon);
items.setAll(files.stream()
.filter(file -> file.getGameVersion().contains(gameVersion.get()))
.sorted(Comparator.comparing(CurseAddon.LatestFile::getParsedFileDate).reversed())
.collect(Collectors.toList()));
return;
return addon.getData().loadVersions()
.filter(file -> file.getGameVersions().contains(gameVersion.get()));
}
}
List<CurseAddon.LatestFile> files = CurseModManager.getFiles(addon);
files.sort(Comparator.comparing(CurseAddon.LatestFile::getParsedFileDate).reversed());
items.setAll(files);
return addon.getData().loadVersions();
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
items.setAll(result
.sorted(Comparator.comparing(DownloadManager.Version::getDatePublished).reversed())
.collect(Collectors.toList()));
setFailed(false);
} else {
setFailed(true);
}
setLoading(false);
}).start();
this.state.set(State.fromTitle(addon.getName()));
this.state.set(State.fromTitle(addon.getTitle()));
}
public CurseAddon getAddon() {
public DownloadManager.Mod getAddon() {
return addon;
}
@@ -138,7 +145,7 @@ public class DownloadPage extends Control implements DecoratorPage {
this.failed.set(failed);
}
public void download(CurseAddon.LatestFile file) {
public void download(DownloadManager.Version file) {
if (this.callback == null) {
saveAs(file);
} else {
@@ -146,20 +153,20 @@ public class DownloadPage extends Control implements DecoratorPage {
}
}
public void saveAs(CurseAddon.LatestFile file) {
String extension = StringUtils.substringAfterLast(file.getFileName(), '.');
public void saveAs(DownloadManager.Version file) {
String extension = StringUtils.substringAfterLast(file.getFile().getFilename(), '.');
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(i18n("button.save_as"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("file"), "*." + extension));
fileChooser.setInitialFileName(file.getFileName());
fileChooser.setInitialFileName(file.getFile().getFilename());
File dest = fileChooser.showSaveDialog(Controllers.getStage());
if (dest == null) {
return;
}
Controllers.taskDialog(
new FileDownloadTask(NetworkUtils.toURL(file.getDownloadUrl()), dest).executor(true),
new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest).executor(true),
i18n("message.downloading")
);
}
@@ -188,20 +195,18 @@ public class DownloadPage extends Control implements DecoratorPage {
BorderPane.setMargin(descriptionPane, new Insets(11, 11, 0, 11));
ImageView imageView = new ImageView();
for (CurseAddon.Attachment attachment : getSkinnable().addon.getAttachments()) {
if (attachment.isDefault()) {
imageView.setImage(new Image(attachment.getThumbnailUrl(), 40, 40, true, true, true));
}
if (StringUtils.isNotBlank(getSkinnable().addon.getIconUrl())) {
imageView.setImage(new Image(getSkinnable().addon.getIconUrl(), 40, 40, true, true, true));
}
descriptionPane.getChildren().add(FXUtils.limitingSize(imageView, 40, 40));
TwoLineListItem content = new TwoLineListItem();
HBox.setHgrow(content, Priority.ALWAYS);
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(getSkinnable().addon.getSlug());
content.setTitle(mod != null ? mod.getDisplayName() : getSkinnable().addon.getName());
content.setSubtitle(getSkinnable().addon.getSummary());
content.setTitle(mod != null ? mod.getDisplayName() : getSkinnable().addon.getTitle());
content.setSubtitle(getSkinnable().addon.getDescription());
content.getTags().setAll(getSkinnable().addon.getCategories().stream()
.map(category -> i18n("curse.category." + category.getCategoryId()))
.map(category -> getSkinnable().page.getLocalizedCategory(category))
.collect(Collectors.toList()));
descriptionPane.getChildren().add(content);
@@ -217,8 +222,8 @@ public class DownloadPage extends Control implements DecoratorPage {
}
}
JFXHyperlink openUrlButton = new JFXHyperlink(i18n("mods.curseforge"));
openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getWebsiteUrl()));
JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage());
openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getPageUrl()));
descriptionPane.getChildren().add(openUrlButton);
@@ -234,10 +239,10 @@ public class DownloadPage extends Control implements DecoratorPage {
}
}, getSkinnable().failedProperty()));
JFXListView<CurseAddon.LatestFile> listView = new JFXListView<>();
JFXListView<DownloadManager.Version> listView = new JFXListView<>();
spinnerPane.setContent(listView);
Bindings.bindContent(listView.getItems(), getSkinnable().items);
listView.setCellFactory(x -> new FloatListCell<CurseAddon.LatestFile>(listView) {
listView.setCellFactory(x -> new FloatListCell<DownloadManager.Version>(listView) {
TwoLineListItem content = new TwoLineListItem();
StackPane graphicPane = new StackPane();
JFXButton saveAsButton = new JFXButton();
@@ -255,23 +260,23 @@ public class DownloadPage extends Control implements DecoratorPage {
}
@Override
protected void updateControl(CurseAddon.LatestFile dataItem, boolean empty) {
protected void updateControl(DownloadManager.Version dataItem, boolean empty) {
if (empty) return;
content.setTitle(dataItem.getDisplayName());
content.setSubtitle(FORMATTER.format(dataItem.getParsedFileDate()));
content.getTags().setAll(dataItem.getGameVersion());
content.setTitle(dataItem.getName());
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished()));
content.getTags().setAll(dataItem.getGameVersions());
saveAsButton.setOnMouseClicked(e -> getSkinnable().saveAs(dataItem));
switch (dataItem.getReleaseType()) {
case 1: // release
switch (dataItem.getVersionType()) {
case Release:
graphicPane.getChildren().setAll(SVG.releaseCircleOutline(Theme.blackFillBinding(), 24, 24));
content.getTags().add(i18n("version.game.release"));
break;
case 2: // beta
case Beta:
graphicPane.getChildren().setAll(SVG.betaCircleOutline(Theme.blackFillBinding(), 24, 24));
content.getTags().add(i18n("version.game.snapshot"));
break;
case 3: // alpha
case Alpha:
graphicPane.getChildren().setAll(SVG.alphaCircleOutline(Theme.blackFillBinding(), 24, 24));
content.getTags().add(i18n("version.game.snapshot"));
break;
@@ -282,7 +287,7 @@ public class DownloadPage extends Control implements DecoratorPage {
listView.setOnMouseClicked(e -> {
if (listView.getSelectionModel().getSelectedIndex() < 0)
return;
CurseAddon.LatestFile selectedItem = listView.getSelectionModel().getSelectedItem();
DownloadManager.Version selectedItem = listView.getSelectionModel().getSelectedItem();
getSkinnable().download(selectedItem);
});
}
@@ -293,19 +298,7 @@ public class DownloadPage extends Control implements DecoratorPage {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).withLocale(Locale.getDefault()).withZone(ZoneId.systemDefault());
public interface Project {
}
public interface ProjectVersion {
}
public interface DownloadSource {
}
public interface DownloadCallback {
void download(Profile profile, @Nullable String version, CurseAddon.LatestFile file);
void download(Profile profile, @Nullable String version, DownloadManager.Version file);
}
}

View File

@@ -17,21 +17,29 @@
*/
package org.jackhuang.hmcl.ui.versions;
import org.jackhuang.hmcl.mod.DownloadManager;
import org.jackhuang.hmcl.mod.curse.CurseAddon;
import org.jackhuang.hmcl.mod.curse.CurseModManager;
import org.jackhuang.hmcl.mod.modrinth.Modrinth;
import org.jackhuang.hmcl.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class ModDownloadListPage extends DownloadListPage {
public ModDownloadListPage(int section, DownloadPage.DownloadCallback callback, boolean versionSelection) {
super(section, callback, versionSelection);
supportChinese.set(true);
downloadSources.get().setAll("mods.curseforge", "mods.modrinth");
downloadSource.set("mods.curseforge");
}
@Override
protected List<CurseAddon> searchImpl(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws Exception {
protected Stream<DownloadManager.Mod> searchImpl(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws Exception {
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
List<ModTranslations.Mod> mods = ModTranslations.searchMod(searchFilter);
List<String> searchFilters = new ArrayList<>();
@@ -47,9 +55,35 @@ public class ModDownloadListPage extends DownloadListPage {
count++;
if (count >= 3) break;
}
return super.searchImpl(gameVersion, category, section, pageOffset, String.join(" ", searchFilters), sort);
return search(gameVersion, category, section, pageOffset, String.join(" ", searchFilters), sort);
} else {
return super.searchImpl(gameVersion, category, section, pageOffset, searchFilter, sort);
return search(gameVersion, category, section, pageOffset, searchFilter, sort);
}
}
private Stream<DownloadManager.Mod> search(String gameVersion, int category, int section, int pageOffset, String searchFilter, int sort) throws Exception {
if ("mods.modrinth".equals(downloadSource.get())) {
return Modrinth.searchPaginated(gameVersion, pageOffset, searchFilter).stream().map(Modrinth.ModResult::toMod);
} else {
return CurseModManager.searchPaginated(gameVersion, category, section, pageOffset, searchFilter, sort).stream().map(CurseAddon::toMod);
}
}
@Override
protected String getLocalizedCategory(String category) {
if ("mods.modrinth".equals(downloadSource.get())) {
return i18n("modrinth.category." + category);
} else {
return i18n("curse.category." + category);
}
}
@Override
protected String getLocalizedOfficialPage() {
if ("mods.modrinth".equals(downloadSource.get())) {
return i18n("mods.modrinth");
} else {
return i18n("mods.curseforge");
}
}
}

View File

@@ -27,7 +27,7 @@ import javafx.scene.control.Control;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.mod.curse.CurseAddon;
import org.jackhuang.hmcl.mod.DownloadManager;
import org.jackhuang.hmcl.mod.curse.CurseModManager;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Theme;
@@ -199,19 +199,19 @@ public class VersionPage extends Control implements DecoratorPage, DownloadPage.
}
@Override
public void download(Profile profile, @Nullable String version, CurseAddon.LatestFile file) {
public void download(Profile profile, @Nullable String version, DownloadManager.Version file) {
if (version == null) {
throw new InternalError();
}
Path dest = profile.getRepository().getRunDirectory(version).toPath().resolve("mods").resolve(file.getFileName());
Path dest = profile.getRepository().getRunDirectory(version).toPath().resolve("mods").resolve(file.getFile().getFilename());
TaskExecutorDialogPane downloadingPane = new TaskExecutorDialogPane(it -> {
});
TaskExecutor executor = Task.composeAsync(() -> {
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getDownloadUrl()), dest.toFile());
task.setName(file.getDisplayName());
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest.toFile());
task.setName(file.getName());
return task;
}).executor(false);

View File

@@ -18,16 +18,14 @@
package org.jackhuang.hmcl.ui.versions;
import com.jfoenix.controls.JFXButton;
import javafx.application.Platform;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.game.GameDirectoryType;
import org.jackhuang.hmcl.game.GameRepository;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.mod.curse.CurseAddon;
import org.jackhuang.hmcl.mod.DownloadManager;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
@@ -87,15 +85,15 @@ public final class Versions {
}
}
public static void downloadModpackImpl(Profile profile, String version, CurseAddon.LatestFile file) {
public static void downloadModpackImpl(Profile profile, String version, DownloadManager.Version file) {
Path modpack;
URL downloadURL;
try {
modpack = Files.createTempFile("modpack", ".zip");
downloadURL = new URL(file.getDownloadUrl());
downloadURL = new URL(file.getFile().getUrl());
} catch (IOException e) {
Controllers.dialog(
i18n("install.failed.downloading.detail", file.getDownloadUrl()) + "\n" + StringUtils.getStackTrace(e),
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
i18n("download.failed"), MessageDialogPane.MessageType.ERROR);
return;
}
@@ -106,7 +104,7 @@ public final class Versions {
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack.toFile()));
} else {
Controllers.dialog(
i18n("install.failed.downloading.detail", file.getDownloadUrl()) + "\n" + StringUtils.getStackTrace(e),
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
i18n("download.failed"), MessageDialogPane.MessageType.ERROR);
}
}).executor(true),

View File

@@ -499,6 +499,20 @@ modpack.wizard.step.initialization.save=Export to...
modpack.wizard.step.initialization.warning=Before creating a modpack, you should ensure that the game can launch successfully,\nand that your Minecraft is a release version.\nDo NOT add mods which cannot be redistributed.
modpack.wizard.step.initialization.server=Click here for more information about server auto-update modpack
modrinth.category.adventure=Adventure
modrinth.category.cursed=Cursed
modrinth.category.decoration=Decoration
modrinth.category.equipment=Equipment
modrinth.category.fabric=Fabric
modrinth.category.food=Food
modrinth.category.library=Library
modrinth.category.magic=Magic
modrinth.category.misc=Misc
modrinth.category.storage=Storage
modrinth.category.technology=Technology
modrinth.category.utility=Utility
modrinth.category.worldgen=Worldgen
mods=Mods
mods.add=Install mods
mods.add.failed=Failed to install mods %s.
@@ -515,6 +529,7 @@ mods.mcbbs=MCBBS
mods.mcmod=MCMOD
mods.mcmod.page=MCMOD
mods.mcmod.search=Search in MCMOD
mods.modrinth=Modrinth
mods.name=Name
mods.not_modded=You should install a modloader first (Fabric, Forge or LiteLoader)
mods.url=Official Page

View File

@@ -506,6 +506,20 @@ modpack.wizard.step.initialization.save=選擇要匯出到的遊戲整合包位
modpack.wizard.step.initialization.warning=在製作整合包前,請您確認您選擇的版本可以正常啟動,\n並保證您的 Minecraft 是正式版而非快照版,\n而且不應將不允許非官方途徑傳播的 Mod 模組、材質包等納入整合包。\n整合包會儲存您目前的下載來源設定
modpack.wizard.step.initialization.server=點選此處查看有關伺服器自動更新整合包的製作教學
modrinth.category.adventure=冒險
modrinth.category.cursed=Cursed
modrinth.category.decoration=裝飾
modrinth.category.equipment=裝備
modrinth.category.fabric=Fabric
modrinth.category.food=食物
modrinth.category.library=支持庫
modrinth.category.magic=魔法
modrinth.category.misc=其他
modrinth.category.storage=儲存
modrinth.category.technology=科技
modrinth.category.utility=實用
modrinth.category.worldgen=世界生成
mods=模組
mods.add=新增模組
mods.add.failed=新增模組 %s 失敗。
@@ -522,6 +536,7 @@ mods.mcbbs=MCBBS
mods.mcmod=MC 百科
mods.mcmod.page=MC 百科頁面
mods.mcmod.search=MC 百科蒐索
mods.modrinth=Modrinth
mods.name=名稱
mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteLoader 才能進行模組管理。
mods.url=官方頁面

View File

@@ -499,6 +499,20 @@ modpack.wizard.step.initialization.save=选择要导出到的游戏整合包位
modpack.wizard.step.initialization.warning=在制作整合包前,请您确认您选择的版本可以正常启动,\n并保证您的 Minecraft 是正式版而非快照版,\n而且不应当将不允许非官方途径传播的 Mod、材质包等纳入整合包。\n整合包会保存您目前的下载源设置
modpack.wizard.step.initialization.server=点击此处查看有关服务器自动更新整合包的制作教程
modrinth.category.adventure=冒险
modrinth.category.cursed=Cursed
modrinth.category.decoration=装饰
modrinth.category.equipment=装备
modrinth.category.fabric=Fabric
modrinth.category.food=食物
modrinth.category.library=支持库
modrinth.category.magic=魔法
modrinth.category.misc=其他
modrinth.category.storage=存储
modrinth.category.technology=科技
modrinth.category.utility=实用
modrinth.category.worldgen=世界生成
mods=模组
mods.add=添加模组
mods.add.failed=添加模组 %s 失败。
@@ -515,6 +529,7 @@ mods.mcbbs=MCBBS
mods.mcmod=MC 百科
mods.mcmod.page=MC 百科页面
mods.mcmod.search=MC 百科搜索
mods.modrinth=Modrinth
mods.name=名称
mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。
mods.url=官方页面