feat: Mod dependencies & Mod downloads should be classified.
This commit is contained in:
@@ -109,6 +109,7 @@ public class ComponentList extends Control {
|
||||
public void onExpand() {
|
||||
if (!expanded && lazyInitializer != null) {
|
||||
lazyInitializer.accept(this);
|
||||
setNeedsLayout(true);
|
||||
}
|
||||
|
||||
expanded = true;
|
||||
|
||||
@@ -22,6 +22,7 @@ import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -137,25 +138,31 @@ class ComponentListCell extends StackPane {
|
||||
|
||||
setExpanded(!isExpanded());
|
||||
|
||||
double newAnimatedHeight = content.prefHeight(-1) * (isExpanded() ? 1 : -1);
|
||||
double newHeight = isExpanded() ? getHeight() + newAnimatedHeight : prefHeight(-1);
|
||||
double contentHeight = isExpanded() ? newAnimatedHeight : 0;
|
||||
|
||||
if (isExpanded()) {
|
||||
updateClip(newHeight);
|
||||
list.onExpand();
|
||||
list.layout();
|
||||
}
|
||||
|
||||
expandAnimation = new Timeline(new KeyFrame(new Duration(320.0),
|
||||
new KeyValue(container.minHeightProperty(), contentHeight, FXUtils.SINE),
|
||||
new KeyValue(container.maxHeightProperty(), contentHeight, FXUtils.SINE)
|
||||
));
|
||||
Platform.runLater(() -> {
|
||||
double newAnimatedHeight = content.prefHeight(-1) * (isExpanded() ? 1 : -1);
|
||||
double newHeight = isExpanded() ? getHeight() + newAnimatedHeight : prefHeight(-1);
|
||||
double contentHeight = isExpanded() ? newAnimatedHeight : 0;
|
||||
|
||||
if (!isExpanded()) {
|
||||
expandAnimation.setOnFinished(e2 -> updateClip(newHeight));
|
||||
}
|
||||
if (isExpanded()) {
|
||||
updateClip(newHeight);
|
||||
}
|
||||
|
||||
expandAnimation.play();
|
||||
expandAnimation = new Timeline(new KeyFrame(new Duration(320.0),
|
||||
new KeyValue(container.minHeightProperty(), contentHeight, FXUtils.SINE),
|
||||
new KeyValue(container.maxHeightProperty(), contentHeight, FXUtils.SINE)
|
||||
));
|
||||
|
||||
if (!isExpanded()) {
|
||||
expandAnimation.setOnFinished(e2 -> updateClip(newHeight));
|
||||
}
|
||||
|
||||
expandAnimation.play();
|
||||
});
|
||||
});
|
||||
|
||||
expandedProperty().addListener((a, b, newValue) ->
|
||||
|
||||
@@ -18,23 +18,24 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXScrollPane;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.*;
|
||||
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.setting.Profile;
|
||||
@@ -45,29 +46,27 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.FloatListCell;
|
||||
import org.jackhuang.hmcl.ui.construct.JFXHyperlink;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.FormatStyle;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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<DownloadManager.Version> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
||||
private final BooleanProperty loaded = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty failed = new SimpleBooleanProperty(false);
|
||||
private final DownloadManager.Mod addon;
|
||||
@@ -76,6 +75,9 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
private final DownloadCallback callback;
|
||||
private final DownloadListPage page;
|
||||
|
||||
private List<DownloadManager.Mod> dependencies;
|
||||
private SimpleMultimap<String, DownloadManager.Version> versions;
|
||||
|
||||
public DownloadPage(DownloadListPage page, DownloadManager.Mod addon, Profile.ProfileVersion version, @Nullable DownloadCallback callback) {
|
||||
this.page = page;
|
||||
this.addon = addon;
|
||||
@@ -89,30 +91,57 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
|
||||
setLoading(true);
|
||||
setFailed(false);
|
||||
Task.supplyAsync(() -> {
|
||||
if (StringUtils.isNotBlank(version.getVersion())) {
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
|
||||
if (gameVersion.isPresent()) {
|
||||
return addon.getData().loadVersions()
|
||||
.filter(file -> file.getGameVersions().contains(gameVersion.get()));
|
||||
}
|
||||
}
|
||||
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();
|
||||
|
||||
Task.allOf(
|
||||
Task.supplyAsync(() -> addon.getData().loadDependencies()),
|
||||
Task.supplyAsync(() -> {
|
||||
Stream<DownloadManager.Version> versions = addon.getData().loadVersions();
|
||||
// if (StringUtils.isNotBlank(version.getVersion())) {
|
||||
// Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
|
||||
// if (gameVersion.isPresent()) {
|
||||
// return sortVersions(
|
||||
// .filter(file -> file.getGameVersions().contains(gameVersion.get())));
|
||||
// }
|
||||
// }
|
||||
return sortVersions(versions);
|
||||
}))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<DownloadManager.Mod> dependencies = (List<DownloadManager.Mod>) result.get(0);
|
||||
@SuppressWarnings("unchecked")
|
||||
SimpleMultimap<String, DownloadManager.Version> versions = (SimpleMultimap<String, DownloadManager.Version>) result.get(1);
|
||||
|
||||
this.dependencies = dependencies;
|
||||
this.versions = versions;
|
||||
|
||||
loaded.set(true);
|
||||
setFailed(false);
|
||||
} else {
|
||||
setFailed(true);
|
||||
}
|
||||
setLoading(false);
|
||||
}).start();
|
||||
|
||||
this.state.set(State.fromTitle(addon.getTitle()));
|
||||
}
|
||||
|
||||
private SimpleMultimap<String, DownloadManager.Version> sortVersions(Stream<DownloadManager.Version> versions) {
|
||||
SimpleMultimap<String, DownloadManager.Version> classifiedVersions
|
||||
= new SimpleMultimap<String, DownloadManager.Version>(HashMap::new, ArrayList::new);
|
||||
versions.forEach(version -> {
|
||||
for (String gameVersion : version.getGameVersions()) {
|
||||
classifiedVersions.put(gameVersion, version);
|
||||
}
|
||||
});
|
||||
|
||||
for (String gameVersion : classifiedVersions.keys()) {
|
||||
List<DownloadManager.Version> versionList = (List<DownloadManager.Version>) classifiedVersions.get(gameVersion);
|
||||
versionList.sort(Comparator.comparing(DownloadManager.Version::getDatePublished).reversed());
|
||||
}
|
||||
return classifiedVersions;
|
||||
}
|
||||
|
||||
public DownloadManager.Mod getAddon() {
|
||||
return addon;
|
||||
}
|
||||
@@ -186,49 +215,79 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
protected ModDownloadPageSkin(DownloadPage control) {
|
||||
super(control);
|
||||
|
||||
BorderPane pane = new BorderPane();
|
||||
VBox pane = new VBox(8);
|
||||
pane.getStyleClass().add("gray-background");
|
||||
pane.setPadding(new Insets(10));
|
||||
ScrollPane scrollPane = new ScrollPane(pane);
|
||||
JFXScrollPane.smoothScrolling(scrollPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
|
||||
HBox descriptionPane = new HBox(8);
|
||||
descriptionPane.setAlignment(Pos.CENTER);
|
||||
pane.setTop(descriptionPane);
|
||||
descriptionPane.getStyleClass().add("card");
|
||||
pane.getChildren().add(descriptionPane);
|
||||
descriptionPane.getStyleClass().add("card-non-transparent");
|
||||
BorderPane.setMargin(descriptionPane, new Insets(11, 11, 0, 11));
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
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.getTitle());
|
||||
content.setSubtitle(getSkinnable().addon.getDescription());
|
||||
content.getTags().setAll(getSkinnable().addon.getCategories().stream()
|
||||
.map(category -> getSkinnable().page.getLocalizedCategory(category))
|
||||
.collect(Collectors.toList()));
|
||||
descriptionPane.getChildren().add(content);
|
||||
|
||||
if (getSkinnable().mod != null) {
|
||||
JFXHyperlink openMcmodButton = new JFXHyperlink(i18n("mods.mcmod"));
|
||||
openMcmodButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcmodUrl(getSkinnable().mod.getMcmod())));
|
||||
descriptionPane.getChildren().add(openMcmodButton);
|
||||
|
||||
if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) {
|
||||
JFXHyperlink openMcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
|
||||
openMcbbsButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcbbsUrl(getSkinnable().mod.getMcbbs())));
|
||||
descriptionPane.getChildren().add(openMcbbsButton);
|
||||
{
|
||||
ImageView imageView = new ImageView();
|
||||
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.getTitle());
|
||||
content.setSubtitle(getSkinnable().addon.getDescription());
|
||||
content.getTags().setAll(getSkinnable().addon.getCategories().stream()
|
||||
.map(category -> getSkinnable().page.getLocalizedCategory(category))
|
||||
.collect(Collectors.toList()));
|
||||
descriptionPane.getChildren().add(content);
|
||||
|
||||
if (getSkinnable().mod != null) {
|
||||
JFXHyperlink openMcmodButton = new JFXHyperlink(i18n("mods.mcmod"));
|
||||
openMcmodButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcmodUrl(getSkinnable().mod.getMcmod())));
|
||||
descriptionPane.getChildren().add(openMcmodButton);
|
||||
|
||||
if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) {
|
||||
JFXHyperlink openMcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
|
||||
openMcbbsButton.setOnAction(e -> FXUtils.openLink(ModManager.getMcbbsUrl(getSkinnable().mod.getMcbbs())));
|
||||
descriptionPane.getChildren().add(openMcbbsButton);
|
||||
}
|
||||
}
|
||||
|
||||
JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage());
|
||||
openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getPageUrl()));
|
||||
descriptionPane.getChildren().add(openUrlButton);
|
||||
}
|
||||
|
||||
JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage());
|
||||
openUrlButton.setOnAction(e -> FXUtils.openLink(getSkinnable().addon.getPageUrl()));
|
||||
descriptionPane.getChildren().add(openUrlButton);
|
||||
{
|
||||
ComponentList dependencyPane = new ComponentList();
|
||||
dependencyPane.getStyleClass().add("no-padding");
|
||||
|
||||
FXUtils.onChangeAndOperate(control.loaded, loaded -> {
|
||||
if (loaded) {
|
||||
dependencyPane.getContent().setAll(control.dependencies.stream()
|
||||
.map(dependency -> new DependencyModItem(getSkinnable().page, dependency, control.version, control.callback))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
});
|
||||
|
||||
Node title = ComponentList.createComponentListTitle(i18n("mods.dependencies"));
|
||||
|
||||
BooleanBinding show = Bindings.createBooleanBinding(() -> !control.dependencies.isEmpty(), control.loaded);
|
||||
title.managedProperty().bind(show);
|
||||
title.visibleProperty().bind(show);
|
||||
dependencyPane.managedProperty().bind(show);
|
||||
dependencyPane.visibleProperty().bind(show);
|
||||
|
||||
pane.getChildren().addAll(title, dependencyPane);
|
||||
}
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
pane.setCenter(spinnerPane);
|
||||
VBox.setVgrow(spinnerPane, Priority.ALWAYS);
|
||||
pane.getChildren().add(spinnerPane);
|
||||
{
|
||||
spinnerPane.loadingProperty().bind(getSkinnable().loadingProperty());
|
||||
spinnerPane.failedReasonProperty().bind(Bindings.createStringBinding(() -> {
|
||||
@@ -239,60 +298,101 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
}, getSkinnable().failedProperty()));
|
||||
|
||||
JFXListView<DownloadManager.Version> listView = new JFXListView<>();
|
||||
spinnerPane.setContent(listView);
|
||||
Bindings.bindContent(listView.getItems(), getSkinnable().items);
|
||||
listView.setCellFactory(x -> new FloatListCell<DownloadManager.Version>(listView) {
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
StackPane graphicPane = new StackPane();
|
||||
JFXButton saveAsButton = new JFXButton();
|
||||
ComponentList list = new ComponentList();
|
||||
StackPane.setAlignment(list, Pos.TOP_CENTER);
|
||||
spinnerPane.setContent(list);
|
||||
|
||||
{
|
||||
HBox container = new HBox(8);
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
pane.getChildren().add(container);
|
||||
FXUtils.onChangeAndOperate(control.loaded, loaded -> {
|
||||
if (control.versions == null) return;
|
||||
|
||||
saveAsButton.getStyleClass().add("toggle-icon4");
|
||||
saveAsButton.setGraphic(SVG.contentSaveMoveOutline(Theme.blackFillBinding(), -1, -1));
|
||||
for (String gameVersion : control.versions.keys().stream()
|
||||
.sorted(VersionNumber.VERSION_COMPARATOR.reversed())
|
||||
.collect(Collectors.toList())) {
|
||||
ComponentList sublist = new ComponentList();
|
||||
sublist.setLazyInitializer(self -> {
|
||||
self.getContent().setAll(control.versions.get(gameVersion).stream()
|
||||
.map(version -> new ModItem(version, control))
|
||||
.collect(Collectors.toList()));
|
||||
});
|
||||
sublist.getStyleClass().add("no-padding");
|
||||
sublist.setTitle(gameVersion);
|
||||
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
container.getChildren().setAll(graphicPane, content, saveAsButton);
|
||||
list.getContent().add(sublist);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateControl(DownloadManager.Version dataItem, boolean empty) {
|
||||
if (empty) return;
|
||||
content.setTitle(dataItem.getName());
|
||||
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished()));
|
||||
content.getTags().setAll(dataItem.getGameVersions());
|
||||
saveAsButton.setOnMouseClicked(e -> getSkinnable().saveAs(dataItem));
|
||||
|
||||
switch (dataItem.getVersionType()) {
|
||||
case Release:
|
||||
graphicPane.getChildren().setAll(SVG.releaseCircleOutline(Theme.blackFillBinding(), 24, 24));
|
||||
content.getTags().add(i18n("version.game.release"));
|
||||
break;
|
||||
case Beta:
|
||||
graphicPane.getChildren().setAll(SVG.betaCircleOutline(Theme.blackFillBinding(), 24, 24));
|
||||
content.getTags().add(i18n("version.game.snapshot"));
|
||||
break;
|
||||
case Alpha:
|
||||
graphicPane.getChildren().setAll(SVG.alphaCircleOutline(Theme.blackFillBinding(), 24, 24));
|
||||
content.getTags().add(i18n("version.game.snapshot"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
listView.setOnMouseClicked(e -> {
|
||||
if (listView.getSelectionModel().getSelectedIndex() < 0)
|
||||
return;
|
||||
DownloadManager.Version selectedItem = listView.getSelectionModel().getSelectedItem();
|
||||
getSkinnable().download(selectedItem);
|
||||
});
|
||||
}
|
||||
|
||||
getChildren().setAll(pane);
|
||||
getChildren().setAll(scrollPane);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DependencyModItem extends StackPane {
|
||||
|
||||
DependencyModItem(DownloadListPage page, DownloadManager.Mod addon, Profile.ProfileVersion version, DownloadCallback callback) {
|
||||
HBox pane = new HBox(8);
|
||||
pane.setPadding(new Insets(8));
|
||||
pane.setAlignment(Pos.CENTER_LEFT);
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
ImageView imageView = new ImageView();
|
||||
pane.getChildren().setAll(FXUtils.limitingSize(imageView, 40, 40), content);
|
||||
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
container.setOnMouseClicked(e -> Controllers.navigate(new DownloadPage(page, addon, version, callback)));
|
||||
getChildren().setAll(container);
|
||||
|
||||
ModTranslations.Mod mod = ModTranslations.getModByCurseForgeId(addon.getSlug());
|
||||
content.setTitle(mod != null ? mod.getDisplayName() : addon.getTitle());
|
||||
content.setSubtitle(addon.getDescription());
|
||||
content.getTags().setAll(addon.getCategories().stream()
|
||||
.map(page::getLocalizedCategory)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
if (StringUtils.isNotBlank(addon.getIconUrl())) {
|
||||
imageView.setImage(new Image(addon.getIconUrl(), 40, 40, true, true, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ModItem extends StackPane {
|
||||
ModItem(DownloadManager.Version dataItem, DownloadPage selfPage) {
|
||||
HBox pane = new HBox(8);
|
||||
pane.setPadding(new Insets(8));
|
||||
pane.setAlignment(Pos.CENTER_LEFT);
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
StackPane graphicPane = new StackPane();
|
||||
JFXButton saveAsButton = new JFXButton();
|
||||
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
container.setOnMouseClicked(e -> {
|
||||
selfPage.download(dataItem);
|
||||
});
|
||||
getChildren().setAll(container);
|
||||
|
||||
saveAsButton.getStyleClass().add("toggle-icon4");
|
||||
saveAsButton.setGraphic(SVG.contentSaveMoveOutline(Theme.blackFillBinding(), -1, -1));
|
||||
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
pane.getChildren().setAll(graphicPane, content, saveAsButton);
|
||||
|
||||
content.setTitle(dataItem.getName());
|
||||
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished()));
|
||||
saveAsButton.setOnMouseClicked(e -> selfPage.saveAs(dataItem));
|
||||
|
||||
switch (dataItem.getVersionType()) {
|
||||
case Release:
|
||||
graphicPane.getChildren().setAll(SVG.releaseCircleOutline(Theme.blackFillBinding(), 24, 24));
|
||||
content.getTags().add(i18n("version.game.release"));
|
||||
break;
|
||||
case Beta:
|
||||
graphicPane.getChildren().setAll(SVG.betaCircleOutline(Theme.blackFillBinding(), 24, 24));
|
||||
content.getTags().add(i18n("version.game.snapshot"));
|
||||
break;
|
||||
case Alpha:
|
||||
graphicPane.getChildren().setAll(SVG.alphaCircleOutline(Theme.blackFillBinding(), 24, 24));
|
||||
content.getTags().add(i18n("version.game.snapshot"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ public class GameListPage extends ListPageBase<GameListItem> implements Decorato
|
||||
|
||||
Profiles.registerVersionsListener(this::loadVersions);
|
||||
|
||||
setOnFailedAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
||||
setOnFailedAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
||||
}
|
||||
|
||||
private void loadVersions(Profile profile) {
|
||||
|
||||
@@ -533,6 +533,7 @@ mods.add.success=Successfully installed mods %s.
|
||||
mods.category=Category
|
||||
mods.choose_mod=Choose your mods
|
||||
mods.curseforge=CurseForge
|
||||
mods.dependencies=Dependencies
|
||||
mods.disable=Disable
|
||||
mods.download=Mod Downloads
|
||||
mods.download.title=Mod Downloads - %1s
|
||||
|
||||
@@ -533,6 +533,7 @@ mods.add.success=成功新增模組 %s。
|
||||
mods.category=類別
|
||||
mods.choose_mod=選擇模組
|
||||
mods.curseforge=CurseForge
|
||||
mods.dependencies=前置 Mod
|
||||
mods.disable=停用
|
||||
mods.download=模組下載
|
||||
mods.download.title=模組下載 - %1s
|
||||
|
||||
@@ -533,6 +533,7 @@ mods.add.success=成功添加模组 %s。
|
||||
mods.category=类别
|
||||
mods.choose_mod=选择模组
|
||||
mods.curseforge=CurseForge
|
||||
mods.dependencies=前置 Mod
|
||||
mods.disable=禁用
|
||||
mods.download=模组下载
|
||||
mods.download.title=模组下载 - %1s
|
||||
|
||||
@@ -28,6 +28,7 @@ public final class DownloadManager {
|
||||
}
|
||||
|
||||
public interface IMod {
|
||||
List<Mod> loadDependencies() throws IOException;
|
||||
Stream<Version> loadVersions() throws IOException;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,10 @@ import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -159,6 +161,19 @@ public class CurseAddon implements DownloadManager.IMod {
|
||||
return isExperimental;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DownloadManager.Mod> loadDependencies() throws IOException {
|
||||
Set<Integer> dependencies = latestFiles.stream()
|
||||
.flatMap(latestFile -> latestFile.getDependencies().stream())
|
||||
.map(Dependency::getAddonId)
|
||||
.collect(Collectors.toSet());
|
||||
List<DownloadManager.Mod> mods = new ArrayList<>();
|
||||
for (int dependencyId : dependencies) {
|
||||
mods.add(CurseModManager.getAddon(dependencyId).toMod());
|
||||
}
|
||||
return mods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<DownloadManager.Version> loadVersions() throws IOException {
|
||||
return CurseModManager.getFiles(this).stream()
|
||||
@@ -485,7 +500,7 @@ public class CurseAddon implements DownloadManager.IMod {
|
||||
versionType,
|
||||
new DownloadManager.File(Collections.emptyMap(), getDownloadUrl(), getFileName()),
|
||||
Collections.emptyList(),
|
||||
gameVersion,
|
||||
gameVersion.stream().filter(ver -> ver.startsWith("1.") || ver.contains("w")).collect(Collectors.toList()),
|
||||
Collections.emptyList()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,11 @@ public final class CurseModManager {
|
||||
}.getType());
|
||||
}
|
||||
|
||||
public static CurseAddon getAddon(int id) throws IOException {
|
||||
String response = NetworkUtils.doGet(NetworkUtils.toURL("https://addons-ecs.forgesvc.net/api/v2/addon/" + id));
|
||||
return JsonUtils.fromNonNullJson(response, CurseAddon.class);
|
||||
}
|
||||
|
||||
public static List<CurseAddon.LatestFile> getFiles(CurseAddon addon) throws IOException {
|
||||
String response = NetworkUtils.doGet(NetworkUtils.toURL("https://addons-ecs.forgesvc.net/api/v2/addon/" + addon.getId() + "/files"));
|
||||
return JsonUtils.fromNonNullJson(response, new TypeToken<List<CurseAddon.LatestFile>>() {
|
||||
|
||||
@@ -418,6 +418,11 @@ public final class Modrinth {
|
||||
return latestVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DownloadManager.Mod> loadDependencies() throws IOException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<DownloadManager.Version> loadVersions() throws IOException {
|
||||
return Modrinth.getFiles(this).stream()
|
||||
|
||||
@@ -865,7 +865,7 @@ public abstract class Task<T> {
|
||||
* @param tasks the Tasks
|
||||
* @return a new Task that is completed when all of the given Tasks complete
|
||||
*/
|
||||
public static Task<Void> allOf(Task<?>... tasks) {
|
||||
public static Task<List<Object>> allOf(Task<?>... tasks) {
|
||||
return allOf(Arrays.asList(tasks));
|
||||
}
|
||||
|
||||
@@ -880,14 +880,15 @@ public abstract class Task<T> {
|
||||
* @param tasks the Tasks
|
||||
* @return a new Task that is completed when all of the given Tasks complete
|
||||
*/
|
||||
public static Task<Void> allOf(Collection<Task<?>> tasks) {
|
||||
return new Task<Void>() {
|
||||
public static Task<List<Object>> allOf(Collection<Task<?>> tasks) {
|
||||
return new Task<List<Object>>() {
|
||||
{
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
setResult(tasks.stream().map(Task::getResult).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user