Examples
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -108,12 +108,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>Protocol</artifactId>
|
<artifactId>Protocol</artifactId>
|
||||||
<version>1.0.0-BETA.7.4</version>
|
<version>1.0.0-BETA.7.7</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>OACSwing</artifactId>
|
<artifactId>OACSwing</artifactId>
|
||||||
<version>1.0.0-BETA.1.1</version>
|
<version>1.0.0-BETA.1.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
|
|||||||
@@ -95,6 +95,6 @@ public class Main {
|
|||||||
private static void initDesigns() {
|
private static void initDesigns() {
|
||||||
//TODO
|
//TODO
|
||||||
|
|
||||||
DesignManager.getInstance().registerComponent(DOMContainerPanel.class, OACColor.DARK_BACKGROUND);
|
//DesignManager.getInstance().registerComponent(DOMContainerPanel.class, OACColor.DARK_BACKGROUND);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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