mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-15 14:56:05 -04:00
add: copy instance. Closes #687
This commit is contained in:
parent
3e2bb9678d
commit
b2f6ef72c3
@ -20,6 +20,7 @@ package org.jackhuang.hmcl.game;
|
|||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
|
import org.jackhuang.hmcl.mod.Modpack;
|
||||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||||
@ -36,7 +37,9 @@ import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
|||||||
|
|
||||||
public class HMCLGameRepository extends DefaultGameRepository {
|
public class HMCLGameRepository extends DefaultGameRepository {
|
||||||
private final Profile profile;
|
private final Profile profile;
|
||||||
private final Map<String, VersionSetting> versionSettings = new HashMap<>();
|
|
||||||
|
// local version settings
|
||||||
|
private final Map<String, VersionSetting> localVersionSettings = new HashMap<>();
|
||||||
private final Set<String> beingModpackVersions = new HashSet<>();
|
private final Set<String> beingModpackVersions = new HashSet<>();
|
||||||
|
|
||||||
public boolean checkedModpack = false, checkingModpack = false;
|
public boolean checkedModpack = false, checkingModpack = false;
|
||||||
@ -55,7 +58,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
|||||||
if (beingModpackVersions.contains(id) || isModpack(id))
|
if (beingModpackVersions.contains(id) || isModpack(id))
|
||||||
return getVersionRoot(id);
|
return getVersionRoot(id);
|
||||||
else {
|
else {
|
||||||
VersionSetting vs = profile.getVersionSetting(id);
|
VersionSetting vs = getVersionSetting(id);
|
||||||
switch (vs.getGameDirType()) {
|
switch (vs.getGameDirType()) {
|
||||||
case VERSION_FOLDER: return getVersionRoot(id);
|
case VERSION_FOLDER: return getVersionRoot(id);
|
||||||
case ROOT_FOLDER: return super.getRunDirectory(id);
|
case ROOT_FOLDER: return super.getRunDirectory(id);
|
||||||
@ -67,9 +70,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void refreshVersionsImpl() {
|
protected void refreshVersionsImpl() {
|
||||||
versionSettings.clear();
|
localVersionSettings.clear();
|
||||||
super.refreshVersionsImpl();
|
super.refreshVersionsImpl();
|
||||||
versions.keySet().forEach(this::loadVersionSetting);
|
versions.keySet().forEach(this::loadLocalVersionSetting);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
File file = new File(getBaseDirectory(), "launcher_profiles.json");
|
File file = new File(getBaseDirectory(), "launcher_profiles.json");
|
||||||
@ -95,19 +98,55 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
|||||||
clean(getRunDirectory(id));
|
clean(getRunDirectory(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getVersionSettingFile(String id) {
|
public void duplicateVersion(String srcId, String dstId, boolean copySaves) throws IOException {
|
||||||
|
File srcDir = getVersionRoot(srcId);
|
||||||
|
File dstDir = getVersionRoot(dstId);
|
||||||
|
|
||||||
|
if (dstDir.exists()) throw new IOException("Version exists");
|
||||||
|
FileUtils.copyDirectory(srcDir.toPath(), dstDir.toPath());
|
||||||
|
VersionSetting oldVersionSetting = getVersionSetting(srcId).clone();
|
||||||
|
EnumGameDirectory originalGameDirType = oldVersionSetting.getGameDirType();
|
||||||
|
oldVersionSetting.setUsesGlobal(false);
|
||||||
|
oldVersionSetting.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
|
||||||
|
VersionSetting newVersionSetting = initLocalVersionSetting(dstId, oldVersionSetting);
|
||||||
|
saveVersionSetting(dstId);
|
||||||
|
|
||||||
|
File srcGameDir = getRunDirectory(srcId);
|
||||||
|
File dstGameDir = getRunDirectory(dstId);
|
||||||
|
|
||||||
|
List<String> blackList = new ArrayList<>(Arrays.asList(
|
||||||
|
"regex:(.*?)\\.log",
|
||||||
|
"usernamecache.json", "usercache.json", // Minecraft
|
||||||
|
"launcher_profiles.json", "launcher.pack.lzma", // Minecraft Launcher
|
||||||
|
"backup", "pack.json", "launcher.jar", "cache", // HMCL
|
||||||
|
".curseclient", // Curse
|
||||||
|
".fabric", ".mixin.out", // Fabric
|
||||||
|
"jars", "logs", "versions", "assets", "libraries", "crash-reports", "NVIDIA", "AMD", "screenshots", "natives", "native", "$native", "server-resource-packs", // Minecraft
|
||||||
|
"downloads", // Curse
|
||||||
|
"asm", "backups", "TCNodeTracker", "CustomDISkins", "data", "CustomSkinLoader/caches" // Mods
|
||||||
|
));
|
||||||
|
blackList.add(srcId + ".jar");
|
||||||
|
blackList.add(srcId + ".json");
|
||||||
|
if (!copySaves)
|
||||||
|
blackList.add("saves");
|
||||||
|
|
||||||
|
if (originalGameDirType != EnumGameDirectory.VERSION_FOLDER)
|
||||||
|
FileUtils.copyDirectory(srcGameDir.toPath(), dstGameDir.toPath(), path -> Modpack.acceptFile(path, blackList, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getLocalVersionSettingFile(String id) {
|
||||||
return new File(getVersionRoot(id), "hmclversion.cfg");
|
return new File(getVersionRoot(id), "hmclversion.cfg");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadVersionSetting(String id) {
|
private void loadLocalVersionSetting(String id) {
|
||||||
File file = getVersionSettingFile(id);
|
File file = getLocalVersionSettingFile(id);
|
||||||
if (file.exists())
|
if (file.exists())
|
||||||
try {
|
try {
|
||||||
VersionSetting versionSetting = GSON.fromJson(FileUtils.readText(file), VersionSetting.class);
|
VersionSetting versionSetting = GSON.fromJson(FileUtils.readText(file), VersionSetting.class);
|
||||||
initVersionSetting(id, versionSetting);
|
initLocalVersionSetting(id, versionSetting);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
// If [JsonParseException], [IOException] or [NullPointerException] happens, the json file is malformed and needed to be recreated.
|
// If [JsonParseException], [IOException] or [NullPointerException] happens, the json file is malformed and needed to be recreated.
|
||||||
initVersionSetting(id, new VersionSetting());
|
initLocalVersionSetting(id, new VersionSetting());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,18 +155,18 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
|||||||
* @param id the version id.
|
* @param id the version id.
|
||||||
* @return new version setting, null if given version does not exist.
|
* @return new version setting, null if given version does not exist.
|
||||||
*/
|
*/
|
||||||
public VersionSetting createVersionSetting(String id) {
|
public VersionSetting createLocalVersionSetting(String id) {
|
||||||
if (!hasVersion(id))
|
if (!hasVersion(id))
|
||||||
return null;
|
return null;
|
||||||
if (versionSettings.containsKey(id))
|
if (localVersionSettings.containsKey(id))
|
||||||
return getVersionSetting(id);
|
return getLocalVersionSetting(id);
|
||||||
else
|
else
|
||||||
return initVersionSetting(id, new VersionSetting());
|
return initLocalVersionSetting(id, new VersionSetting());
|
||||||
}
|
}
|
||||||
|
|
||||||
private VersionSetting initVersionSetting(String id, VersionSetting vs) {
|
private VersionSetting initLocalVersionSetting(String id, VersionSetting vs) {
|
||||||
|
localVersionSettings.put(id, vs);
|
||||||
vs.addPropertyChangedListener(a -> saveVersionSetting(id));
|
vs.addPropertyChangedListener(a -> saveVersionSetting(id));
|
||||||
versionSettings.put(id, vs);
|
|
||||||
return vs;
|
return vs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,17 +175,27 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
|||||||
*
|
*
|
||||||
* @param id version id
|
* @param id version id
|
||||||
*
|
*
|
||||||
* @return may return null if the id not exists
|
* @return corresponding version setting, null if the version has no its own version setting.
|
||||||
*/
|
*/
|
||||||
public VersionSetting getVersionSetting(String id) {
|
public VersionSetting getLocalVersionSetting(String id) {
|
||||||
if (!versionSettings.containsKey(id))
|
if (!localVersionSettings.containsKey(id))
|
||||||
loadVersionSetting(id);
|
loadLocalVersionSetting(id);
|
||||||
VersionSetting setting = versionSettings.get(id);
|
VersionSetting setting = localVersionSettings.get(id);
|
||||||
if (setting != null && isModpack(id))
|
if (setting != null && isModpack(id))
|
||||||
setting.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
|
setting.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
|
||||||
return setting;
|
return setting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VersionSetting getVersionSetting(String id) {
|
||||||
|
VersionSetting vs = getLocalVersionSetting(id);
|
||||||
|
if (vs == null || vs.isUsesGlobal()) {
|
||||||
|
profile.getGlobal().setGlobal(true); // always keep global.isGlobal = true
|
||||||
|
profile.getGlobal().setUsesGlobal(true);
|
||||||
|
return profile.getGlobal();
|
||||||
|
} else
|
||||||
|
return vs;
|
||||||
|
}
|
||||||
|
|
||||||
public File getVersionIconFile(String id) {
|
public File getVersionIconFile(String id) {
|
||||||
return new File(getVersionRoot(id), "icon.png");
|
return new File(getVersionRoot(id), "icon.png");
|
||||||
}
|
}
|
||||||
@ -169,14 +218,14 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean saveVersionSetting(String id) {
|
public boolean saveVersionSetting(String id) {
|
||||||
if (!versionSettings.containsKey(id))
|
if (!localVersionSettings.containsKey(id))
|
||||||
return false;
|
return false;
|
||||||
File file = getVersionSettingFile(id);
|
File file = getLocalVersionSettingFile(id);
|
||||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FileUtils.writeText(file, GSON.toJson(versionSettings.get(id)));
|
FileUtils.writeText(file, GSON.toJson(localVersionSettings.get(id)));
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Logging.LOG.log(Level.SEVERE, "Unable to save version setting of " + id, e);
|
Logging.LOG.log(Level.SEVERE, "Unable to save version setting of " + id, e);
|
||||||
@ -190,9 +239,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
|||||||
* @return specialized version setting, null if given version does not exist.
|
* @return specialized version setting, null if given version does not exist.
|
||||||
*/
|
*/
|
||||||
public VersionSetting specializeVersionSetting(String id) {
|
public VersionSetting specializeVersionSetting(String id) {
|
||||||
VersionSetting vs = getVersionSetting(id);
|
VersionSetting vs = getLocalVersionSetting(id);
|
||||||
if (vs == null)
|
if (vs == null)
|
||||||
vs = createVersionSetting(id);
|
vs = createLocalVersionSetting(id);
|
||||||
if (vs == null)
|
if (vs == null)
|
||||||
return null;
|
return null;
|
||||||
vs.setUsesGlobal(false);
|
vs.setUsesGlobal(false);
|
||||||
@ -200,7 +249,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void globalizeVersionSetting(String id) {
|
public void globalizeVersionSetting(String id) {
|
||||||
VersionSetting vs = getVersionSetting(id);
|
VersionSetting vs = getLocalVersionSetting(id);
|
||||||
if (vs != null)
|
if (vs != null)
|
||||||
vs.setUsesGlobal(true);
|
vs.setUsesGlobal(true);
|
||||||
}
|
}
|
||||||
|
@ -166,13 +166,7 @@ public final class Profile implements Observable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public VersionSetting getVersionSetting(String id) {
|
public VersionSetting getVersionSetting(String id) {
|
||||||
VersionSetting vs = repository.getVersionSetting(id);
|
return repository.getVersionSetting(id);
|
||||||
if (vs == null || vs.isUsesGlobal()) {
|
|
||||||
getGlobal().setGlobal(true); // always keep global.isGlobal = true
|
|
||||||
getGlobal().setUsesGlobal(true);
|
|
||||||
return getGlobal();
|
|
||||||
} else
|
|
||||||
return vs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -45,7 +45,7 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
|||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
@JsonAdapter(VersionSetting.Serializer.class)
|
@JsonAdapter(VersionSetting.Serializer.class)
|
||||||
public final class VersionSetting {
|
public final class VersionSetting implements Cloneable {
|
||||||
|
|
||||||
public transient String id;
|
public transient String id;
|
||||||
|
|
||||||
@ -563,6 +563,34 @@ public final class VersionSetting {
|
|||||||
return builder.create();
|
return builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VersionSetting clone() {
|
||||||
|
VersionSetting versionSetting = new VersionSetting();
|
||||||
|
versionSetting.setUsesGlobal(isUsesGlobal());
|
||||||
|
versionSetting.setJava(getJava());
|
||||||
|
versionSetting.setDefaultJavaPath(getDefaultJavaPath());
|
||||||
|
versionSetting.setJavaDir(getJavaDir());
|
||||||
|
versionSetting.setWrapper(getWrapper());
|
||||||
|
versionSetting.setPermSize(getPermSize());
|
||||||
|
versionSetting.setMaxMemory(getMaxMemory());
|
||||||
|
versionSetting.setMinMemory(getMinMemory());
|
||||||
|
versionSetting.setPreLaunchCommand(getPreLaunchCommand());
|
||||||
|
versionSetting.setJavaArgs(getJavaArgs());
|
||||||
|
versionSetting.setMinecraftArgs(getMinecraftArgs());
|
||||||
|
versionSetting.setNoJVMArgs(isNoJVMArgs());
|
||||||
|
versionSetting.setNotCheckGame(isNotCheckGame());
|
||||||
|
versionSetting.setNotCheckJVM(isNotCheckJVM());
|
||||||
|
versionSetting.setShowLogs(isShowLogs());
|
||||||
|
versionSetting.setServerIp(getServerIp());
|
||||||
|
versionSetting.setFullscreen(isFullscreen());
|
||||||
|
versionSetting.setWidth(getWidth());
|
||||||
|
versionSetting.setHeight(getHeight());
|
||||||
|
versionSetting.setGameDirType(getGameDirType());
|
||||||
|
versionSetting.setGameDir(getGameDir());
|
||||||
|
versionSetting.setLauncherVisibility(getLauncherVisibility());
|
||||||
|
return versionSetting;
|
||||||
|
}
|
||||||
|
|
||||||
public static class Serializer implements JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
|
public static class Serializer implements JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
|
||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(VersionSetting src, Type typeOfSrc, JsonSerializationContext context) {
|
public JsonElement serialize(VersionSetting src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
@ -31,6 +31,7 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
|||||||
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
||||||
import org.jackhuang.hmcl.ui.main.RootPage;
|
import org.jackhuang.hmcl.ui.main.RootPage;
|
||||||
@ -40,6 +41,7 @@ import org.jackhuang.hmcl.util.Logging;
|
|||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@ -143,14 +145,19 @@ public final class Controllers {
|
|||||||
dialog(new MessageDialogPane(text, title, onAccept, onCancel));
|
dialog(new MessageDialogPane(text, title, onAccept, onCancel));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompletableFuture<String> prompt(String text, FutureCallback<String> onResult) {
|
public static CompletableFuture<String> prompt(String title, FutureCallback<String> onResult) {
|
||||||
return prompt(text, onResult, "");
|
return prompt(title, onResult, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompletableFuture<String> prompt(String text, FutureCallback<String> onResult, String initialValue) {
|
public static CompletableFuture<String> prompt(String title, FutureCallback<String> onResult, String initialValue) {
|
||||||
InputDialogPane pane = new InputDialogPane(text, onResult);
|
InputDialogPane pane = new InputDialogPane(title, initialValue, onResult);
|
||||||
|
dialog(pane);
|
||||||
|
return pane.getCompletableFuture();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompletableFuture<List<PromptDialogPane.Builder.Question<?>>> prompt(PromptDialogPane.Builder builder) {
|
||||||
|
PromptDialogPane pane = new PromptDialogPane(builder);
|
||||||
dialog(pane);
|
dialog(pane);
|
||||||
pane.setInitialValue(initialValue);
|
|
||||||
return pane.getCompletableFuture();
|
return pane.getCompletableFuture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +67,10 @@ public final class SVG {
|
|||||||
return createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill, width, height);
|
return createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Node copy(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||||
|
return createSVGPath("M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z", fill, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
public static Node dotsVertical(ObjectBinding<? extends Paint> fill, double width, double height) {
|
public static Node dotsVertical(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||||
return createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height);
|
return createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height);
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,10 @@ package org.jackhuang.hmcl.ui.construct;
|
|||||||
|
|
||||||
import com.jfoenix.controls.JFXButton;
|
import com.jfoenix.controls.JFXButton;
|
||||||
import com.jfoenix.controls.JFXTextField;
|
import com.jfoenix.controls.JFXTextField;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
import org.jackhuang.hmcl.util.FutureCallback;
|
import org.jackhuang.hmcl.util.FutureCallback;
|
||||||
|
|
||||||
@ -36,17 +36,20 @@ public class InputDialogPane extends StackPane {
|
|||||||
@FXML
|
@FXML
|
||||||
private JFXButton cancelButton;
|
private JFXButton cancelButton;
|
||||||
@FXML
|
@FXML
|
||||||
private JFXTextField textField;
|
private Label title;
|
||||||
@FXML
|
@FXML
|
||||||
private Label content;
|
private VBox vbox;
|
||||||
@FXML
|
@FXML
|
||||||
private Label lblCreationWarning;
|
private Label lblCreationWarning;
|
||||||
@FXML
|
@FXML
|
||||||
private SpinnerPane acceptPane;
|
private SpinnerPane acceptPane;
|
||||||
|
|
||||||
public InputDialogPane(String text, FutureCallback<String> onResult) {
|
public InputDialogPane(String text, String initialValue, FutureCallback<String> onResult) {
|
||||||
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
|
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
|
||||||
content.setText(text);
|
title.setText(text);
|
||||||
|
JFXTextField textField = new JFXTextField();
|
||||||
|
textField.setText(initialValue);
|
||||||
|
vbox.getChildren().setAll(textField);
|
||||||
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
|
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
|
||||||
acceptButton.setOnMouseClicked(e -> {
|
acceptButton.setOnMouseClicked(e -> {
|
||||||
acceptPane.showSpinner();
|
acceptPane.showSpinner();
|
||||||
@ -60,15 +63,6 @@ public class InputDialogPane extends StackPane {
|
|||||||
lblCreationWarning.setText(msg);
|
lblCreationWarning.setText(msg);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
acceptButton.disableProperty().bind(Bindings.createBooleanBinding(
|
|
||||||
() -> !textField.validate(),
|
|
||||||
textField.textProperty()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInitialValue(String value) {
|
|
||||||
textField.setText(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<String> getCompletableFuture() {
|
public CompletableFuture<String> getCompletableFuture() {
|
||||||
|
@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.ui.construct;
|
||||||
|
|
||||||
|
import com.jfoenix.controls.JFXButton;
|
||||||
|
import com.jfoenix.controls.JFXCheckBox;
|
||||||
|
import com.jfoenix.controls.JFXTextField;
|
||||||
|
import com.jfoenix.validation.base.ValidatorBase;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.util.FutureCallback;
|
||||||
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class PromptDialogPane extends StackPane {
|
||||||
|
private final CompletableFuture<List<Builder.Question<?>>> future = new CompletableFuture<>();
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private JFXButton acceptButton;
|
||||||
|
@FXML
|
||||||
|
private JFXButton cancelButton;
|
||||||
|
@FXML
|
||||||
|
private VBox vbox;
|
||||||
|
@FXML
|
||||||
|
private Label title;
|
||||||
|
@FXML
|
||||||
|
private Label lblCreationWarning;
|
||||||
|
@FXML
|
||||||
|
private SpinnerPane acceptPane;
|
||||||
|
|
||||||
|
public PromptDialogPane(Builder builder) {
|
||||||
|
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
|
||||||
|
this.title.setText(builder.title);
|
||||||
|
|
||||||
|
List<BooleanBinding> bindings = new ArrayList<>();
|
||||||
|
for (Builder.Question<?> question : builder.questions) {
|
||||||
|
if (question instanceof Builder.StringQuestion) {
|
||||||
|
JFXTextField textField = new JFXTextField();
|
||||||
|
textField.textProperty().addListener((a, b, newValue) -> ((Builder.StringQuestion) question).value = textField.getText());
|
||||||
|
textField.setText(((Builder.StringQuestion) question).value);
|
||||||
|
textField.setValidators(((Builder.StringQuestion) question).validators.toArray(new ValidatorBase[0]));
|
||||||
|
bindings.add(Bindings.createBooleanBinding(textField::validate, textField.textProperty()));
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(question.question)) {
|
||||||
|
vbox.getChildren().add(new Label(question.question));
|
||||||
|
}
|
||||||
|
VBox.setMargin(textField, new Insets(0, 0, 20, 0));
|
||||||
|
vbox.getChildren().add(textField);
|
||||||
|
} else if (question instanceof Builder.BooleanQuestion) {
|
||||||
|
HBox hBox = new HBox();
|
||||||
|
JFXCheckBox checkBox = new JFXCheckBox();
|
||||||
|
hBox.getChildren().setAll(checkBox);
|
||||||
|
HBox.setMargin(checkBox, new Insets(0, 0, 0, -10));
|
||||||
|
checkBox.setSelected(((Builder.BooleanQuestion) question).value);
|
||||||
|
checkBox.selectedProperty().addListener((a, b, newValue) -> ((Builder.BooleanQuestion) question).value = newValue);
|
||||||
|
checkBox.setText(question.question);
|
||||||
|
vbox.getChildren().add(hBox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
|
||||||
|
acceptButton.disableProperty().bind(Bindings.createBooleanBinding(
|
||||||
|
() -> bindings.stream().map(BooleanBinding::get).anyMatch(x -> !x),
|
||||||
|
bindings.toArray(new BooleanBinding[0])
|
||||||
|
));
|
||||||
|
|
||||||
|
acceptButton.setOnMouseClicked(e -> {
|
||||||
|
acceptPane.showSpinner();
|
||||||
|
|
||||||
|
builder.callback.call(builder.questions, () -> {
|
||||||
|
acceptPane.hideSpinner();
|
||||||
|
future.complete(builder.questions);
|
||||||
|
fireEvent(new DialogCloseEvent());
|
||||||
|
}, msg -> {
|
||||||
|
acceptPane.hideSpinner();
|
||||||
|
lblCreationWarning.setText(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<List<Builder.Question<?>>> getCompletableFuture() {
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private final List<Question<?>> questions = new ArrayList<>();
|
||||||
|
private final String title;
|
||||||
|
private final FutureCallback<List<Question<?>>> callback;
|
||||||
|
|
||||||
|
public Builder(String title, FutureCallback<List<Question<?>>> callback) {
|
||||||
|
this.title = title;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Builder addQuestion(Question<T> question) {
|
||||||
|
questions.add(question);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Question<T> {
|
||||||
|
public final String question;
|
||||||
|
protected T value;
|
||||||
|
|
||||||
|
public Question(String question) {
|
||||||
|
this.question = question;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StringQuestion extends Question<String> {
|
||||||
|
protected final List<ValidatorBase> validators;
|
||||||
|
|
||||||
|
public StringQuestion(String question, String defaultValue, ValidatorBase... validators) {
|
||||||
|
super(question);
|
||||||
|
this.value = defaultValue;
|
||||||
|
this.validators = Arrays.asList(validators);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BooleanQuestion extends Question<Boolean> {
|
||||||
|
|
||||||
|
public BooleanQuestion(String question, boolean defaultValue) {
|
||||||
|
super(question);
|
||||||
|
this.value = defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -69,6 +69,10 @@ public class GameListItem extends Control {
|
|||||||
Versions.renameVersion(profile, version);
|
Versions.renameVersion(profile, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void duplicate() {
|
||||||
|
Versions.duplicateVersion(profile, version);
|
||||||
|
}
|
||||||
|
|
||||||
public void remove() {
|
public void remove() {
|
||||||
Versions.deleteVersion(profile, version);
|
Versions.deleteVersion(profile, version);
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,7 @@ public class GameListItemSkin extends SkinBase<GameListItem> {
|
|||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.gear(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.manage"), FXUtils.withJFXPopupClosing(() -> currentSkinnable.modifyGameSettings(), popup)),
|
new IconedMenuItem(FXUtils.limitingSize(SVG.gear(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.manage"), FXUtils.withJFXPopupClosing(() -> currentSkinnable.modifyGameSettings(), popup)),
|
||||||
new MenuSeparator(),
|
new MenuSeparator(),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(() -> currentSkinnable.rename(), popup)),
|
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(() -> currentSkinnable.rename(), popup)),
|
||||||
|
new IconedMenuItem(FXUtils.limitingSize(SVG.copy(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.duplicate"), FXUtils.withJFXPopupClosing(() -> currentSkinnable.duplicate(), popup)),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(() -> currentSkinnable.remove(), popup)),
|
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(() -> currentSkinnable.remove(), popup)),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("modpack.export"), FXUtils.withJFXPopupClosing(() -> currentSkinnable.export(), popup)),
|
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("modpack.export"), FXUtils.withJFXPopupClosing(() -> currentSkinnable.export(), popup)),
|
||||||
new MenuSeparator(),
|
new MenuSeparator(),
|
||||||
|
@ -192,6 +192,10 @@ public class VersionPage extends Control implements DecoratorPage {
|
|||||||
Versions.deleteVersion(profile, version);
|
Versions.deleteVersion(profile, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void duplicate() {
|
||||||
|
Versions.duplicateVersion(profile, version);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Skin createDefaultSkin() {
|
protected Skin createDefaultSkin() {
|
||||||
return new Skin(this);
|
return new Skin(this);
|
||||||
@ -228,6 +232,9 @@ public class VersionPage extends Control implements DecoratorPage {
|
|||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(() -> {
|
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(() -> {
|
||||||
Versions.renameVersion(getSkinnable().profile, currentVersion).thenApply(name -> getSkinnable().preferredVersionName = name);
|
Versions.renameVersion(getSkinnable().profile, currentVersion).thenApply(name -> getSkinnable().preferredVersionName = name);
|
||||||
}, listViewItemPopup)),
|
}, listViewItemPopup)),
|
||||||
|
new IconedMenuItem(FXUtils.limitingSize(SVG.copy(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.duplicate"), FXUtils.withJFXPopupClosing(() -> {
|
||||||
|
Versions.duplicateVersion(getSkinnable().profile, currentVersion);
|
||||||
|
}, listViewItemPopup)),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(() -> {
|
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(() -> {
|
||||||
Versions.deleteVersion(getSkinnable().profile, currentVersion);
|
Versions.deleteVersion(getSkinnable().profile, currentVersion);
|
||||||
}, listViewItemPopup)),
|
}, listViewItemPopup)),
|
||||||
@ -313,6 +320,7 @@ public class VersionPage extends Control implements DecoratorPage {
|
|||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(control::generateLaunchScript, managementPopup)),
|
new IconedMenuItem(FXUtils.limitingSize(SVG.script(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.launch_script"), FXUtils.withJFXPopupClosing(control::generateLaunchScript, managementPopup)),
|
||||||
new MenuSeparator(),
|
new MenuSeparator(),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(control::rename, managementPopup)),
|
new IconedMenuItem(FXUtils.limitingSize(SVG.pencil(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.rename"), FXUtils.withJFXPopupClosing(control::rename, managementPopup)),
|
||||||
|
new IconedMenuItem(FXUtils.limitingSize(SVG.copy(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.duplicate"), FXUtils.withJFXPopupClosing(control::duplicate, managementPopup)),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(control::remove, managementPopup)),
|
new IconedMenuItem(FXUtils.limitingSize(SVG.delete(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("version.manage.remove"), FXUtils.withJFXPopupClosing(control::remove, managementPopup)),
|
||||||
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("modpack.export"), FXUtils.withJFXPopupClosing(control::export, managementPopup)),
|
new IconedMenuItem(FXUtils.limitingSize(SVG.export(Theme.blackFillBinding(), 14, 14), 14, 14), i18n("modpack.export"), FXUtils.withJFXPopupClosing(control::export, managementPopup)),
|
||||||
new MenuSeparator(),
|
new MenuSeparator(),
|
||||||
|
@ -24,12 +24,17 @@ import org.jackhuang.hmcl.game.LauncherHelper;
|
|||||||
import org.jackhuang.hmcl.setting.Accounts;
|
import org.jackhuang.hmcl.setting.Accounts;
|
||||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
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.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.FXUtils;
|
import org.jackhuang.hmcl.ui.FXUtils;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
|
||||||
|
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
|
|
||||||
@ -72,6 +77,27 @@ public class Versions {
|
|||||||
FXUtils.openFolder(profile.getRepository().getRunDirectory(version));
|
FXUtils.openFolder(profile.getRepository().getRunDirectory(version));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void duplicateVersion(Profile profile, String version) {
|
||||||
|
Controllers.prompt(
|
||||||
|
new PromptDialogPane.Builder(i18n("version.manage.duplicate.prompt"), (res, resolve, reject) -> {
|
||||||
|
String newVersionName = ((PromptDialogPane.Builder.StringQuestion) res.get(0)).getValue();
|
||||||
|
boolean copySaves = ((PromptDialogPane.Builder.BooleanQuestion) res.get(1)).getValue();
|
||||||
|
Task.runAsync(() -> profile.getRepository().duplicateVersion(version, newVersionName, copySaves))
|
||||||
|
.thenComposeAsync(profile.getRepository().refreshVersionsAsync())
|
||||||
|
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||||
|
if (exception == null) {
|
||||||
|
resolve.run();
|
||||||
|
} else {
|
||||||
|
reject.accept(StringUtils.getStackTrace(exception));
|
||||||
|
profile.getRepository().removeVersionFromDisk(newVersionName);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
})
|
||||||
|
.addQuestion(new PromptDialogPane.Builder.StringQuestion(i18n("version.manage.duplicate.confirm"), version,
|
||||||
|
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().hasVersion(newVersionName))))
|
||||||
|
.addQuestion(new PromptDialogPane.Builder.BooleanQuestion(i18n("version.manage.duplicate.duplicate_save"), false)));
|
||||||
|
}
|
||||||
|
|
||||||
public static void updateVersion(Profile profile, String version) {
|
public static void updateVersion(Profile profile, String version) {
|
||||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, version));
|
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, version));
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,20 @@
|
|||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.layout.StackPane?>
|
<?import javafx.scene.layout.StackPane?>
|
||||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
<fx:root xmlns="http://javafx.com/javafx"
|
<fx:root xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
type="StackPane">
|
type="StackPane">
|
||||||
<JFXDialogLayout>
|
<JFXDialogLayout>
|
||||||
<heading>
|
<heading>
|
||||||
<Label fx:id="content" />
|
<HBox>
|
||||||
|
<Label fx:id="title" />
|
||||||
|
</HBox>
|
||||||
</heading>
|
</heading>
|
||||||
<body>
|
<body>
|
||||||
<JFXTextField fx:id="textField" />
|
<VBox fx:id="vbox">
|
||||||
|
</VBox>
|
||||||
</body>
|
</body>
|
||||||
<actions>
|
<actions>
|
||||||
<Label fx:id="lblCreationWarning"/>
|
<Label fx:id="lblCreationWarning"/>
|
||||||
|
@ -445,6 +445,10 @@ version.launch_script.success=Created script %s.
|
|||||||
version.manage=All Versions
|
version.manage=All Versions
|
||||||
version.manage.clean=Clear game directory
|
version.manage.clean=Clear game directory
|
||||||
version.manage.clean.tooltip=Clear logs, crash-reports
|
version.manage.clean.tooltip=Clear logs, crash-reports
|
||||||
|
version.manage.duplicate=Copy game instance
|
||||||
|
version.manage.duplicate.duplicate_save=Copy saves
|
||||||
|
version.manage.duplicate.prompt=Type new version name
|
||||||
|
version.manage.duplicate.confirm=
|
||||||
version.manage.manage=Manage Version
|
version.manage.manage=Manage Version
|
||||||
version.manage.redownload_assets_index=Update Game Asset Files
|
version.manage.redownload_assets_index=Update Game Asset Files
|
||||||
version.manage.remove=Delete this version
|
version.manage.remove=Delete this version
|
||||||
|
@ -444,6 +444,10 @@ version.launch_script.success=啟動指令碼已生成完畢: %s
|
|||||||
version.manage=遊戲列表
|
version.manage=遊戲列表
|
||||||
version.manage.clean=清理遊戲目錄
|
version.manage.clean=清理遊戲目錄
|
||||||
version.manage.clean.tooltip=清理 logs, crash-reports
|
version.manage.clean.tooltip=清理 logs, crash-reports
|
||||||
|
version.manage.duplicate=複製遊戲實例
|
||||||
|
version.manage.duplicate.duplicate_save=複製存檔
|
||||||
|
version.manage.duplicate.prompt=請輸入新遊戲實例名稱
|
||||||
|
version.manage.duplicate.confirm=將鎖定複製產生的新遊戲實例:強制版本隔離、遊戲設置獨立。
|
||||||
version.manage.manage=遊戲管理
|
version.manage.manage=遊戲管理
|
||||||
version.manage.redownload_assets_index=更新遊戲資源檔案
|
version.manage.redownload_assets_index=更新遊戲資源檔案
|
||||||
version.manage.remove=刪除該版本
|
version.manage.remove=刪除該版本
|
||||||
|
@ -444,6 +444,10 @@ version.launch_script.success=启动脚本已生成完毕:%s
|
|||||||
version.manage=游戏列表
|
version.manage=游戏列表
|
||||||
version.manage.clean=清理游戏目录
|
version.manage.clean=清理游戏目录
|
||||||
version.manage.clean.tooltip=清理 logs, crash-reports
|
version.manage.clean.tooltip=清理 logs, crash-reports
|
||||||
|
version.manage.duplicate=复制游戏实例
|
||||||
|
version.manage.duplicate.duplicate_save=复制存档
|
||||||
|
version.manage.duplicate.prompt=请输入新游戏实例名称
|
||||||
|
version.manage.duplicate.confirm=将锁定复制产生的新游戏实例:强制版本隔离、游戏设置独立。
|
||||||
version.manage.manage=游戏管理
|
version.manage.manage=游戏管理
|
||||||
version.manage.redownload_assets_index=更新游戏资源文件
|
version.manage.redownload_assets_index=更新游戏资源文件
|
||||||
version.manage.remove=删除该版本
|
version.manage.remove=删除该版本
|
||||||
|
@ -45,7 +45,7 @@ public interface ModAdviser {
|
|||||||
"regex:(.*?)\\.log",
|
"regex:(.*?)\\.log",
|
||||||
"usernamecache.json", "usercache.json", // Minecraft
|
"usernamecache.json", "usercache.json", // Minecraft
|
||||||
"launcher_profiles.json", "launcher.pack.lzma", // Minecraft Launcher
|
"launcher_profiles.json", "launcher.pack.lzma", // Minecraft Launcher
|
||||||
"pack.json", "launcher.jar", "cache", "modpack.cfg", // HMCL
|
"backup", "pack.json", "launcher.jar", "cache", "modpack.cfg", // HMCL
|
||||||
"manifest.json", "minecraftinstance.json", ".curseclient", // Curse
|
"manifest.json", "minecraftinstance.json", ".curseclient", // Curse
|
||||||
".fabric", ".mixin.out", // Fabric
|
".fabric", ".mixin.out", // Fabric
|
||||||
"jars", "logs", "versions", "assets", "libraries", "crash-reports", "NVIDIA", "AMD", "screenshots", "natives", "native", "$native", "server-resource-packs", // Minecraft
|
"jars", "logs", "versions", "assets", "libraries", "crash-reports", "NVIDIA", "AMD", "screenshots", "natives", "native", "$native", "server-resource-packs", // Minecraft
|
||||||
|
@ -93,6 +93,8 @@ public final class Modpack {
|
|||||||
for (String s : blackList)
|
for (String s : blackList)
|
||||||
if (path.equals(s))
|
if (path.equals(s))
|
||||||
return false;
|
return false;
|
||||||
|
if (whiteList == null || whiteList.isEmpty())
|
||||||
|
return true;
|
||||||
for (String s : whiteList)
|
for (String s : whiteList)
|
||||||
if (path.equals(s))
|
if (path.equals(s))
|
||||||
return true;
|
return true;
|
||||||
|
@ -21,5 +21,14 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface FutureCallback<T> {
|
public interface FutureCallback<T> {
|
||||||
void call(T obj, Runnable resolve, Consumer<String> reject);
|
|
||||||
|
/**
|
||||||
|
* Callback of future, called after future finishes.
|
||||||
|
* This callback gives the feedback whether the result of future is acceptable or not,
|
||||||
|
* if not, giving the reason, and future will be relaunched when necessary.
|
||||||
|
* @param result result of the future
|
||||||
|
* @param resolve accept the result
|
||||||
|
* @param reject reject the result with failure reason
|
||||||
|
*/
|
||||||
|
void call(T result, Runnable resolve, Consumer<String> reject);
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import java.nio.file.attribute.BasicFileAttributes;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
@ -196,20 +197,30 @@ public final class FileUtils {
|
|||||||
* @throws IOException if an I/O error occurs.
|
* @throws IOException if an I/O error occurs.
|
||||||
*/
|
*/
|
||||||
public static void copyDirectory(Path src, Path dest) throws IOException {
|
public static void copyDirectory(Path src, Path dest) throws IOException {
|
||||||
|
copyDirectory(src, dest, path -> true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void copyDirectory(Path src, Path dest, Predicate<String> filePredicate) throws IOException {
|
||||||
Files.walkFileTree(src, new SimpleFileVisitor<Path>(){
|
Files.walkFileTree(src, new SimpleFileVisitor<Path>(){
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
if (!filePredicate.test(src.relativize(file).toString())) {
|
||||||
|
return FileVisitResult.SKIP_SUBTREE;
|
||||||
|
}
|
||||||
|
|
||||||
Path destFile = dest.resolve(src.relativize(file).toString());
|
Path destFile = dest.resolve(src.relativize(file).toString());
|
||||||
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
|
Files.copy(file, destFile, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||||
|
if (!filePredicate.test(src.relativize(dir).toString())) {
|
||||||
|
return FileVisitResult.SKIP_SUBTREE;
|
||||||
|
}
|
||||||
|
|
||||||
Path destDir = dest.resolve(src.relativize(dir).toString());
|
Path destDir = dest.resolve(src.relativize(dir).toString());
|
||||||
Files.createDirectories(destDir);
|
Files.createDirectories(destDir);
|
||||||
|
|
||||||
return FileVisitResult.CONTINUE;
|
return FileVisitResult.CONTINUE;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user