server state and stdio streaming
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
79
islands/fabricVersions.tsx
Normal file
79
islands/fabricVersions.tsx
Normal 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
48
islands/terminal.tsx
Normal 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>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user