|
@@ -5,6 +5,12 @@ using Astralis;
|
|
|
|
|
|
|
|
namespace Spry {
|
|
namespace Spry {
|
|
|
|
|
|
|
|
|
|
+ public errordomain ComponentError {
|
|
|
|
|
+ INVALID_TYPE,
|
|
|
|
|
+ ELEMENT_NOT_FOUND,
|
|
|
|
|
+ TYPE_NOT_FOUND;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public abstract class Component : Object, Renderable {
|
|
public abstract class Component : Object, Renderable {
|
|
|
|
|
|
|
|
private static Dictionary<Type, ComponentTemplate> templates;
|
|
private static Dictionary<Type, ComponentTemplate> templates;
|
|
@@ -29,7 +35,9 @@ namespace Spry {
|
|
|
|
|
|
|
|
private PathProvider _path_provider = inject<PathProvider>();
|
|
private PathProvider _path_provider = inject<PathProvider>();
|
|
|
private ContinuationProvider _continuation_provider = inject<ContinuationProvider>();
|
|
private ContinuationProvider _continuation_provider = inject<ContinuationProvider>();
|
|
|
|
|
+ private ComponentFactory _component_factory = inject<ComponentFactory>();
|
|
|
private Catalogue<string, Renderable> _children = new Catalogue<string, Renderable>();
|
|
private Catalogue<string, Renderable> _children = new Catalogue<string, Renderable>();
|
|
|
|
|
+ private Dictionary<string, Component> _child_components = new Dictionary<string, Component>();
|
|
|
private HashSet<Component> _global_sources = new HashSet<Component>();
|
|
private HashSet<Component> _global_sources = new HashSet<Component>();
|
|
|
private MarkupDocument _instance;
|
|
private MarkupDocument _instance;
|
|
|
|
|
|
|
@@ -108,32 +116,93 @@ namespace Spry {
|
|
|
_global_sources.add(component);
|
|
_global_sources.add(component);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ protected T get_component_child<T>(string sid) throws Error {
|
|
|
|
|
+ var node = query_one(@"//spry-component[@sid='$sid']");
|
|
|
|
|
+ if(node == null) {
|
|
|
|
|
+ throw new ComponentError.ELEMENT_NOT_FOUND(@"No spry-component element with sid '$sid' found.");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var component = get_component_instance_from_component_node(node);
|
|
|
|
|
+ if(!component.get_type().is_a(typeof(T))) {
|
|
|
|
|
+ throw new ComponentError.INVALID_TYPE(@"Component type $(component.get_type().name()) is not a $(typeof(T).name())");
|
|
|
|
|
+ }
|
|
|
|
|
+ return component;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public async MarkupDocument to_document() throws Error {
|
|
public async MarkupDocument to_document() throws Error {
|
|
|
yield prepare();
|
|
yield prepare();
|
|
|
var final_instance = instance.copy();
|
|
var final_instance = instance.copy();
|
|
|
|
|
|
|
|
- // Replace outlets
|
|
|
|
|
- var outlets = final_instance.select("//spry-outlet");
|
|
|
|
|
|
|
+ remove_hidden_blocks(final_instance);
|
|
|
|
|
+ yield transform_outlets(final_instance);
|
|
|
|
|
+ yield transform_components(final_instance);
|
|
|
|
|
+ replace_control_blocks(final_instance);
|
|
|
|
|
+ transform_action_nodes(final_instance);
|
|
|
|
|
+ transform_target_nodes(final_instance);
|
|
|
|
|
+ transform_global_nodes(final_instance);
|
|
|
|
|
+ transform_script_nodes(final_instance);
|
|
|
|
|
+ transform_continuation_nodes(final_instance);
|
|
|
|
|
+ remove_internal_sids(final_instance);
|
|
|
|
|
+ yield append_globals(final_instance);
|
|
|
|
|
+
|
|
|
|
|
+ return final_instance;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+ public async HttpResult to_result() throws Error {
|
|
|
|
|
+ var document = yield to_document();
|
|
|
|
|
+ return document.to_result(get_status());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private class ComponentTemplate : MarkupTemplate {
|
|
|
|
|
+ private string _markup;
|
|
|
|
|
+ protected override string markup { get { return _markup; } }
|
|
|
|
|
+
|
|
|
|
|
+ public ComponentTemplate(string markup) {
|
|
|
|
|
+ this._markup = markup;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Component get_component_instance_from_component_node(MarkupNode node) throws Error {
|
|
|
|
|
+ Component component;
|
|
|
|
|
+ // If no SID, create one to keep track of the instance
|
|
|
|
|
+ var sid = node.get_attribute("sid");
|
|
|
|
|
+ if(sid == null) {
|
|
|
|
|
+ sid = Uuid.string_random();
|
|
|
|
|
+ node.set_attribute("sid", sid);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if(!_child_components.try_get(sid, out component)) {
|
|
|
|
|
+ component = _component_factory.create_by_name(node.get_attribute("name"));
|
|
|
|
|
+ _child_components[sid] = component;
|
|
|
|
|
+ }
|
|
|
|
|
+ return component;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private async void transform_outlets(MarkupDocument doc) throws Error {
|
|
|
|
|
+ var outlets = doc.select("//spry-outlet");
|
|
|
foreach (var outlet in outlets) {
|
|
foreach (var outlet in outlets) {
|
|
|
var nodes = new Series<MarkupNode>();
|
|
var nodes = new Series<MarkupNode>();
|
|
|
foreach(var renderable in _children.get_or_empty(outlet.get_attribute("sid"))) {
|
|
foreach(var renderable in _children.get_or_empty(outlet.get_attribute("sid"))) {
|
|
|
var document = yield renderable.to_document();
|
|
var document = yield renderable.to_document();
|
|
|
nodes.add_all(document.body.children);
|
|
nodes.add_all(document.body.children);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
outlet.replace_with_nodes(nodes);
|
|
outlet.replace_with_nodes(nodes);
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Remove hidden blocks
|
|
|
|
|
- final_instance.select("//*[@spry-hidden]")
|
|
|
|
|
|
|
+ private void remove_hidden_blocks(MarkupDocument doc) {
|
|
|
|
|
+ doc.select("//*[@spry-hidden]")
|
|
|
.iterate(n => n.remove());
|
|
.iterate(n => n.remove());
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Replace control blocks with their children
|
|
|
|
|
- final_instance.select("//spry-control")
|
|
|
|
|
|
|
+ private void replace_control_blocks(MarkupDocument doc) {
|
|
|
|
|
+ doc.select("//spry-control")
|
|
|
.iterate(n => n.replace_with_nodes(n.children));
|
|
.iterate(n => n.replace_with_nodes(n.children));
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- var action_nodes = final_instance.select("//*[@spry-action]");
|
|
|
|
|
|
|
+ private void transform_action_nodes(MarkupDocument doc) throws Error {
|
|
|
|
|
+ var action_nodes = doc.select("//*[@spry-action]");
|
|
|
foreach(var node in action_nodes) {
|
|
foreach(var node in action_nodes) {
|
|
|
var action = node.get_attribute("spry-action").split(":", 2);
|
|
var action = node.get_attribute("spry-action").split(":", 2);
|
|
|
var component_name = action[0].replace(".", "");
|
|
var component_name = action[0].replace(".", "");
|
|
@@ -145,10 +214,12 @@ namespace Spry {
|
|
|
node.remove_attribute("spry-action");
|
|
node.remove_attribute("spry-action");
|
|
|
node.set_attribute("hx-get", _path_provider.get_action_path(component_name, component_action));
|
|
node.set_attribute("hx-get", _path_provider.get_action_path(component_name, component_action));
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var target_nodes = final_instance.select("//*[@spry-target]");
|
|
|
|
|
|
|
+ private void transform_target_nodes(MarkupDocument doc) {
|
|
|
|
|
+ var target_nodes = doc.select("//*[@spry-target]");
|
|
|
foreach(var node in target_nodes) {
|
|
foreach(var node in target_nodes) {
|
|
|
- var target_node = final_instance.select_one(@"//*[@sid='$(node.get_attribute("spry-target"))']");
|
|
|
|
|
|
|
+ var target_node = doc.select_one(@"//*[@sid='$(node.get_attribute("spry-target"))']");
|
|
|
if(target_node.id == null) {
|
|
if(target_node.id == null) {
|
|
|
target_node.id = "_spry-" + Uuid.string_random();
|
|
target_node.id = "_spry-" + Uuid.string_random();
|
|
|
}
|
|
}
|
|
@@ -156,14 +227,18 @@ namespace Spry {
|
|
|
node.set_attribute("hx-target", @"#$(target_node.id)");
|
|
node.set_attribute("hx-target", @"#$(target_node.id)");
|
|
|
node.remove_attribute("spry-target");
|
|
node.remove_attribute("spry-target");
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var global_nodes = final_instance.select("//*[@spry-global]");
|
|
|
|
|
|
|
+ private void transform_global_nodes(MarkupDocument doc) {
|
|
|
|
|
+ var global_nodes = doc.select("//*[@spry-global]");
|
|
|
foreach(var node in global_nodes) {
|
|
foreach(var node in global_nodes) {
|
|
|
var key = node.get_attribute("spry-global");
|
|
var key = node.get_attribute("spry-global");
|
|
|
node.set_attribute("hx-swap-oob", @"[spry-global=\"$key\"]");
|
|
node.set_attribute("hx-swap-oob", @"[spry-global=\"$key\"]");
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var script_nodes = final_instance.select("//script[@spry-res]");
|
|
|
|
|
|
|
+ private void transform_script_nodes(MarkupDocument doc) {
|
|
|
|
|
+ var script_nodes = doc.select("//script[@spry-res]");
|
|
|
foreach(var node in script_nodes) {
|
|
foreach(var node in script_nodes) {
|
|
|
var res = node.get_attribute("spry-res");
|
|
var res = node.get_attribute("spry-res");
|
|
|
if(res != null) {
|
|
if(res != null) {
|
|
@@ -171,8 +246,10 @@ namespace Spry {
|
|
|
}
|
|
}
|
|
|
node.remove_attribute("spry-res");
|
|
node.remove_attribute("spry-res");
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var continuation_nodes = final_instance.select("//*[@spry-continuation]");
|
|
|
|
|
|
|
+ private void transform_continuation_nodes(MarkupDocument doc) {
|
|
|
|
|
+ var continuation_nodes = doc.select("//*[@spry-continuation]");
|
|
|
foreach(var node in continuation_nodes) {
|
|
foreach(var node in continuation_nodes) {
|
|
|
var path = _continuation_provider.get_continuation_path(this);
|
|
var path = _continuation_provider.get_continuation_path(this);
|
|
|
node.set_attribute("hx-ext", "sse");
|
|
node.set_attribute("hx-ext", "sse");
|
|
@@ -180,37 +257,31 @@ namespace Spry {
|
|
|
node.set_attribute("sse-close", "_spry-close");
|
|
node.set_attribute("sse-close", "_spry-close");
|
|
|
node.remove_attribute("spry-continuation");
|
|
node.remove_attribute("spry-continuation");
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Remove all internal SIDs
|
|
|
|
|
- final_instance.select("//*[@sid]")
|
|
|
|
|
|
|
+ private void remove_internal_sids(MarkupDocument doc) {
|
|
|
|
|
+ doc.select("//*[@sid]")
|
|
|
.iterate(n => n.remove_attribute("sid"));
|
|
.iterate(n => n.remove_attribute("sid"));
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Add globals
|
|
|
|
|
|
|
+ private async void append_globals(MarkupDocument doc) throws Error {
|
|
|
foreach(var source in _global_sources) {
|
|
foreach(var source in _global_sources) {
|
|
|
var document = yield source.to_document();
|
|
var document = yield source.to_document();
|
|
|
var globals = document.select("//*[@spry-global]");
|
|
var globals = document.select("//*[@spry-global]");
|
|
|
- final_instance.body.append_nodes(globals);
|
|
|
|
|
|
|
+ doc.body.append_nodes(globals);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- return final_instance;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- public async HttpResult to_result() throws Error {
|
|
|
|
|
- var document = yield to_document();
|
|
|
|
|
- return document.to_result(get_status());
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private class ComponentTemplate : MarkupTemplate {
|
|
|
|
|
- private string _markup;
|
|
|
|
|
- protected override string markup { get { return _markup; } }
|
|
|
|
|
-
|
|
|
|
|
- public ComponentTemplate(string markup) {
|
|
|
|
|
- this._markup = markup;
|
|
|
|
|
|
|
+ private async void transform_components(MarkupDocument doc) throws Error {
|
|
|
|
|
+ var components = doc.select("//spry-component");
|
|
|
|
|
+ foreach (var component_node in components) {
|
|
|
|
|
+ var component = get_component_instance_from_component_node(component_node);
|
|
|
|
|
+ var document = yield component.to_document();
|
|
|
|
|
+ component_node.replace_with_nodes(document.body.children);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|