使用 Jsoup 解析并渲染 HTML 页面 (#3321)
This commit is contained in:
@@ -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 <T> void bind(JFXTextField textField, Property<T> property, StringConverter<T> converter) {
|
||||
textField.setText(converter == null ? (String) property.getValue() : converter.toString(property.getValue()));
|
||||
TextFieldBindingListener<T> listener = new TextFieldBindingListener<>(textField, property, converter);
|
||||
|
||||
266
HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java
Normal file
266
HMCL/src/main/java/org/jackhuang/hmcl/ui/HTMLRenderer.java
Normal file
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 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;
|
||||
|
||||
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<javafx.scene.Node> children = new ArrayList<>();
|
||||
private final List<Node> 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<URI> onClickHyperlink;
|
||||
|
||||
public HTMLRenderer(Consumer<URI> 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;
|
||||
}
|
||||
}
|
||||
@@ -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")));
|
||||
}
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
});
|
||||
setBody(webView);
|
||||
} catch (NoClassDefFoundError | UnsatisfiedLinkError e) {
|
||||
LOG.warning("WebView is missing or initialization failed", e);
|
||||
FXUtils.openLink(url);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
JFXHyperlink openInBrowser = new JFXHyperlink(i18n("web.view_in_browser"));
|
||||
openInBrowser.setExternalLink(url);
|
||||
|
||||
{
|
||||
JFXButton updateButton = new JFXButton(i18n("update.accept"));
|
||||
updateButton.getStyleClass().add("dialog-accept");
|
||||
updateButton.setOnMouseClicked(e -> updateRunnable.run());
|
||||
updateButton.setOnAction(e -> updateRunnable.run());
|
||||
|
||||
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
|
||||
cancelButton.getStyleClass().add("dialog-cancel");
|
||||
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
|
||||
cancelButton.setOnAction(e -> fireEvent(new DialogCloseEvent()));
|
||||
|
||||
setActions(updateButton, cancelButton);
|
||||
setActions(openInBrowser, updateButton, cancelButton);
|
||||
onEscPressed(this, cancelButton::fire);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java
Normal file
74
HMCL/src/main/java/org/jackhuang/hmcl/ui/WebPage.java
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2024 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;
|
||||
|
||||
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<DecoratorPage.State> 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<DecoratorPage.State> stateProperty() {
|
||||
return stateProperty;
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -23,7 +23,8 @@ public interface WizardPage {
|
||||
default void onNavigate(Map<String, Object> settings) {
|
||||
}
|
||||
|
||||
void cleanup(Map<String, Object> settings);
|
||||
default void cleanup(Map<String, Object> settings) {
|
||||
}
|
||||
|
||||
String getTitle();
|
||||
}
|
||||
|
||||
@@ -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<DependencyDescriptor> readDependencies() {
|
||||
List<DependencyDescriptor> dependencies;
|
||||
//noinspection ConstantConditions
|
||||
try (Reader reader = new InputStreamReader(SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE), UTF_8)) {
|
||||
Map<String, List<DependencyDescriptor>> 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=管理資料包
|
||||
|
||||
@@ -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=管理数据包
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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<String> = listOf()
|
||||
val version: String = jfxVersion
|
||||
) {
|
||||
val modules: List<String> = 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 {
|
||||
|
||||
Reference in New Issue
Block a user