|
|
@@ -7,8 +7,10 @@ using Spry;
|
|
|
/**
|
|
|
* DemoPage - Interactive demo page
|
|
|
*
|
|
|
- * Features a live Aurora Borealis simulation that polls the server
|
|
|
- * every 5 seconds for new wave/color parameters
|
|
|
+ * Features a live Aurora Borealis simulation and counter that use
|
|
|
+ * Server-Sent Events (SSE) for real-time updates.
|
|
|
+ *
|
|
|
+ * Updates are pushed when users click action buttons - no polling!
|
|
|
*/
|
|
|
public class DemoPage : PageComponent {
|
|
|
|
|
|
@@ -19,12 +21,12 @@ public class DemoPage : PageComponent {
|
|
|
|
|
|
public override string markup { get {
|
|
|
return """
|
|
|
- <div sid="demo-page" class="section">
|
|
|
+ <div sid="demo-page" class="section" hx-ext="sse" sse-connect="/sse/aurora" sse-close="close">
|
|
|
<div class="container">
|
|
|
<!-- Header -->
|
|
|
<div class="section-header">
|
|
|
<h1>Interactive Demo</h1>
|
|
|
- <p>Experience the power of Spry with a live, server-backed Aurora simulation</p>
|
|
|
+ <p>Experience the power of Spry with real-time Server-Sent Events</p>
|
|
|
</div>
|
|
|
|
|
|
<!-- Aurora Demo -->
|
|
|
@@ -32,37 +34,31 @@ public class DemoPage : PageComponent {
|
|
|
<div class="demo-header">
|
|
|
<h2>🌌 Live Aurora Borealis</h2>
|
|
|
<p style="color: var(--color-text-muted); margin-bottom: var(--space-4);">
|
|
|
- This aurora is generated server-side! It polls for new parameters every 5 seconds.
|
|
|
+ This aurora uses SSE for real-time updates! Click a button to update all connected clients.
|
|
|
</p>
|
|
|
<div class="demo-controls">
|
|
|
- <button class="demo-btn" spry-action=":BoostSolarWind" spry-target="demo-page" hx-swap="outerHTML">
|
|
|
+ <button class="demo-btn" hx-post="/aurora/action/boost" hx-swap="none">
|
|
|
🌞 Boost Solar Wind
|
|
|
</button>
|
|
|
- <button class="demo-btn" spry-action=":CalmAurora" spry-target="demo-page" hx-swap="outerHTML">
|
|
|
+ <button class="demo-btn" hx-post="/aurora/action/calm" hx-swap="none">
|
|
|
🌙 Calm Aurora
|
|
|
</button>
|
|
|
- <button class="demo-btn" spry-action=":ShiftColors" spry-target="demo-page" hx-swap="outerHTML">
|
|
|
+ <button class="demo-btn" hx-post="/aurora/action/shift" hx-swap="none">
|
|
|
🎨 Shift Colors
|
|
|
</button>
|
|
|
- <button class="demo-btn" spry-action=":AddWave" spry-target="demo-page" hx-swap="outerHTML">
|
|
|
+ <button class="demo-btn" hx-post="/aurora/action/wave" hx-swap="none">
|
|
|
🌊 Add Wave
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <!-- Aurora Canvas with auto-refresh -->
|
|
|
- <div class="aurora-canvas" sid="aurora-canvas"
|
|
|
- spry-action="AuroraCanvasEndpoint:Poll"
|
|
|
- hx-trigger="every 5s"
|
|
|
- hx-swap="innerHTML transition:true">
|
|
|
+ <!-- Aurora Canvas - updated via SSE -->
|
|
|
+ <div class="aurora-canvas" sse-swap="aurora-canvas" hx-swap="innerHTML transition:true">
|
|
|
<spry-outlet sid="aurora-waves"/>
|
|
|
</div>
|
|
|
|
|
|
- <!-- Aurora Stats -->
|
|
|
- <div class="aurora-stats" sid="aurora-stats"
|
|
|
- spry-action="AuroraStatsEndpoint:Poll"
|
|
|
- hx-trigger="every 5s"
|
|
|
- hx-swap="outerHTML transition:true">
|
|
|
+ <!-- Aurora Stats - updated via SSE -->
|
|
|
+ <div class="aurora-stats" sse-swap="aurora-stats" hx-swap="innerHTML transition:true">
|
|
|
<div class="aurora-stat">
|
|
|
<div class="aurora-stat-value" sid="solar-wind"></div>
|
|
|
<div class="aurora-stat-label">Solar Wind</div>
|
|
|
@@ -82,30 +78,30 @@ public class DemoPage : PageComponent {
|
|
|
</div>
|
|
|
|
|
|
<p style="margin-top: var(--space-4); text-align: center; color: var(--color-text-muted);">
|
|
|
- The aurora updates every 5 seconds via HTMX polling. Try the controls above!
|
|
|
+ Updates are pushed via SSE when anyone clicks the buttons. Try it from multiple browsers!
|
|
|
</p>
|
|
|
</section>
|
|
|
|
|
|
<!-- Counter Demo -->
|
|
|
- <section class="demo-section">
|
|
|
- <h2>🔢 Simple Counter</h2>
|
|
|
- <p>A minimal example of Spry component state and actions.</p>
|
|
|
+ <section class="demo-section" hx-ext="sse" sse-connect="/sse/counter" sse-close="close">
|
|
|
+ <h2>🔢 Shared Counter</h2>
|
|
|
+ <p>A counter that syncs across all connected clients using SSE.</p>
|
|
|
|
|
|
<div style="display: flex; align-items: center; gap: var(--space-4); justify-content: center; margin: var(--space-6) 0;">
|
|
|
<div class="stat-card" style="text-align: center; min-width: 200px;">
|
|
|
- <div class="stat-value" sid="counter-value"></div>
|
|
|
+ <div class="stat-value" sse-swap="counter-value" hx-swap="innerHTML"></div>
|
|
|
<div class="stat-label">Current Count</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div style="display: flex; gap: var(--space-3); justify-content: center;">
|
|
|
- <button class="demo-btn" spry-action=":Decrement" spry-target="demo-page" hx-swap="outerHTML">
|
|
|
+ <button class="demo-btn" hx-post="/counter/action/decrement" hx-swap="none">
|
|
|
− Decrement
|
|
|
</button>
|
|
|
- <button class="demo-btn" spry-action=":Reset" spry-target="demo-page" hx-swap="outerHTML">
|
|
|
+ <button class="demo-btn" hx-post="/counter/action/reset" hx-swap="none">
|
|
|
↺ Reset
|
|
|
</button>
|
|
|
- <button class="demo-btn" spry-action=":Increment" spry-target="demo-page" hx-swap="outerHTML">
|
|
|
+ <button class="demo-btn" hx-post="/counter/action/increment" hx-swap="none">
|
|
|
+ Increment
|
|
|
</button>
|
|
|
</div>
|
|
|
@@ -114,23 +110,23 @@ public class DemoPage : PageComponent {
|
|
|
<!-- How It Works -->
|
|
|
<section class="demo-section">
|
|
|
<h2>⚙ How It Works</h2>
|
|
|
- <p>This demo showcases several Spry features working together:</p>
|
|
|
+ <p>This demo showcases Server-Sent Events (SSE) for real-time updates:</p>
|
|
|
|
|
|
<div class="features-grid" style="margin-top: var(--space-6);">
|
|
|
<div class="feature-card">
|
|
|
- <div class="feature-icon purple">🔄</div>
|
|
|
- <h3>Server Polling</h3>
|
|
|
- <p>hx-trigger="every 5s" fetches fresh aurora data from the server periodically.</p>
|
|
|
+ <div class="feature-icon purple">📡</div>
|
|
|
+ <h3>Server-Sent Events</h3>
|
|
|
+ <p>SSE pushes updates to all connected clients instantly when actions occur.</p>
|
|
|
</div>
|
|
|
<div class="feature-card">
|
|
|
- <div class="feature-icon blue">⚡</div>
|
|
|
+ <div class="feature-icon blue">🔗</div>
|
|
|
<h3>Shared State</h3>
|
|
|
- <p>AuroraState is a singleton - all visitors see the same aurora!</p>
|
|
|
+ <p>AuroraState is a singleton - all visitors see the same aurora and counter!</p>
|
|
|
</div>
|
|
|
<div class="feature-card">
|
|
|
- <div class="feature-icon green">🎨</div>
|
|
|
- <h3>CSS Gradients</h3>
|
|
|
- <p>Server generates CSS gradient strips for beautiful aurora waves.</p>
|
|
|
+ <div class="feature-icon green">⚡</div>
|
|
|
+ <h3>Event-Driven</h3>
|
|
|
+ <p>No polling! Updates only happen when someone clicks a button.</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -143,17 +139,16 @@ public class DemoPage : PageComponent {
|
|
|
</div>
|
|
|
<span class="code-lang">HTML</span>
|
|
|
</div>
|
|
|
- <pre><code><!-- Auto-refreshing aurora canvas -->
|
|
|
-<div hx-get="/demo/aurora"
|
|
|
- hx-trigger="every 5s"
|
|
|
- hx-swap="innerHTML">
|
|
|
- <!-- Aurora waves render here -->
|
|
|
+ <pre><code><!-- SSE connection on parent -->
|
|
|
+<div hx-ext="sse" sse-connect="/sse/aurora">
|
|
|
+ <!-- Auto-updates when "aurora-canvas" event received -->
|
|
|
+ <div sse-swap="aurora-canvas">
|
|
|
+ Aurora waves render here
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
-<!-- Control buttons -->
|
|
|
-<button spry-action=":BoostSolarWind"
|
|
|
- spry-target="demo-page"
|
|
|
- hx-swap="outerHTML">
|
|
|
+<!-- Button triggers action endpoint -->
|
|
|
+<button hx-post="/aurora/action/boost" hx-swap="none">
|
|
|
Boost Solar Wind
|
|
|
</button></code></pre>
|
|
|
</div>
|
|
|
@@ -174,7 +169,7 @@ public class DemoPage : PageComponent {
|
|
|
}}
|
|
|
|
|
|
public override async void prepare() throws Error {
|
|
|
- // Render aurora waves
|
|
|
+ // Render initial aurora waves
|
|
|
var waves = new Series<Renderable>();
|
|
|
int wave_num = 0;
|
|
|
foreach (var wave in aurora_state.get_waves()) {
|
|
|
@@ -198,35 +193,10 @@ public class DemoPage : PageComponent {
|
|
|
this["intensity"].text_content = "%.0f%%".printf(aurora_state.intensity * 100);
|
|
|
this["color-mode"].text_content = aurora_state.color_mode;
|
|
|
|
|
|
- // Update counter
|
|
|
+ // Update counter (initial value - will be updated via SSE)
|
|
|
+ // Note: We need to set the stat-value div's content
|
|
|
this["counter-value"].text_content = aurora_state.counter.to_string();
|
|
|
}
|
|
|
-
|
|
|
- public async override void handle_action(string action) throws Error {
|
|
|
- switch (action) {
|
|
|
- case "BoostSolarWind":
|
|
|
- aurora_state.boost_solar_wind();
|
|
|
- break;
|
|
|
- case "CalmAurora":
|
|
|
- aurora_state.calm();
|
|
|
- break;
|
|
|
- case "ShiftColors":
|
|
|
- aurora_state.shift_colors();
|
|
|
- break;
|
|
|
- case "AddWave":
|
|
|
- aurora_state.add_wave();
|
|
|
- break;
|
|
|
- case "Increment":
|
|
|
- aurora_state.counter++;
|
|
|
- break;
|
|
|
- case "Decrement":
|
|
|
- aurora_state.counter--;
|
|
|
- break;
|
|
|
- case "Reset":
|
|
|
- aurora_state.counter = 0;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
/**
|