Add setting UI for port/reconnect, swap wrong API handlers

This commit is contained in:
Zoë Hoekstra 2022-07-31 19:53:15 +02:00
parent d20f621b47
commit 5fc67c328b
No known key found for this signature in database
GPG Key ID: F9B7B7D8130F3323
7 changed files with 127 additions and 52 deletions

@ -0,0 +1,46 @@
import React from "react";
interface baubleState {
connection: boolean;
callback: () => boolean;
}
interface baubleProps {
callback: () => boolean;
}
export class ConnectionBauble extends React.Component<baubleProps> {
timerID: NodeJS.Timer;
state: baubleState;
constructor(props: baubleProps) {
super(props);
this.state = {
connection: false,
callback: props.callback
};
}
componentDidMount() : void {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() : void {
clearInterval(this.timerID);
}
tick() : void {
this.setState({
connection: this.state.callback()
});
}
render() : string {
return (
this.state.connection? "Connected" : "Disconnected"
);
}
}

@ -1,12 +1,15 @@
import { MenuItem, Select, SelectChangeEvent, TextField, Tooltip, Typography } from "@mui/material";
import { MenuItem, Select, SelectChangeEvent, TextField, Tooltip, Typography, Box } from "@mui/material";
import React, { useState } from "react";
import { IPlayer } from "../../PersonObjects/IPlayer";
import { isRemoteFileApiConnectionLive, newRemoteFileApiConnection } from "../../RemoteFileAPI/RemoteFileAPI";
import { Settings } from "../../Settings/Settings";
import { OptionSwitch } from "../../ui/React/OptionSwitch";
import { formatTime } from "../../utils/helpers/formatTime";
import { GameOptionsTab } from "../GameOptionsTab";
import { GameOptionsPage } from "./GameOptionsPage";
import { OptionsSlider } from "./OptionsSlider";
import Button from "@mui/material/Button";
import { ConnectionBauble } from "./ConnectionBauble";
interface IProps {
currentTab: GameOptionsTab;
@ -21,6 +24,7 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
const [terminalSize, setTerminalSize] = useState(Settings.MaxTerminalCapacity);
const [autosaveInterval, setAutosaveInterval] = useState(Settings.AutosaveInterval);
const [timestampFormat, setTimestampFormat] = useState(Settings.TimestampsFormat);
const [remoteFileApiPort, setRemoteFileApiPort] = useState(Settings.RemoteFileApiPort);
const [locale, setLocale] = useState(Settings.Locale);
function handleExecTimeChange(
@ -81,6 +85,11 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
Settings.TimestampsFormat = event.target.value;
}
function handleRemoteFileApiPortChange(event: React.ChangeEvent<HTMLInputElement>): void {
setRemoteFileApiPort(Number(event.target.value) as number);
Settings.RemoteFileApiPort = Number(event.target.value);
}
const pages = {
[GameOptionsTab.SYSTEM]: (
<GameOptionsPage title="System">
@ -361,6 +370,35 @@ export const CurrentOptionsPage = (props: IProps): React.ReactElement => {
</>
}
/>
<Tooltip title={
<Typography>
This port number is used to connect to a Remote File API port,
please ensure that it matches with the port the Remote File API server is publishing on (12525 by default).
Click the reconnect button to try and re-establish connection.
The little colored bauble shows whether the connection is live or not.
</Typography>
}>
<TextField
InputProps={{
startAdornment: (
<Typography
color={remoteFileApiPort > 0 && remoteFileApiPort <= 65535? "success" : "error"}
>
Remote File API port:
</Typography>
),
endAdornment: (
<Box>
<Button onClick={newRemoteFileApiConnection}>Reconnect</Button>
<ConnectionBauble callback={isRemoteFileApiConnectionLive}/>
</Box>
),
}}
value={remoteFileApiPort}
onChange={handleRemoteFileApiPortChange}
placeholder="12525"
/>
</Tooltip>
</GameOptionsPage>
),
};

@ -77,26 +77,12 @@ export const RFARequestHandler: Record<string, (message: RFAMessage) => void | R
return new RFAMessage({ result: "OK", id: msg.id });
},
getFileNames: function (msg: RFAMessage): RFAMessage {
getFileNames: function (msg: RFAMessage): RFAMessage {
if (!isFileServer(msg.params)) return error("getFileNames message misses parameters", msg);
const server = GetServer(msg.params.server);
if (server == null) return error("Server hostname invalid", msg);
const fileList: FileContent[] = [
...server.textFiles.map((txt): FileContent => { return { filename: txt.filename, content: txt.text } }),
...server.scripts.map((scr): FileContent => { return { filename: scr.filename, content: scr.code } })
];
return new RFAMessage({ result: JSON.stringify(fileList), id: msg.id });
},
getAllFiles: function (msg: RFAMessage): RFAMessage {
if (!isFileServer(msg.params)) return error("getAllFiles message misses parameters", msg);
const server = GetServer(msg.params.server);
if (server == null) return error("Server hostname invalid", msg);
const fileNameList: string[] = [
...server.textFiles.map((txt): string => txt.filename),
...server.scripts.map((scr): string => scr.filename)
@ -105,6 +91,20 @@ export const RFARequestHandler: Record<string, (message: RFAMessage) => void | R
return new RFAMessage({ result: JSON.stringify(fileNameList), id: msg.id });
},
getAllFiles: function (msg: RFAMessage): RFAMessage {
if (!isFileServer(msg.params)) return error("getAllFiles message misses parameters", msg);
const server = GetServer(msg.params.server);
if (server == null) return error("Server hostname invalid", msg);
const fileList: FileContent[] = [
...server.textFiles.map((txt): FileContent => { return { filename: txt.filename, content: txt.text } }),
...server.scripts.map((scr): FileContent => { return { filename: scr.filename, content: scr.code } })
];
return new RFAMessage({ result: JSON.stringify(fileList), id: msg.id });
},
calculateRam: function (msg: RFAMessage): RFAMessage {
if (!isFileLocation(msg.params)) return error("calculateRam message misses parameters", msg);
const fileData: FileLocation = msg.params;

@ -13,36 +13,18 @@ export class Remote {
this.port = port;
}
public stopConnection() : void {
this.connection?.close();
}
public startConnection() : void {
this.startConnectionRecurse(1, 5);
}
handleCloseEvent():void {
delete this.connection;
RFALogger.log("Connection closed.");
}
startConnectionRecurse(retryN : number, retryMax : number) : void {
RFALogger.log("Trying to connect.");
this.connection = new WebSocket(this.protocol + "://" + this.ipaddr + ":" + this.port);
this.connection.addEventListener("error", (e:Event) => {
if(!this.connection) return;
// When having trouble connecting, try again.
if (this.connection.readyState === 3) {
RFALogger.log(`Connection lost, retrying (try #${retryN}).`);
if (retryN <= retryMax) this.startConnectionRecurse(retryN + 1, retryMax);
return;
}
// Else handle the error normally
RFALogger.error(e);
});
this.connection.addEventListener("error", (e:Event) => RFALogger.error(e));
this.connection.addEventListener("message", handleMessageEvent);
this.connection.addEventListener("open", () => RFALogger.log("Connection established: ", this.ipaddr, ":", this.port));
this.connection.addEventListener("close", this.handleCloseEvent);
this.connection.addEventListener("close", () => RFALogger.log("Connection closed"));
}
}

@ -1,16 +1,19 @@
import { Settings } from "../Settings/Settings";
import { Remote } from "./Remote";
class RemoteFileAPI {
server : Remote;
constructor(){
this.server = new Remote("localhost", 12525);
return;
}
let server: Remote;
enable() : void {
this.server.startConnection();
export function newRemoteFileApiConnection() : void {
if(server == undefined)
server = new Remote("localhost", Settings.RemoteFileApiPort);
else {
server.stopConnection();
server = new Remote("localhost", Settings.RemoteFileApiPort);
server.startConnection();
}
}
export const RFA = new RemoteFileAPI;
export function isRemoteFileApiConnectionLive() : boolean {
return server.connection != undefined && server.connection.readyState == 1;
}

@ -84,6 +84,11 @@ interface IDefaultSettings {
*/
MaxTerminalCapacity: number;
/**
* Port the Remote File API client will try to connect to.
*/
RemoteFileApiPort: number;
/**
* Save the game when you save any file.
*/
@ -206,6 +211,7 @@ export const defaultSettings: IDefaultSettings = {
MaxLogCapacity: 50,
MaxPortCapacity: 50,
MaxTerminalCapacity: 500,
RemoteFileApiPort: 12525,
SaveGameOnFileSave: true,
SuppressBuyAugmentationConfirmation: false,
SuppressFactionInvites: false,
@ -248,6 +254,7 @@ export const Settings: ISettings & ISelfInitializer & ISelfLoading = {
MaxTerminalCapacity: defaultSettings.MaxTerminalCapacity,
OwnedAugmentationsOrder: OwnedAugmentationsOrderSetting.AcquirementTime,
PurchaseAugmentationsOrder: PurchaseAugmentationsOrderSetting.Default,
RemoteFileApiPort: defaultSettings.RemoteFileApiPort,
SaveGameOnFileSave: defaultSettings.SaveGameOnFileSave,
SuppressBuyAugmentationConfirmation: defaultSettings.SuppressBuyAugmentationConfirmation,
SuppressFactionInvites: defaultSettings.SuppressFactionInvites,

@ -5,7 +5,7 @@ import { TTheme as Theme, ThemeEvents, refreshTheme } from "./Themes/ui/Theme";
import { LoadingScreen } from "./ui/LoadingScreen";
import { initElectron } from "./Electron";
import { RFA } from "./RemoteFileAPI/RemoteFileAPI";
import { newRemoteFileApiConnection } from "./RemoteFileAPI/RemoteFileAPI";
initElectron();
globalThis["React"] = React;
@ -17,8 +17,7 @@ ReactDOM.render(
document.getElementById("root"),
);
console.log("[RFA] Fix this hack ASAP");
RFA.enable();
newRemoteFileApiConnection();
function rerender(): void {
refreshTheme();