| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- 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<string> completed_tasks { get; set; default = new Series<string>(); }
-
- 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" >
- <div class="progress-bar" spry-fragment="progress-bar"
- content-expr='format("%i%%", this.percent)' style-width-expr='format("%i%%", this.percent)'>
- 0%
- </div>
- </div>
-
- <div class="status" spry-fragment="status">
- <strong>Status:</strong> <span content-expr="this.status">Initializing...</span>
- </div>
-
- <div class="log" sid="log" spry-fragment="log">
- <div spry-per-task="this.completed_tasks" content-expr="task"></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(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-bar");
-
- // Send status update - HTML that will be swapped into the status div
- yield continuation_context.send_fragment("status");
-
- // Send log message - HTML that will be appended to the log
- yield continuation_context.send_fragment("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-bar");
- yield continuation_context.send_fragment("status");
- }
- }
- 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);
- }
- }
|