Improvements

This commit is contained in:
2026-02-09 15:12:34 +01:00
parent 33f467cf02
commit d0027e32d2
6 changed files with 45 additions and 25 deletions
+5 -5
View File
@@ -4,11 +4,11 @@ import * as Core from "@/modules/live-run/core";
import { getSession } from "@/modules/account/core"; import { getSession } from "@/modules/account/core";
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {
const { runId, moves } = await req.json(); const { runId, moves, file } = await req.json();
if (!runId || !moves) if (!runId || !moves || file === undefined)
return NextResponse.json( return NextResponse.json(
{ error: "body requires runId and moves" }, { error: "body requires runId and moves and file" },
{ status: 400 }, { status: 400 },
); );
@@ -27,9 +27,9 @@ export async function POST(req: NextRequest) {
{ status: 401 }, { status: 401 },
); );
console.log(moves); console.log(moves, file);
const run = await Core.addLiveRunMoves(liveRun.id, moves); const run = await Core.addLiveRunMoves(liveRun.id, file, moves);
return NextResponse.json({ ok: true, run }); return NextResponse.json({ ok: true, run });
} }
+8 -9
View File
@@ -1,7 +1,7 @@
import { WebSocketServer, WebSocket } from "ws"; import { WebSocketServer, WebSocket } from "ws";
import Redis from "ioredis"; import Redis from "ioredis";
import { LiveRunMove } from "@/modules/live-run/types";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { LiveRunEvent } from "@/modules/live-run/types";
export function GET(req: NextRequest) { export function GET(req: NextRequest) {
const headers = new Headers(); const headers = new Headers();
@@ -11,17 +11,18 @@ export function GET(req: NextRequest) {
} }
export function UPGRADE(client: WebSocket, server: WebSocketServer) { export function UPGRADE(client: WebSocket, server: WebSocketServer) {
console.log("A client connected");
const subscriber = new Redis(); const subscriber = new Redis();
let runId: string; let runId: string;
client.once("message", (message) => { client.once("message", (message) => {
runId = message.toString(); runId = message.toString();
let file: string | null;
const sendText = async () => { const sendText = async () => {
const text = await subscriber.get(`liveRunText:${runId}`); const text = await subscriber.get(`liveRunText:${runId}`);
client.send(JSON.stringify({ moves: [], text })); if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ file, moves: [], text }));
}
}; };
sendText(); sendText();
@@ -32,15 +33,14 @@ export function UPGRADE(client: WebSocket, server: WebSocketServer) {
if (err) { if (err) {
console.error("Failed to subscribe", err); console.error("Failed to subscribe", err);
client.close(1011, "Redis subscription failed"); client.close(1011, "Redis subscription failed");
} else {
console.log(`Subscribed to ${count} channels`);
} }
}); });
subscriber.on("message", (channel, message) => { subscriber.on("message", (channel, message) => {
const moves = JSON.parse(message); const data: LiveRunEvent = JSON.parse(message);
file = data.file;
if (client.readyState === WebSocket.OPEN) { if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ moves, text: null })); client.send(JSON.stringify({ moves: data.moves, file, text: null }));
} }
}); });
}); });
@@ -48,7 +48,6 @@ export function UPGRADE(client: WebSocket, server: WebSocketServer) {
client.once("close", async () => { client.once("close", async () => {
if (runId) await subscriber.unsubscribe(`liveRun:${runId}`); if (runId) await subscriber.unsubscribe(`liveRun:${runId}`);
subscriber.quit(); subscriber.quit();
console.log("Client disconnected, Redis unsubscribed");
}); });
client.on("error", (err) => { client.on("error", (err) => {
+14 -4
View File
@@ -39,7 +39,13 @@ function getLanguageExtension(lang: Language) {
} }
} }
export default function Editor({ run }: { run: LiveRun | null | undefined }) { export default function Editor({
run,
setFile,
}: {
run: LiveRun | null | undefined;
setFile: (s: string | null) => void;
}) {
const [editorView, setEditorView] = useState<EditorView>(); const [editorView, setEditorView] = useState<EditorView>();
const scheduledTimeouts = useRef<NodeJS.Timeout[]>([]); const scheduledTimeouts = useRef<NodeJS.Timeout[]>([]);
const [text, setText] = useState(""); const [text, setText] = useState("");
@@ -55,9 +61,13 @@ export default function Editor({ run }: { run: LiveRun | null | undefined }) {
}; };
ws.onmessage = (m) => { ws.onmessage = (m) => {
const data: { moves: LiveRunMove[]; text: string | null } = JSON.parse( const data: {
m.data, moves: LiveRunMove[];
); file: string | null;
text: string | null;
} = JSON.parse(m.data);
setFile(data.file);
if (data.text) { if (data.text) {
setText(data.text); setText(data.text);
+6 -5
View File
@@ -12,19 +12,20 @@ import ProblemCard from "@/app/problems/components/ProblemCard";
import ProfileCard from "./components/ProfileCard"; import ProfileCard from "./components/ProfileCard";
import { getProblem } from "@/modules/problems/actions"; import { getProblem } from "@/modules/problems/actions";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { useState } from "react";
export default function Run() { export default function Run() {
const params = useParams<{ runId: string }>(); const params = useParams<{ runId: string }>();
const [run] = useActionInterval(() => getLiveRun(params.runId), 5000, [ const [run] = useAction(() => getLiveRun(params.runId), [params]);
params,
]);
const [problem] = useAction( const [problem] = useAction(
async () => (run?.id ? getProblem(run.problem) : null), async () => (run?.id ? getProblem(run.problem) : null),
[run], [run],
); );
const [file, setFile] = useState<string | null>(null);
const chatMessages: ChatMessage[] = [ const chatMessages: ChatMessage[] = [
{ user: "Alice", message: "Nice split!", time: "1:53" }, { user: "Alice", message: "Nice split!", time: "1:53" },
{ user: "Bob", message: "Watch out for the string problem!", time: "1:54" }, { user: "Bob", message: "Watch out for the string problem!", time: "1:54" },
@@ -83,10 +84,10 @@ export default function Run() {
{/* Code */} {/* Code */}
<div className="flex-1 w-full max-h-svh md:max-h-full h-full pl-0 md:pl-5 flex flex-col"> <div className="flex-1 w-full max-h-svh md:max-h-full h-full pl-0 md:pl-5 flex flex-col">
<header className="text-sm font-medium text-muted-foreground flex items-center gap-1"> <header className="text-sm font-medium text-muted-foreground flex items-center gap-1">
<File size={16} /> myfile.js <File size={16} /> {file}
</header> </header>
<div className="flex-1 pt-3 pb-4 md:pb-5 w-full h-full"> <div className="flex-1 pt-3 pb-4 md:pb-5 w-full h-full">
<Editor run={run} /> <Editor run={run} setFile={setFile} />
</div> </div>
</div> </div>
</CardContent> </CardContent>
+6 -2
View File
@@ -50,7 +50,11 @@ export async function removeLiveRun(id: string) {
/** /**
* Add moves to a live run (ephemeral) * Add moves to a live run (ephemeral)
*/ */
export async function addLiveRunMoves(runId: string, moves: LiveRunMove[]) { export async function addLiveRunMoves(
runId: string,
file: string,
moves: LiveRunMove[],
) {
const redis = new Redis(); const redis = new Redis();
// Make sure the live run exists // Make sure the live run exists
@@ -58,7 +62,7 @@ export async function addLiveRunMoves(runId: string, moves: LiveRunMove[]) {
if (!runExists) throw "Invalid live run"; if (!runExists) throw "Invalid live run";
// Publish the new moves to subscribers // Publish the new moves to subscribers
await redis.publish(`liveRunMoves:${runId}`, JSON.stringify(moves)); await redis.publish(`liveRunMoves:${runId}`, JSON.stringify({ file, moves }));
// Get the last text (from Redis) // Get the last text (from Redis)
const lastText = (await redis.get(`liveRunText:${runId}`)) || ""; const lastText = (await redis.get(`liveRunText:${runId}`)) || "";
+6
View File
@@ -18,3 +18,9 @@ export interface LiveRunMove {
insert: string; insert: string;
}; };
} }
export interface LiveRunEvent {
moves: LiveRunMove[];
file: string | null;
text: string | null;
}