updated sse supporting react-openapi
This commit is contained in:
96
react-openapi/src/components/SseStreamView.tsx
Normal file
96
react-openapi/src/components/SseStreamView.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
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<any[]>(() => 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 (
|
||||
<Paper variant="outlined" sx={{ p: 3, borderRadius: 2 }}>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2.5 }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1.5 }}>
|
||||
<Typography variant="subtitle1" fontWeight={700}>
|
||||
{resource.displayName}
|
||||
</Typography>
|
||||
<SseConnectionStatus resourceName={resource.name} />
|
||||
</Box>
|
||||
<Chip
|
||||
label={eventCount > 0 ? `${eventCount} event${eventCount !== 1 ? "s" : ""}` : "No events"}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color={eventCount > 0 ? "primary" : "default"}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{latestEvent ? (
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: "grey.50",
|
||||
borderRadius: 1,
|
||||
p: 2,
|
||||
border: "1px solid",
|
||||
borderColor: "divider",
|
||||
fontFamily: "monospace",
|
||||
fontSize: "0.875rem",
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mb: 0.5, display: "block" }}>
|
||||
Latest event (#{latestEvent._seq})
|
||||
</Typography>
|
||||
<Typography>
|
||||
{applyDisplayFormat(latestEvent, resource.displayFormat)}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ py: 2, textAlign: "center" }}>
|
||||
Waiting for events…
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Snackbar
|
||||
open={snackbarOpen}
|
||||
autoHideDuration={2000}
|
||||
onClose={() => setSnackbarOpen(false)}
|
||||
message={snackbarMsg}
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user