diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index cbedf9fb4..df712fe24 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -52,7 +52,6 @@ import javafx.util.StringConverter; import org.glavo.png.PNGType; import org.glavo.png.PNGWriter; import org.glavo.png.javafx.PNGJavaFXUtils; -import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.animation.AnimationUtils; import org.jackhuang.hmcl.ui.construct.JFXHyperlink; @@ -70,12 +69,9 @@ import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import javax.swing.*; -import javax.swing.event.HyperlinkEvent; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import java.awt.*; import java.io.*; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; @@ -527,52 +523,6 @@ public final class FXUtils { }); } - public static void showWebDialog(String title, String content) { - showWebDialog(title, content, 800, 480); - } - - public static void showWebDialog(String title, String content, int width, int height) { - try { - WebStage stage = new WebStage(width, height); - stage.getWebView().getEngine().loadContent(content); - stage.setTitle(title); - stage.showAndWait(); - } catch (NoClassDefFoundError | UnsatisfiedLinkError e) { - LOG.warning("WebView is missing or initialization failed, use JEditorPane replaced", e); - - SwingUtils.initLookAndFeel(); - SwingUtilities.invokeLater(() -> { - final JFrame frame = new JFrame(title); - frame.setSize(width, height); - frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - frame.setLocationByPlatform(true); - frame.setIconImage(new ImageIcon(FXUtils.class.getResource("/assets/img/icon.png")).getImage()); - frame.setLayout(new BorderLayout()); - - final JProgressBar progressBar = new JProgressBar(); - progressBar.setIndeterminate(true); - frame.add(progressBar, BorderLayout.PAGE_START); - - Schedulers.defaultScheduler().execute(() -> { - final JEditorPane pane = new JEditorPane("text/html", content); - pane.setEditable(false); - pane.addHyperlinkListener(event -> { - if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - openLink(event.getURL().toExternalForm()); - } - }); - SwingUtilities.invokeLater(() -> { - progressBar.setVisible(false); - frame.add(new JScrollPane(pane), BorderLayout.CENTER); - }); - }); - - frame.setVisible(true); - frame.toFront(); - }); - } - } - public static void bind(JFXTextField textField, Property property, StringConverter converter) { textField.setText(converter == null ? (String) property.getValue() : converter.toString(property.getValue())); TextFieldBindingListener listener = new TextFieldBindingListener<>(textField, property, converter); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java new file mode 100644 index 000000000..d311996a9 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java @@ -0,0 +1,266 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui 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 . + */ +package org.jackhuang.hmcl.ui; + +import javafx.scene.Cursor; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; +import org.jsoup.nodes.Node; +import org.jsoup.nodes.TextNode; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public final class HTMLRenderer { + private static URI resolveLink(Node linkNode) { + String href = linkNode.absUrl("href"); + if (href.isEmpty()) + return null; + + try { + return new URI(href); + } catch (Throwable e) { + return null; + } + } + + private final List children = new ArrayList<>(); + private final List stack = new ArrayList<>(); + + private boolean bold; + private boolean italic; + private boolean underline; + private boolean strike; + private boolean highlight; + private String headerLevel; + private Node hyperlink; + + private final Consumer onClickHyperlink; + + public HTMLRenderer(Consumer onClickHyperlink) { + this.onClickHyperlink = onClickHyperlink; + } + + private void updateStyle() { + bold = false; + italic = false; + underline = false; + strike = false; + highlight = false; + headerLevel = null; + hyperlink = null; + + for (Node node : stack) { + String nodeName = node.nodeName(); + switch (nodeName) { + case "b": + case "strong": + bold = true; + break; + case "i": + case "em": + italic = true; + break; + case "ins": + underline = true; + break; + case "del": + strike = true; + break; + case "mark": + highlight = true; + break; + case "a": + hyperlink = node; + break; + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + headerLevel = nodeName; + break; + } + } + } + + private void pushNode(Node node) { + stack.add(node); + updateStyle(); + } + + private void popNode() { + stack.remove(stack.size() - 1); + updateStyle(); + } + + private void applyStyle(Text text) { + if (hyperlink != null) { + URI target = resolveLink(hyperlink); + if (target != null) { + text.setOnMouseClicked(event -> onClickHyperlink.accept(target)); + text.setCursor(Cursor.HAND); + } + text.getStyleClass().add("html-hyperlink"); + } + + if (hyperlink != null || underline) + text.setUnderline(true); + + if (strike) + text.setStrikethrough(true); + + if (bold || highlight) + text.getStyleClass().add("html-bold"); + + if (italic) + text.getStyleClass().add("html-italic"); + + if (headerLevel != null) + text.getStyleClass().add("html-" + headerLevel); + } + + private void appendText(String text) { + Text textNode = new Text(text); + applyStyle(textNode); + children.add(textNode); + } + + private void appendImage(Node node) { + String src = node.absUrl("src"); + URI imageUri = null; + try { + if (!src.isEmpty()) + imageUri = URI.create(src); + } catch (Exception ignored) { + } + + String alt = node.attr("alt"); + + if (imageUri != null) { + URI uri = URI.create(src); + + String widthAttr = node.attr("width"); + String heightAttr = node.attr("height"); + + double width = 0; + double height = 0; + + if (!widthAttr.isEmpty() && !heightAttr.isEmpty()) { + try { + width = Double.parseDouble(widthAttr); + height = Double.parseDouble(heightAttr); + } catch (NumberFormatException ignored) { + } + + if (width <= 0 || height <= 0) { + width = 0; + height = 0; + } + } + + Image image = FXUtils.newRemoteImage(uri.toString(), width, height, true, true, false); + if (image.isError()) { + LOG.warning("Failed to load image: " + uri, image.getException()); + } else { + ImageView imageView = new ImageView(image); + if (hyperlink != null) { + URI target = resolveLink(hyperlink); + if (target != null) { + imageView.setOnMouseClicked(event -> onClickHyperlink.accept(target)); + imageView.setCursor(Cursor.HAND); + } + } + children.add(imageView); + return; + } + } + + if (!alt.isEmpty()) + appendText(alt); + } + + public void appendNode(Node node) { + if (node instanceof TextNode) { + appendText(((TextNode) node).text()); + } + + String name = node.nodeName(); + switch (name) { + case "img": + appendImage(node); + break; + case "li": + appendText("\n \u2022 "); + break; + case "dt": + appendText(" "); + break; + case "p": + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + case "tr": + if (!children.isEmpty()) + appendText("\n\n"); + break; + } + + if (node.childNodeSize() > 0) { + pushNode(node); + for (Node childNode : node.childNodes()) { + appendNode(childNode); + } + popNode(); + } + + switch (name) { + case "br": + case "dd": + case "p": + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + appendText("\n"); + break; + } + } + + public TextFlow render() { + TextFlow textFlow = new TextFlow(); + textFlow.getStyleClass().add("html"); + textFlow.getChildren().setAll(children); + return textFlow; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java index e8602e394..e6b1e9925 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java @@ -19,58 +19,73 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXDialogLayout; -import javafx.concurrent.Worker; import javafx.scene.control.Label; -import javafx.scene.web.WebEngine; -import javafx.scene.web.WebView; -import org.jackhuang.hmcl.Metadata; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.ScrollPane; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; +import org.jackhuang.hmcl.ui.construct.JFXHyperlink; import org.jackhuang.hmcl.upgrade.RemoteVersion; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; +import org.jsoup.nodes.Node; +import java.io.IOException; +import java.net.URL; import static org.jackhuang.hmcl.Metadata.CHANGELOG_URL; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; -import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; -public class UpgradeDialog extends JFXDialogLayout { +public final class UpgradeDialog extends JFXDialogLayout { public UpgradeDialog(RemoteVersion remoteVersion, Runnable updateRunnable) { - { - setHeading(new Label(i18n("update.changelog"))); - } + setHeading(new Label(i18n("update.changelog"))); + setBody(new ProgressIndicator()); - { - String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html#nowchange"; - try { - WebView webView = new WebView(); - webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile()); - WebEngine engine = webView.getEngine(); - engine.load(url); - engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> { - if (newValue == Worker.State.FAILED) { - LOG.warning("Failed to load update log, trying to open it in browser"); - FXUtils.openLink(url); - setBody(); - } - }); - setBody(webView); - } catch (NoClassDefFoundError | UnsatisfiedLinkError e) { - LOG.warning("WebView is missing or initialization failed", e); + String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html"; + Task.supplyAsync(Schedulers.io(), () -> { + Document document = Jsoup.parse(new URL(url), 30 * 1000); + Node node = document.selectFirst("#nowchange"); + if (node == null) + throw new IOException("Cannot find #nowchange in document"); + + HTMLRenderer renderer = new HTMLRenderer(uri -> { + LOG.info("Open link: " + uri); + FXUtils.openLink(uri.toString()); + }); + + do { + renderer.appendNode(node); + node = node.nextSibling(); + } while (node != null); + + return renderer.render(); + }).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception == null) { + ScrollPane scrollPane = new ScrollPane(result); + scrollPane.setFitToWidth(true); + setBody(scrollPane); + } else { + LOG.warning("Failed to load update log, trying to open it in browser"); FXUtils.openLink(url); + setBody(); } - } + }).start(); - { - JFXButton updateButton = new JFXButton(i18n("update.accept")); - updateButton.getStyleClass().add("dialog-accept"); - updateButton.setOnMouseClicked(e -> updateRunnable.run()); + JFXHyperlink openInBrowser = new JFXHyperlink(i18n("web.view_in_browser")); + openInBrowser.setExternalLink(url); - JFXButton cancelButton = new JFXButton(i18n("button.cancel")); - cancelButton.getStyleClass().add("dialog-cancel"); - cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent())); + JFXButton updateButton = new JFXButton(i18n("update.accept")); + updateButton.getStyleClass().add("dialog-accept"); + updateButton.setOnAction(e -> updateRunnable.run()); - setActions(updateButton, cancelButton); - onEscPressed(this, cancelButton::fire); - } + JFXButton cancelButton = new JFXButton(i18n("button.cancel")); + cancelButton.getStyleClass().add("dialog-cancel"); + cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent())); + + setActions(openInBrowser, updateButton, cancelButton); + onEscPressed(this, cancelButton::fire); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java new file mode 100644 index 000000000..7dca291e0 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java @@ -0,0 +1,74 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2024 huangyuhui 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 . + */ +package org.jackhuang.hmcl.ui; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.Background; +import javafx.scene.paint.Color; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.construct.SpinnerPane; +import org.jackhuang.hmcl.ui.decorator.DecoratorPage; +import org.jsoup.Jsoup; +import org.jsoup.nodes.Document; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; + +/** + * @author Glavo + */ +public final class WebPage extends SpinnerPane implements DecoratorPage { + + private final ObjectProperty stateProperty; + + public WebPage(String title, String content) { + this.stateProperty = new SimpleObjectProperty<>(DecoratorPage.State.fromTitle(title)); + this.setBackground(Background.fill(Color.WHITE)); + + Task.supplyAsync(() -> { + Document document = Jsoup.parseBodyFragment(content); + HTMLRenderer renderer = new HTMLRenderer(uri -> { + Controllers.confirm(i18n("web.open_in_browser", uri), i18n("message.confirm"), () -> { + FXUtils.openLink(uri.toString()); + }, null); + }); + renderer.appendNode(document); + return renderer.render(); + }).whenComplete(Schedulers.javafx(), ((result, exception) -> { + if (exception == null) { + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setFitToWidth(true); + scrollPane.setContent(result); + setContent(scrollPane); + setFailedReason(null); + } else { + LOG.warning("Failed to load content", exception); + setFailedReason(i18n("web.failed")); + } + })).start(); + } + + @Override + public ReadOnlyObjectProperty stateProperty() { + return stateProperty; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java deleted file mode 100644 index 1b57ca0aa..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/WebStage.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui 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 . - */ -package org.jackhuang.hmcl.ui; - -import com.jfoenix.controls.JFXProgressBar; -import javafx.beans.binding.Bindings; -import javafx.scene.Scene; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.StackPane; -import javafx.scene.web.WebEngine; -import javafx.scene.web.WebView; -import javafx.stage.Stage; -import org.jackhuang.hmcl.Metadata; -import org.jackhuang.hmcl.setting.Theme; - -import static org.jackhuang.hmcl.setting.ConfigHolder.config; - -public class WebStage extends Stage { - protected final StackPane pane = new StackPane(); - protected final JFXProgressBar progressBar = new JFXProgressBar(); - protected final WebView webView = new WebView(); - protected final WebEngine webEngine = webView.getEngine(); - - public WebStage() { - this(800, 480); - } - - public WebStage(int width, int height) { - setScene(new Scene(pane, width, height)); - getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily())); - FXUtils.setIcon(this); - webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile()); - webView.setContextMenuEnabled(false); - progressBar.progressProperty().bind(webView.getEngine().getLoadWorker().progressProperty()); - - progressBar.visibleProperty().bind(Bindings.createBooleanBinding(() -> { - switch (webView.getEngine().getLoadWorker().getState()) { - case SUCCEEDED: - case FAILED: - case CANCELLED: - return false; - default: - return true; - } - }, webEngine.getLoadWorker().stateProperty())); - - BorderPane borderPane = new BorderPane(); - borderPane.setPickOnBounds(false); - borderPane.setTop(progressBar); - progressBar.prefWidthProperty().bind(borderPane.widthProperty()); - pane.getChildren().setAll(webView, borderPane); - } - - public WebView getWebView() { - return webView; - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java index 54480e552..4c71fd6ee 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/LocalModpackPage.java @@ -31,6 +31,7 @@ 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.WebPage; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RequiredValidator; import org.jackhuang.hmcl.ui.construct.Validator; @@ -153,9 +154,8 @@ public final class LocalModpackPage extends ModpackPage { } protected void onDescribe() { - if (manifest != null) { - FXUtils.showWebDialog(i18n("modpack.description"), manifest.getDescription()); - } + if (manifest != null) + Controllers.navigate(new WebPage(i18n("modpack.description"), manifest.getDescription())); } public static final String MODPACK_FILE = "MODPACK_FILE"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java index b38c66346..9f396efec 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/RemoteModpackPage.java @@ -22,7 +22,7 @@ import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.mod.server.ServerModpackManifest; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.ui.WebPage; import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.RequiredValidator; import org.jackhuang.hmcl.ui.construct.Validator; @@ -84,7 +84,7 @@ public final class RemoteModpackPage extends ModpackPage { } protected void onDescribe() { - FXUtils.showWebDialog(i18n("modpack.description"), manifest.getDescription()); + Controllers.navigate(new WebPage(i18n("modpack.description"), manifest.getDescription())); } public static final String MODPACK_SERVER_MANIFEST = "MODPACK_SERVER_MANIFEST"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java index cf5c2438b..b9d04ba34 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/wizard/WizardPage.java @@ -23,7 +23,8 @@ public interface WizardPage { default void onNavigate(Map settings) { } - void cleanup(Map settings); + default void cleanup(Map settings) { + } String getTitle(); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java index 79efb3848..8d88115fd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/SelfDependencyPatcher.java @@ -105,28 +105,14 @@ public final class SelfDependencyPatcher { private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies").resolve(Platform.getPlatform().toString()).resolve("openjfx"); static List readDependencies() { - List dependencies; //noinspection ConstantConditions try (Reader reader = new InputStreamReader(SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE), UTF_8)) { Map> allDependencies = JsonUtils.GSON.fromJson(reader, mapTypeOf(String.class, listTypeOf(DependencyDescriptor.class))); - dependencies = allDependencies.get(Platform.getPlatform().toString()); + return allDependencies.get(Platform.getPlatform().toString()); } catch (IOException e) { throw new UncheckedIOException(e); } - - if (dependencies == null) return null; - - try { - ClassLoader classLoader = SelfDependencyPatcher.class.getClassLoader(); - Class.forName("netscape.javascript.JSObject", false, classLoader); - Class.forName("org.w3c.dom.html.HTMLDocument", false, classLoader); - } catch (Throwable e) { - LOG.warning("Disable javafx.web because JRE is incomplete", e); - dependencies.removeIf(it -> "javafx.web".equals(it.module) || "javafx.media".equals(it.module)); - } - - return dependencies; } public String module; diff --git a/HMCL/src/main/resources/assets/css/root.css b/HMCL/src/main/resources/assets/css/root.css index 36c589bbe..ddb77d812 100644 --- a/HMCL/src/main/resources/assets/css/root.css +++ b/HMCL/src/main/resources/assets/css/root.css @@ -1438,3 +1438,37 @@ .fit-width { -fx-pref-width: 100%; } + +/******************************************************************************* +* * +* HTML Renderer * +* * +*******************************************************************************/ + +.html { + -fx-font-size: 16; +} + +.html-hyperlink { + -fx-fill: blue; +} + +.html-h1 { + -fx-font-size: 22; +} + +.html-h2 { + -fx-font-size: 20; +} + +.html-h3 { + -fx-font-size: 18; +} + +.html-bold { + -fx-font-weight: bold; +} + +.html-italic { + -fx-font-style: italic; +} diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index c455bbff3..7c3098022 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -978,6 +978,10 @@ datapack.choose_datapack=Select a datapack to import datapack.extension=Datapack datapack.title=World %s - Datapacks +web.failed=Page loading failed +web.open_in_browser=Do you want to open this address in a browser:\n%s +web.view_in_browser=View in browser + world=Worlds world.add=Add a World (.zip) world.datapack=Manage Datapacks diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index bfbda9136..42d71bf8c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -847,6 +847,10 @@ datapack.choose_datapack=選擇要匯入的資料包壓縮檔 datapack.extension=資料包 datapack.title=世界 %s - 資料包 +web.failed=加載頁面失敗 +web.open_in_browser=是否要在瀏覽器中打開此連結:\n%s +web.view_in_browser=在瀏覽器中查看 + world=世界 world.add=加入世界 world.datapack=管理資料包 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 21090c4d4..0d8202b40 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -846,6 +846,10 @@ datapack.choose_datapack=选择要导入的数据包压缩包 datapack.extension=数据包 datapack.title=世界 %s - 数据包 +web.failed=加载页面失败 +web.open_in_browser=是否要在浏览器中打开此链接:\n%s +web.view_in_browser=在浏览器中查看 + world=世界 world.add=添加世界 world.datapack=管理数据包 diff --git a/HMCLCore/build.gradle.kts b/HMCLCore/build.gradle.kts index c94473b50..e7c4b4ab5 100644 --- a/HMCLCore/build.gradle.kts +++ b/HMCLCore/build.gradle.kts @@ -12,5 +12,6 @@ dependencies { api("com.github.steveice10:opennbt:1.5") api("org.nanohttpd:nanohttpd:2.3.1") api("org.apache.commons:commons-compress:1.25.0") + api("org.jsoup:jsoup:1.18.1") compileOnlyApi("org.jetbrains:annotations:24.1.0") } diff --git a/javafx.gradle.kts b/javafx.gradle.kts index 734639eeb..f64e36b97 100644 --- a/javafx.gradle.kts +++ b/javafx.gradle.kts @@ -14,11 +14,8 @@ data class Platform( val name: String, val classifier: String, val groupId: String = "org.openjfx", - val version: String = jfxVersion, - val unsupportedModules: List = listOf() + val version: String = jfxVersion ) { - val modules: List = jfxModules.filter { it !in unsupportedModules } - fun fileUrl( module: String, classifier: String, ext: String, repo: String = "https://repo1.maven.org/maven2" @@ -28,22 +25,22 @@ data class Platform( ).toURL() } -val jfxModules = listOf("base", "graphics", "controls", "media", "web") +val jfxModules = listOf("base", "graphics", "controls") val jfxMirrorRepos = listOf("https://mirrors.cloud.tencent.com/nexus/repository/maven-public") val jfxDependenciesFile = project("HMCL").layout.buildDirectory.file("openjfx-dependencies.json").get().asFile val jfxPlatforms = listOf( Platform("windows-x86", "win-x86"), Platform("windows-x86_64", "win"), - Platform("windows-arm64", "win", groupId = "org.glavo.hmcl.openjfx", version = "18.0.2+1-arm64", unsupportedModules = listOf("media", "web")), + Platform("windows-arm64", "win", groupId = "org.glavo.hmcl.openjfx", version = "18.0.2+1-arm64"), Platform("osx-x86_64", "mac"), Platform("osx-arm64", "mac-aarch64"), Platform("linux-x86_64", "linux"), - Platform("linux-arm32", "linux-arm32-monocle", unsupportedModules = listOf("media", "web")), + Platform("linux-arm32", "linux-arm32-monocle"), Platform("linux-arm64", "linux-aarch64"), Platform("linux-loongarch64", "linux", groupId = "org.glavo.hmcl.openjfx", version = "17.0.8-loongarch64"), - Platform("linux-loongarch64_ow", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19-ea+10-loongson64", unsupportedModules = listOf("media", "web")), - Platform("linux-riscv64", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19.0.2.1-riscv64", unsupportedModules = listOf("media", "web")), - Platform("freebsd-x86_64", "freebsd", groupId = "org.glavo.hmcl.openjfx", version = "14.0.2.1-freebsd", unsupportedModules = listOf("media", "web")), + Platform("linux-loongarch64_ow", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19-ea+10-loongson64"), + Platform("linux-riscv64", "linux", groupId = "org.glavo.hmcl.openjfx", version = "19.0.2.1-riscv64"), + Platform("freebsd-x86_64", "freebsd", groupId = "org.glavo.hmcl.openjfx", version = "14.0.2.1-freebsd"), ) val jfxInClasspath = @@ -93,7 +90,7 @@ rootProject.tasks.create("generateOpenJFXDependencies") { doLast { val jfxDependencies = jfxPlatforms.associate { platform -> - platform.name to platform.modules.map { module -> + platform.name to jfxModules.map { module -> mapOf( "module" to "javafx.$module", "groupId" to platform.groupId, @@ -117,7 +114,7 @@ rootProject.tasks.create("preTouchOpenJFXDependencies") { doLast { for (repo in jfxMirrorRepos) { for (platform in jfxPlatforms) { - for (module in platform.modules) { + for (module in jfxModules) { val url = platform.fileUrl(module, platform.classifier, "jar", repo = repo) logger.quiet("Getting $url") try {