| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- 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 """
- <!DOCTYPE html>
- <html>
- <head>
- <script spry-res="htmx.js"></script>
- <script spry-res="htmx-sse.js"></script>
- <style>
- body { font-family: system-ui, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
- .progress-container {
- background: #e0e0e0;
- border-radius: 8px;
- overflow: hidden;
- margin: 20px 0;
- }
- .progress-bar {
- height: 30px;
- background: linear-gradient(90deg, #4CAF50, #8BC34A);
- transition: width 0.3s ease;
- display: flex;
- align-items: center;
- justify-content: center;
- color: white;
- font-weight: bold;
- min-width: 40px;
- }
- .status {
- padding: 15px;
- background: #f5f5f5;
- border-radius: 4px;
- margin: 10px 0;
- border-left: 4px solid #2196F3;
- }
- .log {
- max-height: 200px;
- overflow-y: auto;
- background: #263238;
- color: #4CAF50;
- padding: 15px;
- border-radius: 4px;
- font-family: monospace;
- font-size: 14px;
- }
- .log-entry { margin: 5px 0; }
- h1 { color: #333; }
- .info { color: #666; font-size: 14px; }
- </style>
- </head>
- <body>
- <h1>Task Progress Demo</h1>
- <p class="info">This example demonstrates Spry's continuation feature for real-time progress updates via Server-Sent Events (SSE).</p>
-
- <div spry-continuation>
- <div class="progress-container" sse-swap="progress">
- <div class="progress-bar" id="progress-bar" style="width: 0%">
- 0%
- </div>
- </div>
-
- <div class="status" sse-swap="status">
- <strong>Status:</strong> Initializing...
- </div>
-
- <div class="log" id="log">
- <div sse-swap="log">Waiting for task to start...</div>
- </div>
- </div>
- </body>
- </html>
- """;
- }}
-
- /**
- * 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",
- @"<div class=\"progress-bar\" id=\"progress-bar\" style=\"width: $percent%\">$percent%</div>"));
-
- // Send status update - HTML that will be swapped into the status div
- yield stream.send_event(new SseEvent.with_type("status",
- @"<strong>Status:</strong> $(steps[i])"));
-
- // Send log message - HTML that will be appended to the log
- yield stream.send_event(new SseEvent.with_type("log",
- @"<div class=\"log-entry\">$(steps[i])</div>"));
-
- // 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",
- "<div class=\"progress-bar\" id=\"progress-bar\" style=\"width: 100%\">100% ✓</div>"));
- yield stream.send_event(new SseEvent.with_type("status",
- "<strong>Status:</strong> Task completed successfully!"));
- yield stream.send_event(new SseEvent.with_type("log",
- "<div class=\"log-entry\">✓ All tasks completed!</div>"));
- }
- }
- class HomePageEndpoint : Object, Endpoint {
- private ProgressComponent progress_component = inject<ProgressComponent>();
- 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<SpryModule>();
- // Register the progress component
- application.add_transient<ProgressComponent>();
- // Register the home page endpoint
- application.add_endpoint<HomePageEndpoint>(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);
- }
- }
|