Billy Barrow 1 vecka sedan
förälder
incheckning
8730aa5af1

+ 33 - 0
demo/Components/AuroraWaveComponent.vala

@@ -0,0 +1,33 @@
+using Astralis;
+using Inversion;
+using Spry;
+
+/**
+ * AuroraWaveComponent - A single aurora wave strip
+ * 
+ * Renders as a CSS gradient strip with wave animation.
+ * Multiple waves layered create the aurora borealis effect.
+ */
+public class AuroraWaveComponent : Component {
+    
+    public int wave_id { get; set; }
+    public double y_offset { get; set; }
+    public double amplitude { get; set; }
+    public double frequency { get; set; }
+    public string color1 { get; set; default = "#22c55e"; }
+    public string color2 { get; set; default = "#7c3aed"; }
+    public double opacity { get; set; default = 0.6; }
+    public double animation_delay { get; set; default = 0; }
+    
+    public override string markup { get {
+        return """
+        <div sid="wave" class="aurora-wave"></div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        // Build CSS custom properties as the style attribute
+        var style = @"--wave-y: $(y_offset)%%; --wave-amplitude: $(amplitude)px; --wave-freq: $(frequency); --wave-color1: $(color1); --wave-color2: $(color2); --wave-opacity: $(opacity); --wave-delay: $(animation_delay)s; animation-delay: var(--wave-delay);";
+        this["wave"].set_attribute("style", style);
+    }
+}

+ 33 - 0
demo/Components/CodeBlockComponent.vala

@@ -0,0 +1,33 @@
+using Spry;
+
+/**
+ * CodeBlockComponent - A styled code block with syntax highlighting
+ * 
+ * Displays code with a header showing language and window dots
+ */
+public class CodeBlockComponent : Component {
+    
+    public string language { set; get; default = "Vala"; }
+    public string code { set; get; default = ""; }
+    
+    public override string markup { get {
+        return """
+        <div class="code-block">
+            <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" sid="language"></span>
+            </div>
+            <pre><code sid="code"></code></pre>
+        </div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        this["language"].text_content = language;
+        this["code"].text_content = code;
+    }
+}

+ 31 - 0
demo/Components/FeatureCardComponent.vala

@@ -0,0 +1,31 @@
+using Spry;
+
+/**
+ * FeatureCardComponent - A reusable feature card
+ * 
+ * Displays an icon, title, and description in a styled card
+ */
+public class FeatureCardComponent : Component {
+    
+    public string icon { set; get; default = "purple"; }
+    public string icon_emoji { set; get; default = "⭐"; }
+    public string title { set; get; default = "Feature"; }
+    public string description { set; get; default = "Description"; }
+    
+    public override string markup { get {
+        return """
+        <div class="feature-card">
+            <div class="feature-icon" sid="icon"></div>
+            <h3 sid="title"></h3>
+            <p sid="description"></p>
+        </div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        this["icon"].text_content = icon_emoji;
+        this["icon"].set_attribute("class", @"feature-icon $icon");
+        this["title"].text_content = title;
+        this["description"].text_content = description;
+    }
+}

+ 25 - 0
demo/Components/ParticleCanvasComponent.vala

@@ -0,0 +1,25 @@
+using Spry;
+
+/**
+ * ParticleCanvasComponent - A single particle in the simulation
+ * 
+ * Renders as a colored circle at a specific position
+ */
+public class ParticleCanvasComponent : Component {
+    
+    public double x { set; get; default = 50; }
+    public double y { set; get; default = 50; }
+    public string color { set; get; default = "#7c3aed"; }
+    public int size { set; get; default = 12; }
+    
+    public override string markup { get {
+        return """
+        <div sid="particle" style="position: absolute; border-radius: 50%; box-shadow: 0 0 10px currentColor;"></div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        var style = @"position: absolute; left: $(x)%; top: $(y)%; width: $(size)px; height: $(size)px; background: $(color); border-radius: 50%; box-shadow: 0 0 $(size / 2)px $(color); transform: translate(-50%, -50%); transition: all 0.1s ease-out;";
+        this["particle"].set_attribute("style", style);
+    }
+}

+ 26 - 0
demo/Components/StatCardComponent.vala

@@ -0,0 +1,26 @@
+using Spry;
+
+/**
+ * StatCardComponent - A statistics display card
+ * 
+ * Shows a large value with a label underneath
+ */
+public class StatCardComponent : Component {
+    
+    public string value { set; get; default = "0"; }
+    public string label { set; get; default = "Stat"; }
+    
+    public override string markup { get {
+        return """
+        <div class="stat-card">
+            <div class="stat-value" sid="value"></div>
+            <div class="stat-label" sid="label"></div>
+        </div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        this["value"].text_content = value;
+        this["label"].text_content = label;
+    }
+}

+ 39 - 0
demo/Main.vala

@@ -0,0 +1,39 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+using Inversion;
+using Spry;
+
+void main(string[] args) {
+    int port = args.length > 1 ? int.parse(args[1]) : 8080;
+    
+    try {
+        var application = new WebApplication(port);
+        
+        // Enable compression
+        application.use_compression();
+        
+        // Add Spry module for component actions
+        application.add_module<SpryModule>();
+        
+        // Configure templates
+        var spry_cfg = application.configure_with<SpryConfigurator>();
+        spry_cfg.add_template<MainTemplate>("");
+
+        // Add Components
+        application.add_transient<AuroraWaveComponent>();
+        application.add_transient<FeatureCardComponent>();
+        application.add_transient<CodeBlockComponent>();
+        application.add_transient<StatCardComponent>();
+        
+        // Register page components
+        application.add_transient<HomePage>();
+        application.add_endpoint<HomePage>(new EndpointRoute("/"));
+        
+        application.run();
+        
+    } catch (Error e) {
+        printerr("Error: %s\n", e.message);
+        Process.exit(1);
+    }
+}

+ 95 - 0
demo/MainTemplate.vala

@@ -0,0 +1,95 @@
+using Astralis;
+using Spry;
+
+/**
+ * SiteLayoutTemplate - The base template for all pages
+ * 
+ * Provides:
+ * - HTML document structure
+ * - Common <head> elements (scripts, styles)
+ * - Site-wide header with navigation
+ * - Site-wide footer
+ */
+public class MainTemplate : PageTemplate {
+    
+    public override string markup { get {
+        return """
+        <!DOCTYPE html>
+        <html lang="en">
+        <head>
+            <meta charset="UTF-8">
+            <meta name="viewport" content="width=device-width, initial-scale=1.0">
+            <title>Spry Framework - Modern Web Development in Vala</title>
+            <meta name="description" content="Spry is a component-based web framework for Vala, featuring HTMX integration, dependency injection, and reactive templates.">
+            <link rel="stylesheet" href="/styles/main.css">
+            <script spry-res="htmx.js"></script>
+            <script spry-res="htmx-sse.js"></script>
+        </head>
+        <body>
+            <header class="site-header">
+                <div class="header-content">
+                    <a href="/" class="logo">
+                        <div class="logo-icon">⚡</div>
+                        Spry
+                    </a>
+                    <nav>
+                        <ul class="nav-links">
+                            <li><a href="/features">Features</a></li>
+                            <li><a href="/ecosystem">Ecosystem</a></li>
+                            <li><a href="/demo">Demo</a></li>
+                            <li><a href="/freedom">Freedom</a></li>
+                        </ul>
+                    </nav>
+                    <a href="/demo" class="nav-cta">Try Demo</a>
+                </div>
+            </header>
+            <main>
+                <spry-template-outlet />
+            </main>
+            <footer class="site-footer">
+                <div class="container">
+                    <div class="footer-content">
+                        <div class="footer-brand">
+                            <a href="/" class="logo">
+                                <div class="logo-icon">⚡</div>
+                                Spry
+                            </a>
+                            <p>A modern, component-based web framework for Vala. Build reactive, real-time web applications with ease.</p>
+                        </div>
+                        <div class="footer-section">
+                            <h4>Framework</h4>
+                            <ul class="footer-links">
+                                <li><a href="/features">Features</a></li>
+                                <li><a href="/ecosystem">Ecosystem</a></li>
+                                <li><a href="/demo">Live Demo</a></li>
+                            </ul>
+                        </div>
+                        <div class="footer-section">
+                            <h4>Ecosystem</h4>
+                            <ul class="footer-links">
+                                <li><a href="/ecosystem">Astralis</a></li>
+                                <li><a href="/ecosystem">Inversion</a></li>
+                                <li><a href="/ecosystem">Spry</a></li>
+                            </ul>
+                        </div>
+                        <div class="footer-section">
+                            <h4>Community</h4>
+                            <ul class="footer-links">
+                                <li><a href="/freedom">Free Software</a></li>
+                                <li><a href="https://github.com" rel="external">GitHub</a></li>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="footer-bottom">
+                        <p>© 2024 Spry Framework. Free and Open Source Software.</p>
+                        <div class="footer-social">
+                            <a href="https://github.com" title="GitHub">⌨</a>
+                        </div>
+                    </div>
+                </div>
+            </footer>
+        </body>
+        </html>
+        """;
+    }}
+}

+ 190 - 0
demo/Pages/HomePage.vala

@@ -0,0 +1,190 @@
+using Astralis;
+using Invercargill;
+using Invercargill.DataStructures;
+using Inversion;
+using Spry;
+
+/**
+ * HomePage - The main landing page
+ * 
+ * Eye-catching hero section with gradient text,
+ * feature highlights, and call-to-action buttons
+ */
+public class HomePage : PageComponent {
+    
+    public const string ROUTE = "/";
+    
+    private ComponentFactory factory = inject<ComponentFactory>();
+    
+    public override string markup { get {
+        return """
+        <div sid="home">
+            <!-- Hero Section -->
+            <section class="hero">
+                <div class="container">
+                    <span class="hero-badge">
+                        ⚡ Free & Open Source
+                    </span>
+                    <h1>Build Modern Web Apps in Vala</h1>
+                    <p class="hero-subtitle">
+                        Spry is a component-based web framework featuring HTMX integration,
+                        reactive templates, and dependency injection. Fast, type-safe, and elegant.
+                    </p>
+                    <div class="hero-actions">
+                        <a href="/demo" class="btn btn-primary">
+                            ✨ Try Live Demo
+                        </a>
+                        <a href="/features" class="btn btn-secondary">
+                            Explore Features
+                        </a>
+                    </div>
+                </div>
+            </section>
+
+            <!-- Quick Stats -->
+            <section class="section">
+                <div class="container">
+                    <div class="stats-grid">
+                        <div class="stat-card">
+                            <div class="stat-value">100%</div>
+                            <div class="stat-label">Type-Safe Vala</div>
+                        </div>
+                        <div class="stat-card">
+                            <div class="stat-value">0</div>
+                            <div class="stat-label">JavaScript Required</div>
+                        </div>
+                        <div class="stat-card">
+                            <div class="stat-value">∞</div>
+                            <div class="stat-label">Possibilities</div>
+                        </div>
+                        <div class="stat-card">
+                            <div class="stat-value">FREE</div>
+                            <div class="stat-label">Forever</div>
+                        </div>
+                    </div>
+                </div>
+            </section>
+
+            <!-- Feature Highlights -->
+            <section class="section">
+                <div class="container">
+                    <div class="section-header">
+                        <h2>Why Choose Spry?</h2>
+                        <p>Build powerful web applications with a framework designed for developer happiness</p>
+                    </div>
+                    <div class="features-grid">
+                        <spry-outlet sid="features"/>
+                    </div>
+                </div>
+            </section>
+
+            <!-- Code Example -->
+            <section class="section">
+                <div class="container">
+                    <div class="section-header">
+                        <h2>Clean, Intuitive Code</h2>
+                        <p>Write components that are easy to understand and maintain</p>
+                    </div>
+                    <div class="code-block">
+                        <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">Vala</span>
+                        </div>
+                        <pre><code>class CounterComponent : Component {
+    private int count = 0;
+
+    public override string markup {
+        owned get {
+            // Use spry-action for interactivity
+            return @"Counter: $(count)
+                [Button: spry-action=':Increment' +]
+                [Button: spry-action=':Decrement' -]";
+        }
+    }
+
+    public async override void handle_action(string action) {
+        if (action == "Increment") count++;
+        else if (action == "Decrement") count--;
+    }
+}</code></pre>
+                    </div>
+                </div>
+            </section>
+
+            <!-- CTA Section -->
+            <section class="section">
+                <div class="container">
+                    <div class="freedom-section">
+                        <div class="freedom-icon">🕊</div>
+                        <h2>Free as in Freedom</h2>
+                        <p>
+                            Spry is Free/Libre Open Source Software. Use it, study it, modify it,
+                            and share it freely. Built by the community, for the community.
+                        </p>
+                        <div class="hero-actions">
+                            <a href="/freedom" class="btn btn-accent">
+                                Learn About Freedom
+                            </a>
+                            <a href="/ecosystem" class="btn btn-secondary">
+                                Explore Ecosystem
+                            </a>
+                        </div>
+                    </div>
+                </div>
+            </section>
+        </div>
+        """;
+    }}
+    
+    public override async void prepare() throws Error {
+        var features = new Series<Renderable>();
+        
+        var feature1 = factory.create<FeatureCardComponent>();
+        feature1.icon = "purple";
+        feature1.icon_emoji = "⚡";
+        feature1.title = "HTMX Integration";
+        feature1.description = "Build dynamic UIs without writing JavaScript. HTMX handles the complexity, you handle the logic.";
+        features.add(feature1);
+        
+        var feature2 = factory.create<FeatureCardComponent>();
+        feature2.icon = "blue";
+        feature2.icon_emoji = "🔧";
+        feature2.title = "Dependency Injection";
+        feature2.description = "Clean architecture with Inversion of Control. Inject services, stores, and components effortlessly.";
+        features.add(feature2);
+        
+        var feature3 = factory.create<FeatureCardComponent>();
+        feature3.icon = "green";
+        feature3.icon_emoji = "🎨";
+        feature3.title = "Reactive Templates";
+        feature3.description = "Declarative templates with outlets and automatic updates. Your UI stays in sync with your data.";
+        features.add(feature3);
+        
+        var feature4 = factory.create<FeatureCardComponent>();
+        feature4.icon = "purple";
+        feature4.icon_emoji = "🔒";
+        feature4.title = "Type-Safe";
+        feature4.description = "Full Vala type safety means fewer runtime errors. The compiler catches bugs before you do.";
+        features.add(feature4);
+        
+        var feature5 = factory.create<FeatureCardComponent>();
+        feature5.icon = "blue";
+        feature5.icon_emoji = "🚀";
+        feature5.title = "High Performance";
+        feature5.description = "Built on Astralis for maximum throughput. Native code performance with web framework convenience.";
+        features.add(feature5);
+        
+        var feature6 = factory.create<FeatureCardComponent>();
+        feature6.icon = "green";
+        feature6.icon_emoji = "📦";
+        feature6.title = "Modular Design";
+        feature6.description = "Use what you need, extend what you want. Clean separation of concerns at every level.";
+        features.add(feature6);
+        
+        set_outlet_children("features", features);
+    }
+}

+ 21 - 0
demo/meson.build

@@ -0,0 +1,21 @@
+# Spry Framework Website
+# A modern, techy website showcasing the Spry framework and its ecosystem
+
+website_sources = files(
+    'Main.vala',
+    'MainTemplate.vala',
+    'Pages/HomePage.vala',
+    'Components/FeatureCardComponent.vala',
+    'Components/AuroraWaveComponent.vala',
+    'Components/CodeBlockComponent.vala',
+    'Components/StatCardComponent.vala',
+)
+
+# Math library for particle physics (sin/cos in explode function)
+m_dep = meson.get_compiler('c').find_library('m', required: false)
+
+executable('spry-demo',
+    website_sources,
+    dependencies: [spry_dep, astralis_dep, invercargill_dep, inversion_dep, m_dep],
+    install: true
+)

+ 40 - 15
important-details.md

@@ -74,9 +74,9 @@ public async override void continuation(ContinuationContext continuation_context
         percent = i;
         status = @"Processing... $(i)%";
         
-        // Send fragment updates to the client
-        yield continuation_context.send_fragment("progress", "progress-bar");
-        yield continuation_context.send_fragment("status", "status");
+        // Send dynamic section updates to the client
+        yield continuation_context.send_dynamic("progress-bar");
+        yield continuation_context.send_dynamic("status");
         
         Timeout.add(500, () => {
             continuation.callback();
@@ -86,7 +86,7 @@ public async override void continuation(ContinuationContext continuation_context
     }
     
     status = "Complete!";
-    yield continuation_context.send_fragment("status", "status");
+    yield continuation_context.send_dynamic("status");
 }
 ```
 
@@ -105,7 +105,7 @@ public async override void continuation_canceled() throws Error {
 
 | Method | Description |
 |--------|-------------|
-| `send_fragment(event_type, sid)` | Send a fragment (by `sid`) as an SSE event with the given event type |
+| `send_dynamic(name)` | Send a dynamic section (by `spry-dynamic` name) as an SSE event |
 | `send_json(event_type, node)` | Send JSON data as an SSE event |
 | `send_string(event_type, data)` | Send raw string data as an SSE event |
 | `send_full_update(event_type)` | Send the entire component document |
@@ -113,17 +113,21 @@ public async override void continuation_canceled() throws Error {
 
 ### The `spry-continuation` Attribute
 
-Add `spry-continuation` to an element to enable SSE:
+Add `spry-continuation` to an element to enable SSE for its children:
 
 ```vala
 public override string markup { get {
     return """
     <div spry-continuation>
-        <div class="progress-bar" sid="progress-bar" sse-swap="progress">
-            0%
+        <div class="progress-container" spry-dynamic="progress-bar">
+            <div class="progress-bar" spry-unique
+                 content-expr='format("%i%%", this.percent)' 
+                 style-width-expr='format("%i%%", this.percent)'>
+                0%
+            </div>
         </div>
-        <div class="status" sid="status" sse-swap="status">
-            Initializing...
+        <div class="status" spry-dynamic="status">
+            <strong>Status:</strong> <span spry-unique content-expr="this.status">Initializing...</span>
         </div>
     </div>
     """;
@@ -135,16 +139,37 @@ The `spry-continuation` attribute is shorthand for:
 - `sse-connect="(auto-generated-endpoint)"` - Connect to the SSE endpoint
 - `sse-close="_spry-close"` - Close event name
 
-### The `sse-swap` Attribute
+### The `spry-dynamic` Attribute
 
-Use `sse-swap="eventname"` on child elements to specify which SSE event type should swap the content:
+Use `spry-dynamic="name"` on child elements to mark them as updatable sections:
 
 ```html
-<div sid="progress-bar" sse-swap="progress">...</div>
-<div sid="status" sse-swap="status">...</div>
+<div class="progress-container" spry-dynamic="progress-bar">...</div>
+<div class="status" spry-dynamic="status">...</div>
 ```
 
-When `continuation_context.send_fragment("progress", "progress-bar")` is called, the fragment with `sid="progress-bar"` is sent as an SSE event with type "progress", and HTMX swaps it into the element listening for that event type.
+**Requirements:**
+- Must be a child of an element with `spry-continuation`
+- Automatically gets `sse-swap="_spry-dynamic-{name}"` and `hx-swap="outerHTML"`
+
+When `continuation_context.send_dynamic("progress-bar")` is called:
+1. The element with `spry-dynamic="progress-bar"` is located
+2. Its HTML content is rendered and sent as an SSE event
+3. HTMX swaps it into the matching element on the client
+
+### The `spry-unique` Attribute
+
+Use `spry-unique` to generate unique IDs for elements that need stable targeting:
+
+```html
+<div class="progress-bar" spry-unique>...</div>
+```
+
+**Restrictions:**
+- Cannot specify an `id` attribute on the same element
+- Cannot be used inside `spry-per-*` loops (or children of loops)
+
+The generated ID format is `_spry-unique-{counter}-{instance_id}`.
 
 ### Required Scripts for SSE
 

+ 1 - 0
meson.build

@@ -20,3 +20,4 @@ subdir('src')
 subdir('examples')
 subdir('tools')
 subdir('website')
+subdir('demo')

+ 1 - 1
website/Main.vala

@@ -55,7 +55,7 @@ void main(string[] args) {
         
         // Configure templates
         var spry_cfg = application.configure_with<SpryConfigurator>();
-        spry_cfg.add_template<SiteLayoutTemplate>("");
+        spry_cfg.add_template<MainTemplate>("");
         
         // Register page components
         application.add_transient<HomePage>();

+ 1 - 1
website/Templates/SiteLayoutTemplate.vala

@@ -10,7 +10,7 @@ using Spry;
  * - Site-wide header with navigation
  * - Site-wide footer
  */
-public class SiteLayoutTemplate : PageTemplate {
+public class MainTemplate : PageTemplate {
     
     public override string markup { get {
         return """