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).

0%
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); } }