Restrict launcher to bundled server modpack installs
Mirror Repository / Mirror to CNB (push) Skipped
Mirror Repository / Mirror to Gitee (push) Skipped
Check Codes / build (push) Failing after 5s
Check Codes / build (pull_request) Failing after 53s
Java CI / build (push) Failing after 2m30s
PR Size Label / label (pull_request_target) Failing after 15s
Java CI / build (pull_request) Failing after 2m34s
Mirror Repository / Mirror to CNB (push) Skipped
Mirror Repository / Mirror to Gitee (push) Skipped
Check Codes / build (push) Failing after 5s
Check Codes / build (pull_request) Failing after 53s
Java CI / build (push) Failing after 2m30s
PR Size Label / label (pull_request_target) Failing after 15s
Java CI / build (pull_request) Failing after 2m34s
Replace free game download with bundled modpack install, drop Help/Feedback and upstream About links, empty the default authlib list, and remove offline account creation while keeping Microsoft and custom authlib logins.
This commit is contained in:
1 parent
f4893d9f75
commit
1e45711006
30 files changed
+103
-442
No files matched your search
@@ -53,7 +53,10 @@ public final class Accounts {
|
||||
public static final OfflineAccountFactory FACTORY_OFFLINE = new OfflineAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER);
|
||||
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(AUTHLIB_INJECTOR_DOWNLOADER, Accounts::getOrCreateAuthlibInjectorServer);
|
||||
public static final MicrosoftAccountFactory FACTORY_MICROSOFT = new MicrosoftAccountFactory(new MicrosoftService(OAUTH_CALLBACK));
|
||||
public static final List<AccountFactory<?>> FACTORIES = List.of(FACTORY_OFFLINE, FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR);
|
||||
/// Account factories offered when creating a new account from the UI.
|
||||
///
|
||||
/// Offline login remains loadable for existing accounts, but is no longer offered for creation.
|
||||
public static final List<AccountFactory<?>> FACTORIES = List.of(FACTORY_MICROSOFT, FACTORY_AUTHLIB_INJECTOR);
|
||||
|
||||
// ==== login type / account factory mapping ====
|
||||
private static final Map<String, AccountFactory<?>> type2factory = new HashMap<>();
|
||||
|
||||
@@ -26,9 +26,8 @@ import java.util.Objects;
|
||||
@NotNullByDefault
|
||||
@JsonSerializable
|
||||
public final class AuthlibInjectorServerList extends ObservableSetting implements JsonSchemaSetting {
|
||||
/// The LittleSkin Yggdrasil API endpoint bundled into newly created server lists.
|
||||
/// The LittleSkin Yggdrasil API endpoint used only when recognizing an existing entry.
|
||||
public static final String LITTLE_SKIN_URL = "https://littleskin.cn/api/yggdrasil/";
|
||||
|
||||
/// The JSON schema supported by this authlib-injector server list.
|
||||
public static final JsonSchema CURRENT_SCHEMA =
|
||||
new JsonSchema("authlib-injector-servers", new JsonSchema.Version(1, 0, 0));
|
||||
@@ -40,17 +39,10 @@ public final class AuthlibInjectorServerList extends ObservableSetting implement
|
||||
}
|
||||
|
||||
/// Creates the default authlib-injector server list for a newly created file.
|
||||
///
|
||||
/// The default list is empty; operators add their own authentication server when needed.
|
||||
public static AuthlibInjectorServerList createDefault() {
|
||||
AuthlibInjectorServerList result = new AuthlibInjectorServerList();
|
||||
result.addLittleSkinIfAbsent();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Adds the bundled LittleSkin server when it is not already present.
|
||||
public void addLittleSkinIfAbsent() {
|
||||
if (getServers().stream().noneMatch(server -> LITTLE_SKIN_URL.equals(server.getUrl()))) {
|
||||
getServers().add(new AuthlibInjectorServer(LITTLE_SKIN_URL));
|
||||
}
|
||||
return new AuthlibInjectorServerList();
|
||||
}
|
||||
|
||||
/// The schema used by this authlib-injector server list file.
|
||||
|
||||
@@ -425,18 +425,12 @@ public final class LegacyConfigMigrator {
|
||||
if (authlibInjectorServers != null) {
|
||||
servers.add("servers", authlibInjectorServers);
|
||||
}
|
||||
JsonElement addedLittleSkin = json.remove("addedLittleSkin");
|
||||
boolean shouldAddLittleSkin = !(addedLittleSkin instanceof JsonPrimitive primitive
|
||||
&& primitive.isBoolean()
|
||||
&& primitive.getAsBoolean());
|
||||
json.remove("addedLittleSkin");
|
||||
|
||||
AuthlibInjectorServerList result = JsonUtils.GSON.fromJson(servers, AuthlibInjectorServerList.class);
|
||||
if (result == null) {
|
||||
result = new AuthlibInjectorServerList();
|
||||
}
|
||||
if (shouldAddLittleSkin) {
|
||||
result.addLittleSkinIfAbsent();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.decorator.Decorator;
|
||||
import org.jackhuang.hmcl.ui.download.DownloadPage;
|
||||
import org.jackhuang.hmcl.ui.main.LauncherSettingsPage;
|
||||
import org.jackhuang.hmcl.ui.main.RootPage;
|
||||
import org.jackhuang.hmcl.ui.instances.GameListPage;
|
||||
@@ -223,26 +222,6 @@ public final class Controllers {
|
||||
return accountListPage;
|
||||
}
|
||||
|
||||
/// The cached download page.
|
||||
private static @Nullable DownloadPage downloadPage;
|
||||
|
||||
/// Creates and attaches the download page for a layout pulse unless already cached.
|
||||
@FXThread
|
||||
public static void prepareDownloadPage() {
|
||||
if (downloadPage == null) {
|
||||
LOG.info("Preparing download page");
|
||||
downloadPage = getDecorator().prewarmPage(new DownloadPage());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the cached download page, creating it if necessary.
|
||||
@FXThread
|
||||
public static DownloadPage getDownloadPage() {
|
||||
if (downloadPage == null) {
|
||||
downloadPage = new DownloadPage();
|
||||
}
|
||||
return downloadPage;
|
||||
}
|
||||
|
||||
|
||||
/// Returns the initialized main-window decorator.
|
||||
@@ -711,10 +690,6 @@ public final class Controllers {
|
||||
public static void onHyperlinkAction(String href) {
|
||||
if (href.startsWith("hmcl://")) {
|
||||
switch (href) {
|
||||
case "hmcl://settings/feedback":
|
||||
Controllers.getSettingsPage().showFeedback();
|
||||
Controllers.navigate(Controllers.getSettingsPage());
|
||||
break;
|
||||
case "hmcl://game/launch":
|
||||
var repository = GameDirectoryManager.getSelectedRepository();
|
||||
Instances.launch(repository.getSelectedInstance(), LauncherHelper::setKeep);
|
||||
@@ -758,7 +733,6 @@ public final class Controllers {
|
||||
rootPage = null;
|
||||
gameInstancePage = null;
|
||||
gameListPage = null;
|
||||
downloadPage = null;
|
||||
accountListPage = null;
|
||||
settingsPage = null;
|
||||
decorator = null;
|
||||
|
||||
@@ -99,25 +99,6 @@ public final class AccountListPage extends DecoratorAnimatedPage implements Deco
|
||||
.startCategory(i18n("account.create").toUpperCase(Locale.ROOT))
|
||||
.add(microsoftItem);
|
||||
|
||||
AdvancedListItem offlineItem = new AdvancedListItem();
|
||||
offlineItem.getStyleClass().add("navigation-drawer-item");
|
||||
offlineItem.setTitle(i18n("account.methods.offline"));
|
||||
offlineItem.setLeftIcon(SVG.PERSON);
|
||||
if (restricted) {
|
||||
offlineItem.setActionDisabled(true);
|
||||
FXUtils.installFastTooltip(offlineItem, i18n("account.login.restricted"));
|
||||
} else {
|
||||
offlineItem.setOnAction(e -> {
|
||||
if (SettingsManager.isUserGameAccountsReadOnly()) {
|
||||
confirmOverwriteUserAccounts(() -> Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE)));
|
||||
} else {
|
||||
Controllers.dialog(new CreateAccountPane(Accounts.FACTORY_OFFLINE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getSidebar().add(offlineItem);
|
||||
|
||||
for (AuthlibInjectorServer server : authServers) {
|
||||
AdvancedListItem item = new AdvancedListItem();
|
||||
item.getStyleClass().add("navigation-drawer-item");
|
||||
|
||||
@@ -89,8 +89,11 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
String preferred = settings().preferredLoginTypeProperty().get();
|
||||
try {
|
||||
factory = Accounts.getAccountFactory(preferred);
|
||||
if (factory == Accounts.FACTORY_OFFLINE) {
|
||||
factory = Accounts.FACTORY_MICROSOFT;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
factory = Accounts.FACTORY_OFFLINE;
|
||||
factory = Accounts.FACTORY_MICROSOFT;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -155,7 +155,6 @@ public class GameInstancePage extends DecoratorAnimatedPage implements Decorator
|
||||
new IconedMenuItem(SVG.EDIT, i18n("instance.manage.rename"), this::rename, managementPopup),
|
||||
new IconedMenuItem(SVG.FOLDER_COPY, i18n("instance.manage.duplicate"), this::duplicate, managementPopup),
|
||||
new IconedMenuItem(SVG.DELETE, i18n("instance.manage.remove"), this::remove, managementPopup),
|
||||
new IconedMenuItem(SVG.OUTPUT, i18n("modpack.export"), this::export, managementPopup),
|
||||
new MenuSeparator(),
|
||||
// Due to the sidebar height limit, we temporarily place the entries for these tabs
|
||||
// in the management menu; they will be moved back to the sidebar after a future UI refactor.
|
||||
@@ -350,13 +349,6 @@ public class GameInstancePage extends DecoratorAnimatedPage implements Decorator
|
||||
}
|
||||
}
|
||||
|
||||
private void export() {
|
||||
HMCLGameInstance gameInstance = requireGameInstance();
|
||||
if (gameInstance != null) {
|
||||
Instances.exportInstance(gameInstance);
|
||||
}
|
||||
}
|
||||
|
||||
private void rename() {
|
||||
HMCLGameInstance gameInstance = requireGameInstance();
|
||||
if (gameInstance != null) {
|
||||
|
||||
@@ -184,7 +184,6 @@ public final class GameListCell extends ListCell<GameListItem> {
|
||||
new IconedMenuItem(SVG.EDIT, i18n("instance.manage.rename"), item::rename, popup),
|
||||
new IconedMenuItem(SVG.FOLDER_COPY, i18n("instance.manage.duplicate"), item::duplicate, popup),
|
||||
new IconedMenuItem(SVG.DELETE, i18n("instance.manage.remove"), item::remove, popup),
|
||||
new IconedMenuItem(SVG.OUTPUT, i18n("modpack.export"), item::export, popup),
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(SVG.FOLDER_OPEN, i18n("folder.game"), item::browse, popup));
|
||||
return popup;
|
||||
|
||||
@@ -47,10 +47,6 @@ public class GameListItem extends GameItem {
|
||||
Instances.deleteInstance(gameInstance);
|
||||
}
|
||||
|
||||
public void export() {
|
||||
Instances.exportInstance(gameInstance);
|
||||
}
|
||||
|
||||
public void browse() {
|
||||
Instances.openFolder(gameInstance);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
GameDirectoryManager.registerVersionsListener(this::loadVersions);
|
||||
|
||||
setOnFailedAction(e -> Instances.addNewGame());
|
||||
setOnFailedAction(e -> Instances.installServerModpack());
|
||||
}
|
||||
|
||||
@FXThread
|
||||
@@ -232,7 +232,7 @@ public class GameListPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
|
||||
searchBar.getChildren().setAll(searchField, closeSearchBar);
|
||||
|
||||
toolbarNormal.getChildren().setAll(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refreshList), createToolbarButton2(i18n("install.new_game"), SVG.DOWNLOAD, Instances::addNewGame), createToolbarButton2(i18n("install.modpack"), SVG.PACKAGE2, Instances::importModpack), createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar)));
|
||||
toolbarNormal.getChildren().setAll(createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refreshList), createToolbarButton2(i18n("install.modpack"), SVG.PACKAGE2, Instances::installServerModpack), createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar)));
|
||||
|
||||
toolbarPane.setContent(toolbarNormal, ContainerAnimations.FADE);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ package org.jackhuang.hmcl.ui.instances;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.GameComponentAnalyzer;
|
||||
import org.jackhuang.hmcl.game.GameComponentType;
|
||||
import org.jackhuang.hmcl.game.HMCLGameInstance;
|
||||
@@ -13,12 +12,8 @@ import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -32,10 +27,6 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
/// @param instanceContext the parent page's instance property
|
||||
public InstallerListPage(ObservableValue<? extends HMCLGameInstance.Optional> instanceContext) {
|
||||
Objects.requireNonNull(instanceContext, "instanceContext");
|
||||
FXUtils.applyDragListener(this, it -> Set.of("jar", "exe").contains(FileUtils.getExtension(it)), mods -> {
|
||||
if (!mods.isEmpty())
|
||||
doInstallOffline(mods.get(0));
|
||||
});
|
||||
|
||||
listenerHolder.add(FXUtils.onWeakChangeAndOperate(instanceContext, current -> {
|
||||
if (current != null) {
|
||||
@@ -87,10 +78,6 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
component.versionProperty().set(null);
|
||||
}
|
||||
|
||||
component.setOnInstall(() -> {
|
||||
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(gameInstance, component.getComponentType(), libraryVersion));
|
||||
});
|
||||
|
||||
component.setOnRemove(() -> repository.getDependency().updateInstanceAsync(
|
||||
gameInstance.getId(),
|
||||
publishedInstance -> repository.getDependency().removeComponentAsync(
|
||||
@@ -129,33 +116,6 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
}
|
||||
}
|
||||
|
||||
public void installOffline() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.modloader.installer"), "*.jar", "*.exe"));
|
||||
Path file = Controllers.showOpenDialog(chooser);
|
||||
if (file != null) doInstallOffline(file);
|
||||
}
|
||||
|
||||
/// Runs a local component installer for the current modifiable instance and displays its result.
|
||||
private void doInstallOffline(Path file) {
|
||||
if (gameInstance == null || !gameInstance.getManifest().isModifiable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
HMCLGameRepository repository = gameInstance.getRepository();
|
||||
Task<?> task = repository.getDependency().updateInstanceAsync(
|
||||
gameInstance.getId(),
|
||||
publishedInstance -> repository.getDependency().installComponentLocalAsync(publishedInstance, file));
|
||||
task.setName(i18n("install.installer.install_offline"));
|
||||
Controllers.taskDialog(task.whenComplete(Schedulers.javafx(), (@Nullable Exception exception) -> {
|
||||
if (exception == null) {
|
||||
reloadCurrentInstance();
|
||||
Controllers.dialog(i18n("install.success"));
|
||||
} else {
|
||||
UpdateInstallerWizardProvider.alertFailureMessage(exception, null);
|
||||
}
|
||||
}), i18n("install.installer.install_offline"), TaskCancellationAction.NO_CANCEL);
|
||||
}
|
||||
|
||||
private class InstallerListPageSkin extends ToolbarListPageSkin<InstallerItem, InstallerListPage> {
|
||||
|
||||
@@ -165,9 +125,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> {
|
||||
|
||||
@Override
|
||||
protected List<Node> initializeToolbar(InstallerListPage skinnable) {
|
||||
return Collections.singletonList(
|
||||
createToolbarButton2(i18n("install.installer.install_offline"), SVG.ADD, skinnable::installOffline)
|
||||
);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ package org.jackhuang.hmcl.ui.instances;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
@@ -23,7 +24,6 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
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.export.ExportWizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
@@ -46,9 +46,24 @@ public final class Instances {
|
||||
private Instances() {
|
||||
}
|
||||
|
||||
public static void addNewGame() {
|
||||
Controllers.getDownloadPage().showGameDownloads();
|
||||
Controllers.navigate(Controllers.getDownloadPage());
|
||||
/// Installs or updates the server client from the launcher-bundled modpack when present.
|
||||
///
|
||||
/// Falls back to the local/remote modpack import wizard when no bundled package is found.
|
||||
public static void installServerModpack() {
|
||||
HMCLGameRepository repository = GameDirectoryManager.getSelectedRepository();
|
||||
if (!repository.isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path bundled = Metadata.findBundledModpackFile();
|
||||
if (bundled != null) {
|
||||
Controllers.getDecorator().startWizard(
|
||||
new ModpackInstallWizardProvider(repository, bundled),
|
||||
i18n("install.modpack"));
|
||||
return;
|
||||
}
|
||||
|
||||
importModpack();
|
||||
}
|
||||
|
||||
public static void importModpack() {
|
||||
@@ -58,6 +73,11 @@ public final class Instances {
|
||||
}
|
||||
}
|
||||
|
||||
/// @deprecated use [#installServerModpack()]
|
||||
public static void addNewGame() {
|
||||
installServerModpack();
|
||||
}
|
||||
|
||||
|
||||
public static void deleteInstance(HMCLGameInstance gameInstance) {
|
||||
HMCLGameRepository repository = gameInstance.getRepository();
|
||||
@@ -107,9 +127,6 @@ public final class Instances {
|
||||
new Validator(i18n("install.new_game.already_exists"), newVersionName -> !repository.instanceIdConflicts(newVersionName) || newVersionName.equals(instanceId.toString())));
|
||||
}
|
||||
|
||||
public static void exportInstance(HMCLGameInstance gameInstance) {
|
||||
Controllers.getDecorator().startWizard(new ExportWizardProvider(gameInstance), i18n("modpack.wizard"));
|
||||
}
|
||||
|
||||
public static void openFolder(HMCLGameInstance gameInstance) {
|
||||
FXUtils.openFolder(gameInstance.getRunDirectory());
|
||||
@@ -294,13 +311,13 @@ public final class Instances {
|
||||
@SafeVarargs
|
||||
public static void launch(@Nullable HMCLGameInstance gameInstance, Consumer<LauncherHelper>... injecters) {
|
||||
if (gameInstance == null) {
|
||||
JFXButton gotoDownload = new JFXButton(i18n("instance.empty.launch.goto_download"));
|
||||
gotoDownload.getStyleClass().add("dialog-accept");
|
||||
gotoDownload.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
||||
JFXButton installModpack = new JFXButton(i18n("install.modpack"));
|
||||
installModpack.getStyleClass().add("dialog-accept");
|
||||
installModpack.setOnAction(e -> installServerModpack());
|
||||
|
||||
Controllers.confirmAction(i18n("instance.empty.launch"), i18n("launch.failed"),
|
||||
MessageDialogPane.MessageType.ERROR,
|
||||
gotoDownload,
|
||||
installModpack,
|
||||
null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,13 +187,6 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownload() {
|
||||
if (gameInstance == null) {
|
||||
return;
|
||||
}
|
||||
Controllers.getDownloadPage().showResourcePackDownloads().selectInstance(gameInstance.getId());
|
||||
Controllers.navigate(Controllers.getDownloadPage());
|
||||
}
|
||||
|
||||
private void onOpenFolder() {
|
||||
if (resourcePackDirectory != null) {
|
||||
@@ -371,7 +364,6 @@ public final class ResourcePackListPage extends ListPageBase<ResourcePackListPag
|
||||
createToolbarButton2(i18n("addon.check_update.button"), SVG.UPDATE, () ->
|
||||
control.checkUpdates(listView.getItems().stream().map(ResourcePackInfoObject::getFile).toList())
|
||||
),
|
||||
createToolbarButton2(i18n("resourcepack.download"), SVG.DOWNLOAD, control::onDownload),
|
||||
createToolbarButton2(i18n("search"), SVG.SEARCH, () -> changeToolbar(searchBar))
|
||||
);
|
||||
|
||||
|
||||
@@ -52,10 +52,10 @@ public final class AboutPage extends SpinnerPane {
|
||||
|
||||
ComponentList about = new ComponentList();
|
||||
{
|
||||
var launcher = LineButton.createExternalLinkButton(Metadata.REPOSITORY_URL);
|
||||
var launcher = new LinePane();
|
||||
launcher.setLargeTitle(true);
|
||||
launcher.setLeading(FXUtils.newBuiltinImage("/assets/img/icon.png"));
|
||||
launcher.setTitle("Hello Minecraft! Launcher");
|
||||
launcher.setTitle(Metadata.FULL_NAME);
|
||||
launcher.setSubtitle(Metadata.VERSION);
|
||||
|
||||
about.getContent().setAll(launcher);
|
||||
@@ -229,17 +229,7 @@ public final class AboutPage extends SpinnerPane {
|
||||
copyright.setTitle(i18n("about.copyright"));
|
||||
copyright.setSubtitle(i18n("about.copyright.statement"));
|
||||
|
||||
var claim = LineButton.createExternalLinkButton(Metadata.EULA_URL);
|
||||
claim.setLargeTitle(true);
|
||||
claim.setTitle(i18n("about.claim"));
|
||||
claim.setSubtitle(i18n("about.claim.statement"));
|
||||
|
||||
var openSource = LineButton.createExternalLinkButton("https://github.com/HMCL-dev/HMCL");
|
||||
openSource.setLargeTitle(true);
|
||||
openSource.setTitle(i18n("about.open_source"));
|
||||
openSource.setSubtitle(i18n("about.open_source.statement"));
|
||||
|
||||
legal.getContent().setAll(copyright, claim, openSource);
|
||||
legal.getContent().setAll(copyright);
|
||||
}
|
||||
|
||||
content.getChildren().setAll(
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2021 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.LineButton;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class FeedbackPage extends SpinnerPane {
|
||||
|
||||
private final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
|
||||
public FeedbackPage() {
|
||||
VBox content = new VBox();
|
||||
content.getStyleClass().add("spinner-pane-content");
|
||||
ScrollPane scrollPane = new ScrollPane(content);
|
||||
scrollPane.setFitToWidth(true);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
setContent(scrollPane);
|
||||
|
||||
ComponentList feedback = new ComponentList();
|
||||
{
|
||||
var github = LineButton.createExternalLinkButton("https://github.com/HMCL-dev/HMCL/issues/new/choose");
|
||||
github.setLargeTitle(true);
|
||||
github.setTitle(i18n("contact.feedback.github"));
|
||||
github.setSubtitle(i18n("contact.feedback.github.statement"));
|
||||
|
||||
holder.add(FXUtils.onWeakChangeAndOperate(Themes.darkModeProperty(), darkMode -> {
|
||||
github.setLeading(darkMode
|
||||
? FXUtils.newBuiltinImage("/assets/img/github-white.png")
|
||||
: FXUtils.newBuiltinImage("/assets/img/github.png"));
|
||||
}));
|
||||
|
||||
feedback.getContent().setAll(github);
|
||||
}
|
||||
|
||||
content.getChildren().addAll(
|
||||
ComponentList.createComponentListTitle(i18n("contact.feedback")),
|
||||
feedback
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright (C) 2021 The HMCL Authors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH AdditionRef-HMCL
|
||||
package org.jackhuang.hmcl.ui.main;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.LineButton;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class HelpPage extends SpinnerPane {
|
||||
|
||||
private final VBox content;
|
||||
|
||||
public HelpPage() {
|
||||
content = new VBox();
|
||||
content.getStyleClass().add("spinner-pane-content");
|
||||
ScrollPane scrollPane = new ScrollPane(content);
|
||||
scrollPane.setFitToWidth(true);
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
setContent(scrollPane);
|
||||
|
||||
var docPane = LineButton.createExternalLinkButton(Metadata.DOCS_URL);
|
||||
docPane.setLargeTitle(true);
|
||||
docPane.setTitle(i18n("help.doc"));
|
||||
docPane.setSubtitle(i18n("help.detail"));
|
||||
|
||||
ComponentList doc = new ComponentList();
|
||||
doc.getContent().setAll(docPane);
|
||||
content.getChildren().add(doc);
|
||||
|
||||
loadHelp();
|
||||
}
|
||||
|
||||
private void loadHelp() {
|
||||
showSpinner();
|
||||
Task.supplyAsync(() -> HttpRequest.GET(Metadata.DOCS_URL + "/index.json").getJson(listTypeOf(HelpCategory.class)))
|
||||
.whenComplete(Schedulers.javafx(), (helpCategories, exception) -> {
|
||||
if (exception != null) {
|
||||
LOG.warning("Failed to load help page: ", exception);
|
||||
showLoadFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
List<Node> nodes = new ArrayList<>();
|
||||
|
||||
for (HelpCategory category : helpCategories) {
|
||||
ComponentList categoryPane = new ComponentList();
|
||||
|
||||
for (Help help : category.items()) {
|
||||
var item = LineButton.createExternalLinkButton(help.url());
|
||||
item.setLargeTitle(true);
|
||||
item.setTitle(help.title());
|
||||
item.setSubtitle(help.subtitle());
|
||||
|
||||
categoryPane.getContent().add(item);
|
||||
}
|
||||
|
||||
nodes.add(ComponentList.createComponentListTitle(category.title()));
|
||||
nodes.add(categoryPane);
|
||||
}
|
||||
|
||||
content.getChildren().addAll(nodes);
|
||||
|
||||
hideSpinner();
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to display help page: ", e);
|
||||
showLoadFailure();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void showLoadFailure() {
|
||||
setFailedReason(i18n("download.failed.refresh"));
|
||||
setOnFailedAction(e -> loadHelp());
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
private record HelpCategory(
|
||||
@SerializedName("title") String title,
|
||||
@SerializedName("items") List<Help> items) {
|
||||
}
|
||||
|
||||
@JsonSerializable
|
||||
private record Help(
|
||||
@SerializedName("title") String title,
|
||||
@SerializedName("subtitle") String subtitle,
|
||||
@SerializedName("url") String url) {
|
||||
}
|
||||
}
|
||||
@@ -33,12 +33,8 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
private final TabItem<PersonalizationPage> personalizationTab;
|
||||
/// The tab for download settings.
|
||||
private final TabItem<DownloadSettingsPage> downloadTab;
|
||||
/// The tab displaying launcher help.
|
||||
private final TabItem<HelpPage> helpTab;
|
||||
/// The tab displaying launcher information.
|
||||
private final TabItem<AboutPage> aboutTab;
|
||||
/// The tab displaying feedback and contact information.
|
||||
private final TabItem<FeedbackPage> feedbackTab;
|
||||
private final TransitionPane transitionPane = new TransitionPane();
|
||||
|
||||
/// Creates the settings tabs and fills the shared navigation sidebar.
|
||||
@@ -48,8 +44,6 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
settingsTab = new TabItem<>(SettingsPage::new);
|
||||
personalizationTab = new TabItem<>(PersonalizationPage::new);
|
||||
downloadTab = new TabItem<>(DownloadSettingsPage::new);
|
||||
helpTab = new TabItem<>(HelpPage::new);
|
||||
feedbackTab = new TabItem<>(FeedbackPage::new);
|
||||
aboutTab = new TabItem<>(AboutPage::new);
|
||||
tabController = new TabController(transitionPane,
|
||||
gameTab,
|
||||
@@ -57,8 +51,6 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
settingsTab,
|
||||
personalizationTab,
|
||||
downloadTab,
|
||||
helpTab,
|
||||
feedbackTab,
|
||||
aboutTab);
|
||||
|
||||
tabController.selectTab(gameTab);
|
||||
@@ -71,9 +63,7 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
.addNavigationDrawerTab(tabController, settingsTab, i18n("settings.launcher.general"), SVG.TUNE)
|
||||
.addNavigationDrawerTab(tabController, personalizationTab, i18n("settings.launcher.appearance"), SVG.STYLE, SVG.STYLE_FILL)
|
||||
.addNavigationDrawerTab(tabController, downloadTab, i18n("download"), SVG.DOWNLOAD)
|
||||
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tabController, helpTab, i18n("help"), SVG.HELP, SVG.HELP_FILL)
|
||||
.addNavigationDrawerTab(tabController, feedbackTab, i18n("contact"), SVG.FEEDBACK, SVG.FEEDBACK_FILL)
|
||||
.startCategory(i18n("about").toUpperCase(Locale.ROOT))
|
||||
.addNavigationDrawerTab(tabController, aboutTab, i18n("about"), SVG.INFO, SVG.INFO_FILL);
|
||||
|
||||
setContent(transitionPane);
|
||||
@@ -94,10 +84,6 @@ public class LauncherSettingsPage extends DecoratorAnimatedPage implements Decor
|
||||
tabController.selectTab(gameTab, false);
|
||||
}
|
||||
|
||||
public void showFeedback() {
|
||||
tabController.selectTab(feedbackTab, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||
return state.getReadOnlyProperty();
|
||||
|
||||
@@ -22,37 +22,25 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.setting.DownloadProviders;
|
||||
import org.jackhuang.hmcl.setting.GameDirectoryManager;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.theme.Themes;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.instances.GameListPopupMenu;
|
||||
import org.jackhuang.hmcl.ui.instances.Instances;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.UnmodifiableView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.download.ComponentRemoteVersion.Type.RELEASE;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/// Displays the launcher home controls for the currently selected game repository.
|
||||
public final class MainPage extends StackPane implements DecoratorPage {
|
||||
@@ -205,48 +193,14 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
Instances.launch(repository.getSelectedInstance());
|
||||
}
|
||||
|
||||
/// Installs the latest supported release in the currently selected repository, then launches
|
||||
/// that instance even if the selected repository or instance changes during installation.
|
||||
/// Opens the server modpack install/update flow when no instance is selected.
|
||||
private void launchNoGame() {
|
||||
HMCLGameRepository repository = GameDirectoryManager.getSelectedRepository();
|
||||
DownloadProvider downloadProvider = DownloadProviders.getDownloadProvider();
|
||||
|
||||
Task<?> task = downloadProvider.getVersionsAsync(GameComponentType.GAME, null, false)
|
||||
.thenApplyAsync(versions -> versions.stream()
|
||||
.filter(it -> it.getVersionType() == RELEASE)
|
||||
.filter(it -> NativePatcher.checkSupportedStatus(it.getGameVersion(), Platform.SYSTEM_PLATFORM, OperatingSystem.SYSTEM_VERSION) != NativePatcher.SupportStatus.UNSUPPORTED)
|
||||
.sorted()
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IOException("No versions found")))
|
||||
.thenComposeAsync(version -> {
|
||||
HMCLDependencyManager dependency = repository.getDependency();
|
||||
|
||||
String gameVersion = version.getGameVersion().toString();
|
||||
GameInstanceID instanceId = new GameInstanceID(gameVersion);
|
||||
|
||||
try (HMCLGameBuilder builder = dependency.newGameBuilder(instanceId)) {
|
||||
return builder
|
||||
.component(GameComponentType.GAME, gameVersion)
|
||||
.buildAsync()
|
||||
.thenApplyAsync(ignored -> repository.getInstance(instanceId));
|
||||
}
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
repository.setSelectedInstance(result);
|
||||
Instances.launch(result);
|
||||
} else if (!(exception instanceof CancellationException)) {
|
||||
LOG.warning("Failed to install game", exception);
|
||||
Controllers.dialog(StringUtils.getStackTrace(exception),
|
||||
i18n("install.failed"),
|
||||
MessageDialogPane.MessageType.WARNING);
|
||||
}
|
||||
});
|
||||
Controllers.taskDialog(task, i18n("instance.launch.empty.installing"), TaskCancellationAction.NORMAL);
|
||||
Instances.installServerModpack();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public ReadOnlyObjectWrapper<State> stateProperty() {
|
||||
return state;
|
||||
|
||||
@@ -86,16 +86,6 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
}
|
||||
|
||||
// forth item in left sidebar
|
||||
AdvancedListItem downloadItem = new AdvancedListItem();
|
||||
downloadItem.setLeftIcon(SVG.DOWNLOAD);
|
||||
downloadItem.setTitle(i18n("download"));
|
||||
downloadItem.setOnAction(e -> Controllers.navigate(Controllers.getDownloadPage()));
|
||||
|
||||
if (AnimationUtils.isAnimationEnabled()) {
|
||||
downloadItem.setPrepareAction(Controllers::prepareDownloadPage);
|
||||
}
|
||||
|
||||
// fifth item in left sidebar
|
||||
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||
launcherSettingsItem.setLeftIcon(SVG.SETTINGS);
|
||||
launcherSettingsItem.setTitle(i18n("settings"));
|
||||
@@ -116,7 +106,6 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
.startCategory(i18n("instance").toUpperCase(Locale.ROOT))
|
||||
.add(gameInstanceItem)
|
||||
.add(gameListItem)
|
||||
.add(downloadItem)
|
||||
.startCategory(i18n("settings.launcher.general").toUpperCase(Locale.ROOT))
|
||||
.add(launcherSettingsItem);
|
||||
|
||||
|
||||
@@ -777,10 +777,10 @@ install.success=Successfully installed.
|
||||
instance=Games
|
||||
instance.name=Instance Name
|
||||
instance.empty=No Instances
|
||||
instance.empty.add=Add new instance
|
||||
instance.empty.launch=No available instances.\nYou can go to the "Download" page to get the game, or switch the game directory in the "All Instances" page.
|
||||
instance.empty.launch.goto_download=Go to Download Page
|
||||
instance.empty.hint=There are no Minecraft instances here.\nYou can try switching to another game directory or clicking here to download one.
|
||||
instance.empty.add=Install the server modpack
|
||||
instance.empty.launch=No available instances.\nInstall the bundled server modpack, or switch the game directory in the "All Instances" page.
|
||||
instance.empty.launch.goto_download=Install Modpack
|
||||
instance.empty.hint=There are no Minecraft instances here.\nYou can try switching to another game directory or clicking here to install the server modpack.
|
||||
instance.game.all=All
|
||||
instance.game.april_fools=April Fools
|
||||
instance.game.old=Historical
|
||||
@@ -795,7 +795,7 @@ instance.launch=Launch Game
|
||||
instance.launch_and_enter_world=Play World
|
||||
instance.launch.empty=Start Game
|
||||
instance.launch.empty.installing=Installing Game
|
||||
instance.launch.empty.tooltip=Install and launch the latest official release
|
||||
instance.launch.empty.tooltip=Install and launch the bundled server modpack
|
||||
instance.launch.test=Test Launch
|
||||
instance.switch=Switch Instance
|
||||
instance.launch_script=Export Launch Script
|
||||
|
||||
@@ -778,10 +778,10 @@ install.success=تم التثبيت بنجاح.
|
||||
instance=الألعاب
|
||||
instance.name=اسم النسخة
|
||||
instance.empty=لا توجد نسخ
|
||||
instance.empty.add=إضافة نسخة جديدة
|
||||
instance.empty.launch=لا توجد نسخ متاحة.\nيمكنك الانتقال إلى صفحة "التنزيل" للحصول على اللعبة، أو تغيير مجلد اللعبة في صفحة "جميع النسخ".
|
||||
instance.empty.launch.goto_download=الانتقال إلى صفحة التنزيل
|
||||
instance.empty.hint=لا توجد نسخ Minecraft هنا.\nيمكنك محاولة التبديل إلى مجلد لعبة آخر أو النقر هنا لتنزيل واحدة.
|
||||
instance.empty.add=تثبيت حزمة خادم التعديلات
|
||||
instance.empty.launch=لا توجد نسخ متاحة.\nثبّت حزمة خادم التعديلات المدمجة، أو غيّر مجلد اللعبة في صفحة "جميع النسخ".
|
||||
instance.empty.launch.goto_download=تثبيت الحزمة
|
||||
instance.empty.hint=لا توجد نسخ Minecraft هنا.\nيمكنك محاولة التبديل إلى مجلد لعبة آخر أو النقر هنا لتثبيت حزمة خادم التعديلات.
|
||||
instance.game.all=الكل
|
||||
instance.game.april_fools=كذبة أبريل
|
||||
instance.game.old=تاريخي
|
||||
@@ -796,7 +796,7 @@ instance.launch=تشغيل اللعبة
|
||||
instance.launch_and_enter_world=لعب في العالم
|
||||
instance.launch.empty=تشغيل اللعبة
|
||||
instance.launch.empty.installing=جارٍ تثبيت اللعبة
|
||||
instance.launch.empty.tooltip=تثبيت وتشغيل أحدث إصدار رسمي
|
||||
instance.launch.empty.tooltip=تثبيت وتشغيل حزمة خادم التعديلات المدمجة
|
||||
instance.launch.test=تشغيل تجريبي
|
||||
instance.switch=تبديل النسخة
|
||||
instance.launch_script=تصدير سكريبت التشغيل
|
||||
|
||||
@@ -776,10 +776,10 @@ install.success=Erfolgreich installiert.
|
||||
instance=Spiele
|
||||
instance.name=Instanzname
|
||||
instance.empty=Keine Instanzen
|
||||
instance.empty.add=Neue Instanz hinzufügen
|
||||
instance.empty.launch=Keine verfügbaren Instanzen.\nSie können zur Seite "Herunterladen" gehen, um das Spiel zu erhalten, oder das Spielverzeichnis auf der Seite "Alle Instanzen" wechseln.
|
||||
instance.empty.launch.goto_download=Zur Download-Seite gehen
|
||||
instance.empty.hint=Hier gibt es keine Minecraft-Instanzen.\nSie können versuchen, in ein anderes Spielverzeichnis zu wechseln, oder hier klicken, um eine herunterzuladen.
|
||||
instance.empty.add=Server-Modpack installieren
|
||||
instance.empty.launch=Keine verfügbaren Instanzen.\nInstallieren Sie das mitgelieferte Server-Modpack, oder wechseln Sie das Spielverzeichnis auf der Seite "Alle Instanzen".
|
||||
instance.empty.launch.goto_download=Modpack installieren
|
||||
instance.empty.hint=Hier gibt es keine Minecraft-Instanzen.\nSie können versuchen, in ein anderes Spielverzeichnis zu wechseln, oder hier klicken, um das Server-Modpack zu installieren.
|
||||
instance.game.all=Alle
|
||||
instance.game.april_fools=Aprilscherz
|
||||
instance.game.old=Historisch
|
||||
@@ -794,7 +794,7 @@ instance.launch=Spiel starten
|
||||
instance.launch_and_enter_world=Welt spielen
|
||||
instance.launch.empty=Spiel starten
|
||||
instance.launch.empty.installing=Installiere Spiel
|
||||
instance.launch.empty.tooltip=Die neueste offizielle Release installieren und starten
|
||||
instance.launch.empty.tooltip=Das mitgelieferte Server-Modpack installieren und starten
|
||||
instance.launch.test=Teststart
|
||||
instance.switch=Instanz wechseln
|
||||
instance.launch_script=Startskript exportieren
|
||||
|
||||
@@ -770,10 +770,10 @@ install.success=Instalado con éxito.
|
||||
instance=Juegos
|
||||
instance.name=Nombre de instancia
|
||||
instance.empty=No hay instancias
|
||||
instance.empty.add=Añadir una instancia
|
||||
instance.empty.add=Instalar el modpack del servidor
|
||||
# instance.empty.launch=
|
||||
# instance.empty.launch.goto_download=
|
||||
instance.empty.hint=No hay instancias de Minecraft aquí.\nPuedes intentar cambiar a otro directorio del juego o hacer clic aquí para descargar una.
|
||||
instance.empty.hint=No hay instancias de Minecraft aquí.\nPuedes intentar cambiar a otro directorio del juego o hacer clic aquí para instalar el modpack del servidor.
|
||||
instance.game.all=Todos
|
||||
instance.game.april_fools=Día de los Inocentes
|
||||
instance.game.old=Histórico
|
||||
@@ -788,7 +788,7 @@ instance.launch=Iniciar Minecraft
|
||||
# instance.launch_and_enter_world=
|
||||
instance.launch.empty=Iniciar Minecraft
|
||||
instance.launch.empty.installing=Instalando el juego
|
||||
instance.launch.empty.tooltip=Instala y ejecuta la última versión oficial del juego.
|
||||
instance.launch.empty.tooltip=Instala y ejecuta el modpack del servidor incluido.
|
||||
instance.launch.test=Probar instalación
|
||||
instance.switch=Cambiar Instancia
|
||||
instance.launch_script=Exportar script de ejecución
|
||||
|
||||
@@ -586,10 +586,10 @@ install.success=正常にインストールされました
|
||||
instance=ゲーム
|
||||
# instance.name=
|
||||
instance.empty=ゲームなし
|
||||
instance.empty.add=起動構成を作成
|
||||
instance.empty.add=サーバー用統合パックをインストール
|
||||
# instance.empty.launch=
|
||||
# instance.empty.launch.goto_download=
|
||||
instance.empty.hint=インストールされているゲームバージョンはありません。新しいゲームをダウンロードするには、ここをクリックしてください。
|
||||
instance.empty.hint=インストールされているゲームバージョンはありません。サーバー用統合パックをインストールするには、ここをクリックしてください。
|
||||
# instance.game.all=
|
||||
# instance.game.april_fools=
|
||||
instance.game.old=Old
|
||||
|
||||
@@ -597,10 +597,10 @@ install.success=裝成
|
||||
instance=戲
|
||||
instance.name=戲例之名
|
||||
instance.empty=無游戲例
|
||||
instance.empty.add=入引頁以裝戲
|
||||
instance.empty.add=裝伺服器整合包
|
||||
# instance.empty.launch=
|
||||
# instance.empty.launch.goto_download=
|
||||
instance.empty.hint=無畢裝者。\n君且更他戲夾,抑擊斯以往戲之引頁。
|
||||
instance.empty.hint=無畢裝者。\n君且更他戲夾,抑擊斯以裝伺服器整合包。
|
||||
instance.game.all=全覽
|
||||
instance.game.april_fools=愚人節
|
||||
instance.game.old=古典版
|
||||
@@ -615,7 +615,7 @@ instance.launch=戲
|
||||
# instance.launch_and_enter_world=
|
||||
instance.launch.empty=啟戲
|
||||
instance.launch.empty.installing=裝戲
|
||||
instance.launch.empty.tooltip=裝啟至新之當版
|
||||
instance.launch.empty.tooltip=裝啟伺服器整合包
|
||||
instance.launch.test=試其戲
|
||||
instance.switch=迭例
|
||||
instance.launch_script=成啟戲角本
|
||||
|
||||
@@ -711,10 +711,10 @@ install.success=Успешно установлено.
|
||||
instance=Игры
|
||||
instance.name=Название сборки
|
||||
instance.empty=Нет сборок
|
||||
instance.empty.add=Добавить новую сборку
|
||||
instance.empty.launch=Нет доступных сборок.\nСкачайте игру на странице «Скачать» или смените папку с игрой на странице «Все сборки».
|
||||
instance.empty.launch.goto_download=Перейти к скачиванию
|
||||
instance.empty.hint=Нет сборок.\nВы можете попробовать перейти в другую папку с игрой или нажать здесь, чтобы скачать игру.
|
||||
instance.empty.add=Установить серверный модпак
|
||||
instance.empty.launch=Нет доступных сборок.\nУстановите встроенный серверный модпак или смените папку с игрой на странице «Все сборки».
|
||||
instance.empty.launch.goto_download=Установить модпак
|
||||
instance.empty.hint=Нет сборок.\nВы можете попробовать перейти в другую папку с игрой или нажать здесь, чтобы установить серверный модпак.
|
||||
instance.game.all=Все
|
||||
instance.game.april_fools=День смеха
|
||||
instance.game.old=Исторические
|
||||
@@ -729,7 +729,7 @@ instance.launch=Играть
|
||||
instance.launch_and_enter_world=Играть в этом мире
|
||||
instance.launch.empty=Играть
|
||||
instance.launch.empty.installing=Установка игры
|
||||
instance.launch.empty.tooltip=Установить и запустить последнюю официальную версию игры.
|
||||
instance.launch.empty.tooltip=Установить и запустить встроенный серверный модпак.
|
||||
instance.launch.test=Тестовый запуск
|
||||
instance.switch=Сменить сборку
|
||||
instance.launch_script=Создать сценарий запуска
|
||||
|
||||
@@ -733,10 +733,10 @@ install.success=Успішно встановлено.
|
||||
instance=Ігри
|
||||
instance.name=Назва екземпляра
|
||||
instance.empty=Немає екземплярів
|
||||
instance.empty.add=Додати новий екземпляр
|
||||
instance.empty.launch=Немає доступних екземплярів.\nВи можете перейти на сторінку "Завантажити", щоб отримати гру, або переключити ігровий каталог на сторінці "Усі екземпляри".
|
||||
instance.empty.launch.goto_download=Перейти до сторінки завантаження
|
||||
instance.empty.hint=Тут немає екземплярів Minecraft. Ви можете спробувати переключитися на інший ігровий каталог або натиснути тут, щоб завантажити один.
|
||||
instance.empty.add=Встановити серверний модпак
|
||||
instance.empty.launch=Немає доступних екземплярів.\nВстановіть вбудований серверний модпак або переключіть ігровий каталог на сторінці "Усі екземпляри".
|
||||
instance.empty.launch.goto_download=Встановити модпак
|
||||
instance.empty.hint=Тут немає екземплярів Minecraft.\nВи можете спробувати переключитися на інший ігровий каталог або натиснути тут, щоб встановити серверний модпак.
|
||||
instance.game.all=Усі
|
||||
instance.game.april_fools=Перший квітня
|
||||
instance.game.old=Історичні
|
||||
@@ -751,7 +751,7 @@ instance.launch=Запустити гру
|
||||
instance.launch_and_enter_world=Грати у світ
|
||||
instance.launch.empty=Почати гру
|
||||
instance.launch.empty.installing=Встановлення гри
|
||||
instance.launch.empty.tooltip=Встановити та запустити останній офіційний реліз
|
||||
instance.launch.empty.tooltip=Встановити та запустити вбудований серверний модпак
|
||||
instance.launch.test=Тестовий запуск
|
||||
instance.switch=Переключити екземпляр
|
||||
instance.launch_script=Експортувати скрипт запуску
|
||||
|
||||
@@ -600,10 +600,10 @@ install.success=安装成功
|
||||
instance=游戏
|
||||
instance.name=游戏实例名称
|
||||
instance.empty=没有游戏实例
|
||||
instance.empty.add=进入下载页安装游戏
|
||||
instance.empty.launch=没有可启动的游戏。\n你可以前往下载页面下载新游戏,或在“实例列表”中切换游戏文件夹。
|
||||
instance.empty.launch.goto_download=前往下载页面
|
||||
instance.empty.hint=没有已安装的游戏。\n你可以切换其他游戏文件夹,或者点击此处进入游戏下载页面。
|
||||
instance.empty.add=安装服务器整合包
|
||||
instance.empty.launch=没有可启动的游戏。\n请安装自带的服务器整合包,或在“实例列表”中切换游戏文件夹。
|
||||
instance.empty.launch.goto_download=安装整合包
|
||||
instance.empty.hint=没有已安装的游戏。\n你可以切换其他游戏文件夹,或者点击此处安装服务器整合包。
|
||||
instance.game.all=全部
|
||||
instance.game.april_fools=愚人节
|
||||
instance.game.old=远古版
|
||||
@@ -618,7 +618,7 @@ instance.launch=启动游戏
|
||||
instance.launch_and_enter_world=进入世界
|
||||
instance.launch.empty=开始游戏
|
||||
instance.launch.empty.installing=安装游戏
|
||||
instance.launch.empty.tooltip=安装并启动最新正式版游戏
|
||||
instance.launch.empty.tooltip=安装并启动服务器整合包
|
||||
instance.launch.test=测试游戏
|
||||
instance.switch=切换实例
|
||||
instance.launch_script=生成启动脚本
|
||||
|
||||
@@ -594,10 +594,10 @@ install.success=安裝成功
|
||||
instance=遊戲
|
||||
instance.name=遊戲實例名稱
|
||||
instance.empty=沒有遊戲實例
|
||||
instance.empty.add=進入下載頁安裝遊戲
|
||||
instance.empty.launch=沒有可啟動的遊戲。\n你可以前往「下載」頁面下載新遊戲,或在「實例清單」切換遊戲目錄。
|
||||
instance.empty.launch.goto_download=前往下載頁面
|
||||
instance.empty.hint=沒有已安裝的遊戲。\n你可以切換其他遊戲目錄,或者點擊此處進入遊戲下載頁面。
|
||||
instance.empty.add=安裝伺服器整合包
|
||||
instance.empty.launch=沒有可啟動的遊戲。\n請安裝內建的伺服器整合包,或在「實例清單」切換遊戲目錄。
|
||||
instance.empty.launch.goto_download=安裝整合包
|
||||
instance.empty.hint=沒有已安裝的遊戲。\n你可以切換其他遊戲目錄,或者點擊此處安裝伺服器整合包。
|
||||
instance.game.all=全部
|
||||
instance.game.april_fools=愚人節
|
||||
instance.game.old=遠古版
|
||||
@@ -612,7 +612,7 @@ instance.launch=啟動遊戲
|
||||
instance.launch_and_enter_world=進入世界
|
||||
instance.launch.empty=開始遊戲
|
||||
instance.launch.empty.installing=安裝遊戲
|
||||
instance.launch.empty.tooltip=安裝並啟動最新正式版遊戲
|
||||
instance.launch.empty.tooltip=安裝並啟動伺服器整合包
|
||||
instance.launch.test=測試遊戲
|
||||
instance.switch=切換實例
|
||||
instance.launch_script=生成啟動指令碼
|
||||
|
||||
@@ -13,13 +13,12 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
/// Tests for detached authlib-injector server lists.
|
||||
@NotNullByDefault
|
||||
public final class AuthlibInjectorServerListTest {
|
||||
/// Tests that newly created server lists contain LittleSkin by default.
|
||||
/// Tests that newly created server lists start empty.
|
||||
@Test
|
||||
public void defaultListContainsLittleSkin() {
|
||||
public void defaultListIsEmpty() {
|
||||
AuthlibInjectorServerList list = AuthlibInjectorServerList.createDefault();
|
||||
|
||||
assertEquals(1, list.getServers().size());
|
||||
assertEquals(AuthlibInjectorServerList.LITTLE_SKIN_URL, list.getServers().get(0).getUrl());
|
||||
assertTrue(list.getServers().isEmpty());
|
||||
}
|
||||
|
||||
/// Tests that server list serialization does not include derived metadata.
|
||||
@@ -68,9 +67,9 @@ public final class AuthlibInjectorServerListTest {
|
||||
assertEquals(AuthlibInjectorServerList.CURRENT_SCHEMA, list.getSchema());
|
||||
}
|
||||
|
||||
/// Tests that legacy configs without `addedLittleSkin` receive LittleSkin during migration.
|
||||
/// Tests that legacy configs without `addedLittleSkin` do not receive LittleSkin.
|
||||
@Test
|
||||
public void addsLittleSkinWhenLegacyFlagIsMissing() {
|
||||
public void doesNotAddLittleSkinWhenLegacyFlagIsMissing() {
|
||||
JsonObject settings = JsonParser.parseString("""
|
||||
{
|
||||
"authlibInjectorServers": [
|
||||
@@ -83,14 +82,13 @@ public final class AuthlibInjectorServerListTest {
|
||||
|
||||
AuthlibInjectorServerList list = LegacyConfigMigrator.extractAuthlibInjectorServers(settings);
|
||||
|
||||
assertEquals(2, list.getServers().size());
|
||||
assertEquals(1, list.getServers().size());
|
||||
assertEquals("https://example.com/api/yggdrasil/", list.getServers().get(0).getUrl());
|
||||
assertEquals(AuthlibInjectorServerList.LITTLE_SKIN_URL, list.getServers().get(1).getUrl());
|
||||
}
|
||||
|
||||
/// Tests that legacy configs with `addedLittleSkin=false` do not add a duplicate LittleSkin entry.
|
||||
/// Tests that an existing LittleSkin entry is preserved during migration.
|
||||
@Test
|
||||
public void addsLittleSkinOnlyOnceWhenLegacyFlagIsFalse() {
|
||||
public void preservesExistingLittleSkinDuringMigration() {
|
||||
JsonObject settings = JsonParser.parseString("""
|
||||
{
|
||||
"authlibInjectorServers": [
|
||||
|
||||
Reference in new issue
Block a user