Examples
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
package org.openautonomousconnection.webclient.recode;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketSendEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientDisconnectedEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
import org.openautonomousconnection.oacswing.component.OACOptionPane;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||
import org.openautonomousconnection.protocol.side.client.ProtocolClient;
|
||||
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolINSServerEvent;
|
||||
import org.openautonomousconnection.protocol.side.client.events.ConnectedToProtocolServerEvent;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecord;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSResponseStatus;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ClientImpl extends ProtocolClient {
|
||||
@Override
|
||||
public boolean trustINS(String caFingerprint) {
|
||||
Object[] options = {"Continue", "Cancel"};
|
||||
int result = OACOptionPane.showOptionDialog(
|
||||
Main.getUi(),
|
||||
"You never connected to this INS before!\n" +
|
||||
"Fingerprint: " + caFingerprint + "\nDo you want to connect?",
|
||||
"INS Connection",
|
||||
OACOptionPane.YES_NO_OPTION,
|
||||
OACOptionPane.INFORMATION_MESSAGE,
|
||||
null,
|
||||
options,
|
||||
options[0] // default button: Continue
|
||||
);
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean trustNewINSFingerprint(String oldCAFingerprint, String newCAFingerprint) {
|
||||
Object[] options = {"Continue", "Cancel"};
|
||||
|
||||
String table = String.format("""
|
||||
Saved Fingerprint\tNew Fingerprint
|
||||
%s\t%s
|
||||
""", oldCAFingerprint, newCAFingerprint);
|
||||
|
||||
int result = OACOptionPane.showOptionDialog(
|
||||
Main.getUi(),
|
||||
"The fingerprint does not match with the saved fingerprint!\n" +
|
||||
"Saved Fingerprint: " + oldCAFingerprint + "\n" +
|
||||
"New Fingerprint: " + newCAFingerprint + "\n" +
|
||||
"Do you want to connect?",
|
||||
"INS Connection",
|
||||
OACOptionPane.YES_NO_OPTION,
|
||||
OACOptionPane.INFORMATION_MESSAGE,
|
||||
null,
|
||||
options,
|
||||
options[0] // default button: Continue
|
||||
);
|
||||
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(INSResponseStatus status, List<INSRecord> records) {
|
||||
try {
|
||||
String host = records.getFirst().value;
|
||||
if (!host.contains(":")) host = host + ":1028";
|
||||
String[] split = host.split(":");
|
||||
Main.getClient().getClientServerConnection().connect(split[0], Integer.parseInt(split[1]));
|
||||
} catch (Exception e) {
|
||||
Main.getClient().getProtocolBridge().getLogger().exception("Failed to connect to Server", e);
|
||||
OACOptionPane.showMessageDialog(Main.getUi(), "Failed to connect to Server:\n" + e.getMessage(),
|
||||
"Server Connection", OACOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
super.onResponse(status, records);
|
||||
}
|
||||
|
||||
@Listener
|
||||
public void onConnected(ConnectedToProtocolINSServerEvent event) {
|
||||
try {
|
||||
Main.getClient().buildServerConnection(null, true);
|
||||
} catch (Exception e) {
|
||||
Main.getClient().getProtocolBridge().getLogger().exception("Failed to build Server connection", e);
|
||||
OACOptionPane.showMessageDialog(Main.getUi(), "Failed to connect to build Server connection:\n" + e.getMessage(),
|
||||
"Server Connection", OACOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
Main.getUi().openNewTab("register.oac");
|
||||
}
|
||||
|
||||
@Listener
|
||||
public void onConnected(ConnectedToProtocolServerEvent event) {
|
||||
try {
|
||||
this.getClientServerConnection().sendPacket(new WebRequestPacket(
|
||||
"index.html",
|
||||
WebRequestMethod.GET,
|
||||
Map.of(),
|
||||
null
|
||||
), TransportProtocol.TCP);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.openautonomousconnection.webclient.recode;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Initializes the JavaFX Toolkit exactly once for Swing embedding.
|
||||
*/
|
||||
public final class FxBootstrap {
|
||||
|
||||
private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false);
|
||||
|
||||
private FxBootstrap() {
|
||||
// Utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures JavaFX Toolkit is initialized.
|
||||
* Must be called before any Platform.runLater() usage.
|
||||
*/
|
||||
public static void ensureInitialized() {
|
||||
if (!INITIALIZED.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Creating a JFXPanel initializes the JavaFX toolkit in Swing apps.
|
||||
new JFXPanel();
|
||||
|
||||
// Optional: keep JavaFX runtime alive even if last window closes.
|
||||
Platform.setImplicitExit(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.openautonomousconnection.webclient.recode;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
|
||||
import lombok.Getter;
|
||||
import org.openautonomousconnection.infonamelib.InfoNames;
|
||||
import org.openautonomousconnection.oacswing.component.OACOptionPane;
|
||||
import org.openautonomousconnection.oacswing.component.design.Design;
|
||||
import org.openautonomousconnection.oacswing.component.design.DesignManager;
|
||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||
import org.openautonomousconnection.protocol.ProtocolValues;
|
||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||
import org.openautonomousconnection.webclient.recode.settings.INSList;
|
||||
import org.openautonomousconnection.webclient.recode.ui.BrowserUI;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
|
||||
public class Main {
|
||||
@Getter
|
||||
private static ClientImpl client;
|
||||
private static ProtocolBridge bridge;
|
||||
|
||||
@Getter
|
||||
private static BrowserUI ui;
|
||||
|
||||
private static void initProtocol() {
|
||||
InfoNames.registerOACInfoNameProtocols();
|
||||
|
||||
ProtocolValues values = new ProtocolValues();
|
||||
|
||||
values.packetHandler = new PacketHandler();
|
||||
values.eventManager = new EventManager();
|
||||
|
||||
client = new ClientImpl();
|
||||
|
||||
try {
|
||||
bridge = new ProtocolBridge(
|
||||
client,
|
||||
values,
|
||||
ProtocolVersion.PV_1_0_0_BETA,
|
||||
new File("logs")
|
||||
);
|
||||
|
||||
client.buildINSConnection();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
initProtocol();
|
||||
FxBootstrap.ensureInitialized();
|
||||
DesignManager.setGlobalDesign(Design.DARK);
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
ui = new BrowserUI();
|
||||
ui.setSize(1200, 800);
|
||||
ui.setLocationRelativeTo(null);
|
||||
ui.setVisible(true);
|
||||
|
||||
try {
|
||||
bridge.getProtocolValues().eventManager.registerListener(client);
|
||||
client.getClientINSConnection().connect(INSList.DEFAULT_INS, INSList.DEFAULT_PORT);
|
||||
} catch (Exception exception) {
|
||||
client.getProtocolBridge().getLogger().exception("Failed to connect to INS", exception);
|
||||
OACOptionPane.showMessageDialog(Main.getUi(), "Failed to connect to INS Server:\n" + exception.getMessage(),
|
||||
"INS Connection", OACOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.openautonomousconnection.webclient.recode.settings;
|
||||
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class INSList {
|
||||
|
||||
public static String DEFAULT_INS = "open-autonomous-connection.org";
|
||||
public static int DEFAULT_PORT = 1026;
|
||||
|
||||
private static HashMap<String, Integer> insList = new HashMap<>();
|
||||
|
||||
public static void registerINS(String host, int tcpPort) {
|
||||
insList.put(host, tcpPort);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package org.openautonomousconnection.webclient.recode.ui;
|
||||
|
||||
import org.openautonomousconnection.webclient.recode.Main;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A logical browser tab consisting of a key (tab id), a TabView (WebView),
|
||||
* and navigation helpers.
|
||||
*/
|
||||
public class BrowserTab {
|
||||
|
||||
private final String key;
|
||||
private final TabView view;
|
||||
|
||||
/**
|
||||
* Creates a browser tab.
|
||||
*
|
||||
* @param initialUrl initial URL (used for initial location value)
|
||||
* @param onLocationChange callback invoked on URL changes
|
||||
*/
|
||||
public BrowserTab(String initialUrl, Consumer<String> onLocationChange) {
|
||||
this.key = Objects.requireNonNull(initialUrl, "initialUrl"); // placeholder key overwritten by BrowserUI
|
||||
this.view = new TabView(onLocationChange);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key (tab id) used by the UI host.
|
||||
*
|
||||
* @param key tab key
|
||||
* @return this
|
||||
*/
|
||||
public BrowserTab withKey(String key) {
|
||||
// The BrowserUI uses titles as keys; keep logic simple.
|
||||
return new BrowserTabKeyed(key, view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a URL.
|
||||
*
|
||||
* @param url URL
|
||||
*/
|
||||
public void load(String url) {
|
||||
view.load(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes back in history if possible.
|
||||
*/
|
||||
public void goBack() {
|
||||
view.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Goes forward in history if possible.
|
||||
*/
|
||||
public void goForward() {
|
||||
view.forward();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the page.
|
||||
*/
|
||||
public void reload() {
|
||||
view.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current location if known.
|
||||
*
|
||||
* @return URL or null
|
||||
*/
|
||||
public String getLocation() {
|
||||
return view.getEngineLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases resources.
|
||||
*/
|
||||
public void dispose() {
|
||||
view.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tab key.
|
||||
*
|
||||
* @return key
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Swing component that renders the web content.
|
||||
*
|
||||
* @return tab view
|
||||
*/
|
||||
public TabView getView() {
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal keyed wrapper so BrowserUI can store a stable key without re-creating the WebView.
|
||||
*/
|
||||
private static final class BrowserTabKeyed extends BrowserTab {
|
||||
|
||||
private final String fixedKey;
|
||||
private final TabView fixedView;
|
||||
|
||||
private BrowserTabKeyed(String fixedKey, TabView fixedView) {
|
||||
super("about:blank", s -> { });
|
||||
this.fixedKey = Objects.requireNonNull(fixedKey, "fixedKey");
|
||||
this.fixedView = Objects.requireNonNull(fixedView, "fixedView");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return fixedKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabView getView() {
|
||||
return fixedView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(String url) {
|
||||
fixedView.load(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void goBack() {
|
||||
fixedView.back();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void goForward() {
|
||||
fixedView.forward();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload() {
|
||||
fixedView.reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocation() {
|
||||
return fixedView.getEngineLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
fixedView.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package org.openautonomousconnection.webclient.recode.ui;
|
||||
|
||||
import org.openautonomousconnection.oacswing.component.OACButton;
|
||||
import org.openautonomousconnection.oacswing.component.OACFrame;
|
||||
import org.openautonomousconnection.oacswing.component.OACPanel;
|
||||
import org.openautonomousconnection.oacswing.component.OACTextField;
|
||||
import org.openautonomousconnection.oacswing.component.design.DesignManager;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A simple multi-tab browser UI:
|
||||
* - Tab headers live in the custom title bar (OACTitleBar / OACTabbedPane).
|
||||
* - Tab content (JavaFX WebView) lives in the frame content area.
|
||||
* - Address bar controls the currently selected tab.
|
||||
*/
|
||||
public class BrowserUI extends OACFrame {
|
||||
|
||||
private static final int TITLE_BAR_HEIGHT = 42;
|
||||
|
||||
private final OACTextField addressField;
|
||||
private final OACButton goButton;
|
||||
private final OACButton newTabButton;
|
||||
private final OACButton closeTabButton;
|
||||
private final OACButton backButton;
|
||||
private final OACButton forwardButton;
|
||||
private final OACButton reloadButton;
|
||||
|
||||
private final CardLayout cardLayout;
|
||||
private final OACPanel pageHost;
|
||||
|
||||
private final Map<String, BrowserTab> tabsByKey = new LinkedHashMap<>();
|
||||
private int tabCounter = 0;
|
||||
|
||||
/**
|
||||
* Creates the browser UI and wires tab selection and address bar to tab content.
|
||||
*/
|
||||
public BrowserUI() {
|
||||
super("OAC Browser");
|
||||
|
||||
// Content pane must be offset because your title bar is an overlay in the layered pane.
|
||||
JComponent content = (JComponent) getContentPane();
|
||||
content.setLayout(new BorderLayout());
|
||||
content.setBorder(BorderFactory.createEmptyBorder(TITLE_BAR_HEIGHT, 0, 0, 0));
|
||||
|
||||
// Address bar (top of content area)
|
||||
OACPanel navBar = new OACPanel(new BorderLayout(8, 0));
|
||||
navBar.setBorder(BorderFactory.createEmptyBorder(8, 10, 8, 10));
|
||||
|
||||
OACPanel leftControls = new OACPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
||||
backButton = new OACButton("←");
|
||||
forwardButton = new OACButton("→");
|
||||
reloadButton = new OACButton("⟳");
|
||||
leftControls.add(backButton);
|
||||
leftControls.add(forwardButton);
|
||||
leftControls.add(reloadButton);
|
||||
|
||||
addressField = new OACTextField();
|
||||
goButton = new OACButton("Go");
|
||||
|
||||
OACPanel rightControls = new OACPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||
newTabButton = new OACButton("+");
|
||||
closeTabButton = new OACButton("✕");
|
||||
rightControls.add(newTabButton);
|
||||
rightControls.add(closeTabButton);
|
||||
|
||||
navBar.add(leftControls, BorderLayout.WEST);
|
||||
navBar.add(addressField, BorderLayout.CENTER);
|
||||
navBar.add(goButton, BorderLayout.EAST);
|
||||
navBar.add(rightControls, BorderLayout.EAST);
|
||||
|
||||
// Fix: BorderLayout only allows one EAST; wrap Go+RightControls in a single panel
|
||||
OACPanel east = new OACPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||
|
||||
east.add(goButton);
|
||||
east.add(newTabButton);
|
||||
east.add(closeTabButton);
|
||||
navBar.add(east, BorderLayout.EAST);
|
||||
|
||||
content.add(navBar, BorderLayout.NORTH);
|
||||
|
||||
// Page host
|
||||
cardLayout = new CardLayout();
|
||||
pageHost = new OACPanel(cardLayout);
|
||||
content.add(pageHost, BorderLayout.CENTER);
|
||||
|
||||
// Wire title bar tab selection to content host
|
||||
getTitleBar().getTabs().addChangeListener(e -> onHeaderTabChanged());
|
||||
|
||||
// Wire address bar actions
|
||||
addressField.addActionListener(e -> navigateCurrent(addressField.getText()));
|
||||
goButton.addActionListener(e -> navigateCurrent(addressField.getText()));
|
||||
|
||||
// Wire navigation buttons
|
||||
backButton.addActionListener(e -> {
|
||||
BrowserTab tab = getCurrentTab();
|
||||
if (tab != null) tab.goBack();
|
||||
});
|
||||
forwardButton.addActionListener(e -> {
|
||||
BrowserTab tab = getCurrentTab();
|
||||
if (tab != null) tab.goForward();
|
||||
});
|
||||
reloadButton.addActionListener(e -> {
|
||||
BrowserTab tab = getCurrentTab();
|
||||
if (tab != null) tab.reload();
|
||||
});
|
||||
|
||||
newTabButton.addActionListener(e -> openNewTab("info.oac"));
|
||||
closeTabButton.addActionListener(e -> closeCurrentTab());
|
||||
|
||||
// Create first tab
|
||||
DesignManager.apply(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new tab and navigates to the given URL.
|
||||
*
|
||||
* @param url initial URL
|
||||
*/
|
||||
public void openNewTab(String url) {
|
||||
String key = nextTabKey();
|
||||
BrowserTab tab = new BrowserTab(url, newLocation -> SwingUtilities.invokeLater(() -> {
|
||||
BrowserTab current = getCurrentTab();
|
||||
if (current != null && Objects.equals(current.getKey(), key)) {
|
||||
addressField.setText(newLocation);
|
||||
}
|
||||
}));
|
||||
|
||||
tabsByKey.put(key, tab);
|
||||
|
||||
// Real page content in center host
|
||||
pageHost.add(tab.getView(), key);
|
||||
|
||||
// Header tab in title bar: DO NOT place the real page here (title bar is only ~42px high).
|
||||
getTitleBar().addTab(key, new OACPanel());
|
||||
|
||||
// Select it
|
||||
int idx = getTitleBar().getTabs().getTabCount() - 1;
|
||||
getTitleBar().getTabs().setSelectedIndex(idx);
|
||||
|
||||
// Navigate
|
||||
tab.load(url);
|
||||
|
||||
// Show content
|
||||
cardLayout.show(pageHost, key);
|
||||
pageHost.revalidate();
|
||||
pageHost.repaint();
|
||||
|
||||
// Update address field immediately
|
||||
addressField.setText(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates the current tab to the given input (URL or host).
|
||||
*
|
||||
* @param input user input
|
||||
*/
|
||||
public void navigateCurrent(String input) {
|
||||
BrowserTab tab = getCurrentTab();
|
||||
if (tab == null) return;
|
||||
|
||||
String url = normalizeUrl(input);
|
||||
addressField.setText(url);
|
||||
tab.load(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the currently selected tab.
|
||||
*/
|
||||
public void closeCurrentTab() {
|
||||
int idx = getTitleBar().getTabs().getSelectedIndex();
|
||||
if (idx < 0) return;
|
||||
|
||||
String key = getTitleBar().getTabs().getTitleAt(idx);
|
||||
|
||||
BrowserTab removed = tabsByKey.remove(key);
|
||||
if (removed != null) {
|
||||
removed.dispose();
|
||||
pageHost.remove(removed.getView());
|
||||
}
|
||||
|
||||
getTitleBar().getTabs().removeTabAt(idx);
|
||||
|
||||
// If no tabs left, open a new one
|
||||
if (getTitleBar().getTabs().getTabCount() == 0) {
|
||||
openNewTab("info.oac");
|
||||
return;
|
||||
}
|
||||
|
||||
// Show selected tab content
|
||||
onHeaderTabChanged();
|
||||
}
|
||||
|
||||
private void onHeaderTabChanged() {
|
||||
BrowserTab tab = getCurrentTab();
|
||||
if (tab == null) return;
|
||||
|
||||
cardLayout.show(pageHost, tab.getKey());
|
||||
pageHost.revalidate();
|
||||
pageHost.repaint();
|
||||
|
||||
// Sync address bar
|
||||
String loc = tab.getLocation();
|
||||
if (loc != null && !loc.isBlank()) {
|
||||
addressField.setText(loc);
|
||||
}
|
||||
}
|
||||
|
||||
public BrowserTab getCurrentTab() {
|
||||
int idx = getTitleBar().getTabs().getSelectedIndex();
|
||||
if (idx < 0) return null;
|
||||
|
||||
String key = getTitleBar().getTabs().getTitleAt(idx);
|
||||
return tabsByKey.get(key);
|
||||
}
|
||||
|
||||
private String nextTabKey() {
|
||||
tabCounter++;
|
||||
return "Tab " + tabCounter;
|
||||
}
|
||||
|
||||
private static String normalizeUrl(String input) {
|
||||
String s = input == null ? "" : input.trim();
|
||||
if (s.isEmpty()) return "info.oac";
|
||||
if (s.startsWith("web://")) return s;
|
||||
return "web://" + s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package org.openautonomousconnection.webclient.recode.ui;
|
||||
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.packets.C_PacketReadEvent;
|
||||
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||
import javafx.application.Platform;
|
||||
import javafx.embed.swing.JFXPanel;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebHistory;
|
||||
import javafx.scene.web.WebView;
|
||||
import org.openautonomousconnection.infonamelib.InfoNames;
|
||||
import org.openautonomousconnection.oacswing.component.OACOptionPane;
|
||||
import org.openautonomousconnection.oacswing.component.OACPanel;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebRequestPacket;
|
||||
import org.openautonomousconnection.protocol.packets.v1_0_0.beta.web.WebResponsePacket;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.INSRecordType;
|
||||
import org.openautonomousconnection.protocol.versions.v1_0_0.beta.WebRequestMethod;
|
||||
import org.openautonomousconnection.webclient.recode.Main;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A Swing panel that embeds a JavaFX WebView.
|
||||
* The JavaFX scene is initialized on first addNotify().
|
||||
*/
|
||||
public final class TabView extends OACPanel {
|
||||
|
||||
private final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
|
||||
private JFXPanel fxPanel;
|
||||
private WebView webView;
|
||||
private WebEngine engine;
|
||||
|
||||
private final Consumer<String> onLocationChanged;
|
||||
|
||||
/**
|
||||
* Creates a new tab view.
|
||||
*
|
||||
* @param onLocationChanged callback invoked when the WebEngine location changes
|
||||
*/
|
||||
public TabView(Consumer<String> onLocationChanged) {
|
||||
super();
|
||||
this.onLocationChanged = Objects.requireNonNull(onLocationChanged, "onLocationChanged");
|
||||
setLayout(new BorderLayout());
|
||||
Main.getClient().getProtocolBridge().getProtocolValues().eventManager.registerListener(new TabListener(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify() {
|
||||
super.addNotify();
|
||||
|
||||
if (!initialized.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
fxPanel = new JFXPanel();
|
||||
add(fxPanel, BorderLayout.CENTER);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
webView = new WebView();
|
||||
engine = webView.getEngine();
|
||||
|
||||
engine.locationProperty().addListener((obs, oldV, newV) -> {
|
||||
if (newV != null) {
|
||||
onLocationChanged.accept(newV);
|
||||
}
|
||||
});
|
||||
|
||||
fxPanel.setScene(new Scene(webView));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a URL in this tab.
|
||||
*
|
||||
* @param url URL to load
|
||||
*/
|
||||
public void load(String url) {
|
||||
String[] parts = url.split("\\.");
|
||||
if (parts.length < 2 || parts.length > 3) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid INS address format: " + url +
|
||||
" (expected name.tln or sub.name.tln)"
|
||||
);
|
||||
}
|
||||
|
||||
String tln = parts[parts.length - 1];
|
||||
String name = parts[parts.length - 2];
|
||||
String sub = (parts.length == 3) ? parts[0] : null;
|
||||
|
||||
try {
|
||||
Main.getClient().sendINSQuery(tln, name, sub, INSRecordType.A);
|
||||
} catch (Exception e) {
|
||||
Main.getClient().getProtocolBridge().getLogger().exception("Failed to send INS Query", e);
|
||||
OACOptionPane.showMessageDialog(Main.getUi(), "Failed to send INS Query:\n" + e.getMessage(),
|
||||
"INS Connection", OACOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TabListener extends EventListener {
|
||||
private TabView view;
|
||||
|
||||
public TabListener(TabView view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
@Listener
|
||||
public void onListen(C_PacketReadEvent event) {
|
||||
if (event.getPacket() instanceof WebResponsePacket response) {
|
||||
view.parseHtml(new String(response.getBody()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void parseHtml(String html) {
|
||||
Platform.runLater(() -> {
|
||||
if (engine != null) engine.loadContent(html);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the current page.
|
||||
*/
|
||||
public void reload() {
|
||||
Platform.runLater(() -> {
|
||||
if (engine != null) {
|
||||
engine.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates one step back in history if possible.
|
||||
*/
|
||||
public void back() {
|
||||
Platform.runLater(() -> {
|
||||
if (engine == null) return;
|
||||
WebHistory h = engine.getHistory();
|
||||
if (h.getCurrentIndex() > 0) {
|
||||
h.go(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates one step forward in history if possible.
|
||||
*/
|
||||
public void forward() {
|
||||
Platform.runLater(() -> {
|
||||
if (engine == null) return;
|
||||
WebHistory h = engine.getHistory();
|
||||
if (h.getCurrentIndex() < h.getEntries().size() - 1) {
|
||||
h.go(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current location (best-effort, may be null until initialized).
|
||||
*
|
||||
* @return current location or null
|
||||
*/
|
||||
public String getEngineLocation() {
|
||||
// No blocking: best-effort for UI sync.
|
||||
return engine != null ? engine.getLocation() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes JavaFX scene references (best-effort).
|
||||
*/
|
||||
public void dispose() {
|
||||
Platform.runLater(() -> {
|
||||
if (engine != null) {
|
||||
try {
|
||||
engine.load(null);
|
||||
} catch (Exception ignored) {
|
||||
// Best-effort cleanup.
|
||||
}
|
||||
}
|
||||
engine = null;
|
||||
webView = null;
|
||||
if (fxPanel != null) {
|
||||
fxPanel.setScene(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user