286 lines
8.6 KiB
TypeScript
286 lines
8.6 KiB
TypeScript
import { useState } from "react";
|
|
import { resolveResource } from "@tauri-apps/api/path";
|
|
import { readTextFile } from "@tauri-apps/api/fs";
|
|
import { invoke } from '@tauri-apps/api/tauri'
|
|
import Modal from "react-modal";
|
|
import { platform } from '@tauri-apps/api/os';
|
|
import "./App.css";
|
|
import { Command } from '@tauri-apps/api/shell'
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import { faGitAlt } from "@fortawesome/free-brands-svg-icons/faGitAlt";
|
|
import { PhysicalPosition, appWindow } from "@tauri-apps/api/window";
|
|
|
|
// TODO: Keyboard usage
|
|
|
|
// const codes = JSON.parse(await readTextFile(await resolveResource("resources/codes.json")));
|
|
|
|
|
|
|
|
type Code = {
|
|
code: number;
|
|
type: string;
|
|
text: string;
|
|
};
|
|
|
|
// Set window to above the mouse cursor
|
|
// TODO: Fix for MacOS
|
|
invoke('get_mouse_pos', {}).then((posJson: any) => {
|
|
let pos = JSON.parse(posJson);
|
|
appWindow.innerSize().then((size: any) => {
|
|
pos.y -= size.height + 32 /*Standard windows titlebar height*/;
|
|
console.log(JSON.stringify(pos));
|
|
appWindow.setPosition(new PhysicalPosition(pos.x, pos.y));
|
|
});
|
|
|
|
});
|
|
|
|
Modal.setAppElement("#root");
|
|
|
|
function App() {
|
|
const [codes, setCodes] = useState<Array<Code>>();
|
|
const [subTopic, setSubTopic] = useState<string>("");
|
|
const [configModalOpen, setConfigModalOpen] = useState<boolean>(false);
|
|
const [droneId, setDroneId] = useState<string>(() => {
|
|
let storedId = localStorage.getItem("droneId");
|
|
return (storedId ? storedId : "0000");
|
|
});
|
|
const [autoSend, setAutoSend] = useState<boolean>(() => {
|
|
let storedAutoSend = localStorage.getItem("autoSend");
|
|
return (storedAutoSend ? storedAutoSend == "true" : false);
|
|
});
|
|
|
|
if (!codes){
|
|
resolveResource("resources/codes.json").then((path: string) => {
|
|
readTextFile(path).then((codesJson: string) => {
|
|
setCodes(JSON.parse(codesJson));
|
|
});
|
|
});
|
|
}
|
|
|
|
const getSubjects = (input: Array<Code>) => {
|
|
// TODO: Put all single-level elements at bottom
|
|
let subjects: Array<string> = [];
|
|
input.forEach((code: Code) => {
|
|
if (!subjects.includes(code.type)) {
|
|
subjects.push(code.type)
|
|
}
|
|
});
|
|
return subjects;
|
|
};
|
|
|
|
const getTopicChildren = (topic: string) => {
|
|
let topicChildren: Array<string> = [];
|
|
if (codes){
|
|
codes.forEach((code: Code) => {
|
|
if (code.type == topic) {
|
|
if (!topicChildren.includes(code.text)) {
|
|
topicChildren.push(code.text)
|
|
}
|
|
}
|
|
});
|
|
}
|
|
return topicChildren;
|
|
};
|
|
|
|
const generatePayload = (code: Code) => {
|
|
let assembledStr: string = droneId.toString();
|
|
let codeId: string = code.code.toString();
|
|
if (code.code < 10) {
|
|
codeId = "00" + codeId;
|
|
}else if (code.code < 100) {
|
|
codeId = "0" + codeId;
|
|
}
|
|
assembledStr += " :: Code " + codeId;
|
|
assembledStr += " :: " + code.type;
|
|
if (code.text != "."){
|
|
assembledStr += " :: " + code.text;
|
|
}
|
|
console.log(assembledStr);
|
|
return assembledStr;
|
|
};
|
|
|
|
const handleSubClick = (topic: string, index: number) => {
|
|
if (codes){
|
|
let text = getTopicChildren(topic)[index];
|
|
let filtered = codes.filter((code: Code) => code.type === topic && code.text === text);
|
|
if (filtered.length == 1) {
|
|
let payload: string = generatePayload(filtered[0]);
|
|
setSubTopic("");
|
|
invoke('type_str', {input: payload, autoSend});
|
|
}
|
|
}
|
|
};
|
|
|
|
const SubMenu = () => {
|
|
if (subTopic == "") {
|
|
return (<div></div>);
|
|
} else {
|
|
return (
|
|
<div className="col-8 menu">
|
|
{getTopicChildren(subTopic).map((text: string, index: number, topics: Array<string>) => {
|
|
let displayText: string = text;
|
|
displayText = displayText.replace(/.*:: /, "");
|
|
if (displayText == "") {
|
|
displayText = "...";
|
|
}
|
|
return(
|
|
<div
|
|
className={`row sub-element ${(topics.length-1 == index) ? "last" : ""}`}
|
|
key={index}
|
|
onClick={() => {
|
|
handleSubClick(subTopic, index)
|
|
}}
|
|
>
|
|
{displayText}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
const handleTopicClick = (type: string) => {
|
|
if (codes) {
|
|
let filtered = codes.filter((code: Code) => code.type === type);
|
|
if (filtered.length == 1) {
|
|
let assembledStr: string = generatePayload(filtered[0]);
|
|
setSubTopic("");
|
|
invoke('type_str', {input: assembledStr, autoSend});
|
|
}else{
|
|
// Open Submenu for type
|
|
setSubTopic(type);
|
|
}
|
|
}
|
|
};
|
|
|
|
const buildMenu = () => {
|
|
if (codes) {
|
|
return (
|
|
<div>
|
|
{getSubjects(codes).map((type: string, index: number) => {
|
|
let dispType: string = type;
|
|
let filtered = codes.filter((code: Code) => code.type === type);
|
|
if (filtered.length == 1) {
|
|
dispType += ((filtered[0].text == "")?" :: ...":((filtered[0].text == ".")?"":(" :: "+filtered[0].text)));
|
|
}
|
|
return (
|
|
<input
|
|
className={`item ${(filtered.length == 1)?"individual":""}`}
|
|
key={index}
|
|
type="button"
|
|
onClick={() => {
|
|
handleTopicClick(type)
|
|
}}
|
|
value={dispType}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}else{
|
|
return (<div></div>);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="container">
|
|
<Modal
|
|
isOpen={configModalOpen}
|
|
contentLabel="Configuration"
|
|
overlayClassName="config-overlay"
|
|
className="config-modal"
|
|
>
|
|
<div className="row">
|
|
<h1 className="col-12">
|
|
<a href="">Drone Input Tool</a>
|
|
</h1>
|
|
</div>
|
|
<div className="row">
|
|
<h2 className="col-12">
|
|
Hexcorp Drone Interface
|
|
</h2>
|
|
</div>
|
|
<div className="row">
|
|
<div className="col-6 text-end">
|
|
Drone ID
|
|
</div>
|
|
<div className="col-6 drone-id-input">
|
|
<input
|
|
type="text"
|
|
value={droneId}
|
|
onChange={(e) => {
|
|
setDroneId(e.target.value);
|
|
localStorage.setItem("droneId", e.target.value);
|
|
}}
|
|
/>
|
|
</div>
|
|
{/* TODO: Optional auto-send on pre-made messages */}
|
|
</div>
|
|
<div className="row">
|
|
<div className="col-6 text-end">
|
|
Auto-Send
|
|
</div>
|
|
<div className="col-6">
|
|
<input
|
|
type="checkbox"
|
|
checked={autoSend}
|
|
onChange={(e) => {
|
|
setAutoSend(e.target.checked);
|
|
localStorage.setItem("autoSend", e.target.checked.toString());
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="row modal-footer">
|
|
<div className="col-4" onClick={() => {
|
|
let url = "https://git.corrupt.link/liz/AllenWrench";
|
|
platform().then((os: string) => {
|
|
switch(os) {
|
|
case "win32":
|
|
new Command("open-link-win", ["-Command", "Start-Process", `${url}`]).spawn();
|
|
break;
|
|
case "linux":
|
|
new Command("open-link-linux", ["-c", "xdg-open", `${url}`]).spawn();
|
|
break;
|
|
case "macos":
|
|
new Command("open-link-macos", ["-c", "open", `${url}`]).spawn();
|
|
break;
|
|
default:
|
|
console.log(`Unknown OS: ${os}`);
|
|
break;
|
|
}
|
|
setConfigModalOpen(false);
|
|
});
|
|
|
|
}}>
|
|
<FontAwesomeIcon className="icon" icon={faGitAlt} size="2x" />
|
|
</div>
|
|
<div className="col-8 text-end modal-close" onClick={() => {
|
|
setConfigModalOpen(false);
|
|
}}>
|
|
Close
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
<div className="row">
|
|
<div className="col-4">
|
|
{buildMenu()}
|
|
</div>
|
|
<SubMenu/>
|
|
</div>
|
|
<div className="row footer">
|
|
<div className="col-12 footer-content" onClick={()=>{
|
|
setSubTopic("");
|
|
console.log(`Config modal open ${configModalOpen}`);
|
|
setConfigModalOpen(!configModalOpen);
|
|
}}>
|
|
{droneId}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default App;
|