import React, { useEffect, useState } from "react"; import { Box, Typography, Paper, Chip, Snackbar, } from "@mui/material"; import type { ResourceConfig } from "../types"; import { useResource, readSseCache, appendSseCache, clearSseCache, nextSseSeq, setSseConnected } from "../context/useResource"; import { applyDisplayFormat } from "./fields"; import { SseConnectionStatus } from "./SseConnectionStatus"; interface SseStreamViewProps { resource: ResourceConfig; } export function SseStreamView({ resource }: SseStreamViewProps) { const { stream } = useResource(resource.name); const [events, setEvents] = useState(() => readSseCache(resource.name)); const [snackbarOpen, setSnackbarOpen] = useState(false); const [snackbarMsg, setSnackbarMsg] = useState(""); useEffect(() => { if (!stream) return; setSseConnected(resource.name, false); const sub = stream({ onEvent: (evt) => { const enriched = { ...evt, _received_at: new Date().toISOString(), _seq: nextSseSeq() }; const updated = appendSseCache(resource.name, enriched); setEvents([...updated]); setSnackbarMsg(applyDisplayFormat(evt, resource.displayFormat)); setSnackbarOpen(true); }, onOpen: () => setSseConnected(resource.name, true), onError: () => setSseConnected(resource.name, false), }); return () => { setSseConnected(resource.name, false); sub.close(); }; }, [resource.name]); const eventCount = events.length; const latestEvent = events[events.length - 1] ?? null; return ( {resource.displayName} 0 ? `${eventCount} event${eventCount !== 1 ? "s" : ""}` : "No events"} size="small" variant="outlined" color={eventCount > 0 ? "primary" : "default"} /> {latestEvent ? ( Latest event (#{latestEvent._seq}) {applyDisplayFormat(latestEvent, resource.displayFormat)} ) : ( Waiting for events… )} setSnackbarOpen(false)} message={snackbarMsg} anchorOrigin={{ vertical: "bottom", horizontal: "right" }} /> ); }