feat(mod): mod update.
This commit is contained in:
@@ -60,7 +60,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> {
|
||||
mods.forEach(it -> {
|
||||
try {
|
||||
modManager.addMod(it);
|
||||
modManager.addMod(it.toPath());
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e);
|
||||
}
|
||||
@@ -125,7 +125,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
Task.runAsync(() -> {
|
||||
for (File file : res) {
|
||||
try {
|
||||
modManager.addMod(file);
|
||||
modManager.addMod(file.toPath());
|
||||
succeeded.add(file.getName());
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to add mod " + file, e);
|
||||
@@ -196,6 +196,15 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
});
|
||||
}
|
||||
|
||||
public void rollback(LocalModFile from, LocalModFile to) {
|
||||
try {
|
||||
modManager.rollback(from, to);
|
||||
refresh();
|
||||
} catch (IOException ex) {
|
||||
Controllers.showToast(i18n("message.failed"));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isModded() {
|
||||
return modded.get();
|
||||
}
|
||||
|
||||
@@ -17,10 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.*;
|
||||
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
@@ -43,6 +40,7 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.Lazy;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
@@ -56,6 +54,7 @@ import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
|
||||
@@ -259,9 +258,13 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
}
|
||||
}
|
||||
|
||||
static class ModInfoListCell extends MDListCell<ModInfoObject> {
|
||||
private static Lazy<PopupMenu> menu = new Lazy<>(PopupMenu::new);
|
||||
private static Lazy<JFXPopup> popup = new Lazy<>(() -> new JFXPopup(menu.get()));
|
||||
|
||||
class ModInfoListCell extends MDListCell<ModInfoObject> {
|
||||
JFXCheckBox checkBox = new JFXCheckBox();
|
||||
TwoLineListItem content = new TwoLineListItem();
|
||||
JFXButton restoreButton = new JFXButton();
|
||||
JFXButton infoButton = new JFXButton();
|
||||
JFXButton revealButton = new JFXButton();
|
||||
BooleanProperty booleanProperty;
|
||||
@@ -276,13 +279,18 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
content.setMouseTransparent(true);
|
||||
setSelectable();
|
||||
|
||||
restoreButton.getStyleClass().add("toggle-icon4");
|
||||
restoreButton.setGraphic(FXUtils.limitingSize(SVG.restore(Theme.blackFillBinding(), 24, 24), 24, 24));
|
||||
|
||||
FXUtils.installFastTooltip(restoreButton, i18n("mods.restore"));
|
||||
|
||||
revealButton.getStyleClass().add("toggle-icon4");
|
||||
revealButton.setGraphic(FXUtils.limitingSize(SVG.folderOutline(Theme.blackFillBinding(), 24, 24), 24, 24));
|
||||
|
||||
infoButton.getStyleClass().add("toggle-icon4");
|
||||
infoButton.setGraphic(FXUtils.limitingSize(SVG.informationOutline(Theme.blackFillBinding(), 24, 24), 24, 24));
|
||||
|
||||
container.getChildren().setAll(checkBox, content, revealButton, infoButton);
|
||||
container.getChildren().setAll(checkBox, content, restoreButton, revealButton, infoButton);
|
||||
|
||||
StackPane.setMargin(container, new Insets(8));
|
||||
getContainer().getChildren().setAll(container);
|
||||
@@ -302,6 +310,17 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
checkBox.selectedProperty().unbindBidirectional(booleanProperty);
|
||||
}
|
||||
checkBox.selectedProperty().bindBidirectional(booleanProperty = dataItem.active);
|
||||
restoreButton.setVisible(!dataItem.getModInfo().getMod().getOldFiles().isEmpty());
|
||||
restoreButton.setOnMouseClicked(e -> {
|
||||
menu.get().getContent().setAll(dataItem.getModInfo().getMod().getOldFiles().stream()
|
||||
.map(localModFile -> new IconedMenuItem(null, localModFile.getVersion(), () -> {
|
||||
getSkinnable().rollback(dataItem.getModInfo(), localModFile);
|
||||
}))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
popup.get().show(restoreButton, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, restoreButton.getHeight());
|
||||
});
|
||||
revealButton.setOnMouseClicked(e -> {
|
||||
FXUtils.showFileInExplorer(dataItem.getModInfo().getFile());
|
||||
});
|
||||
|
||||
@@ -32,15 +32,16 @@ import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseAddon;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
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.construct.JFXCheckBoxTreeTableCell;
|
||||
import org.jackhuang.hmcl.ui.construct.MDListCell;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
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.Pair;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
@@ -120,13 +121,26 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
private void updateMods() {
|
||||
ModUpdateTask task = new ModUpdateTask(
|
||||
modManager,
|
||||
objects.stream()
|
||||
.filter(o -> o.enabled.get())
|
||||
.map(object -> pair(object.data.getLocalMod(), object.data.getCandidates().get(0)))
|
||||
.collect(Collectors.toList()));
|
||||
Controllers.taskDialog(
|
||||
new ModUpdateTask(
|
||||
modManager,
|
||||
objects.stream()
|
||||
.filter(o -> o.enabled.get())
|
||||
.map(object -> pair(object.data.getLocalMod(), object.data.getCandidates().get(0)))
|
||||
.collect(Collectors.toList())),
|
||||
task.whenComplete(Schedulers.javafx(), exception -> {
|
||||
fireEvent(new PageCloseEvent());
|
||||
if (!task.getFailedMods().isEmpty()) {
|
||||
Controllers.dialog(i18n("mods.check_updates.failed") + "\n" +
|
||||
task.getFailedMods().stream().map(LocalModFile::getFileName).collect(Collectors.joining("\n")),
|
||||
i18n("install.failed"),
|
||||
MessageDialogPane.MessageType.ERROR);
|
||||
}
|
||||
|
||||
if (exception == null) {
|
||||
Controllers.dialog(i18n("install.success"));
|
||||
}
|
||||
}),
|
||||
i18n("mods.check_updates.update"),
|
||||
t -> {
|
||||
});
|
||||
@@ -249,6 +263,7 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
|
||||
public static class ModUpdateTask extends Task<Void> {
|
||||
private final Collection<Task<?>> dependents;
|
||||
private final List<LocalModFile> failedMods = new ArrayList<>();
|
||||
|
||||
ModUpdateTask(ModManager modManager, List<Pair<LocalModFile, RemoteMod.Version>> mods) {
|
||||
setStage("mods.check_updates.update");
|
||||
@@ -257,16 +272,33 @@ public class ModUpdatesPage extends BorderPane implements DecoratorPage {
|
||||
dependents = mods.stream()
|
||||
.map(mod -> {
|
||||
return Task
|
||||
.supplyAsync(() -> {
|
||||
return null;
|
||||
.runAsync(Schedulers.javafx(), () -> {
|
||||
mod.getKey().setOld(true);
|
||||
})
|
||||
.thenComposeAsync(() -> {
|
||||
FileDownloadTask task = new FileDownloadTask(
|
||||
new URL(mod.getValue().getFile().getUrl()),
|
||||
modManager.getModsDirectory().resolve(mod.getValue().getFile().getFilename()).toFile());
|
||||
|
||||
task.setName(mod.getValue().getName());
|
||||
return task;
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), exception -> {
|
||||
if (exception != null) {
|
||||
// restore state if failed
|
||||
mod.getKey().setOld(false);
|
||||
failedMods.add(mod.getKey());
|
||||
}
|
||||
})
|
||||
.setName(mod.getKey().getName())
|
||||
.setSignificance(TaskSignificance.MAJOR)
|
||||
.withCounter("mods.check_updates.update");
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<LocalModFile> getFailedMods() {
|
||||
return failedMods;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task<?>> getDependents() {
|
||||
return dependents;
|
||||
|
||||
@@ -486,6 +486,7 @@ message.default=Default
|
||||
message.doing=Please wait
|
||||
message.downloading=Downloading...
|
||||
message.error=Error
|
||||
message.failed=Operation failed
|
||||
message.info=Info
|
||||
message.success=Job completed successfully
|
||||
message.unknown=Unknown
|
||||
@@ -583,6 +584,7 @@ mods.add.success=Successfully installed mods %s.
|
||||
mods.category=Category
|
||||
mods.check_updates=Check updates
|
||||
mods.check_updates.current_version=Current
|
||||
mods.check_updates.failed=Failed to download some of files
|
||||
mods.check_updates.file=File
|
||||
mods.check_updates.source=Source
|
||||
mods.check_updates.target_version=Target
|
||||
@@ -602,6 +604,7 @@ 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.restore=Restore
|
||||
mods.url=Official Page
|
||||
|
||||
multiplayer=Multiplayer
|
||||
|
||||
@@ -486,6 +486,7 @@ message.default=預設
|
||||
message.doing=請耐心等待
|
||||
message.downloading=正在下載…
|
||||
message.error=錯誤
|
||||
message.failed=操作失敗
|
||||
message.info=資訊
|
||||
message.success=完成
|
||||
message.unknown=未知
|
||||
@@ -583,6 +584,7 @@ mods.add.success=成功新增模組 %s。
|
||||
mods.category=類別
|
||||
mods.check_updates=檢查模組更新
|
||||
mods.check_updates.current_version=當前版本
|
||||
mods.check_updates.failed=部分文件下載失敗
|
||||
mods.check_updates.file=文件
|
||||
mods.check_updates.source=來源
|
||||
mods.check_updates.target_version=目標版本
|
||||
@@ -602,6 +604,7 @@ mods.mcmod.search=MC 百科蒐索
|
||||
mods.modrinth=Modrinth
|
||||
mods.name=名稱
|
||||
mods.not_modded=你需要先在自動安裝頁面安裝 Fabric、Forge 或 LiteLoader 才能進行模組管理。
|
||||
mods.restore=回退
|
||||
mods.url=官方頁面
|
||||
|
||||
multiplayer=多人聯機
|
||||
|
||||
@@ -486,6 +486,7 @@ message.default=默认
|
||||
message.doing=请耐心等待
|
||||
message.downloading=正在下载
|
||||
message.error=错误
|
||||
message.failed=操作失败
|
||||
message.info=提示
|
||||
message.success=已完成
|
||||
message.unknown=未知
|
||||
@@ -583,6 +584,7 @@ mods.add.success=成功添加模组 %s。
|
||||
mods.category=类别
|
||||
mods.check_updates=检查模组更新
|
||||
mods.check_updates.current_version=当前版本
|
||||
mods.check_updates.failed=部分文件下载失败
|
||||
mods.check_updates.file=文件
|
||||
mods.check_updates.source=来源
|
||||
mods.check_updates.target_version=目标版本
|
||||
@@ -602,6 +604,7 @@ mods.mcmod.search=MC 百科搜索
|
||||
mods.modrinth=Modrinth
|
||||
mods.name=名称
|
||||
mods.not_modded=你需要先在自动安装页面安装 Fabric、Forge 或 LiteLoader 才能进行模组管理。
|
||||
mods.restore=回退
|
||||
mods.url=官方页面
|
||||
|
||||
multiplayer=多人联机
|
||||
|
||||
Reference in New Issue
Block a user