server state and stdio streaming

This commit is contained in:
2023-10-03 02:57:35 -06:00
parent dc4c8efeb2
commit 4a4563ba85
25 changed files with 502 additions and 80 deletions

View File

@@ -1,16 +0,0 @@
import type { Signal } from "@preact/signals";
import { Button } from "../components/Button.tsx";
interface CounterProps {
count: Signal<number>;
}
export default function Counter(props: CounterProps) {
return (
<div class="flex gap-8 py-6">
<Button color="fire" onClick={() => props.count.value -= 1}>-1</Button>
<p class="text-3xl">{props.count}</p>
<Button color="sky" onClick={() => props.count.value += 1}>+1</Button>
</div>
);
}

View File

@@ -0,0 +1,79 @@
import { useEffect, useState } from "preact/hooks";
import { FabricGame, FabricInstaller, FabricLoader } from "../types/fabric.ts";
import { Loader } from "../components/Loader.tsx";
import { Button } from "../components/Button.tsx";
export function FabricVersions() {
const [game, setGame] = useState<FabricGame[]>([]);
const [installer, setInstaller] = useState<FabricInstaller[]>([]);
const [loader, setLoader] = useState<FabricLoader[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [includeSnapshots, setIncludeSnapshots] = useState(false);
useEffect(() => {
let poll: number;
if (isLoading) {
poll = setInterval(async () => {
const res = await fetch("/api/fabric");
if (res.status !== 200) return;
const json: {
gameVersions: FabricGame[];
installerVersions: FabricInstaller[];
loaderVersions: FabricLoader[];
} = await res.json();
setGame(json.gameVersions);
setInstaller(json.installerVersions);
setLoader(json.loaderVersions);
setIsLoading(false);
}, 500);
}
return () => clearInterval(poll);
}, [isLoading]);
return isLoading ? <Loader /> : (
<form action="/api/fabric" method="POST">
<div class="grid grid-cols-3 gap-8">
<div>
<label class="block" htmlFor="game">Game Version</label>
<select name="game">
{game.filter((g) => includeSnapshots || g.stable).map((g) => (
<option>{g.version}</option>
))}
</select>
<br />
<label>
<input
type="checkbox"
checked={includeSnapshots}
onChange={() => setIncludeSnapshots(!includeSnapshots)}
/>{" "}
Include Snapshots?
</label>
</div>
<div>
<label class="block" htmlFor="loader">Loader Version</label>
<select name="loader">
{loader.map((g) => <option>{g.version}</option>)}
</select>
<small class="block">
You probably don't need to change this one.
</small>
</div>
<div>
<label class="block" htmlFor="installer">Installer Version</label>
<select name="installer">
{installer.map((g) => <option>{g.version}</option>)}
</select>
<small class="block">
You probably don't need to change this one.
</small>
</div>
</div>
<div class="w-full">
<Button class="ml-auto text-right text-lg font-pixel block mt-8" type="submit">Let's Go!</Button>
</div>
</form>
);
}

48
islands/terminal.tsx Normal file
View File

@@ -0,0 +1,48 @@
import { IS_BROWSER } from "$fresh/runtime.ts";
import { useEffect, useRef, useState } from "preact/hooks";
import { Sockpuppet } from "puppet/client";
export function Terminal(props: { channelId: string }) {
const puppet = useRef(new Sockpuppet("ws://sockpuppet.cyborggrizzly.com"));
const [lines, setLines] = useState<string[]>([]);
const divRef = useRef<HTMLDivElement>(null);
const [command, setCommand] = useState("");
useEffect(() => {
if (!IS_BROWSER) return;
puppet.current.joinChannel(props.channelId, (line) => {
setLines((l) => [...l, line]);
});
}, []);
useEffect(() => {
if (divRef.current) {
divRef.current.scrollTop = divRef.current.scrollHeight;
}
}, [lines]);
const sendCommand = (e: Event) => {
e.preventDefault();
puppet.current.getChannel(props.channelId)?.send(command);
setCommand("");
};
return (
<div>
<div ref={divRef} class="flex flex-col h-[600px] w-full overflow-auto">
{lines.map((l) => <div class="font-mono w-full">{l}</div>)}
</div>
<div>
<form onSubmit={sendCommand}>
<input
type="text"
class="w-full bg-smoke-600 text-white"
value={command}
onInput={(e) => setCommand((e.target as HTMLInputElement).value)}
/>
</form>
</div>
</div>
);
}