feat: WIP: FeedbackPage
This commit is contained in:
@@ -444,4 +444,34 @@ public final class SVG {
|
||||
"M13,19H14A1,1 0 0,1 15,20H22V22H15A1,1 0 0,1 14,23H10A1,1 0 0,1 9,22H2V20H9A1,1 0 0,1 10,19H11V17H4A1,1 0 0,1 3,16V12A1,1 0 0,1 4,11H20A1,1 0 0,1 21,12V16A1,1 0 0,1 20,17H13V19M4,3H20A1,1 0 0,1 21,4V8A1,1 0 0,1 20,9H4A1,1 0 0,1 3,8V4A1,1 0 0,1 4,3M9,7H10V5H9V7M9,15H10V13H9V15M5,5V7H7V5H5M5,13V15H7V13H5Z",
|
||||
fill, width, height);
|
||||
}
|
||||
|
||||
public static Node messageAlertOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath(
|
||||
"M13,10H11V6H13V10M13,12H11V14H13V12M22,4V16A2,2 0 0,1 20,18H6L2,22V4A2,2 0 0,1 4,2H20A2,2 0 0,1 22,4M20,4H4V17.2L5.2,16H20V4Z",
|
||||
fill, width, height);
|
||||
}
|
||||
|
||||
public static Node checkCircleOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath(
|
||||
"M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M12 20C7.59 20 4 16.41 4 12S7.59 4 12 4 20 7.59 20 12 16.41 20 12 20M16.59 7.58L10 14.17L7.41 11.59L6 13L10 17L18 9L16.59 7.58Z",
|
||||
fill, width, height);
|
||||
}
|
||||
|
||||
public static Node closeCircleOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath(
|
||||
"M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M14.59,8L12,10.59L9.41,8L8,9.41L10.59,12L8,14.59L9.41,16L12,13.41L14.59,16L16,14.59L13.41,12L16,9.41L14.59,8Z",
|
||||
fill, width, height);
|
||||
}
|
||||
|
||||
public static Node clockOutline(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath(
|
||||
"M12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22C6.47,22 2,17.5 2,12A10,10 0 0,1 12,2M12.5,7V12.25L17,14.92L16.25,16.15L11,13V7H12.5Z",
|
||||
fill, width, height);
|
||||
}
|
||||
|
||||
public static Node magnify(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath(
|
||||
"M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z",
|
||||
fill, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,14 +67,13 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
private boolean showMethodSwitcher;
|
||||
private AccountFactory<?> factory;
|
||||
|
||||
private Label lblErrorMessage;
|
||||
private JFXButton btnAccept;
|
||||
private SpinnerPane spinner;
|
||||
private JFXButton btnCancel;
|
||||
private Node body;
|
||||
private final Label lblErrorMessage;
|
||||
private final JFXButton btnAccept;
|
||||
private final SpinnerPane spinner;
|
||||
private final Node body;
|
||||
|
||||
private Node detailsPane; // AccountDetailsInputPane for Offline / Mojang / authlib-injector, Label for Microsoft
|
||||
private Pane detailsContainer;
|
||||
private final Pane detailsContainer;
|
||||
|
||||
private TaskExecutor loginTask;
|
||||
|
||||
@@ -109,7 +108,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
{
|
||||
lblErrorMessage = new Label();
|
||||
|
||||
btnAccept = new JFXButton(i18n("button.ok"));
|
||||
btnAccept = new JFXButton(i18n("account.login"));
|
||||
btnAccept.getStyleClass().add("dialog-accept");
|
||||
btnAccept.setOnAction(e -> onAccept());
|
||||
|
||||
@@ -117,7 +116,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
spinner.getStyleClass().add("small-spinner-pane");
|
||||
spinner.setContent(btnAccept);
|
||||
|
||||
btnCancel = new JFXButton(i18n("button.cancel"));
|
||||
JFXButton btnCancel = new JFXButton(i18n("button.cancel"));
|
||||
btnCancel.getStyleClass().add("dialog-cancel");
|
||||
btnCancel.setOnAction(e -> onCancel());
|
||||
onEscPressed(this, btnCancel::fire);
|
||||
|
||||
375
HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java
Normal file
375
HMCL/src/main/java/org/jackhuang/hmcl/ui/main/FeedbackPage.java
Normal file
@@ -0,0 +1,375 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 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.main;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
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.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class FeedbackPage extends VBox {
|
||||
private final ObjectProperty<HMCLAccount> account = new SimpleObjectProperty<>();
|
||||
private final ObservableList<FeedbackResponse> feedbacks = FXCollections.observableArrayList();
|
||||
private final SpinnerPane spinnerPane = new SpinnerPane();
|
||||
|
||||
public FeedbackPage() {
|
||||
setSpacing(10);
|
||||
setPadding(new Insets(10));
|
||||
|
||||
{
|
||||
HBox loginPane = new HBox(16);
|
||||
loginPane.getStyleClass().add("card");
|
||||
|
||||
TwoLineListItem accountInfo = new TwoLineListItem();
|
||||
accountInfo.titleProperty().bind(BindingMapping.of(account).map(account -> account == null ? i18n("account.not_logged_in") : account.getNickname()));
|
||||
accountInfo.subtitleProperty().bind(BindingMapping.of(account).map(account -> account == null ? i18n("account.not_logged_in") : account.getEmail()));
|
||||
|
||||
JFXButton logButton = new JFXButton();
|
||||
logButton.textProperty().bind(BindingMapping.of(account).map(account -> account == null ? i18n("account.login") : i18n("account.logout")));
|
||||
logButton.setOnAction(e -> log());
|
||||
|
||||
loginPane.getChildren().setAll(accountInfo, logButton);
|
||||
getChildren().add(loginPane);
|
||||
}
|
||||
|
||||
{
|
||||
HBox searchPane = new HBox(8);
|
||||
searchPane.getStyleClass().add("card");
|
||||
getChildren().add(searchPane);
|
||||
|
||||
JFXTextField searchField = new JFXTextField();
|
||||
searchField.setOnAction(e -> search(searchField.getText()));
|
||||
HBox.setHgrow(searchField, Priority.ALWAYS);
|
||||
searchField.setPromptText(i18n("search"));
|
||||
|
||||
JFXButton searchButton = new JFXButton();
|
||||
searchButton.getStyleClass().add("toggle-icon4");
|
||||
searchButton.setGraphic(SVG.magnify(Theme.blackFillBinding(), -1, -1));
|
||||
searchButton.setOnAction(e -> addFeedback());
|
||||
|
||||
searchPane.getChildren().setAll(searchField, searchButton);
|
||||
}
|
||||
|
||||
{
|
||||
spinnerPane.getStyleClass().add("card");
|
||||
VBox.setVgrow(spinnerPane, Priority.ALWAYS);
|
||||
JFXListView<FeedbackResponse> listView = new JFXListView<>();
|
||||
spinnerPane.setContent(listView);
|
||||
Bindings.bindContent(listView.getItems(), feedbacks);
|
||||
getChildren().add(spinnerPane);
|
||||
}
|
||||
}
|
||||
|
||||
private void search(String keyword) {
|
||||
Task.supplyAsync(() -> {
|
||||
return HttpRequest.GET("https://hmcl.huangyuhui.net/api/feedback", pair("s", keyword)).<List<FeedbackResponse>>getJson(new TypeToken<List<FeedbackResponse>>(){}.getType());
|
||||
}).whenComplete(Schedulers.defaultScheduler(), (result, exception) -> {
|
||||
spinnerPane.hideSpinner();
|
||||
if (exception != null) {
|
||||
spinnerPane.setFailedReason(i18n("feedback.failed"));
|
||||
} else {
|
||||
feedbacks.setAll(result);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void log() {
|
||||
if (account.get() == null) {
|
||||
// login
|
||||
Controllers.dialog(new LoginDialog());
|
||||
} else {
|
||||
// logout
|
||||
account.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void addFeedback() {
|
||||
if (account.get() == null) {
|
||||
Controllers.dialog(i18n("feedback.add.login"));
|
||||
return;
|
||||
}
|
||||
|
||||
Controllers.dialog(new AddFeedbackDialog());
|
||||
}
|
||||
|
||||
private static class HMCLAccount {
|
||||
private final String nickname;
|
||||
private final String email;
|
||||
private final String accessToken;
|
||||
|
||||
public HMCLAccount(String nickname, String email, String accessToken) {
|
||||
this.nickname = nickname;
|
||||
this.email = email;
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HMCLLoginResponse {
|
||||
private final int err;
|
||||
private final String nickname;
|
||||
private final String email;
|
||||
private final String accessToken;
|
||||
|
||||
public HMCLLoginResponse(int err, String nickname, String email, String accessToken) {
|
||||
this.err = err;
|
||||
this.nickname = nickname;
|
||||
this.email = email;
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public int getErr() {
|
||||
return err;
|
||||
}
|
||||
|
||||
public String getNickname() {
|
||||
return nickname;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public static final int ERR_OK = 0;
|
||||
public static final int ERR_WRONG = 400;
|
||||
}
|
||||
|
||||
private class LoginDialog extends JFXDialogLayout {
|
||||
private final SpinnerPane spinnerPane = new SpinnerPane();
|
||||
private final Label errorLabel = new Label();
|
||||
|
||||
public LoginDialog() {
|
||||
setHeading(new Label(i18n("feedback.login")));
|
||||
|
||||
GridPane body = new GridPane();
|
||||
ColumnConstraints fieldColumn = new ColumnConstraints();
|
||||
fieldColumn.setFillWidth(true);
|
||||
body.getColumnConstraints().setAll(new ColumnConstraints(), fieldColumn);
|
||||
body.setVgap(8);
|
||||
body.setHgap(8);
|
||||
setBody(body);
|
||||
|
||||
JFXTextField usernameField = new JFXTextField();
|
||||
usernameField.setValidators(new RequiredValidator());
|
||||
body.addRow(0, new Label(i18n("account.username")), usernameField);
|
||||
|
||||
JFXPasswordField passwordField = new JFXPasswordField();
|
||||
passwordField.setValidators(new RequiredValidator());
|
||||
body.addRow(1, new Label(i18n("account.password")), passwordField);
|
||||
|
||||
JFXButton registerButton = new JFXButton();
|
||||
registerButton.setText(i18n("account.register"));
|
||||
registerButton.setOnAction(e -> FXUtils.openLink("https://hmcl.huangyuhui.net/user/login"));
|
||||
|
||||
JFXButton loginButton = new JFXButton();
|
||||
spinnerPane.setContent(loginButton);
|
||||
loginButton.setText(i18n("account.login"));
|
||||
loginButton.setOnAction(e -> login(usernameField.getText(), passwordField.getText()));
|
||||
|
||||
JFXButton cancelButton = new JFXButton();
|
||||
cancelButton.setText(i18n("button.cancel"));
|
||||
cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
|
||||
onEscPressed(this, cancelButton::fire);
|
||||
|
||||
setActions(errorLabel, registerButton, spinnerPane, cancelButton);
|
||||
}
|
||||
|
||||
private void login(String username, String password) {
|
||||
spinnerPane.showSpinner();
|
||||
errorLabel.setText("");
|
||||
Task.supplyAsync(() -> {
|
||||
return HttpRequest.POST("https://hmcl.huangyuhui.net/api/user/login")
|
||||
.json(mapOf(
|
||||
pair("username", username),
|
||||
pair("password", password)
|
||||
)).getJson(HMCLLoginResponse.class);
|
||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception != null) {
|
||||
if (exception instanceof IOException) {
|
||||
if (exception instanceof ResponseCodeException && ((ResponseCodeException) exception).getResponseCode() == HttpURLConnection.HTTP_BAD_REQUEST) {
|
||||
errorLabel.setText(i18n("account.failed.invalid_password"));
|
||||
} else {
|
||||
errorLabel.setText(i18n("account.failed.connect_authentication_server"));
|
||||
}
|
||||
} else if (exception instanceof JsonParseException) {
|
||||
errorLabel.setText(i18n("account.failed.server_response_malformed"));
|
||||
} else {
|
||||
errorLabel.setText(exception.getClass().getName() + ": " + exception.getLocalizedMessage());
|
||||
}
|
||||
} else {
|
||||
if (result.err == HMCLLoginResponse.ERR_OK) {
|
||||
account.setValue(new HMCLAccount(result.getNickname(), result.getEmail(), result.getAccessToken()));
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else if (result.err == HMCLLoginResponse.ERR_WRONG) {
|
||||
errorLabel.setText(i18n("account.failed.invalid_password"));
|
||||
} else {
|
||||
errorLabel.setText(i18n("account.failed", result.err));
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
private static class AddFeedbackDialog extends JFXDialogLayout {
|
||||
|
||||
public AddFeedbackDialog() {
|
||||
setHeading(new Label(i18n("feedback.add")));
|
||||
|
||||
GridPane body = new GridPane();
|
||||
body.setVgap(8);
|
||||
body.setHgap(8);
|
||||
|
||||
HintPane searchHintPane = new HintPane(MessageDialogPane.MessageType.WARNING);
|
||||
GridPane.setColumnSpan(searchHintPane, 2);
|
||||
searchHintPane.setText(i18n("feedback.add.hint.search_before_add"));
|
||||
body.addRow(0, searchHintPane);
|
||||
|
||||
HintPane titleHintPane = new HintPane(MessageDialogPane.MessageType.INFORMATION);
|
||||
GridPane.setColumnSpan(titleHintPane, 2);
|
||||
titleHintPane.setText(i18n("feedback.add.hint.title"));
|
||||
body.addRow(1, titleHintPane);
|
||||
|
||||
JFXTextField titleField = new JFXTextField();
|
||||
titleField.setValidators(new RequiredValidator());
|
||||
body.addRow(2, new Label(i18n("feedback.title")), titleField);
|
||||
|
||||
JFXComboBox<FeedbackType> comboBox = new JFXComboBox<>();
|
||||
comboBox.setMaxWidth(-1);
|
||||
comboBox.getItems().setAll(FeedbackType.values());
|
||||
comboBox.getSelectionModel().select(0);
|
||||
comboBox.setConverter(stringConverter(e -> i18n("feedback.type." + e.name().toLowerCase())));
|
||||
body.addRow(3, new Label(i18n("feedback.type")), comboBox);
|
||||
|
||||
Label contentLabel = new Label(i18n("feedback.content"));
|
||||
GridPane.setColumnSpan(contentLabel, 2);
|
||||
body.addRow(4, contentLabel);
|
||||
|
||||
JFXTextArea contentArea = new JFXTextArea();
|
||||
contentArea.setValidators(new RequiredValidator());
|
||||
contentArea.setPromptText(i18n("feedback.add.hint.content"));
|
||||
GridPane.setColumnSpan(contentArea, 2);
|
||||
body.addRow(5, contentArea);
|
||||
|
||||
setBody(body);
|
||||
|
||||
JFXButton okButton = new JFXButton();
|
||||
okButton.setText(i18n("button.ok"));
|
||||
okButton.setOnAction(e -> addFeedback(titleField.getText(), comboBox.getSelectionModel().getSelectedItem(), contentArea.getText()));
|
||||
|
||||
JFXButton cancelButton = new JFXButton();
|
||||
cancelButton.setText(i18n("button.cancel"));
|
||||
cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
|
||||
onEscPressed(this, cancelButton::fire);
|
||||
|
||||
setActions(okButton, cancelButton);
|
||||
}
|
||||
|
||||
private void addFeedback(String title, FeedbackType feedbackType, String content) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
private static class FeedbackResponse {
|
||||
private final int id;
|
||||
private final String title;
|
||||
private final String content;
|
||||
private final String launcherVersion;
|
||||
private final String gameVersion;
|
||||
private final FeedbackType type;
|
||||
|
||||
public FeedbackResponse(int id, String title, String content, String launcherVersion, String gameVersion, FeedbackType type) {
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.launcherVersion = launcherVersion;
|
||||
this.gameVersion = gameVersion;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public String getLauncherVersion() {
|
||||
return launcherVersion;
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
public FeedbackType getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
private enum FeedbackType {
|
||||
FEATURE_REQUEST,
|
||||
BUG_REPORT
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage {
|
||||
private final TabHeader.Tab<DownloadSettingsPage> downloadTab = new TabHeader.Tab<>("downloadSettingsPage");
|
||||
private final TabHeader.Tab<HelpPage> helpTab = new TabHeader.Tab<>("helpPage");
|
||||
private final TabHeader.Tab<AboutPage> aboutTab = new TabHeader.Tab<>("aboutPage");
|
||||
private final TabHeader.Tab<FeedbackPage> feedbackTab = new TabHeader.Tab<>("feedbackPage");
|
||||
private final TabHeader.Tab<SponsorPage> sponsorTab = new TabHeader.Tab<>("sponsorPage");
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
|
||||
@@ -53,9 +54,10 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage {
|
||||
personalizationTab.setNodeSupplier(PersonalizationPage::new);
|
||||
downloadTab.setNodeSupplier(DownloadSettingsPage::new);
|
||||
helpTab.setNodeSupplier(HelpPage::new);
|
||||
feedbackTab.setNodeSupplier(FeedbackPage::new);
|
||||
sponsorTab.setNodeSupplier(SponsorPage::new);
|
||||
aboutTab.setNodeSupplier(AboutPage::new);
|
||||
tab = new TabHeader(gameTab, settingsTab, personalizationTab, downloadTab, helpTab, sponsorTab, aboutTab);
|
||||
tab = new TabHeader(gameTab, settingsTab, personalizationTab, downloadTab, helpTab, feedbackTab, sponsorTab, aboutTab);
|
||||
|
||||
tab.getSelectionModel().select(gameTab);
|
||||
gameTab.initializeIfNeeded();
|
||||
@@ -99,6 +101,12 @@ public class LauncherSettingsPage extends BorderPane implements DecoratorPage {
|
||||
helpItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(helpTab));
|
||||
helpItem.setOnAction(e -> tab.getSelectionModel().select(helpTab));
|
||||
})
|
||||
.addNavigationDrawerItem(feedbackItem -> {
|
||||
feedbackItem.setTitle(i18n("feedback"));
|
||||
feedbackItem.setLeftGraphic(wrap(SVG.messageAlertOutline(Theme.blackFillBinding(), 24, 24)));
|
||||
feedbackItem.activeProperty().bind(tab.getSelectionModel().selectedItemProperty().isEqualTo(feedbackTab));
|
||||
feedbackItem.setOnAction(e -> tab.getSelectionModel().select(feedbackTab));
|
||||
})
|
||||
.addNavigationDrawerItem(sponsorItem -> {
|
||||
sponsorItem.setTitle(i18n("sponsor"));
|
||||
sponsorItem.setLeftGraphic(wrap(SVG.handHearOutline(Theme.blackFillBinding(), 24, 24)));
|
||||
|
||||
@@ -43,6 +43,7 @@ account.character=character
|
||||
account.choose=Choose a character
|
||||
account.create=Create a new account
|
||||
account.email=Email
|
||||
account.failed=Error code: %d
|
||||
account.failed.character_deleted=The character has been deleted.
|
||||
account.failed.connect_authentication_server=Cannot connect to the authentication server. Check your internet connection.
|
||||
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
|
||||
@@ -61,6 +62,10 @@ account.injector.link.register=Register
|
||||
account.injector.server=Auth Server
|
||||
account.injector.server_url=Server URL
|
||||
account.injector.server_name=Server Name
|
||||
account.login=Login
|
||||
account.login.hint=We will not save your password.
|
||||
account.logout=Logout
|
||||
account.register=註冊
|
||||
account.manage=Account List
|
||||
account.methods=Login Type
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
@@ -77,6 +82,7 @@ account.methods.offline=Offline
|
||||
account.methods.yggdrasil=Mojang
|
||||
account.missing=No Account
|
||||
account.missing.add=Click here to add
|
||||
account.not_logged_in=Not logged in
|
||||
account.password=Password
|
||||
account.skin.file=Skin file
|
||||
account.skin.upload=Upload skin
|
||||
@@ -85,6 +91,7 @@ account.skin.invalid_skin=Unrecognized skin file
|
||||
account.username=Name
|
||||
|
||||
archive.author=Authors
|
||||
archive.date=Publish Date
|
||||
archive.game_version=Game
|
||||
archive.name=Nickname
|
||||
archive.version=Version
|
||||
@@ -245,6 +252,29 @@ fatal.config_loading_failure=The configuration is not accessible.\nPlease ensure
|
||||
fatal.migration_requires_manual_reboot=The update is complete. Please reopen Hello Minecraft! Launcher.
|
||||
fatal.apply_update_failure=We're sorry, Hello Minecraft! Launcher couldn't finish the upgrade because something went wrong.\nBut you can still manually finish the upgrade by downloading Hello Minecraft! Launcher from %s.\nPlease consider reporting this issue to us.
|
||||
|
||||
feedback=Feedback
|
||||
feedback.add=Add Feedback
|
||||
feedback.add.hint.search_before_add=Before adding new feedback, you should search the keyword to find out what you want to report has been already reported or not. If has, you can upvote it to increase its priorit
|
||||
feedback.add.hint.title=The title of the feedback should be able to summarize your needs concisely. Titles such as "I have a problem", "I have an idea", "Game won't open", etc. that cannot let others see the general problem at a glance are not acceptable.
|
||||
feedback.add.hint.content=The feedback content needs to express your needs completely and concisely. If you encounter a problem, you need to describe the recurrence path in detail, such as what button is clicked after opening the launcher, and what problem is triggered after what operation is done. If you want to add new features, you need to elaborate: why players need the feature, what problems the feature can solve, and how the feature can be implemented.
|
||||
feedback.add.login=You must login/register HMCL account to gain feedback permission.
|
||||
feedback.add.permission=You must gain feedback permission to add new feedback.
|
||||
feedback.author=Author
|
||||
feedback.content=Content
|
||||
feedback.failed=Failed to load
|
||||
feedback.like=Like
|
||||
feedback.login=Log in to HMCL account
|
||||
feedback.response=Response
|
||||
feedback.state.accepted=Accepted
|
||||
feedback.state.pending=Pending
|
||||
feedback.state.rejected=Rejected
|
||||
feedback.title=Title
|
||||
feedback.type=Type
|
||||
feedback.type.bug_report=Bug Report
|
||||
feedback.type.feature_request=Feature Request
|
||||
feedback.unlike=Unlike
|
||||
feedback.version=Launcher Version
|
||||
|
||||
file=File
|
||||
|
||||
folder.config=Configs
|
||||
|
||||
@@ -36,6 +36,7 @@ account.character=角色
|
||||
account.choose=請選擇角色
|
||||
account.create=建立帳戶
|
||||
account.email=電子信箱
|
||||
account.failed=錯誤碼: %d
|
||||
account.failed.character_deleted=已刪除此角色
|
||||
account.failed.connect_authentication_server=無法連線至認證伺服器,可能是網路問題
|
||||
account.failed.connect_injector_server=無法連線至認證伺服器,可能是網路故障或網址輸入錯誤
|
||||
@@ -54,6 +55,10 @@ account.injector.link.register=註冊
|
||||
account.injector.server=認證伺服器
|
||||
account.injector.server_url=伺服器位址
|
||||
account.injector.server_name=伺服器名稱
|
||||
account.login=登錄
|
||||
account.login.hint=我們不會保存你的密碼
|
||||
account.logout=登出
|
||||
account.register=註冊
|
||||
account.manage=帳戶列表
|
||||
account.methods=登入方式
|
||||
account.methods.authlib_injector=authlib-injector 登入
|
||||
@@ -71,6 +76,7 @@ account.methods.offline=離線模式
|
||||
account.methods.yggdrasil=正版登入
|
||||
account.missing=沒有遊戲帳戶
|
||||
account.missing.add=按一下此處加入帳戶
|
||||
account.not_logged_in=未登錄
|
||||
account.password=密碼
|
||||
account.skin.file=皮膚圖片檔案
|
||||
account.skin.upload=上傳皮膚
|
||||
@@ -79,6 +85,7 @@ account.skin.invalid_skin=無法識別的皮膚文件
|
||||
account.username=使用者名稱
|
||||
|
||||
archive.author=作者
|
||||
archive.date=發布日期
|
||||
archive.game_version=遊戲版本
|
||||
archive.name=名稱
|
||||
archive.version=版本
|
||||
@@ -235,6 +242,28 @@ fatal.config_loading_failure=Hello Minecraft! Launcher 無法載入設定檔案
|
||||
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即將升級完成,請重新開啟 Hello Minecraft! Launcher。
|
||||
fatal.apply_update_failure=我們很抱歉 Hello Minecraft! Launcher 無法自動完成升級程序,因為出現了一些問題。\n但你依然可以從 %s 處手動下載 Hello Minecraft! Launcher 來完成升級。\n請考慮向我們回報該問題。
|
||||
|
||||
feedback=回饋
|
||||
feedback.add=新增回饋
|
||||
feedback.add.hint.search_before_add=添加回饋前,請先搜索已有回饋中是否已經有人提出過相關內容,如果有,你可以透過給對應回饋按讚來提升對應回饋的優先度。
|
||||
feedback.add.hint.title=回饋標題需能簡練概括你的需求。"我有問題"、"我有一個想法"、"遊戲打不開" 等無法讓其他人一眼看出大致問題的標題是不被接受的。
|
||||
feedback.add.hint.content=回饋內容需完整且簡練地表達你的需求。如果你遇到了問題,你需要詳細描述復現路徑,比如在打開啟動器後通過點擊什麼按鈕,做了什麼操作後觸發了什麼問題。如果你希望添加新功能,你需要闡述:為什麼玩家需要該功能,該功能能解決什麼問題,該功能可以怎麼實現。
|
||||
feedback.add.login=你需要先登錄/註冊 HMCL 回饋帳號並獲得回饋權限才能添加回饋。
|
||||
feedback.add.permission=你需要獲得回饋權限才能添加回饋。
|
||||
feedback.author=發布者
|
||||
feedback.content=正文
|
||||
feedback.like=贊成
|
||||
feedback.login=登錄 HMCL 帳號
|
||||
feedback.response=回復
|
||||
feedback.state.accepted=接受
|
||||
feedback.state.pending=審核中
|
||||
feedback.state.rejected=拒絕
|
||||
feedback.title=標題
|
||||
feedback.type=類型
|
||||
feedback.type.bug_report=問題回饋
|
||||
feedback.type.feature_request=新功能請求
|
||||
feedback.unlike=反對
|
||||
feedback.version=啟動器版本
|
||||
|
||||
file=檔案
|
||||
|
||||
folder.config=設定資料夾
|
||||
|
||||
@@ -48,6 +48,7 @@ account.create.yggdrasil=添加 Mojang 账户
|
||||
account.create.offline=添加离线模式账户
|
||||
account.create.authlibInjector=添加外置登录账户 (authlib-injector)
|
||||
account.email=邮箱
|
||||
account.failed=错误码: %d
|
||||
account.failed.character_deleted=此角色已被删除
|
||||
account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题
|
||||
account.failed.connect_injector_server=无法连接认证服务器,可能是网络故障或 URL 输入错误
|
||||
@@ -64,6 +65,10 @@ account.injector.link.register=注册
|
||||
account.injector.server=认证服务器
|
||||
account.injector.server_url=服务器地址
|
||||
account.injector.server_name=服务器名称
|
||||
account.login=登录
|
||||
account.login.hint=我们不会保存你的密码
|
||||
account.logout=登出
|
||||
account.register=注册
|
||||
account.manage=账户列表
|
||||
account.methods=登录方式
|
||||
account.methods.authlib_injector=外置登录 (authlib-injector)
|
||||
@@ -81,6 +86,7 @@ account.methods.offline=离线模式
|
||||
account.methods.yggdrasil=Mojang 账号
|
||||
account.missing=没有游戏账户
|
||||
account.missing.add=点击此处添加账户
|
||||
account.not_logged_in=未登录
|
||||
account.password=密码
|
||||
account.skin.file=皮肤图片文件
|
||||
account.skin.upload=上传皮肤
|
||||
@@ -89,6 +95,7 @@ account.skin.invalid_skin=无法识别的皮肤文件
|
||||
account.username=用户名
|
||||
|
||||
archive.author=作者
|
||||
archive.date=发布日期
|
||||
archive.game_version=游戏版本
|
||||
archive.name=名称
|
||||
archive.version=版本
|
||||
@@ -252,6 +259,29 @@ fatal.config_loading_failure=Hello Minecraft! Launcher 无法加载配置文件
|
||||
fatal.migration_requires_manual_reboot=Hello Minecraft! Launcher 即将完成升级,请重新打开 Hello Minecraft! Launcher。
|
||||
fatal.apply_update_failure=我们很抱歉 Hello Minecraft! Launcher 无法自动完成升级,因为出现了一些问题。\n但你依然可以从 %s 处手动下载 Hello Minecraft! Launcher 来完成升级。\n请考虑向我们反馈该问题。
|
||||
|
||||
feedback=反馈
|
||||
feedback.add=新增反馈
|
||||
feedback.add.hint.search_before_add=添加反馈前,请先搜索已有反馈中是否已经有人提出过相关内容,如果有,你可以通过给对应反馈点赞来提升对应反馈的优先级。
|
||||
feedback.add.hint.title=反馈标题需能简练概括你的需求。带有 "我有问题"、"我有一个想法"、"游戏打不开" 等无法让其他人一眼看出大致问题的标题的反馈将会被直接关闭。
|
||||
feedback.add.hint.content=反馈内容需完整且简练地表达你的需求。如果你遇到了问题,你需要详细描述复现路径,比如在打开启动器后通过点击什么按钮,做了什么操作后触发了什么问题。如果你希望添加新功能,你需要阐述:为什么玩家需要该功能,该功能能解决什么问题,该功能可以怎么实现。
|
||||
feedback.add.login=你需要先登录/注册 HMCL 反馈账号并获得反馈权限才能添加反馈。
|
||||
feedback.add.permission=你需要获得反馈权限才能添加反馈。
|
||||
feedback.author=发布者
|
||||
feedback.content=正文
|
||||
feedback.failed=載入失敗
|
||||
feedback.like=赞成
|
||||
feedback.login=登录 HMCL 账号
|
||||
feedback.response=回复
|
||||
feedback.state.accepted=接受
|
||||
feedback.state.pending=审核中
|
||||
feedback.state.rejected=拒绝
|
||||
feedback.title=标题
|
||||
feedback.type=类型
|
||||
feedback.type.bug_report=问题反馈
|
||||
feedback.type.feature_request=新功能请求
|
||||
feedback.unlike=反对
|
||||
feedback.version=启动器版本
|
||||
|
||||
file=文件
|
||||
|
||||
folder.config=配置文件夹
|
||||
@@ -261,6 +291,9 @@ folder.resourcepacks=资源包文件夹
|
||||
folder.saves=存档文件夹
|
||||
folder.screenshots=截图文件夹
|
||||
|
||||
game=游戏
|
||||
game.version=游戏版本
|
||||
|
||||
help=帮助
|
||||
help.doc=Hello Minecraft! Launcher 帮助文档
|
||||
help.detail=可查阅数据包、整合包制作指南等内容。
|
||||
|
||||
@@ -151,6 +151,10 @@ public abstract class HttpRequest {
|
||||
|
||||
if (responseCodeTester != null) {
|
||||
responseCodeTester.accept(new URL(url), con.getResponseCode());
|
||||
} else {
|
||||
if (con.getResponseCode() / 100 != 2) {
|
||||
throw new ResponseCodeException(new URL(url), con.getResponseCode());
|
||||
}
|
||||
}
|
||||
|
||||
try (OutputStream os = con.getOutputStream()) {
|
||||
|
||||
Reference in New Issue
Block a user