using Astralis;
using Invercargill;
using Invercargill.DataStructures;
using Inversion;
using Spry;
/**
* ProgressExample demonstrates the continuation feature for server-sent events (SSE).
*
* The continuation feature allows a Component to send real-time progress updates
* to the client via SSE. This is useful for:
* - Long-running task progress reporting
* - Real-time status updates
* - Live data streaming
*
* How it works:
* 1. Add `spry-continuation` attribute to an element in your markup
* (This is shorthand for: hx-ext="sse" sse-connect="(endpoint)" sse-close="_spry-close")
* 2. Use `sse-swap="eventname"` on child elements to swap content when events arrive
* 3. Override the `continuation(SseStream stream)` method in your Component
* 4. Use `stream.send_event()` to send SSE events with HTML content to swap
*/
class ProgressComponent : Component {
public override string markup { get {
return """
Task Progress Demo
This example demonstrates Spry's continuation feature for real-time progress updates via Server-Sent Events (SSE).
Status: Initializing...
Waiting for task to start...
""";
}}
/**
* The continuation method is called when a client connects to the SSE endpoint.
* This is where you can send real-time updates to the client.
*
* The event data should be HTML content that will be swapped into elements
* with matching sse-swap="eventname" attributes.
*/
public async override void continuation(SseStream stream) throws Error {
// Simulate a long-running task with progress updates
var steps = new string[] {
"Initializing task...",
"Loading configuration...",
"Connecting to database...",
"Fetching records...",
"Processing batch 1/5...",
"Processing batch 2/5...",
"Processing batch 3/5...",
"Processing batch 4/5...",
"Processing batch 5/5...",
"Validating results...",
"Generating report...",
"Finalizing..."
};
for (int i = 0; i < steps.length; i++) {
// Calculate progress percentage
int percent = (int)(((i + 1) / (double)steps.length) * 100);
// Send progress bar update - HTML that will be swapped into the progress bar
yield stream.send_event(new SseEvent.with_type("progress",
@"$percent%
"));
// Send status update - HTML that will be swapped into the status div
yield stream.send_event(new SseEvent.with_type("status",
@"Status: $(steps[i])"));
// Send log message - HTML that will be appended to the log
yield stream.send_event(new SseEvent.with_type("log",
@"$(steps[i])
"));
// Simulate work being done (500ms per step)
Timeout.add(500, () => {
continuation.callback();
return false;
});
yield;
}
// Send final completion messages
yield stream.send_event(new SseEvent.with_type("progress",
"100% ✓
"));
yield stream.send_event(new SseEvent.with_type("status",
"Status: Task completed successfully!"));
yield stream.send_event(new SseEvent.with_type("log",
"✓ All tasks completed!
"));
}
}
class HomePageEndpoint : Object, Endpoint {
private ProgressComponent progress_component = inject();
public async Astralis.HttpResult handle_request(Astralis.HttpContext http_context, Astralis.RouteContext route_context) throws Error {
return yield progress_component.to_result();
}
}
void main(string[] args) {
int port = args.length > 1 ? int.parse(args[1]) : 8080;
try {
var application = new WebApplication(port);
// Register compression components
application.use_compression();
// Add Spry module (includes ContinuationProvider for SSE)
application.add_module();
// Register the progress component
application.add_transient();
// Register the home page endpoint
application.add_endpoint(new EndpointRoute("/"));
print("Progress Example running on http://localhost:%d/\n", port);
print("Open the URL in your browser to see real-time progress updates via SSE.\n");
application.run();
} catch (Error e) {
printerr("Error: %s\n", e.message);
Process.exit(1);
}
}