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 int percent { get; set; } public string status { get; set; default = "Initializing..."; } public Series completed_tasks { get; set; default = new Series(); } 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(ContinuationContext continuation_context) 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++) { // Update the template percent = (int)(((i + 1) / (double)steps.length) * 100); status = steps[i]; completed_tasks.add_start(steps[i]); // Send progress bar update - HTML that will be swapped into the progress bar yield continuation_context.send_fragment("progress", "progress-bar"); // Send status update - HTML that will be swapped into the status div yield continuation_context.send_fragment("status", "status"); // Send log message - HTML that will be appended to the log yield continuation_context.send_fragment("log", "log"); // Simulate work being done (500ms per step) Timeout.add(500, () => { continuation.callback(); return false; }); yield; } // Send final completion messages percent = 100; status = "Task completed successfully!"; yield continuation_context.send_fragment("progress", "progress-bar"); yield continuation_context.send_fragment("status", "status"); } } 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); } }