| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- using Astralis;
- using Invercargill;
- using Invercargill.DataStructures;
- using Inversion;
- using Spry;
- /**
- * DemoPage - Interactive demo page
- *
- * 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 {
-
- public const string ROUTE = "/demo";
-
- private AuroraState aurora_state = inject<AuroraState>();
- private ComponentFactory factory = inject<ComponentFactory>();
-
- public override string markup { get {
- return """
- <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 real-time Server-Sent Events</p>
- </div>
- <!-- Aurora Demo -->
- <section class="demo-section">
- <div class="demo-header">
- <h2>🌌 Live Aurora Borealis</h2>
- <p style="color: var(--color-text-muted); margin-bottom: var(--space-4);">
- 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" hx-post="/aurora/action/boost" hx-swap="none">
- 🌞 Boost Solar Wind
- </button>
- <button class="demo-btn" hx-post="/aurora/action/calm" hx-swap="none">
- 🌙 Calm Aurora
- </button>
- <button class="demo-btn" hx-post="/aurora/action/shift" hx-swap="none">
- 🎨 Shift Colors
- </button>
- <button class="demo-btn" hx-post="/aurora/action/wave" hx-swap="none">
- 🌊 Add Wave
- </button>
- </div>
- </div>
-
- <!-- 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 - 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>
- </div>
- <div class="aurora-stat">
- <div class="aurora-stat-value" sid="wave-count"></div>
- <div class="aurora-stat-label">Waves</div>
- </div>
- <div class="aurora-stat">
- <div class="aurora-stat-value" sid="intensity"></div>
- <div class="aurora-stat-label">Intensity</div>
- </div>
- <div class="aurora-stat">
- <div class="aurora-stat-value" sid="color-mode"></div>
- <div class="aurora-stat-label">Color Mode</div>
- </div>
- </div>
-
- <p style="margin-top: var(--space-4); text-align: center; color: var(--color-text-muted);">
- Updates are pushed via SSE when anyone clicks the buttons. Try it from multiple browsers!
- </p>
- </section>
- <!-- Counter Demo -->
- <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" 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" hx-post="/counter/action/decrement" hx-swap="none">
- − Decrement
- </button>
- <button class="demo-btn" hx-post="/counter/action/reset" hx-swap="none">
- ↺ Reset
- </button>
- <button class="demo-btn" hx-post="/counter/action/increment" hx-swap="none">
- + Increment
- </button>
- </div>
- </section>
- <!-- How It Works -->
- <section class="demo-section">
- <h2>⚙ How It Works</h2>
- <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-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>
- <h3>Shared State</h3>
- <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>Event-Driven</h3>
- <p>No polling! Updates only happen when someone clicks a button.</p>
- </div>
- </div>
-
- <div class="code-block" style="margin-top: var(--space-6);">
- <div class="code-header">
- <div class="code-dots">
- <div class="code-dot red"></div>
- <div class="code-dot yellow"></div>
- <div class="code-dot green"></div>
- </div>
- <span class="code-lang">HTML</span>
- </div>
- <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>
- <!-- Button triggers action endpoint -->
- <button hx-post="/aurora/action/boost" hx-swap="none">
- Boost Solar Wind
- </button></code></pre>
- </div>
- </section>
- <!-- CTA -->
- <div class="text-center" style="margin-top: var(--space-12);">
- <h2>Want to Learn More?</h2>
- <p style="margin-bottom: var(--space-6);">Explore the features that make Spry powerful.</p>
- <div class="hero-actions" style="justify-content: center;">
- <a href="/features" class="btn btn-primary">View Features</a>
- <a href="/ecosystem" class="btn btn-secondary">See Ecosystem</a>
- </div>
- </div>
- </div>
- </div>
- """;
- }}
-
- public override async void prepare() throws Error {
- // Render initial aurora waves
- var waves = new Series<Renderable>();
- int wave_num = 0;
- foreach (var wave in aurora_state.get_waves()) {
- var component = factory.create<AuroraWaveComponent>();
- component.wave_id = wave_num;
- component.y_offset = wave.y_offset;
- component.amplitude = wave.amplitude;
- component.frequency = wave.frequency;
- component.color1 = wave.color1;
- component.color2 = wave.color2;
- component.opacity = wave.opacity;
- component.animation_delay = wave.animation_delay;
- waves.add(component);
- wave_num++;
- }
- set_outlet_children("aurora-waves", waves);
-
- // Update stats
- this["solar-wind"].text_content = "%.1f km/s".printf(aurora_state.solar_wind_speed);
- this["wave-count"].text_content = aurora_state.wave_count.to_string();
- this["intensity"].text_content = "%.0f%%".printf(aurora_state.intensity * 100);
- this["color-mode"].text_content = aurora_state.color_mode;
-
- // 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();
- }
- }
- /**
- * AuroraWaveData - Data for a single aurora wave
- */
- public class AuroraWaveData : Object {
- public double y_offset { get; set; }
- public double amplitude { get; set; }
- public double frequency { get; set; }
- public string color1 { get; set; default = "#7c3aed"; }
- public string color2 { get; set; default = "#22c55e"; }
- public double opacity { get; set; default = 0.6; }
- public double animation_delay { get; set; default = 0; }
- }
- /**
- * ColorPalette - A pair of colors for aurora waves
- */
- public class ColorPalette : Object {
- public string color1 { get; construct set; }
- public string color2 { get; construct set; }
- public string name { get; construct set; }
-
- public ColorPalette(string color1, string color2, string name) {
- Object(color1: color1, color2: color2, name: name);
- }
- }
- /**
- * AuroraState - Singleton state for the aurora simulation
- */
- public class AuroraState : Object {
- private GenericArray<AuroraWaveData> waves = new GenericArray<AuroraWaveData>();
- private Series<ColorPalette> color_palettes;
-
- public double solar_wind_speed { get; set; default = 400.0; }
- public double intensity { get; set; default = 0.5; }
- public string color_mode { get; set; default = "Green"; }
- public int counter { get; set; default = 0; }
-
- private int current_palette = 0;
-
- public int wave_count {
- get { return (int)waves.length; }
- }
-
- construct {
- // Initialize color palettes using Series
- color_palettes = new Series<ColorPalette>();
- color_palettes.add(new ColorPalette("#22c55e", "#16a34a", "Green"));
- color_palettes.add(new ColorPalette("#7c3aed", "#a855f7", "Purple"));
- color_palettes.add(new ColorPalette("#2563eb", "#3b82f6", "Blue"));
- color_palettes.add(new ColorPalette("#22c55e", "#7c3aed", "Mixed"));
- color_palettes.add(new ColorPalette("#f59e0b", "#ef4444", "Fire"));
- color_palettes.add(new ColorPalette("#06b6d4", "#22c55e", "Ocean"));
-
- // Start with some waves
- add_wave();
- add_wave();
- add_wave();
- }
-
- private ColorPalette get_current_palette() {
- uint idx = (uint)current_palette;
- return color_palettes.element_at_or_default(idx);
- }
-
- public void add_wave() {
- var palette = get_current_palette();
-
- var wave = new AuroraWaveData() {
- y_offset = 20 + Random.double_range(0, 40),
- amplitude = 10 + Random.double_range(0, 20),
- frequency = 0.5 + Random.double_range(0, 1.5),
- color1 = palette.color1,
- color2 = palette.color2,
- opacity = 0.3 + Random.double_range(0, 0.5),
- animation_delay = Random.double_range(0, 3)
- };
- waves.add(wave);
-
- // Keep max 8 waves
- if (waves.length > 8) {
- waves.remove_index(0);
- }
- }
-
- public void boost_solar_wind() {
- solar_wind_speed = 600 + Random.double_range(0, 400);
- intensity = 0.8 + Random.double_range(0, 0.2);
-
- // Increase all wave amplitudes
- for (int i = 0; i < waves.length; i++) {
- var w = waves.get(i);
- w.amplitude *= 1.3;
- w.opacity = double.min(0.9, w.opacity + 0.1);
- }
- }
-
- public void calm() {
- solar_wind_speed = 300 + Random.double_range(0, 100);
- intensity = 0.3 + Random.double_range(0, 0.2);
-
- // Decrease all wave amplitudes
- for (int i = 0; i < waves.length; i++) {
- var w = waves.get(i);
- w.amplitude *= 0.7;
- w.opacity = double.max(0.2, w.opacity - 0.1);
- }
- }
-
- public void shift_colors() {
- current_palette = (current_palette + 1) % 6;
- var palette = get_current_palette();
-
- // Update color mode name
- color_mode = palette.name;
-
- // Update all wave colors
- for (int i = 0; i < waves.length; i++) {
- var w = waves.get(i);
- w.color1 = palette.color1;
- w.color2 = palette.color2;
- }
- }
-
- public void evolve() {
- // Called periodically to naturally evolve the aurora
- solar_wind_speed = 300 + Random.double_range(0, 300);
- intensity = 0.4 + Random.double_range(0, 0.4);
-
- // Slightly modify waves
- for (int i = 0; i < waves.length; i++) {
- var w = waves.get(i);
- w.y_offset += Random.double_range(-5, 5);
- w.y_offset = double.max(10, double.min(70, w.y_offset));
- w.amplitude += Random.double_range(-3, 3);
- w.amplitude = double.max(5, double.min(40, w.amplitude));
- }
- }
-
- public GenericArray<AuroraWaveData> get_waves() {
- return waves;
- }
- }
|