Something went wrong. Try again.
Starship Simulator Controls thoriumsim.com
Something went wrong. Try again.
8.1 kB · 279 lines
TSX
at develop
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280import { clientId, q } from "@thorium/context/AppContext";import { useCardContext } from "@thorium/context/CardContext";import { useStation } from "@thorium/routes/station/useStation";import Button from "@thorium/ui/Button";import Select from "@thorium/ui/Select";import SineWave from "@thorium/ui/SineWave";import { cn } from "@thorium/utils/cn";import throttle from "lodash.throttle";import { useCallback, useRef, useState } from "react";
import { SatelliteMap } from "./SatelliteMap";import { useRandomCharacterState } from "./useRandomCharacterState";
export function OutboxPage({ pageLoaded }: { pageLoaded: boolean }) { const { shipId } = useStation(); const { cardLoaded } = useCardContext(); const [longRangeComm] = q.longRangeComm.get.useNetRequest( { shipId }, { callback: (data) => { if (draggingRef.current) return; setFrequencyValue(data.frequency); setGainValue(data.gain); }, }, ); const [outgoingMessages] = q.longRangeComm.outgoingMessages.useNetRequest({ shipId, filter: "pending", });
const draggingRef = useRef(false); const [frequency, setFrequencyValue] = useState(longRangeComm.frequency || 276.25); const [gain, setGainValue] = useState(longRangeComm.gain || 1); const [selectedMessageId, setSelectedMessageId] = useState<number | null>(null); const [selectedSatellite, setSelectedSatellite] = useState<number | null>(null);
function selectSatellite(id: number | null) { setSelectedSatellite(id); q.thorium.genericEvent.netSend({ clientId, eventName: "long-range-comm-satellite-selected", properties: `${id}`, }); } const selectedMessage = outgoingMessages.find((m) => m.id === selectedMessageId);
q.longRangeComm.systemStream.useDataStream({ shipId });
const shadeCountRef = useRef(0);
const setFrequencyNetSend = useCallback( throttle((value: number) => { q.longRangeComm.setFrequency.netSend({ shipId, frequency: value }); }, 100), [], ); const setGainNetSend = useCallback( throttle((value: number) => { q.longRangeComm.setGain.netSend({ shipId, gain: value }); }, 100), [], ); function setFrequency(value: number) { setFrequencyValue(value); setFrequencyNetSend(value); } function setGain(value: number) { setGainValue(value); setGainNetSend(value); }
const scanningTextRef = useRef<HTMLDivElement>(null); function updateSatelliteText(text: string) { if (scanningTextRef.current) { scanningTextRef.current.innerText = text; } }
const [encodedMessage, setEncodedMessage] = useRandomCharacterState();
return ( <div className="grid h-full w-full grid-cols-[16rem_auto_1fr] gap-8 overflow-hidden"> <div className="outgoing-message-list row-span-2 flex h-full min-h-0 flex-col"> <h3>Outgoing Messages</h3> <ul className="panel panel-alert flex-auto overflow-y-auto"> {outgoingMessages.map((o) => ( <li key={o.id} className={cn("list-group-item cursor-pointer", { selected: selectedMessageId === o.id, })} onClick={() => { setSelectedMessageId(o.id); setEncodedMessage(o.encodedMessage, o.encodedMessage, true); q.thorium.genericEvent.netSend({ clientId, eventName: "pick-outgoing-message", properties: `${o.id}`, }); }} > {o.destinationShipName} <small className="block">{o.senderStation}</small> </li> ))} </ul> </div> <SatelliteMap className="panel panel-black panel-opaque satellite-map aspect-square w-full rounded-full" radius={gain * longRangeComm.maxSatelliteRange} shouldRender={cardLoaded && pageLoaded} frequency={frequency} updateSatelliteText={updateSatelliteText} selectedSatellite={selectedSatellite} setSelectedSatellite={selectSatellite} />
<div className="sliders-area row-span-2 flex max-h-full min-h-0 flex-col gap-4"> <div className="panel panel-neutral panel-opaque aspect-16/7 w-full"> <SineWave className="faded-scroll-x" shouldRender={cardLoaded && pageLoaded} waves={[ { amplitude: gain * 0.15, frequency: frequency / 10, phase: Math.PI / 2, }, { amplitude: gain * 0.1, frequency: (frequency / 100) ** 2, phase: Math.PI / 4, }, { amplitude: gain * 0.2, frequency: (frequency / 50) ** 2, phase: Math.PI / 3, }, ]} callFrame={(ctx, width, height) => { const widthDivisor = 4; const shadeCount = shadeCountRef.current; const shade_grad = ctx.createLinearGradient( shadeCount, 0, shadeCount + width / widthDivisor, height / widthDivisor, ); shade_grad.addColorStop(0, "rgba(255,255,255,1)"); shade_grad.addColorStop(0.1, `rgba(255,255,255,${0.5 - gain})`); shade_grad.addColorStop(0.9, `rgba(255,255,255,${0.5 - gain})`); shade_grad.addColorStop(1, "rgba(255,255,255,1)");
ctx.fillStyle = shade_grad; // new opaque pixels "erase" previous content ctx.globalCompositeOperation = "destination-out"; ctx.fillRect(0, 0, width, height);
shadeCountRef.current = shadeCount + 3; if (shadeCountRef.current > width * 1.2) { shadeCountRef.current = -width / widthDivisor; } }} /> </div> <div> <label htmlFor="frequency" className="block"> Frequency </label> <input id="frequency" type="range" className="range range-primary block w-full" min={100} max={350} step={0.25} dir="rtl" value={frequency} onPointerDown={() => { draggingRef.current = true; window.addEventListener( "pointerup", () => { draggingRef.current = false; }, { once: true }, ); }} onInput={(e) => setFrequency(Number(e.currentTarget.value))} /> </div> <div> <label htmlFor="amplitude" className="block"> Gain </label> <input id="amplitude" type="range" className="range range-error block w-full" min={0} max={1} step={0.01} value={gain} onPointerDown={() => { draggingRef.current = true; window.addEventListener( "pointerup", () => { draggingRef.current = false; }, { once: true }, ); }} onInput={(e) => { setGain(Math.min(Number(e.currentTarget.value), 1)); }} /> </div>
<div className="panel panel-alert flex-auto overflow-y-auto p-4 wrap-anywhere hyphens-auto whitespace-pre-line"> {encodedMessage} </div> <Select disabled={selectedMessage === null} className="select-primary" label="Encoding" placeholder="Encoding" labelHidden selected={selectedMessage?.encodingType || "decoded"} setSelected={async (value) => { if (!value || !selectedMessage?.id) return; const { encodedMessage } = await q.longRangeComm.setMessageEncoding.netSend({ messageId: selectedMessage.id, encoding: value, }); setEncodedMessage(selectedMessage.message, encodedMessage); }} items={[ { id: "decoded", label: "No Encoding", }, { id: "waves", label: "Marconi Encoding", }, { id: "rotation", label: "Haartsen Encoding", }, { id: "replacement", label: "Lamarr Encoding", }, ]} /> <Button className="btn-lg btn-info w-full" disabled={!selectedMessage || selectedSatellite === null} onClick={() => { if (!selectedMessage || selectedSatellite === null) return; q.longRangeComm.sendMessage.netSend({ messageId: selectedMessage.id, satelliteId: selectedSatellite, }); setSelectedMessageId(null); setEncodedMessage("", ""); }} > Send Message </Button> </div> <p className="col-start-2 mt-4 text-center text-4xl font-bold" ref={scanningTextRef}> Scanning For Satellites </p> </div> );}