Active player list

This commit is contained in:
2023-10-04 04:09:01 -06:00
parent 4a4563ba85
commit b63db82a99
27 changed files with 544 additions and 136 deletions

55
islands/players.tsx Normal file
View File

@@ -0,0 +1,55 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { StatusManager } from "./statusManager.tsx";
import { IS_BROWSER } from "$fresh/runtime.ts";
import { PlayerData } from "../util/players.ts";
import { Button } from "../components/Button.tsx";
export function ActivePlayerList() {
const [players, setPlayers] = useState<PlayerData[]>([]);
useEffect(() => {
if (!IS_BROWSER) return;
configureEventSource();
}, []);
const eventSource = useRef<EventSource>();
const configureEventSource = () => {
if (eventSource.current?.OPEN) eventSource.current.close();
eventSource.current = new EventSource("/api/players");
eventSource.current.addEventListener("players", (e) => {
setPlayers(JSON.parse(e.data));
});
};
const followStatusManager = (action: string) => {
if (action === "started" || action === "restarted") {
configureEventSource();
}
};
return (
<div>
<StatusManager onAction={followStatusManager} />
<div class="grid grid-cols-2 p-8">
<h2 class="col-span-2 font-pixel text-xl">Active Players</h2>
{players.map((p) => <PlayerCard player={p} />)}
</div>
</div>
);
}
function PlayerCard({ player }: { player: PlayerData }) {
return (
<div class="flex gap-4">
<img class="w-16" src={player.avatar} alt={`${player.username}'s avatar`} />
<div>
<h3>{player.username}</h3>
<small class="opacity-50">UUID: {player.id}</small>
<div>
Did you know that bats are the only mammal that can fly?
</div>
</div>
</div>
);
}

51
islands/statusManager.tsx Normal file
View File

@@ -0,0 +1,51 @@
import { JSX } from "preact";
import { Button } from "../components/Button.tsx";
import { ManageAction } from "../routes/api/manage.ts";
import { useEffect, useState } from "preact/hooks";
import { IS_BROWSER } from "$fresh/runtime.ts";
export function StatusManager(
props: JSX.HTMLAttributes<HTMLDivElement> & {
onAction?: (res: string) => void;
},
) {
const sendCommand = async (action: ManageAction) => {
console.log(action);
const res = await fetch("/api/manage", {
method: "POST",
body: action,
});
const body = await res.text();
props.onAction && props.onAction(body);
};
const [status, setStatus] = useState('');
const getStatus = async () => {
const res = await fetch('/api/manage');
const body = await res.text();
setStatus(body);
}
useEffect(() => {
if (IS_BROWSER) getStatus();
},[])
return (
<div {...props}>
{!!status && <small>Server is {status}</small>}
<div class="flex gap-4">
<Button color="wasabi" disabled={!status || status === 'running'} onClick={() => sendCommand(ManageAction.start)}>
Start
</Button>
<Button color="fire" disabled={!status || status === 'stopped'} onClick={() => sendCommand(ManageAction.stop)}>
Stop
</Button>
<Button color="sky" disabled={!status} onClick={() => sendCommand(ManageAction.restart)}>
Restart
</Button>
</div>
</div>
);
}

View File

@@ -1,19 +1,40 @@
import { IS_BROWSER } from "$fresh/runtime.ts";
import { useEffect, useRef, useState } from "preact/hooks";
import { JSX } from "preact/jsx-runtime";
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 storeKey = 'commandHistory';
const [commandHistory, setCommandHistory] = useState<string[]>(JSON.parse(localStorage.getItem(storeKey) || '[]'));
const [historyIndex, setHistoryIndex] = useState(commandHistory.length);
const [command, setCommand] = useState("");
const changeHistoryIndex = (by: number) => setHistoryIndex(i => i + by);
useEffect(() => {
if (!IS_BROWSER) return;
puppet.current.joinChannel(props.channelId, (line) => {
setLines((l) => [...l, line]);
});
document.addEventListener('keyup', (e) => {
console.log(e.key)
switch (e.key) {
case 'Up':
case 'ArrowUp':
changeHistoryIndex(-1);
break;
case 'Down':
case 'ArrowDown':
changeHistoryIndex(1);
break;
}
})
}, []);
useEffect(() => {
@@ -24,22 +45,38 @@ export function Terminal(props: { channelId: string }) {
const sendCommand = (e: Event) => {
e.preventDefault();
puppet.current.getChannel(props.channelId)?.send(command);
puppet.current.getChannel(props.channelId)?.send(historyIndex === commandHistory.length ? command : commandHistory[historyIndex]);
setCommandHistory(c => [...c, command]);
setHistoryIndex(commandHistory.length + 1)
setCommand("");
};
const handleCommandUpdate = (e: JSX.TargetedEvent<HTMLInputElement, Event>) => {
const value = e.currentTarget.value;
if (historyIndex !== commandHistory.length) {
setHistoryIndex(commandHistory.length);
}
setCommand(value || '')
}
useEffect(() => {
localStorage.setItem(storeKey, JSON.stringify(commandHistory.slice(0,100)));
}, [commandHistory])
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>)}
{lines.map((l) => <pre class="font-mono w-full">{l}</pre>)}
</div>
<div>
<div class="mt-4">
<form onSubmit={sendCommand}>
<input
type="text"
class="w-full bg-smoke-600 text-white"
value={command}
onInput={(e) => setCommand((e.target as HTMLInputElement).value)}
value={commandHistory[historyIndex] || command}
onInput={handleCommandUpdate}
/>
</form>
</div>