Improvements

This commit is contained in:
2026-02-09 16:01:28 +01:00
parent d0027e32d2
commit c6763fb63f
6 changed files with 50 additions and 29 deletions
+4 -6
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, file } = await req.json(); const { runId, moves, file, language } = await req.json();
if (!runId || !moves || file === undefined) if (!runId || !moves || file === undefined || language === undefined)
return NextResponse.json( return NextResponse.json(
{ error: "body requires runId and moves and file" }, { error: "body requires runId, moves, file and language" },
{ status: 400 }, { status: 400 },
); );
@@ -27,9 +27,7 @@ export async function POST(req: NextRequest) {
{ status: 401 }, { status: 401 },
); );
console.log(moves, file); const run = await Core.addLiveRunEvent(liveRun.id, file, language, moves);
const run = await Core.addLiveRunMoves(liveRun.id, file, moves);
return NextResponse.json({ ok: true, run }); return NextResponse.json({ ok: true, run });
} }
+15 -6
View File
@@ -14,14 +14,22 @@ export function UPGRADE(client: WebSocket, server: WebSocketServer) {
const subscriber = new Redis(); const subscriber = new Redis();
let runId: string; let runId: string;
client.once("message", (message) => { client.once("message", async (message) => {
runId = message.toString(); runId = message.toString();
let file: string | null; let lastEvent: LiveRunEvent | null = null;
const sendText = async () => { const sendText = async () => {
const text = await subscriber.get(`liveRunText:${runId}`); const text = await subscriber.get(`liveRunText:${runId}`);
if (client.readyState === WebSocket.OPEN) { if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ file, moves: [], text })); client.send(
JSON.stringify({
file: lastEvent?.file,
language: lastEvent?.language,
moves: [],
text,
}),
);
} }
}; };
@@ -29,7 +37,7 @@ export function UPGRADE(client: WebSocket, server: WebSocketServer) {
client.on("message", sendText); client.on("message", sendText);
subscriber.subscribe(`liveRunMoves:${runId}`, (err, count) => { subscriber.subscribe(`liveRunEvent:${runId}`, (err, count) => {
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");
@@ -38,9 +46,10 @@ export function UPGRADE(client: WebSocket, server: WebSocketServer) {
subscriber.on("message", (channel, message) => { subscriber.on("message", (channel, message) => {
const data: LiveRunEvent = JSON.parse(message); const data: LiveRunEvent = JSON.parse(message);
file = data.file;
lastEvent = data;
if (client.readyState === WebSocket.OPEN) { if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({ moves: data.moves, file, text: null })); client.send(JSON.stringify({ ...data, text: null }));
} }
}); });
}); });
+12 -11
View File
@@ -9,7 +9,7 @@ import { json } from "@codemirror/lang-json";
import { python } from "@codemirror/lang-python"; import { python } from "@codemirror/lang-python";
import { java } from "@codemirror/lang-java"; import { java } from "@codemirror/lang-java";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { LiveRun, LiveRunMove } from "@/modules/live-run/types"; import { LiveRun, LiveRunEvent, LiveRunMove } from "@/modules/live-run/types";
type Language = type Language =
| "javascript" | "javascript"
@@ -20,7 +20,7 @@ type Language =
| "java" | "java"
| "json"; | "json";
function getLanguageExtension(lang: Language) { function getLanguageExtension(lang: Language | null) {
switch (lang) { switch (lang) {
case "javascript": case "javascript":
return javascript(); return javascript();
@@ -41,14 +41,15 @@ function getLanguageExtension(lang: Language) {
export default function Editor({ export default function Editor({
run, run,
setFile, onEvent,
}: { }: {
run: LiveRun | null | undefined; run: LiveRun | null | undefined;
setFile: (s: string | null) => void; onEvent: (s: LiveRunEvent) => 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("");
const [language, setLanguage] = useState<Language | null>(null);
// WebSocket connection // WebSocket connection
useEffect(() => { useEffect(() => {
@@ -61,13 +62,13 @@ export default function Editor({
}; };
ws.onmessage = (m) => { ws.onmessage = (m) => {
const data: { const data: LiveRunEvent = JSON.parse(m.data);
moves: LiveRunMove[];
file: string | null;
text: string | null;
} = JSON.parse(m.data);
setFile(data.file); onEvent(data);
if (data.language !== language) {
setLanguage(data.language as Language);
}
if (data.text) { if (data.text) {
setText(data.text); setText(data.text);
@@ -104,7 +105,7 @@ export default function Editor({
className="h-full w-full" className="h-full w-full"
value={text} value={text}
extensions={[ extensions={[
getLanguageExtension("javascript"), getLanguageExtension(language),
EditorState.transactionFilter.of((tr) => { EditorState.transactionFilter.of((tr) => {
if (tr.selection && tr.isUserEvent("select")) { if (tr.selection && tr.isUserEvent("select")) {
return []; return [];
+11 -3
View File
@@ -24,7 +24,10 @@ export default function Run() {
[run], [run],
); );
const [file, setFile] = useState<string | null>(null); const [file, setFile] = useState<{
file: string | null;
language: string | null;
}>({ file: null, language: null });
const chatMessages: ChatMessage[] = [ const chatMessages: ChatMessage[] = [
{ user: "Alice", message: "Nice split!", time: "1:53" }, { user: "Alice", message: "Nice split!", time: "1:53" },
@@ -84,10 +87,15 @@ 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} /> {file} <File size={16} /> {file.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} setFile={setFile} /> <Editor
run={run}
onEvent={(event) => {
setFile({ file: event.file, language: event.language });
}}
/>
</div> </div>
</div> </div>
</CardContent> </CardContent>
+7 -3
View File
@@ -42,7 +42,7 @@ export async function getLiveRuns(): Promise<LiveRun[]> {
* Remove a live run from Redis * Remove a live run from Redis
*/ */
export async function removeLiveRun(id: string) { export async function removeLiveRun(id: string) {
await redis.del(`liveRun:${id}`, `liveRunMoves:${id}`); await redis.del(`liveRun:${id}`, `liveRunEvent:${id}`);
} }
/** -------------------- Live Run Moves -------------------- */ /** -------------------- Live Run Moves -------------------- */
@@ -50,9 +50,10 @@ export async function removeLiveRun(id: string) {
/** /**
* Add moves to a live run (ephemeral) * Add moves to a live run (ephemeral)
*/ */
export async function addLiveRunMoves( export async function addLiveRunEvent(
runId: string, runId: string,
file: string, file: string,
language: string,
moves: LiveRunMove[], moves: LiveRunMove[],
) { ) {
const redis = new Redis(); const redis = new Redis();
@@ -62,7 +63,10 @@ export async function addLiveRunMoves(
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({ file, moves })); await redis.publish(
`liveRunEvent:${runId}`,
JSON.stringify({ file, language, 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}`)) || "";
+1
View File
@@ -22,5 +22,6 @@ export interface LiveRunMove {
export interface LiveRunEvent { export interface LiveRunEvent {
moves: LiveRunMove[]; moves: LiveRunMove[];
file: string | null; file: string | null;
language: string | null;
text: string | null; text: string | null;
} }