世界管理新增删除世界功能 (#4263)

Co-authored-by: 3gf8jv4dv <3gf8jv4dv@gmail.com>
Co-authored-by: Glavo <zjx001202@gmail.com>
This commit is contained in:
Wulian233 2025-09-15 23:06:30 +08:00 committed by GitHub
parent f594abc37d
commit 7c024473d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 68 additions and 13 deletions

View File

@ -147,7 +147,7 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
WorldBackupsPage.this.getItems().sort(Comparator.naturalOrder());
Controllers.dialog(i18n("world.backup.create.success", result.getKey()), null, MessageDialogPane.MessageType.INFO);
} else if (exception instanceof WorldLockedException) {
Controllers.dialog(i18n("world.backup.create.locked"), null, MessageDialogPane.MessageType.WARNING);
Controllers.dialog(i18n("world.locked.failed"), null, MessageDialogPane.MessageType.WARNING);
} else {
LOG.warning("Failed to create backup", exception);
Controllers.dialog(i18n("world.backup.create.failed", StringUtils.getStackTrace(exception)), null, MessageDialogPane.MessageType.WARNING);

View File

@ -21,9 +21,14 @@ import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.game.WorldLockedException;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.File;
import java.nio.file.Path;
@ -33,10 +38,12 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class WorldListItem extends Control {
private final World world;
private final Path backupsDir;
private final WorldListPage parent;
public WorldListItem(World world, Path backupsDir) {
public WorldListItem(WorldListPage parent, World world, Path backupsDir) {
this.world = world;
this.backupsDir = backupsDir;
this.parent = parent;
}
@Override
@ -61,6 +68,24 @@ public final class WorldListItem extends Control {
Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller -> new WorldExportPage(world, file.toPath(), controller::onFinish)));
}
public void delete() {
Controllers.confirm(
i18n("button.remove.confirm"),
i18n("world.delete"),
() -> Task.runAsync(world::delete)
.whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
parent.remove(this);
} else if (exception instanceof WorldLockedException) {
Controllers.dialog(i18n("world.locked.failed"), null, MessageType.WARNING);
} else {
Controllers.dialog(i18n("world.delete.failed", StringUtils.getStackTrace(exception)), null, MessageType.WARNING);
}
}).start(),
null
);
}
public void reveal() {
FXUtils.openFolder(world.getFile().toFile());
}

View File

@ -136,6 +136,7 @@ public final class WorldListItemSkin extends SkinBase<WorldListItem> {
popupMenu.getContent().addAll(
new MenuSeparator(),
new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), item::export, popup),
new IconedMenuItem(SVG.DELETE, i18n("world.delete"), item::delete, popup),
new IconedMenuItem(SVG.FOLDER_OPEN, i18n("folder.world"), item::reveal, popup));
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup);

View File

@ -39,8 +39,8 @@ import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable {
private final BooleanProperty showAll = new SimpleBooleanProperty(this, "showAll", false);
@ -61,7 +61,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
if (worlds != null)
itemsProperty().setAll(worlds.stream()
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(world -> new WorldListItem(world, backupsDir)).collect(Collectors.toList()));
.map(world -> new WorldListItem(this, world, backupsDir)).toList());
});
}
@ -79,6 +79,10 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
refresh();
}
public void remove(WorldListItem item) {
itemsProperty().remove(item);
}
public void refresh() {
if (profile == null || id == null)
return;
@ -94,8 +98,10 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
worlds = result;
setLoading(false);
if (exception == null) {
itemsProperty().setAll(result.stream().filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(world -> new WorldListItem(world, backupsDir)).collect(Collectors.toList()));
itemsProperty().setAll(result.stream()
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
.map(world -> new WorldListItem(this, world, backupsDir))
.collect(Collectors.toList()));
} else {
LOG.warning("Failed to load world list page", exception);
}
@ -125,7 +131,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
Controllers.prompt(i18n("world.name.enter"), (name, resolve, reject) -> {
Task.runAsync(() -> world.install(savesDir, name))
.whenComplete(Schedulers.javafx(), () -> {
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name)), backupsDir));
itemsProperty().add(new WorldListItem(this, new World(savesDir.resolve(name)), backupsDir));
resolve.run();
}, e -> {
if (e instanceof FileAlreadyExistsException)

View File

@ -1113,7 +1113,6 @@ world.add=Add World
world.backup=World Backup
world.backup.create.new_one=Create New Backup
world.backup.create.failed=Failed to create backup.\n%s
world.backup.create.locked=The world is currently in use. Please close the game and try again.
world.backup.create.success=Successfully created a new backup: %s
world.backup.delete=Delete this backup
world.backup.processing=Backing up ...
@ -1124,6 +1123,8 @@ world.chunkbase.stronghold=Stronghold
world.chunkbase.nether_fortress=Nether Fortress
world.datapack=Datapacks
world.datetime=Last played on %s
world.delete=Delete the World
world.delete.failed=Failed to delete world.\n%s
world.download=Download World
world.download.title=Download World - %1s
world.export=Export the World
@ -1167,6 +1168,7 @@ world.info.random_seed=Seed
world.info.time=Game Time
world.info.time.format=%s days
world.locked=In use
world.locked.failed=The world is currently in use. Please close the game and try again.
world.manage=Worlds
world.manage.button=World Management
world.manage.title=World - %s

View File

@ -1113,7 +1113,6 @@ world.add=Añadir mundo
world.backup=Copia de seguridad
world.backup.create.new_one=Crear uno nuevo
world.backup.create.failed=No se ha podido crear la copia de seguridad.\n%s
world.backup.create.locked=El mundo está actualmente en uso. Por favor, cierra el juego e inténtalo de nuevo.
world.backup.create.success=Creada con éxito una nueva copia de seguridad: %s
world.backup.delete=Eliminar esta copia de seguridad
world.backup.processing=Creando nueva copia de seguridad ...
@ -1124,6 +1123,8 @@ world.chunkbase.stronghold=Fortaleza
world.chunkbase.nether_fortress=Fortaleza del Nether
world.datapack=Paquetes de datos
world.datetime=Jugado por última vez en %s
world.delete=Eliminar este mundo
world.delete.failed=No se pudo eliminar el mundo.\n%s
world.download=Descargar Mundo
world.download.title=Descargar mundo - %1s
world.export=Exportar el mundo
@ -1166,6 +1167,7 @@ world.info.random_seed=Semilla
world.info.time=Tiempo de juego
world.info.time.format=%s días
world.locked=En uso
world.locked.failed=El mundo está actualmente en uso. Por favor, cierra el juego e inténtalo de nuevo.
world.manage=Mundos
world.manage.button=Administrar
world.manage.title=Mundo - %s

View File

@ -705,6 +705,8 @@ world=マップ
world.add=マップを追加(.zip
world.datapack=データパックの管理
world.datetime=最終ゲーム時刻:%s
world.delete=このマップを削除
world.delete.failed=マップの削除に失敗しました\n%s
world.download=ダウンロード
world.export=このマップをエクスポートする
world.export.title=保存する場所を選択してください

View File

@ -929,6 +929,8 @@ world.chunkbase.stronghold=要塞輿圖
world.chunkbase.nether_fortress=焱界府輿圖
world.datapack=司錄囊
world.datetime=前戲之時辰: %s
world.delete=刪斯生界
world.delete.failed=刪斯生界未成\n%s
world.download=引生界
world.download.title=引生界 - %1s
world.export=錄出生界

View File

@ -1112,7 +1112,6 @@ world.add=Добавить мир
world.backup=Резервный мир
world.backup.create.new_one=Создать новый
world.backup.create.failed=Не удалось создать резервную копию.\n%s
world.backup.create.locked=В настоящее время мир находится в эксплуатации. Закройте игру и попробуйте снова.
world.backup.create.success=Успешно создано новое резервное копирование: %s
world.backup.delete=Удалить эту резервную копию
world.backup.processing=Создание новой резервной копии ...
@ -1121,6 +1120,8 @@ world.chunkbase.end_city=Город Края
world.chunkbase.seed_map=Предпросмотр генерации мира
world.chunkbase.stronghold=Крепость
world.chunkbase.nether_fortress=Крепость Нижнего мира
world.delete=Удалить этот мир
world.delete.failed=Не удалось удалить мир.\n%s
world.datapack=Наборы данных
world.datetime=Последний запуск игры %s
world.download=Скачать мир
@ -1165,6 +1166,7 @@ world.info.random_seed=Ключ генератора мира
world.info.time=Время игры
world.info.time.format=%s дн.
world.locked=В эксплуатации
world.locked.failed=В настоящее время мир находится в эксплуатации. Закройте игру и попробуйте снова.
world.manage=Миры
world.manage.button=Управлять
world.manage.title=Мир - %s

View File

@ -1053,7 +1053,6 @@ world.add=Додати світ
world.backup=Резервне копіювання світу
world.backup.create.new_one=Створити нову резервну копію
world.backup.create.failed=Не вдалося створити резервну копію. \n%s
world.backup.create.locked=Світ наразі використовується. Закрийте гру та спробуйте знову.
world.backup.create.success=Успішно створено нову резервну копію: %s
world.backup.delete=Видалити цю резервну копію
world.backup.processing=Резервне копіювання ...
@ -1064,6 +1063,8 @@ world.chunkbase.stronghold=Фортеця
world.chunkbase.nether_fortress=Форт Незеру
world.datapack=Datapacks
world.datetime=Останній раз грали %s
world.delete=Видалити цей світ
world.delete.failed=Не вдалося видалити світ.\n%s
world.download=Завантажити світ
world.download.title=Завантажити світ - %1s
world.export=Експортувати світ
@ -1107,6 +1108,7 @@ world.info.random_seed=Насіння
world.info.time=Час гри
world.info.time.format=%s днів
world.locked=Використовується
world.locked.failed=Світ наразі використовується. Закрийте гру та спробуйте знову.
world.manage=Світи
world.manage.button=Керування світами
world.manage.title=Світ - %s

View File

@ -910,7 +910,6 @@ world.add=新增世界
world.backup=世界備份
world.backup.create.new_one=建立新備份
world.backup.create.failed=建立備份失敗。\n%s
world.backup.create.locked=該世界正在使用中,請關閉遊戲後重試。
world.backup.create.success=成功建立新備份:%s
world.backup.delete=刪除此備份
world.backup.processing=正在備份中……
@ -921,6 +920,8 @@ world.chunkbase.stronghold=要塞地圖
world.chunkbase.nether_fortress=地獄要塞地圖
world.datapack=資料包管理
world.datetime=上一次遊戲時間: %s
world.delete=刪除此世界
world.delete.failed=刪除世界失敗。\n%s
world.download=下載世界
world.download.title=世界下載 - %1s
world.export=匯出此世界
@ -963,6 +964,7 @@ world.info.random_seed=種子碼
world.info.time=遊戲內時間
world.info.time.format=%s 天
world.locked=使用中
world.locked.failed=該世界正在使用中,請關閉遊戲後重試。
world.game_version=遊戲版本
world.manage=世界管理
world.manage.button=世界管理

View File

@ -920,7 +920,6 @@ world.add=添加世界
world.backup=备份管理
world.backup.create.new_one=创建新备份
world.backup.create.failed=创建备份失败。\n%s
world.backup.create.locked=该世界正在使用中,请关闭游戏后重试。
world.backup.create.success=成功创建新备份:%s
world.backup.delete=删除此备份
world.backup.processing=正在备份中……
@ -931,6 +930,8 @@ world.chunkbase.stronghold=要塞地图
world.chunkbase.nether_fortress=下界要塞地图
world.datapack=数据包管理
world.datetime=上一次游戏时间: %s
world.delete=删除此世界
world.delete.failed=删除世界失败。\n%s
world.download=下载世界
world.download.title=世界下载 - %1s
world.export=导出此世界
@ -974,6 +975,7 @@ world.info.random_seed=种子
world.info.time=游戏内时间
world.info.time.format=%s 天
world.locked=使用中
world.locked.failed=该世界正在使用中,请关闭游戏后重试。
world.manage=世界管理
world.manage.button=世界管理
world.manage.title=世界管理 - %s

View File

@ -275,6 +275,13 @@ public final class World {
}
}
public void delete() throws IOException {
if (isLocked()) {
throw new WorldLockedException("The world " + getFile() + " has been locked");
}
FileUtils.deleteDirectory(file);
}
public CompoundTag readLevelDat() throws IOException {
if (!Files.isDirectory(file))
throw new IOException("Not a valid world directory");