Basic mod manager
This commit is contained in:
parent
5779cd9efc
commit
1e9868348d
@ -3,37 +3,43 @@ import { useEffect, useRef, useState } from "preact/hooks";
|
|||||||
import { JSX } from "preact/jsx-runtime";
|
import { JSX } from "preact/jsx-runtime";
|
||||||
import { Sockpuppet } from "puppet/client";
|
import { Sockpuppet } from "puppet/client";
|
||||||
|
|
||||||
|
|
||||||
export function Terminal(props: { channelId: string }) {
|
export function Terminal(props: { channelId: string }) {
|
||||||
const puppet = useRef(new Sockpuppet("ws://sockpuppet.cyborggrizzly.com"));
|
const puppet = useRef(new Sockpuppet("ws://sockpuppet.cyborggrizzly.com"));
|
||||||
const [lines, setLines] = useState<string[]>([]);
|
const [lines, setLines] = useState<string[]>([]);
|
||||||
const divRef = useRef<HTMLDivElement>(null);
|
const divRef = useRef<HTMLDivElement>(null);
|
||||||
const storeKey = 'commandHistory';
|
const storeKey = "commandHistory";
|
||||||
const [commandHistory, setCommandHistory] = useState<string[]>(JSON.parse(localStorage.getItem(storeKey) || '[]'));
|
const [commandHistory, setCommandHistory] = useState<string[]>(
|
||||||
|
JSON.parse(localStorage.getItem(storeKey) || "[]"),
|
||||||
|
);
|
||||||
const [historyIndex, setHistoryIndex] = useState(commandHistory.length);
|
const [historyIndex, setHistoryIndex] = useState(commandHistory.length);
|
||||||
|
|
||||||
const [command, setCommand] = useState("");
|
const [command, setCommand] = useState("");
|
||||||
|
|
||||||
const changeHistoryIndex = (by: number) => setHistoryIndex(i => i + by);
|
const changeHistoryIndex = (by: number) => setHistoryIndex((i) => i + by);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!IS_BROWSER) return;
|
if (!IS_BROWSER) return;
|
||||||
puppet.current.joinChannel(props.channelId, (line) => {
|
puppet.current.joinChannel(props.channelId, (line) => {
|
||||||
setLines((l) => [...l, line]);
|
setLines((l) => [...l, line]);
|
||||||
});
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
const channel = puppet.current.getChannel(props.channelId)
|
||||||
|
// console.log(channel)
|
||||||
|
channel?.send("log");
|
||||||
|
}, 200);
|
||||||
|
|
||||||
document.addEventListener('keyup', (e) => {
|
document.addEventListener("keyup", (e) => {
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'Up':
|
case "Up":
|
||||||
case 'ArrowUp':
|
case "ArrowUp":
|
||||||
changeHistoryIndex(-1);
|
changeHistoryIndex(-1);
|
||||||
break;
|
break;
|
||||||
case 'Down':
|
case "Down":
|
||||||
case 'ArrowDown':
|
case "ArrowDown":
|
||||||
changeHistoryIndex(1);
|
changeHistoryIndex(1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -44,25 +50,34 @@ export function Terminal(props: { channelId: string }) {
|
|||||||
|
|
||||||
const sendCommand = (e: Event) => {
|
const sendCommand = (e: Event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
puppet.current.getChannel(props.channelId)?.send(historyIndex === commandHistory.length ? command : commandHistory[historyIndex]);
|
puppet.current.getChannel(props.channelId)?.send(
|
||||||
setCommandHistory(c => [...c, command]);
|
historyIndex === commandHistory.length
|
||||||
setHistoryIndex(commandHistory.length + 1)
|
? command
|
||||||
|
: commandHistory[historyIndex],
|
||||||
|
);
|
||||||
|
setCommandHistory((c) => [...c, command]);
|
||||||
|
setHistoryIndex(commandHistory.length + 1);
|
||||||
setCommand("");
|
setCommand("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCommandUpdate = (e: JSX.TargetedEvent<HTMLInputElement, Event>) => {
|
const handleCommandUpdate = (
|
||||||
|
e: JSX.TargetedEvent<HTMLInputElement, Event>,
|
||||||
|
) => {
|
||||||
const value = e.currentTarget.value;
|
const value = e.currentTarget.value;
|
||||||
|
|
||||||
if (historyIndex !== commandHistory.length) {
|
if (historyIndex !== commandHistory.length) {
|
||||||
setHistoryIndex(commandHistory.length);
|
setHistoryIndex(commandHistory.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
setCommand(value || '')
|
setCommand(value || "");
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem(storeKey, JSON.stringify(commandHistory.slice(0,100)));
|
localStorage.setItem(
|
||||||
}, [commandHistory])
|
storeKey,
|
||||||
|
JSON.stringify(commandHistory.slice(0, 100)),
|
||||||
|
);
|
||||||
|
}, [commandHistory]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { AppProps } from "$fresh/server.ts";
|
import { AppProps } from "$fresh/server.ts";
|
||||||
import { Nav } from "../components/nav/index.tsx";
|
import { Nav } from "../components/nav/index.tsx";
|
||||||
|
import { StatusManager } from "../islands/statusManager.tsx";
|
||||||
|
|
||||||
export default function App({ Component }: AppProps) {
|
export default function App({ Component }: AppProps) {
|
||||||
return (
|
return (
|
||||||
@ -44,6 +45,10 @@ export default function App({ Component }: AppProps) {
|
|||||||
<Component />
|
<Component />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="fixed bottom-0 right-0 rounded-tl-3xl border-t-4 border-l-4 border-sky px-4 py-2 bg-smoke-600">
|
||||||
|
<StatusManager />
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
@ -2,35 +2,159 @@ import { FunctionComponent } from "preact";
|
|||||||
import { Content } from "../../components/Content.tsx";
|
import { Content } from "../../components/Content.tsx";
|
||||||
import { SERVER_STATE } from "../../state/serverState.ts";
|
import { SERVER_STATE } from "../../state/serverState.ts";
|
||||||
import { FileUploader } from "../../islands/fileUploader.tsx";
|
import { FileUploader } from "../../islands/fileUploader.tsx";
|
||||||
|
import { Handlers, PageProps } from "$fresh/server.ts";
|
||||||
|
import { ensureFile } from "$std/fs/ensure_file.ts";
|
||||||
|
import { ensureDir } from "$std/fs/ensure_dir.ts";
|
||||||
|
import { Button } from "../../components/Button.tsx";
|
||||||
|
|
||||||
export default async function ModsFolder() {
|
export const handler: Handlers = {
|
||||||
const files: string[] = [];
|
async POST(req, _ctx) {
|
||||||
|
const formData = await req.formData();
|
||||||
|
const filePath = formData.get("filePath") as string;
|
||||||
|
if (typeof filePath !== "string") {
|
||||||
|
throw "File path not included in mod enable/disable";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formData.get("delete")) {
|
||||||
|
await Deno.remove(filePath);
|
||||||
|
return Response.redirect(req.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = filePath.split("/").at(-1);
|
||||||
|
|
||||||
|
if (!fileName) throw "Unable to infer filename in mod enable/disable";
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Deno.lstat(filePath);
|
||||||
|
} catch {
|
||||||
|
return Response.redirect(req.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filePath.includes("disabled")) {
|
||||||
|
await ensureFile("./server/mods/" + fileName);
|
||||||
|
await Deno.rename(filePath, "./server/mods/" + fileName);
|
||||||
|
} else {
|
||||||
|
await ensureFile("./server/disabled-mods/" + fileName);
|
||||||
|
await Deno.rename(filePath, "./server/disabled-mods/" + fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.redirect(req.url);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function ModsFolder({ url }: PageProps) {
|
||||||
|
const activeMods: string[] = [];
|
||||||
|
const disabledMods: string[] = [];
|
||||||
if (
|
if (
|
||||||
SERVER_STATE.serverType !== "unset" && SERVER_STATE.serverType !== "vanilla"
|
SERVER_STATE.serverType !== "unset" && SERVER_STATE.serverType !== "vanilla"
|
||||||
) {
|
) {
|
||||||
for await (const fileEntry of Deno.readDir("./server/mods")) {
|
for await (const fileEntry of Deno.readDir("./server/mods")) {
|
||||||
if (fileEntry.isFile) {
|
if (fileEntry.isFile) {
|
||||||
files.push(fileEntry.name);
|
activeMods.push(fileEntry.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ensureDir("./server/disabled-mods");
|
||||||
|
for await (const fileEntry of Deno.readDir("./server/disabled-mods")) {
|
||||||
|
if (fileEntry.isFile) {
|
||||||
|
disabledMods.push(fileEntry.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container p-8">
|
<div className="container p-8 flex flex-col gap-8">
|
||||||
<Content>
|
<Content>
|
||||||
<h2 class="font-pixel text-xl">Active Mods</h2>
|
<h2 class="font-pixel text-xl">Active Mods</h2>
|
||||||
<FileUploader path="./server/mods">
|
<FileUploader path="./server/mods">
|
||||||
<div className="relative grid lg:grid-cols-3 min-h-[100px]">
|
<div class="min-h-[100px]">
|
||||||
{!files.length && (
|
<div className="relative grid lg:grid-cols-3 gap-8">
|
||||||
<div class="absolute place-self-center">Drop files here to upload</div>
|
{!activeMods.length && (
|
||||||
|
<div class="absolute place-self-center">
|
||||||
|
Drop files here to upload
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{files.map((f) => (
|
{activeMods.map((f) => (
|
||||||
|
<div class="flex justify-between">
|
||||||
<div class="flex gap-2 items-center">
|
<div class="flex gap-2 items-center">
|
||||||
<FileIcon fileName={f} />
|
<FileIcon fileName={f} />
|
||||||
{f}
|
{f}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<form action={url.pathname} method="POST">
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="filePath"
|
||||||
|
value={"./server/mods/" + f}
|
||||||
|
/>
|
||||||
|
<Button type="submit">Disable</Button>
|
||||||
|
</form>
|
||||||
|
<form action={url.pathname} method="POST">
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="filePath"
|
||||||
|
value={"./server/disabled-mods/" + f}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="delete"
|
||||||
|
value="true"
|
||||||
|
/>
|
||||||
|
<Button color="fire" type="submit">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</FileUploader>
|
||||||
|
</Content>
|
||||||
|
<Content>
|
||||||
|
<h2 class="font-pixel text-xl">Disabled Mods</h2>
|
||||||
|
<FileUploader path="./server/disabled-mods">
|
||||||
|
<div class="min-h-[100px]">
|
||||||
|
<div className="relative grid lg:grid-cols-3 gap-8">
|
||||||
|
{!disabledMods.length && (
|
||||||
|
<div class="absolute place-self-center">
|
||||||
|
Drop files here to upload
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{disabledMods.map((f) => (
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<FileIcon fileName={f} />
|
||||||
|
{f}
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<form action={url.pathname} method="POST">
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="filePath"
|
||||||
|
value={"./server/disabled-mods/" + f}
|
||||||
|
/>
|
||||||
|
<Button type="submit">Enable</Button>
|
||||||
|
</form>
|
||||||
|
<form action={url.pathname} method="POST">
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="filePath"
|
||||||
|
value={"./server/disabled-mods/" + f}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="delete"
|
||||||
|
value="true"
|
||||||
|
/>
|
||||||
|
<Button color="fire" type="submit">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</FileUploader>
|
</FileUploader>
|
||||||
</Content>
|
</Content>
|
||||||
</div>
|
</div>
|
||||||
@ -39,9 +163,9 @@ export default async function ModsFolder() {
|
|||||||
|
|
||||||
const FileIcon: FunctionComponent<{ fileName: string }> = ({ fileName }) => {
|
const FileIcon: FunctionComponent<{ fileName: string }> = ({ fileName }) => {
|
||||||
let icon;
|
let icon;
|
||||||
switch (fileName.split(".")[1]) {
|
switch (fileName.split(".").at(-1)) {
|
||||||
case "jar":
|
case "jar":
|
||||||
icon = "fa-brand fa-java";
|
icon = "fab fa-java";
|
||||||
break;
|
break;
|
||||||
case "tmp":
|
case "tmp":
|
||||||
case "temp":
|
case "temp":
|
||||||
|
@ -56,7 +56,11 @@ class ServerState {
|
|||||||
this.sockpuppet = new Sockpuppet(
|
this.sockpuppet = new Sockpuppet(
|
||||||
"ws://sockpuppet.cyborggrizzly.com",
|
"ws://sockpuppet.cyborggrizzly.com",
|
||||||
() => {
|
() => {
|
||||||
this.sockpuppet.joinChannel(this.channelId, (msg) => {
|
this.sockpuppet.joinChannel(this.channelId, async (msg) => {
|
||||||
|
if (msg === 'log' && !IS_BROWSER) {
|
||||||
|
const log = await Deno.readTextFile('./server/logs/latest.log');
|
||||||
|
this.channel?.send(log);
|
||||||
|
} else
|
||||||
this.sendStdIn(msg);
|
this.sendStdIn(msg);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user