优化输入框校验 (#5129)

Co-authored-by: Glavo <zjx001202@gmail.com>
This commit is contained in:
辞庐
2026-01-03 16:41:40 +08:00
committed by GitHub
parent e25e58cfad
commit 11d182f907
7 changed files with 42 additions and 30 deletions

View File

@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialogLayout; import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.validation.base.ValidatorBase;
import javafx.animation.KeyFrame; import javafx.animation.KeyFrame;
import javafx.animation.KeyValue; import javafx.animation.KeyValue;
import javafx.animation.Timeline; import javafx.animation.Timeline;
@@ -72,9 +73,10 @@ import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import static org.jackhuang.hmcl.setting.ConfigHolder.*; import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class Controllers { public final class Controllers {
public static final String JAVA_VERSION_TIP = "javaVersion"; public static final String JAVA_VERSION_TIP = "javaVersion";
@@ -505,8 +507,8 @@ public final class Controllers {
return prompt(title, onResult, ""); return prompt(title, onResult, "");
} }
public static CompletableFuture<String> prompt(String title, FutureCallback<String> onResult, String initialValue) { public static CompletableFuture<String> prompt(String title, FutureCallback<String> onResult, String initialValue, ValidatorBase... validators) {
InputDialogPane pane = new InputDialogPane(title, initialValue, onResult); InputDialogPane pane = new InputDialogPane(title, initialValue, onResult, validators);
dialog(pane); dialog(pane);
return pane.getCompletableFuture(); return pane.getCompletableFuture();
} }

View File

@@ -25,11 +25,10 @@ import javafx.scene.layout.*;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer; import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations; import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane; import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.construct.DialogAware; import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
@@ -88,6 +87,10 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
addServerPane.setBody(txtServerUrl); addServerPane.setBody(txtServerUrl);
addServerPane.setActions(lblCreationWarning, actions); addServerPane.setActions(lblCreationWarning, actions);
txtServerUrl.getValidators().addAll(new RequiredValidator(), new URLValidator());
FXUtils.setValidateWhileTextChanged(txtServerUrl, true);
btnAddNext.disableProperty().bind(txtServerUrl.activeValidatorProperty().isNotNull());
} }
confirmServerPane = new JFXDialogLayout(); confirmServerPane = new JFXDialogLayout();
@@ -149,7 +152,6 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
this.setContent(addServerPane, ContainerAnimations.NONE); this.setContent(addServerPane, ContainerAnimations.NONE);
lblCreationWarning.maxWidthProperty().bind(((FlowPane) lblCreationWarning.getParent()).widthProperty()); lblCreationWarning.maxWidthProperty().bind(((FlowPane) lblCreationWarning.getParent()).widthProperty());
btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty());
nextPane.hideSpinner(); nextPane.hideSpinner();
onEscPressed(this, this::onAddCancel); onEscPressed(this, this::onAddCancel);

View File

@@ -20,9 +20,11 @@ package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialogLayout; import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXTextField; import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.base.ValidatorBase;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.FutureCallback; import org.jackhuang.hmcl.util.FutureCallback;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -36,6 +38,16 @@ public class InputDialogPane extends JFXDialogLayout implements DialogAware {
private final JFXTextField textField; private final JFXTextField textField;
private final Label lblCreationWarning; private final Label lblCreationWarning;
private final SpinnerPane acceptPane; private final SpinnerPane acceptPane;
private final JFXButton acceptButton;
public InputDialogPane(String text, String initialValue, FutureCallback<String> onResult, ValidatorBase... validators) {
this(text, initialValue, onResult);
if (validators != null && validators.length > 0) {
textField.getValidators().addAll(validators);
FXUtils.setValidateWhileTextChanged(textField, true);
acceptButton.disableProperty().bind(textField.activeValidatorProperty().isNotNull());
}
}
public InputDialogPane(String text, String initialValue, FutureCallback<String> onResult) { public InputDialogPane(String text, String initialValue, FutureCallback<String> onResult) {
textField = new JFXTextField(initialValue); textField = new JFXTextField(initialValue);
@@ -47,7 +59,7 @@ public class InputDialogPane extends JFXDialogLayout implements DialogAware {
acceptPane = new SpinnerPane(); acceptPane = new SpinnerPane();
acceptPane.getStyleClass().add("small-spinner-pane"); acceptPane.getStyleClass().add("small-spinner-pane");
JFXButton acceptButton = new JFXButton(i18n("button.ok")); acceptButton = new JFXButton(i18n("button.ok"));
acceptButton.getStyleClass().add("dialog-accept"); acceptButton.getStyleClass().add("dialog-accept");
acceptPane.setContent(acceptButton); acceptPane.setContent(acceptButton);
@@ -69,7 +81,6 @@ public class InputDialogPane extends JFXDialogLayout implements DialogAware {
lblCreationWarning.setText(msg); lblCreationWarning.setText(msg);
}); });
}); });
textField.setOnAction(event -> acceptButton.fire()); textField.setOnAction(event -> acceptButton.fire());
onEscPressed(this, cancelButton::fire); onEscPressed(this, cancelButton::fire);
} }

View File

@@ -39,6 +39,7 @@ import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox; import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.TabHeader; import org.jackhuang.hmcl.ui.construct.TabHeader;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.ui.versions.DownloadListPage; import org.jackhuang.hmcl.ui.versions.DownloadListPage;
@@ -134,10 +135,6 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory(); Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory();
Controllers.prompt(i18n("archive.file.name"), (result, resolve, reject) -> { Controllers.prompt(i18n("archive.file.name"), (result, resolve, reject) -> {
if (!FileUtils.isNameValid(result)) {
reject.accept(i18n("install.new_game.malformed"));
return;
}
Path dest = runDirectory.resolve(subdirectoryName).resolve(result); Path dest = runDirectory.resolve(subdirectoryName).resolve(result);
Controllers.taskDialog(Task.composeAsync(() -> { Controllers.taskDialog(Task.composeAsync(() -> {
@@ -155,9 +152,8 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
Controllers.showToast(i18n("install.success")); Controllers.showToast(i18n("install.success"));
} }
}), i18n("message.downloading"), TaskCancellationAction.NORMAL); }), i18n("message.downloading"), TaskCancellationAction.NORMAL);
resolve.run(); resolve.run();
}, file.getFile().getFilename()); }, file.getFile().getFilename(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid));
} }

View File

@@ -29,11 +29,14 @@ import javafx.scene.shape.SVGPath;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.ModpackHelper; import org.jackhuang.hmcl.game.ModpackHelper;
import org.jackhuang.hmcl.mod.server.ServerModpackManifest; import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Schedulers;
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.SVG; import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem; import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.ui.construct.URLValidator;
import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage; import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.SettingsMap; import org.jackhuang.hmcl.util.SettingsMap;
@@ -161,7 +164,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
} catch (IOException e) { } catch (IOException e) {
reject.accept(e.getMessage()); reject.accept(e.getMessage());
} }
}); }, "", new URLValidator());
} }
public void onChooseRepository() { public void onChooseRepository() {

View File

@@ -26,7 +26,10 @@ import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.game.*; import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
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.account.CreateAccountPane; import org.jackhuang.hmcl.ui.account.CreateAccountPane;
@@ -50,8 +53,8 @@ import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class Versions { public final class Versions {
private Versions() { private Versions() {
@@ -124,10 +127,6 @@ public final class Versions {
public static CompletableFuture<String> renameVersion(Profile profile, String version) { public static CompletableFuture<String> renameVersion(Profile profile, String version) {
return Controllers.prompt(i18n("version.manage.rename.message"), (newName, resolve, reject) -> { return Controllers.prompt(i18n("version.manage.rename.message"), (newName, resolve, reject) -> {
if (!HMCLGameRepository.isValidVersionId(newName)) {
reject.accept(i18n("install.new_game.malformed"));
return;
}
if (profile.getRepository().renameVersion(version, newName)) { if (profile.getRepository().renameVersion(version, newName)) {
resolve.run(); resolve.run();
profile.getRepository().refreshVersionsAsync() profile.getRepository().refreshVersionsAsync()
@@ -139,7 +138,8 @@ public final class Versions {
} else { } else {
reject.accept(i18n("version.manage.rename.fail")); reject.accept(i18n("version.manage.rename.fail"));
} }
}, version); }, version, new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId),
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName)));
} }
public static void exportVersion(Profile profile, String version) { public static void exportVersion(Profile profile, String version) {
@@ -155,10 +155,6 @@ public final class Versions {
new PromptDialogPane.Builder(i18n("version.manage.duplicate.prompt"), (res, resolve, reject) -> { new PromptDialogPane.Builder(i18n("version.manage.duplicate.prompt"), (res, resolve, reject) -> {
String newVersionName = ((PromptDialogPane.Builder.StringQuestion) res.get(1)).getValue(); String newVersionName = ((PromptDialogPane.Builder.StringQuestion) res.get(1)).getValue();
boolean copySaves = ((PromptDialogPane.Builder.BooleanQuestion) res.get(2)).getValue(); boolean copySaves = ((PromptDialogPane.Builder.BooleanQuestion) res.get(2)).getValue();
if (!HMCLGameRepository.isValidVersionId(newVersionName)) {
reject.accept(i18n("install.new_game.malformed"));
return;
}
Task.runAsync(() -> profile.getRepository().duplicateVersion(version, newVersionName, copySaves)) Task.runAsync(() -> profile.getRepository().duplicateVersion(version, newVersionName, copySaves))
.thenComposeAsync(profile.getRepository().refreshVersionsAsync()) .thenComposeAsync(profile.getRepository().refreshVersionsAsync())
.whenComplete(Schedulers.javafx(), (result, exception) -> { .whenComplete(Schedulers.javafx(), (result, exception) -> {
@@ -174,6 +170,7 @@ public final class Versions {
}) })
.addQuestion(new PromptDialogPane.Builder.HintQuestion(i18n("version.manage.duplicate.confirm"))) .addQuestion(new PromptDialogPane.Builder.HintQuestion(i18n("version.manage.duplicate.confirm")))
.addQuestion(new PromptDialogPane.Builder.StringQuestion(null, version, .addQuestion(new PromptDialogPane.Builder.StringQuestion(null, version,
new Validator(i18n("install.new_game.malformed"), HMCLGameRepository::isValidVersionId),
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName)))) new Validator(i18n("install.new_game.already_exists"), newVersionName -> !profile.getRepository().versionIdConflicts(newVersionName))))
.addQuestion(new PromptDialogPane.Builder.BooleanQuestion(i18n("version.manage.duplicate.duplicate_save"), false))); .addQuestion(new PromptDialogPane.Builder.BooleanQuestion(i18n("version.manage.duplicate.duplicate_save"), false)));
} }

View File

@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.*; import org.jackhuang.hmcl.ui.*;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
@@ -141,7 +142,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
else else
reject.accept(i18n("world.import.failed", e.getClass().getName() + ": " + e.getLocalizedMessage())); reject.accept(i18n("world.import.failed", e.getClass().getName() + ": " + e.getLocalizedMessage()));
}).start(); }).start();
}, world.getWorldName()); }, world.getWorldName(), new Validator(i18n("install.new_game.malformed"), FileUtils::isNameValid));
}, e -> { }, e -> {
LOG.warning("Unable to parse world file " + zipFile, e); LOG.warning("Unable to parse world file " + zipFile, e);
Controllers.dialog(i18n("world.import.invalid")); Controllers.dialog(i18n("world.import.invalid"));