Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.jackhuang.hmcl.ui;

import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.validation.base.ValidatorBase;
import javafx.animation.KeyFrame;
Expand Down Expand Up @@ -72,6 +73,7 @@
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
Expand Down Expand Up @@ -471,6 +473,28 @@ public static void confirmAction(String text, String title, MessageType type, Bu
dialog(new MessageDialogPane.Builder(text, title, type).actionOrCancel(actionButton, cancel).build());
}

public static void deleteConfirm(Consumer<Boolean> action, String body, String bodyTrash) {
JFXCheckBox checkBox = new JFXCheckBox(i18n("button.remove.confirm.notrash"));

MessageDialogPane pane = new MessageDialogPane.Builder(bodyTrash, i18n("button.remove"), MessageType.QUESTION)
.addNode(checkBox)
.ok(() -> {
action.accept(checkBox.isSelected());
})
.addCancel(null).build();

checkBox.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) pane.setText(body);
else pane.setText(bodyTrash);
});

dialog(pane);
}

public static void deleteConfirm(Consumer<Boolean> action, String name) {
deleteConfirm(action, i18n("button.remove.confirm"), i18n("button.remove.confirm.trash", name));
}

public static void confirmWithCountdown(String text, String title, int seconds, MessageType messageType,
@Nullable Runnable ok, @Nullable Runnable cancel) {
if (seconds <= 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public String getDisplayName() {
}

private final HBox actions;
private final EnhancedTextFlow textFlow;

private @Nullable ButtonBase cancelButton;

Expand All @@ -91,7 +92,7 @@ public MessageDialogPane(@NotNull String text, @Nullable String title, @NotNull

StackPane content = new StackPane();
content.getStyleClass().add("jfx-layout-body");
EnhancedTextFlow textFlow = new EnhancedTextFlow(text);
textFlow = new EnhancedTextFlow(text);
textFlow.setStyle("-fx-font-size: 14px;");
if (textFlow.computePrefHeight(400.0) <= 350.0)
content.getChildren().setAll(textFlow);
Expand Down Expand Up @@ -119,6 +120,10 @@ public MessageDialogPane(@NotNull String text, @Nullable String title, @NotNull
});
}

public void setText(String text) {
textFlow.setText(text);
}

public void addButton(Node btn) {
btn.addEventHandler(ActionEvent.ACTION, e -> fireEvent(new DialogCloseEvent()));
actions.getChildren().add(btn);
Expand All @@ -134,7 +139,13 @@ public ButtonBase getCancelButton() {

private static final class EnhancedTextFlow extends TextFlow {
EnhancedTextFlow(String text) {
this.getChildren().setAll(FXUtils.parseSegment(text, Controllers::onHyperlinkAction));
setText(text);
}

public void setText(String newText) {
this.getChildren().setAll(
FXUtils.parseSegment(newText, Controllers::onHyperlinkAction)
);
}

@Override
Expand All @@ -157,6 +168,11 @@ public Builder addHyperLink(String text, String externalLink) {
return this;
}

public Builder addNode(Node node) {
dialog.actions.getChildren().add(node);
return this;
}

public Builder addAction(Node actionNode) {
dialog.addButton(actionNode);
actionNode.getStyleClass().add("dialog-accept");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,14 @@ public void refresh() {
setLoading(true);
Task.supplyAsync(Schedulers.io(), () -> {
try (Stream<Path> stream = Files.list(resourcepackDirectory)) {
return stream.sorted(Comparator.comparing(FileUtils::getName))
.flatMap(item -> {
try {
return Stream.of(ResourcepackFile.parse(item)).filter(Objects::nonNull).map(ResourcepackInfoObject::new);
} catch (IOException e) {
LOG.warning("Failed to load resourcepack " + item, e);
return Stream.empty();
}
})
.toList();
return stream.sorted(Comparator.comparing(FileUtils::getName)).flatMap(item -> {
try {
return Stream.of(ResourcepackFile.parse(item)).filter(Objects::nonNull).map(ResourcepackInfoObject::new);
} catch (IOException e) {
LOG.warning("Failed to load resourcepack " + item, e);
return Stream.empty();
}
}).toList();
}
}).whenComplete(Schedulers.javafx(), ((result, exception) -> {
if (exception == null) {
Expand Down Expand Up @@ -143,11 +141,7 @@ private ResourcepackListPageSkin(ResourcepackListPage control) {
HBox toolbar = new HBox();
toolbar.setAlignment(Pos.CENTER_LEFT);
toolbar.setPickOnBounds(false);
toolbar.getChildren().setAll(
createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, control::refresh),
createToolbarButton2(i18n("resourcepack.add"), SVG.ADD, control::onAddFiles),
createToolbarButton2(i18n("resourcepack.download"), SVG.DOWNLOAD, control::onDownload)
);
toolbar.getChildren().setAll(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, control::refresh), createToolbarButton2(i18n("resourcepack.add"), SVG.ADD, control::onAddFiles), createToolbarButton2(i18n("resourcepack.download"), SVG.DOWNLOAD, control::onDownload));
root.getContent().add(toolbar);

SpinnerPane center = new SpinnerPane();
Expand Down Expand Up @@ -192,8 +186,7 @@ Image getIcon() {
}
}

if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 ||
(Math.abs(image.getWidth() - image.getHeight()) >= 1)) {
if (image == null || image.isError() || image.getWidth() <= 0 || image.getHeight() <= 0 || (Math.abs(image.getWidth() - image.getHeight()) >= 1)) {
image = FXUtils.newBuiltinImage("/assets/img/unknown_pack.png");
}
iconCache = new WeakReference<>(image);
Expand Down Expand Up @@ -258,23 +251,25 @@ protected void updateControl(ResourcepackListPage.ResourcepackInfoObject item, b
FXUtils.installFastTooltip(btnReveal, i18n("reveal.in_file_manager"));
btnReveal.setOnAction(event -> FXUtils.showFileInExplorer(file.getPath()));

btnDelete.setOnAction(event ->
Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"),
() -> onDelete(file), null));
}

private void onDelete(ResourcepackFile file) {
try {
if (Files.isDirectory(file.getPath())) {
FileUtils.deleteDirectory(file.getPath());
} else {
Files.delete(file.getPath());
}
btnDelete.setOnAction(event -> {
Controllers.deleteConfirm((b) -> {
try {
if (b) {
if (Files.isDirectory(file.getPath())) {
FileUtils.deleteDirectory(file.getPath());
} else {
Files.delete(file.getPath());
}
} else {
FileUtils.moveToTrash(file.getPath());
}
} catch (IOException e) {
Controllers.dialog(i18n("resourcepack.delete.failed", e.getMessage()), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
LOG.warning("Failed to delete resourcepack", e);
}
},file.getPath().getFileName().toString());
page.refresh();
} catch (IOException e) {
Controllers.dialog(i18n("resourcepack.delete.failed", e.getMessage()), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
LOG.warning("Failed to delete resourcepack", e);
}
});
}
}
}
17 changes: 6 additions & 11 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/Versions.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,21 +111,16 @@ public static void downloadModpackImpl(Profile profile, String version, RemoteMo

public static void deleteVersion(Profile profile, String version) {
boolean isIndependent = profile.getVersionSetting(version).getGameDirType() == GameDirectoryType.VERSION_FOLDER;
String message = isIndependent ? i18n("version.manage.remove.confirm.independent", version) :
i18n("version.manage.remove.confirm.trash", version, version + "_removed");

JFXButton deleteButton = new JFXButton(i18n("button.delete"));
deleteButton.getStyleClass().add("dialog-error");
deleteButton.setOnAction(e -> {
Task.supplyAsync(Schedulers.io(), () -> profile.getRepository().removeVersionFromDisk(version))
Controllers.deleteConfirm((b) -> {
Task.supplyAsync(Schedulers.io(), () -> profile.getRepository().removeVersionFromDisk(version,!b))
.whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception != null || !Boolean.TRUE.equals(result)) {
Controllers.dialog(i18n("version.manage.remove.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
}
}).start();
});

Controllers.confirmAction(message, i18n("message.warning"), MessageDialogPane.MessageType.WARNING, deleteButton);
},
isIndependent ? i18n("version.manage.remove.confirm.independent", version) : i18n("version.manage.remove.confirm", version),
isIndependent ? i18n("version.manage.remove.confirm.independent.trash", version) : i18n("version.manage.remove.confirm.trash", version, version));
}

public static CompletableFuture<String> renameVersion(Profile profile, String version) {
Expand All @@ -146,7 +141,7 @@ public static CompletableFuture<String> renameVersion(Profile profile, String ve
reject.accept(i18n("version.manage.rename.fail"));
}
}, version, new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId),
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName) || newVersionName.equals(version)));
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName) || newVersionName.equals(version)));
}

public static void exportVersion(Profile profile, String version) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jetbrains.annotations.NotNull;

import java.nio.file.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
Expand Down Expand Up @@ -203,16 +205,15 @@ void onReveal() {
FXUtils.showFileInExplorer(file);
}

void onDelete() {
WorldBackupsPage.this.getItems().remove(this);
Task.runAsync(() -> Files.delete(file)).start();
}

@Override
public int compareTo(@NotNull WorldBackupsPage.BackupInfo that) {
int c = this.backupTime.compareTo(that.backupTime);
return c != 0 ? c : Integer.compare(this.count, that.count);
}

public void refresh() {
WorldBackupsPage.this.refresh();
}
}

private static final class BackupInfoSkin extends SkinBase<BackupInfo> {
Expand Down Expand Up @@ -266,7 +267,19 @@ private static final class BackupInfoSkin extends SkinBase<BackupInfo> {
FXUtils.installFastTooltip(btnDelete, i18n("world.backup.delete"));
btnDelete.getStyleClass().add("toggle-icon4");
btnDelete.setGraphic(SVG.DELETE.createIcon());
btnDelete.setOnAction(event -> Controllers.confirm(i18n("button.remove.confirm"), i18n("button.remove"), skinnable::onDelete, null));
btnDelete.setOnAction(event -> Controllers.deleteConfirm((b) -> {
Task.runAsync(() -> {
if (b) Files.delete(world.getFile());
else FileUtils.moveToTrash(world.getFile());
}).whenComplete(Schedulers.javafx(), (v, exception) -> {
if (exception != null) {
Controllers.dialog(i18n("message.failed", exception.getMessage()), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
LOG.warning("Failed to delete world backup", exception);
} else {
skinnable.refresh();
}
}).start();
}, world.getFile().getFileName().toString()));
}

getChildren().setAll(new RipplerContainer(root));
Expand Down
6 changes: 5 additions & 1 deletion HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ button.reveal_dir=打开文件夹
button.refresh=刷新
button.remove=删除
button.remove.confirm=你确定要删除吗?此操作无法撤销!
button.remove.confirm.notrash=永久删除
button.remove.confirm.trash=你确定要删除吗?你可以在系统的回收站中还原“%s”来找回。
button.retry=重试
button.save=保存
button.save_as=另存为
Expand Down Expand Up @@ -1389,7 +1391,9 @@ version.manage.manage.title=实例管理 - %1s
version.manage.redownload_assets_index=更新游戏资源文件
version.manage.remove=删除该实例
version.manage.remove.confirm.trash=真的要删除实例“%s”吗?你可以在系统的回收站中还原“%s”文件夹来找回该实例。
version.manage.remove.confirm.independent=由于该游戏启用了“(全局/实例特定) 游戏设置 → 版本隔离 → 各实例独立”选项,删除该实例将导致该游戏的世界等数据一同被删除!真的要删除实例“%s”吗?
version.manage.remove.confirm=真的要删除实例“%s”吗?此操作无法撤销!
version.manage.remove.confirm.independent.trash=由于该游戏启用了“(全局/实例特定) 游戏设置 → 版本隔离 → 各实例独立”选项,删除该实例将导致该游戏的世界等数据一同被删除!真的要删除实例“%s”吗?此操作无法撤销!
version.manage.remove.confirm.independent=由于该游戏启用了“(全局/实例特定) 游戏设置 → 版本隔离 → 各实例独立”选项,删除该实例将导致该游戏的世界等数据一同被删除!真的要删除实例“%s”吗?你可以在系统的回收站中还原“%s”文件夹来找回该实例。
version.manage.remove.failed=删除实例失败。可能文件被占用。
version.manage.remove_assets=删除所有游戏资源文件
version.manage.remove_libraries=删除所有库文件
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
Expand Down Expand Up @@ -234,6 +233,10 @@ public boolean renameVersion(String from, String to) {
}

public boolean removeVersionFromDisk(String id) {
return removeVersionFromDisk(id, true);
}

public boolean removeVersionFromDisk(String id, boolean trash) {
if (EventBus.EVENT_BUS.fireEvent(new RemoveVersionEvent(this, id)) == Event.Result.DENY)
return false;
if (!versions.containsKey(id))
Expand All @@ -242,19 +245,15 @@ public boolean removeVersionFromDisk(String id) {
if (Files.notExists(file))
return true;
// test if no file in this version directory is occupied.
Path removedFile = file.toAbsolutePath().resolveSibling(FileUtils.getName(file) + "_removed");
try {
Files.move(file, removedFile, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
LOG.warning("Failed to rename file " + file, e);
return false;
}
Path removedFile = file.toAbsolutePath().resolveSibling(FileUtils.getName(file));

try {
versions.remove(id);

if (FileUtils.moveToTrash(removedFile)) {
return true;
if (trash) {
if (FileUtils.moveToTrash(removedFile)) {
return true;
}
}

// remove json files first to ensure HMCL will not recognize this folder as a valid version.
Expand Down
Loading