Billy Barrow 1 nedēļu atpakaļ
vecāks
revīzija
22c84310cf
2 mainītis faili ar 220 papildinājumiem un 37 dzēšanām
  1. 10 30
      examples/ProgressExample.vala
  2. 210 7
      src/Component.vala

+ 10 - 30
examples/ProgressExample.vala

@@ -22,31 +22,9 @@ using Spry;
  */
 class ProgressComponent : Component {
     
-    private int _percent = 0;
-    private string _status = "Initializing...";
-    
-    public int percent {
-        get { return _percent; }
-        set {
-            _percent = value;
-            var progress_bar = this["progress-bar"];
-            if (progress_bar != null) {
-                progress_bar.set_attribute("style", @"width: $(_percent)%");
-                progress_bar.text_content = @"$(_percent)%";
-            }
-        }
-    }
-    
-    public string status {
-        get { return _status; }
-        set {
-            _status = value;
-            var status_el = this["status"];
-            if (status_el != null) {
-                status_el.text_content = @"Status: $(_status)";
-            }
-        }
-    }
+    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 """
@@ -102,17 +80,18 @@ class ProgressComponent : Component {
         
         <div spry-continuation>
             <div class="progress-container" sse-swap="progress">
-                <div class="progress-bar" id="progress-bar" sid="progress-bar" style="width: 0%">
+                <div class="progress-bar" id="progress-bar" sid="progress-bar" spry-prop="percent; style.width = percent:%i%%">
                     0%
                 </div>
             </div>
             
             <div class="status" sse-swap="status" sid="status" hx-swap="outerHTML">
-                <strong>Status:</strong> Initializing...
+                <strong>Status:</strong> <span spry-prop="status">Initializing...</span>
             </div>
             
-            <div class="log" id="log">
-                <div sse-swap="log" hx-swap="afterbegin">Waiting for task to start...</div>
+            <div class="log" id="log" sid="log" sse-swap="log">
+                <div>Waiting for task to start...</div>
+                <div spry-per="completed_tasks" spry-prop="value"></div>
             </div>
         </div>
         </body>
@@ -148,6 +127,7 @@ class ProgressComponent : Component {
             // 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", "progress-bar");
@@ -156,7 +136,7 @@ class ProgressComponent : Component {
             yield continuation_context.send_fragment("status", "status");
             
             // Send log message - HTML that will be appended to the log
-            yield continuation_context.send_string("log", @"<div class=\"log-entry\">$(steps[i])</div>");
+            yield continuation_context.send_fragment("log", "log");
             
             // Simulate work being done (500ms per step)
             Timeout.add(500, () => {

+ 210 - 7
src/Component.vala

@@ -8,7 +8,8 @@ namespace Spry {
     public errordomain ComponentError {
         INVALID_TYPE,
         ELEMENT_NOT_FOUND,
-        TYPE_NOT_FOUND;
+        TYPE_NOT_FOUND,
+        PROPERTY_NOT_FOUND;
     }
 
     public abstract class Component : Object, Renderable {
@@ -198,9 +199,10 @@ namespace Spry {
 
         private async void transform_document(MarkupDocument doc) throws Error {
             remove_hidden_blocks(doc);
+            yield transform_per_attributes(doc);
+            yield transform_property_attributes(doc);
             yield transform_outlets(doc);
             yield transform_components(doc);
-            replace_control_blocks(doc);
             transform_action_nodes(doc);
             transform_target_nodes(doc);
             transform_global_nodes(doc);
@@ -227,11 +229,6 @@ namespace Spry {
                 .iterate(n => n.remove());
         }
 
-        private void replace_control_blocks(MarkupDocument doc) {
-            doc.select("//spry-control")
-                .iterate(n => n.replace_with_nodes(n.children));
-        }
-
         private void transform_action_nodes(MarkupDocument doc) throws Error {
             var action_nodes = doc.select("//*[@spry-action]");
             foreach(var node in action_nodes) {
@@ -312,6 +309,212 @@ namespace Spry {
             }
         }
 
+        private void transform_if_attributes(MarkupDocument doc) throws Error {
+            var nodes = doc.select("//*[@spry-if]");
+            foreach (var node in nodes) {
+                var expression = node.get_attribute("spry-per");
+
+            }
+        }
+
+        private async void transform_per_attributes(MarkupDocument doc) throws Error {
+            var nodes = doc.select("//*[@spry-per]");
+            foreach (var node in nodes) {
+                var prop_path = node.get_attribute("spry-per");
+                var prop_value = get_ultimate_value(prop_path);
+
+                if(!prop_value.type().is_a(typeof(Enumerable))) {
+                    throw new ComponentError.INVALID_TYPE(@"The spry-per attribute must refer to a value of type Invercargill.Enumerable, $prop_path is a $(prop_value.type().name())");
+                }
+
+                node.remove_attribute("spry-per");
+                var values = ((Enumerable)prop_value).as_values();
+                var output_nodes = new Series<MarkupNode>();
+                foreach(var value in values) {
+                    var fragment = new MarkupDocument();
+                    fragment.body.append_node(node);
+                    yield transform_property_attributes(fragment, value);
+                    output_nodes.add_all(fragment.body.children);
+                }
+                node.replace_with_nodes(output_nodes);
+            }
+        }
+
+        private async void transform_property_attributes(MarkupDocument doc, Value? context = null) throws Error {
+            var nodes = doc.select("//*[@spry-prop]");
+            foreach (var node in nodes) {
+                var attr_string = node.get_attribute("spry-prop");
+                var paths = attr_string.split(";");
+                foreach (var prop_path in paths) {
+                    string attribute = null;
+                    string formatter = null;
+                    if(prop_path.contains("=")) {
+                        var parts = prop_path.split("=", 2);
+                        attribute = parts[0].chomp().chug();
+                        prop_path = parts[1].chomp().chug();
+                    }
+                    if(prop_path.contains(":")) {
+                        var parts = prop_path.split(":", 2);
+                        prop_path = parts[0].chomp().chug();
+                        formatter = parts[1].chomp().chug();
+                    }
+
+                    // Resolve the property path
+                    var value = get_ultimate_value(prop_path, context);
+                    var type = value.type();
+
+                    // Render renderables
+                    if(type.is_a(typeof(Renderable))) {
+                        var renderable = (Renderable)value.get_object();
+                        if(renderable != null) {
+                            var document = yield renderable.to_document();
+                            node.replace_with_nodes(document.body.children);
+                        }
+                        else {
+                            node.remove();
+                        }
+                        continue;
+                    }
+
+                    if(attribute.has_prefix("class.")) {
+                        var class_name = attribute[6:];
+                        if(!value.type().is_a(typeof(bool))) {
+                            throw new ComponentError.INVALID_TYPE(@"Property \'$prop_path\' is not assignable to \'$attribute\', only bools are valid");
+                        }
+                        if(value.get_boolean()) {
+                            node.add_class(class_name);
+                        }
+                        else {
+                            node.remove_class(class_name);
+                        }
+                        continue;
+                    }
+                    
+                    // Set text for stringified properties
+                    var output = get_string_from_value(value, formatter);
+                    if(attribute != null) {
+                        if(attribute.has_prefix("style.")) {
+                            node.set_style(attribute[6:], output);
+                        }
+                        else {
+                            node.set_attribute(attribute, output);
+                        }
+                    }
+                    else {
+                        node.text_content = output;
+                    }
+                }
+
+                node.remove_attribute("spry-prop");
+            }
+
+        }
+
+        private Value get_ultimate_value(string property_path, Value? context = null) throws Error {
+            var path = property_path.split(".");
+            var value = context;
+
+            if(context == null || path[0] == "this") {
+                value = Value(get_type());
+                value.set_object(this);
+                if(path[0] == "this") {
+                    path = path[1:];
+                }
+            }
+            else if(path[0] == "value") {
+                if(value.type().is_a(typeof(Object)) && value.get_object() == this) {
+                    throw new ComponentError.PROPERTY_NOT_FOUND("Identifier 'value' not valid in this context");
+                }
+                path = path[1:];
+            }
+
+            foreach(var prop_name in path) {
+                if(!value.type().is_a(typeof(Object))) {
+                    throw new ComponentError.PROPERTY_NOT_FOUND(@"Cannot navigate to property \"$prop_name\" on type $(value.type_name()): Only Object types are navigable");
+                }
+                var source = value.get_object();
+                var prop_spec = source.get_class().find_property(prop_name);
+                if(prop_spec == null) {
+                    throw new ComponentError.PROPERTY_NOT_FOUND(@"Could not find property \"$prop_name\" on object $(source.get_type().name())");
+                }
+    
+                var type = prop_spec.value_type;
+                value = Value(type);                
+                source.get_property(prop_name, ref value);
+            }
+
+            return value;
+        }
+
+        private string get_string_from_value(Value value, string? formatter) {
+            var type = value.type();
+            
+            if (type == typeof(bool)) {
+                return value.get_boolean() ? "true" : "false";
+            } else if (type == typeof(int)) {
+                var int_val = value.get_int();
+                return formatter != null ? int_val.to_string(formatter) : int_val.to_string();
+            } else if (type == typeof(uint)) {
+                var uint_val = value.get_uint();
+                return formatter != null ? uint_val.to_string(formatter) : uint_val.to_string();
+            } else if (type == typeof(long)) {
+                var long_val = value.get_long();
+                return formatter != null ? long_val.to_string(formatter) : long_val.to_string();
+            } else if (type == typeof(ulong)) {
+                var ulong_val = value.get_ulong();
+                return formatter != null ? ulong_val.to_string(formatter) : ulong_val.to_string();
+            } else if (type == typeof(int64)) {
+                var int64_val = value.get_int64();
+                return formatter != null ? int64_val.to_string(formatter) : int64_val.to_string();
+            } else if (type == typeof(uint64)) {
+                var uint64_val = value.get_uint64();
+                return formatter != null ? uint64_val.to_string(formatter) : uint64_val.to_string();
+            } else if (type == typeof(float)) {
+                var float_val = value.get_float();
+                return formatter != null ? float_val.to_string(formatter) : float_val.to_string();
+            } else if (type == typeof(double)) {
+                var double_val = value.get_double();
+                return formatter != null ? @"%$(formatter)".printf(double_val) : double_val.to_string();
+            } else if (type == typeof(string)) {
+                return value.get_string() ?? "";
+            } else if (type == typeof(char)) {
+                var char_val = value.get_schar();
+                return formatter != null ? char_val.to_string(formatter) : char_val.to_string();
+            } else if (type == typeof(uchar)) {
+                var uchar_val = value.get_uchar();
+                return formatter != null ? uchar_val.to_string(formatter) : uchar_val.to_string();
+            } else if (type == typeof(int8)) {
+                var int8_val = value.get_schar();
+                return formatter != null ? int8_val.to_string(formatter) : int8_val.to_string();
+            } else if (type.is_enum()) {
+                var enum_val = value.get_enum();
+                return formatter != null ? enum_val.to_string(formatter) : enum_val.to_string();
+            } else if (type.is_flags()) {
+                var flags_val = value.get_flags();
+                return formatter != null ? flags_val.to_string(formatter) : flags_val.to_string();
+            } else if (type == typeof(Type)) {
+                return value.get_gtype().name();
+            } else if (type.is_object()) {
+                var obj = value.get_object();
+                if (obj == null) {
+                    return "null";
+                }
+                return @"<$(type.name())>";
+            } else if (type == typeof(Variant)) {
+                var variant = value.get_variant();
+                return variant != null ? variant.print(true) : "null";
+            } else if (type == typeof(ParamSpec)) {
+                var param = value.get_param();
+                return param != null ? param.name : "null";
+            } else if (type.is_derived() && !type.is_object() && !type.is_interface()) {
+                // Handle boxed types - derived but not object or interface
+                return @"<boxed $(type.name())>";
+            } else if (type == typeof(void*)) {
+                return @"<pointer 0x$(((int)value).to_string("%x"))>";
+            }
+            
+            return "";
+        }
 
     }