From f488f63cbbdca6090ef3ed20c14ea9fb1d35b99e Mon Sep 17 00:00:00 2001 From: rd4com <144297616+rd4com@users.noreply.github.com> Date: Tue, 23 Apr 2024 18:21:46 +0200 Subject: [PATCH 1/8] Integration prototype --- integration prototype/AnyThing.mojo | 102 +++++++++ integration prototype/Component.mojo | 21 ++ integration prototype/ComponentManager.mojo | 225 ++++++++++++++++++++ integration prototype/DomEvent.mojo | 20 ++ integration prototype/DomTree.mojo | 108 ++++++++++ integration prototype/Helpers.mojo | 30 +++ integration prototype/README.md | 36 ++++ integration prototype/Server.mojo | 111 ++++++++++ integration prototype/js_renderer.js | 65 ++++++ integration prototype/main.mojo | 88 ++++++++ 10 files changed, 806 insertions(+) create mode 100644 integration prototype/AnyThing.mojo create mode 100644 integration prototype/Component.mojo create mode 100644 integration prototype/ComponentManager.mojo create mode 100644 integration prototype/DomEvent.mojo create mode 100644 integration prototype/DomTree.mojo create mode 100644 integration prototype/Helpers.mojo create mode 100644 integration prototype/README.md create mode 100644 integration prototype/Server.mojo create mode 100644 integration prototype/js_renderer.js create mode 100644 integration prototype/main.mojo diff --git a/integration prototype/AnyThing.mojo b/integration prototype/AnyThing.mojo new file mode 100644 index 0000000..0cd8fd7 --- /dev/null +++ b/integration prototype/AnyThing.mojo @@ -0,0 +1,102 @@ +#Used for props, should be replaced by parametrized props +struct AnyThing: + var p:Pointer[Int] + var ref_count: Pointer[Int] + var free_function: Pointer[Int] + + fn __init__[T:CollectionElement](inout self,arg:T): + self.p = __type_of(self.p).alloc(1) + var tmp = UnsafePointer[T].alloc(1) + __get_address_as_uninit_lvalue(tmp.value)=(arg) + self.p[]=int(tmp) + + self.free_function = __type_of(self.free_function).alloc(1) + fn f(to_free :Int)escaping->None: + var tmp_ = UnsafePointer[T](address=to_free) + destroy_pointee(tmp_) + #_=__get_address_as_owned_value(tmp.value)^ #<- T.__del__ ❤️🔥 + tmp_.free() + var ptr = UnsafePointer[fn(Int)escaping->None].alloc(1) + initialize_pointee[fn(Int)escaping->None](ptr,f) + self.free_function[]=int(ptr) + + self.ref_count=Pointer[Int].alloc(1) + self.ref_count[]=1 + + fn deref[T:CollectionElement](inout self)->T: + return UnsafePointer[T](address=self.p[])[] + + fn set[T:CollectionElement](inout self,arg:T): + UnsafePointer[fn(Int)escaping->None](address=self.free_function[])[](self.p[]) + destroy_pointee(UnsafePointer[fn(Int)escaping->None](address=self.free_function[])) + UnsafePointer[fn(Int)escaping->None](address=self.free_function[]).free() + fn f(to_free :Int)escaping->None: + var tmp_ = UnsafePointer[T](address=to_free) + destroy_pointee(tmp_) + #_=__get_address_as_owned_value(tmp_.value)^ + tmp_.free() + var ptr = UnsafePointer[fn(Int)escaping->None].alloc(1) + #__get_address_as_uninit_lvalue(ptr.value)=f + initialize_pointee[fn(Int)escaping->None](ptr,f) + self.free_function[]=int(ptr) + + var tmp = UnsafePointer[T].alloc(1) + initialize_pointee(tmp,arg) + #__get_address_as_uninit_lvalue(tmp.value)=arg + self.p[]=int(tmp) + + fn __copyinit__(inout self, other:Self): + self.ref_count = other.ref_count + self.p = other.p + self.ref_count[] += 1 + self.free_function=other.free_function + + fn __moveinit__(inout self, owned other:Self): + self.ref_count = other.ref_count + self.p = other.p + self.free_function=other.free_function + + fn __del__(owned self): + if self.ref_count[] == 1: + #print("del") + UnsafePointer[fn(Int)escaping->None]( + address=self.free_function[] + )[](self.p[]) + destroy_pointee( + UnsafePointer[fn(Int)escaping->None]( + address=self.free_function[] + ) + ) + UnsafePointer[fn(Int)escaping->None](address=self.free_function[]).free() + self.free_function.free() + self.p.free() + self.ref_count.free() + else: + self.ref_count[]-=1 + #print(self.ref_count[]) + +#def MyFunc2(**args:Prop): +# val_a = args["arg_a"].deref[StringLiteral]() +# val_b = args["arg_b"].deref[Tuple[Int,Int]]() +# print(val_a) +# print(val_b.get[0](),val_b.get[1]()) +# +#def main(): +# MyFunc2(arg_a = "Hello world",arg_b = (1,2)) +# +# tmp = Prop(1.1) +# print(tmp.deref[Float64]()) +# cp = tmp +# cp.set("Hello world") +# print(tmp.deref[StringLiteral]()) +# for i in range(10): +# tmp2 = Prop(String("hello world")+str(i)) +# print(tmp2.deref[String]()) +# #tmp2.set(1/i) +# #print(tmp2.deref[Float64]()) + + + + + +# diff --git a/integration prototype/Component.mojo b/integration prototype/Component.mojo new file mode 100644 index 0000000..92bc381 --- /dev/null +++ b/integration prototype/Component.mojo @@ -0,0 +1,21 @@ +from DomTree import * +from AnyThing import * +from DomEvent import * +alias PropsType = Dict[String,AnyThing] + +trait Component(CollectionElement): + @staticmethod + fn component_name()->String:... + fn __init__(inout self): ... + fn event(inout self, e:DomEvent): ... + fn render( + inout self, + owned props:PropsType + ) raises ->Element: ... + +@value +struct Component_T: + var instance_name:String + var component_name: String + var props: PropsType + diff --git a/integration prototype/ComponentManager.mojo b/integration prototype/ComponentManager.mojo new file mode 100644 index 0000000..db809f0 --- /dev/null +++ b/integration prototype/ComponentManager.mojo @@ -0,0 +1,225 @@ +from Component import * +from DomTree import * +from python import Python +@value +struct ComponentManager[*Ts:Component]: + var states: Dict[ + String, #InstanceName + UnsafePointer[NoneType] + ] + var types: Dict[String,String] #InstanceName,ComponentName + var previously_rendered_instances: List[String] #InstanceName + + fn __init__(inout self): + constrained[ + TsUniques[Ts](), + "Two components have the same name" + ]() + + self.states = __type_of(self.states)() + self.types = __type_of(self.types)() + self.previously_rendered_instances = + __type_of(self.previously_rendered_instances)() + + #only for debug, rendering is on the client-side from json + fn RenderIntoHtml( + inout self, + inout elements: Element, + indent:Int=0 + )->String: + var tmp_result: String = "" + var indent_ = String("") + for i in range(indent): indent_+="\t" + if elements.tag == "TextNode": + try: tmp_result+= indent_+ elements.attributes["value"] + except e: print(e) + else: + tmp_result += "\n"+indent_+"<"+elements.tag+" " + for a in elements.attributes: + try: tmp_result+=a[]+"=\""+elements.attributes[a[]]+"\" " + except e:print(e) + tmp_result += ">\n" + for i in elements.inner: + tmp_result += self.RenderIntoHtml( + i[]._get_ptr[Element]()[], + indent=indent+1 + ) + tmp_result += "\n"+indent_ +""+elements.tag+">" + return tmp_result + + fn RenderIntoJson[First:Bool=False]( + inout self, + inout elements: Element, + )->PythonObject: + try: + var tmp_result = Python.evaluate("lambda **kwargs: kwargs")( + tag=elements.tag, + inner=[] + ) + if elements.tag == "TextNode": + tmp_result["value"]=elements.attributes["value"] + else: + for a in elements.attributes: + tmp_result[a[]]=elements.attributes[a[]] + for i in elements.inner: + if i[].isa[Element](): + tmp_result["inner"].append( + self.RenderIntoJson( + i[]._get_ptr[Element]()[] + ) + ) + @parameter + if First: return Python.import_module("json").dumps(tmp_result) + else: return tmp_result + except e: print("RenderToJson",e) + return None + + fn RenderComponentsIntoElements[first:Bool]( + inout self, + inout arg: Element + ): + #TODO: check not instance_name rendered twice + @parameter + if first: self.previously_rendered_instances.clear() + + if arg.component: + var tmp = arg.component.value() + @parameter + fn loop[I:Int](): + if is_component[Ts[I]](tmp.component_name): + self.TsRenderInto[Ts[I]](arg,tmp) + unroll[loop,len(VariadicList(Ts))]() + _=tmp + + for i in arg.inner: + if i[].isa[Element](): + if i[]._get_ptr[Element]()[].component: + var tmp = i[]._get_ptr[Element]()[] + self.RenderComponentsIntoElements[False](tmp) + i[]._get_ptr[Element]()[] = tmp + + fn CreateInstance[T:Component]( + inout self, + c:Component_T + )->UnsafePointer[T]: + var tmp=UnsafePointer[T].alloc(1) + initialize_pointee(tmp,T()) + self.states[c.instance_name]=tmp.bitcast[NoneType]() + self.types[c.instance_name]=c.component_name + return tmp + + fn GetInstance[T:Component]( + inout self, + c:Component_T + )->UnsafePointer[T]: + for k in self.states: + if k[] == c.instance_name: + try: + var tmp2= self.states[k[]].bitcast[T]() + return tmp2 + except e: print(e) + var tmp = self.CreateInstance[T](c) + return tmp + + fn TsRenderInto[T:Component]( + inout self, + inout e:Element, + c: Component_T + ): + #if instance_name in dict InstanceManager + #else create instance + var instance = self.GetInstance[T](c)#T() + try: + var tmp=instance[].render(c.props) + tmp.attributes["data-instance_name"]=c.instance_name + e=tmp + except e:print("TsRenderInto",e) + + self.previously_rendered_instances.append( + c.instance_name + ) + + fn delete_instances[only_unrendered:Bool=False](inout self): + var counter = 0 + var deleted = Dict[String,Bool]() + for instance_name in self.states: + if self.types.find(instance_name[]): + try: + var tmp_ptr = self.states[instance_name[]] + var t= self.types[instance_name[]] + @parameter + fn loop[I:Int](): + if instance_name[] in deleted: return + if is_component[Ts[I]](t): + try: + @parameter + if only_unrendered: + var found = False + for p in self.previously_rendered_instances: + if p[]==instance_name[]: + found = True + if found: return + _=self.types.pop(instance_name[]) + deleted[instance_name[]]=True + destroy_pointee(tmp_ptr.bitcast[Ts[I]]().value) + tmp_ptr.free() + counter+=1 + except e:print("delete_instances/loop",e) + unroll[loop,len(VariadicList(Ts))]() + _=tmp_ptr + except e: print("delete_instances",e) + try: + print("__del__() :",counter) + for i in deleted: + var tmp_del = self.states.pop(i[]) + print("\t",i[]) + except e: print("delete_instances",e) + @parameter + if only_unrendered == False: + self.previously_rendered_instances.clear() + _=deleted + _=self.states + _=self.types + +fn is_component[T:Component](name:String)->Bool: + return T.component_name()==name + +fn TsEquals[T:Component,T2:Component]()->Bool: + return T.component_name() == T2.component_name() + +fn TsIndex[*Ts:Component](name:String)->Int: + var result=-1 + @parameter + fn loop[I:Int](): + if is_component[Ts[I]](name): result = I + unroll[loop,len(VariadicList(Ts))]() + return result + +fn TsCount[T:Component,*Ts:Component]()->Int: + var result=0 + @parameter + fn loop[I:Int](): + if TsEquals[Ts[I],T](): result += 1 + unroll[loop,len(VariadicList(Ts))]() + return result + +fn TsUniques[*Ts:Component]()->Bool: + var result = True + @parameter + fn loop[I:Int](): + if TsCount[Ts[I],Ts]()>1: + result = False + unroll[loop,len(VariadicList(Ts))]() + return result + +fn TsContains[*Ts:Component,name:StringLiteral]()->Bool: + var result = False + @parameter + fn loop[I:Int](): + result = result|TsEq[Ts[I],name]() + unroll[loop,len(VariadicList(Ts))]() + return result + +fn TsEq[Ts:Component,arg:StringLiteral]()->Bool: + return Ts.component_name() == arg + diff --git a/integration prototype/DomEvent.mojo b/integration prototype/DomEvent.mojo new file mode 100644 index 0000000..84b8899 --- /dev/null +++ b/integration prototype/DomEvent.mojo @@ -0,0 +1,20 @@ +from utils.variant import Variant +from Component import * + +@value +struct DomEvent: + alias MouseEnter = "MouseEnter" + alias Click = "Click" + var target:String #TODO: id attribute + var type: String #TODO: "MouseEnter" + var name:String # On[DomEvent.Click]("name") + var data:String #TODO: json, depend on event type + +@value +struct EventHandler: + var type: StringLiteral + var instance_name:String + var event_name:String + +fn On[e:StringLiteral](name:StringLiteral)->EventHandler: + return EventHandler(e,"",name) diff --git a/integration prototype/DomTree.mojo b/integration prototype/DomTree.mojo new file mode 100644 index 0000000..03e06e2 --- /dev/null +++ b/integration prototype/DomTree.mojo @@ -0,0 +1,108 @@ +from utils.variant import Variant +from Helpers import * +from DomEvent import EventHandler +from collections import Optional +from Component import Component_T +from ComponentManager import * + +#TODO: Splat: fn H[T:List[Variant[Component,Html]]]()->Element) + +fn H[T:Component]( + InstanceName:String, + **args:AnyThing +)->Element: + var tmp = Element("") + tmp.component = Component_T( + instance_name=InstanceName, + component_name=T.component_name(), + props = args._dict + ) + return tmp + +trait Html: + @staticmethod + fn tag()->String: ... + +fn H[T:Html]( + *args:Variant[Element,String,StringLiteral,CSS_T,EventHandler], + **kwargs: String +) -> Element: + var tmp = Element(T.tag()) + for k in kwargs: + try:tmp.attributes[k[]]=kwargs[k[]] + except e:print(e) + if len(args)>0: + for i in range(len(args)): + var arg = args[i] + if arg.isa[Element](): + tmp.inner.append(arg.take[Element]()) + if arg.isa[String](): + var tmp3 = Element("TextNode") + tmp3.attributes["value"] = arg.take[String]() + tmp.inner.append(tmp3) + if arg.isa[StringLiteral](): + var tmp3 = Element("TextNode") + tmp3.attributes["value"] = arg.take[StringLiteral]() + tmp.inner.append(tmp3) + if arg.isa[EventHandler](): + var tmp_arg = arg.take[EventHandler]() + tmp.attributes[String("data-event_")+tmp_arg.type]= + tmp_arg.event_name + if arg.isa[CSS_T](): + tmp.attributes["style"]=arg.take[CSS_T]().value + return tmp + +struct Element: + var component: Optional[Component_T] + var tag: String + var inner:List[Variant[Self,NoneType]] + var attributes: Dict[String,String] + + fn __init__(inout self,tag:String): + self.component = None + self.tag=tag + self.inner=__type_of(self.inner)() + self.attributes = Dict[String,String]() + + fn __copyinit__(inout self,other:Self): + self.component = other.component + self.tag= other.tag + self.inner = other.inner + self.attributes = other.attributes + fn __moveinit__(inout self,owned other:Self): + self.component = other.component + self.tag= other.tag + self.inner = other.inner + self.attributes = other.attributes + + +@value +struct Div: + @staticmethod + fn tag()->String: return "Div" +@value +struct H1: + @staticmethod + fn tag()->String: return "H1" +@value +struct A: + @staticmethod + fn tag()->String: return "a" +@value +struct Span: + @staticmethod + fn tag()->String: return "span" +@value +struct Button: + @staticmethod + fn tag()->String: return "button" + +@value +struct Br: + @staticmethod + fn tag()->String: return "br" + + + + + diff --git a/integration prototype/Helpers.mojo b/integration prototype/Helpers.mojo new file mode 100644 index 0000000..9d97c58 --- /dev/null +++ b/integration prototype/Helpers.mojo @@ -0,0 +1,30 @@ +from utils.variant import Variant + +#f["hello {message} {emoji}"](message="world", emoji="🔥") +fn f[fmt:StringLiteral](**kwargs:String) ->String: + var res = String(fmt) + try: + for k in kwargs: + if(kwargs[k[]].count("{")+kwargs[k[]].count("}"))!=0: + raise Error("simple safeguard: remplacement value could contains {}") + try: res=res.replace("{"+k[]+"}",kwargs[k[]]) + except e: + res = "Error raised by f" #TODO: better + raise e + except e: print("f formatting function:",e) + return res + + +#print(CSS(color="red")) +@value +struct CSS_T: + var value:String + +fn CSS(**args:String)->CSS_T: + var res = CSS_T("") + + for a in args: + try: res.value+=a[]+":"+args[a[]]+";" + except e: print("CSS function, render == True :",e) + + return res diff --git a/integration prototype/README.md b/integration prototype/README.md new file mode 100644 index 0000000..6c57f96 --- /dev/null +++ b/integration prototype/README.md @@ -0,0 +1,36 @@ +# Integration prototype + +> [!NOTE] +> This prototype was made to ameliorate and gauge the design of LSX. +> It should not be used for anything in production. +# ⚠️ +- Does start a socket based http server: ```ip: 127.0.0.1 port: 8080` +- At least a fixed amount of 5 unfreed pointers. +- No current support for multiple clients/visitors (not a web-framework, yet). + - A session system would be needed in order to further organize instances. +# What is done so far: +- Storing states (instances of structs that implement the Component trait) +- Disposing of instances once they are "unmounted" +- Rendering from a tree on the client-side with JS +- Basic event distribution to individual instances of same Component type +- Instances have instance_name as a unique id +# Challenges +- Props should ideally become Parametrized (currently uses UnsafePointer) +- Probably more things +# Why +- By having another use case for the design, we can identify what could be done in order to ameliorate it. +# Hopes +- further the development of web-frameworks with mojo +# Current conclusions +- LSX is a nice and expressive way to build a DOM, deserves active attention and work + - the current design is capable of handling more advanced use cases + - great degree of composability + - in synergy with mojo +- Mojo is awesome and will become more awesome as the refinement loop unrolls + - great capabilities in the web/ui domain + - variadic arguments and keyword arguments brings a great level of expressiveness + - constrain function can be used on Components, they implement fn component_name()->StringLiteral +# Available features that could bring more to LSX: + - functions with same name and different signature ! +# Possible feature request: + - __name_of[App]() -> StringLiteral: "App" (struct name) diff --git a/integration prototype/Server.mojo b/integration prototype/Server.mojo new file mode 100644 index 0000000..bb938c6 --- /dev/null +++ b/integration prototype/Server.mojo @@ -0,0 +1,111 @@ +from ComponentManager import * +from python import Python + +@value +struct Server[*Ts:Component]: + var socket:PythonObject + var component_manager:ComponentManager[Ts] + var js_renderer: String + fn __init__(inout self,owned arg:ComponentManager[Ts]): + self.socket=None + self.component_manager=arg + try: + with open("js_renderer.js","r") as f: + self.js_renderer = f.read() + except e: + print("Error importing js_renderer.js: " + str(e)) + self.js_renderer = "" + #TODO: set self.error = True + + fn Start[App_T:Component](inout self): + constrained[ + TsCount[App_T,Ts]()==1, + "Component not registered in ComponentManager" + ]() + try: + var socket = Python.import_module("socket") + var r = Reference(self.socket) + r[] = socket.socket( + socket.AF_INET, + socket.SOCK_STREAM + ) + r[].setsockopt( + socket.SOL_SOCKET, + socket.SO_REUSEADDR, + 1 + ) + var host = "127.0.0.1" + var port = 8080 + self.socket.bind((host, port)) + self.socket.listen(1) + + var url:String= "http://"+str(host)+":"+port + print(url) + + while True: + var client = self.socket.accept() + if client[1][0] != '127.0.0.1': + print("Exit, request from: "+str(client[1][0])) + return + + var request = client[0].recv(1024).decode() + var full_request = request.split('\n') + request = request.split('\n')[0].split(" ") + if request[1]=="/": + var response:String = "HTTP/1.0 200 OK\n\n" + response += "
" + response += "" + response += "" + response+="" + client[0].sendall(PythonObject(response).encode()) + client[0].close() + elif request[1]=="/event": #Event + var json_req = Python.import_module("json").loads( + full_request[-1] + ) + var response:String = "HTTP/1.0 200 OK\n\n" + + if str(json_req["type"]) == "DomEvent": + #print(json_req) + var instance_name = str(json_req["instance_name"]) + var event_name = str(json_req["event_name"]) + for t in self.component_manager.types: + if t[] == instance_name: + var component_name = self.component_manager.types[t[]] + @parameter + fn _loop[I:Int](): + if is_component[Self.Ts[I]](component_name): + try: + var p = self.component_manager.states[instance_name] + p.bitcast[Self.Ts[I]]()[].event( + DomEvent( + "", + "type", + event_name, + "data" + ) + ) + except e: print('error component.event()',e) + unroll[_loop,len(VariadicList(Self.Ts))]() + _=event_name + _=instance_name + + var elements = H[App_T]("AppInstance",url=url) + self.component_manager.RenderComponentsIntoElements[ + True + ](elements) + + response += self.component_manager.RenderIntoJson[ + True + ](elements) + + self.component_manager.delete_instances[ + only_unrendered=True + ]() + + client[0].sendall(PythonObject(response).encode()) + client[0].close() + + except e: + print(e) + return diff --git a/integration prototype/js_renderer.js b/integration prototype/js_renderer.js new file mode 100644 index 0000000..a77958b --- /dev/null +++ b/integration prototype/js_renderer.js @@ -0,0 +1,65 @@ +async function Event(data) { + const response = await fetch("/event", { + method: "POST", + headers: { "Content-Type": "application/json",}, + body: JSON.stringify(data), + }); + + const json = await response.json(); + var render_res = Render(json,json["instance_name"]) //append + var b = document.createElement("BODY") + b.appendChild(render_res) + document.body=b + +} +window.addEventListener("load", (event) => { + const data = { type: "first render" }; + Event(data); +}); + +function Render(json,instance_name){ + var instance_n = instance_name + if (json.hasOwnProperty("data-instance_name")){ + instance_n = json["data-instance_name"] + } + var tag = json["tag"] + if (tag == "TextNode"){ + return document.createTextNode(json["value"]); + } + var inner = json["inner"] + delete json["inner"]; + var el = document.createElement(tag); + for (attr in json){ + if (attr.startsWith("data-event_")){ + console.log(attr,json[attr]) + var ev = attr.split("data-event_")[1] + var ev_val = json[attr] + if (ev == "Click"){ + el.addEventListener("click",(e)=>{ + Event({ + type: "DomEvent" , + event_name: ev_val, + instance_name: instance_n + }); + }) + } + if (ev == "MouseEnter"){ + el.addEventListener("mouseenter",(e)=>{ + console.log(ev_val) + Event({ + type: "DomEvent" , + event_name: ev_val, + instance_name: instance_n + }); + }) + } + } + el.setAttribute(attr,json[attr]) + } + inner.forEach((inner_el) => { + el.appendChild(Render(inner_el,instance_n)) + }); + return el + +} + diff --git a/integration prototype/main.mojo b/integration prototype/main.mojo new file mode 100644 index 0000000..71fd723 --- /dev/null +++ b/integration prototype/main.mojo @@ -0,0 +1,88 @@ +from Helpers import * +from DomEvent import * +from DomTree import * +from Component import * +from ComponentManager import * +from Server import * + +def main(): + m = ComponentManager[ + App, + Counter + ]() + s = Server(m^) + s.Start[App]() + +@value +struct App(Component): + @staticmethod + fn component_name()->String: return "App" + + var some_state:Int + fn __init__(inout self): self.some_state = 1 + + fn event(inout self, e:DomEvent): + if e.name == "toggle_event": + print("Toggle!") + self.some_state *=-1 + + def render(inout self, props: PropsType) -> Element: + var color = "green" + if self.some_state == -1: color = "yellow" + var toggle_button = + H[A]( + CSS( + `font-size`="32px", + border=String("8px solid ")+color + ), + On[DomEvent.Click]("toggle_event"), + "ToggleButton", + H[Span]( + str(self.some_state), + CSS(`background-color`=color) + ), + ) + if self.some_state == -1: + return toggle_button + + return + H[Div]( + H[H1]( + f["{url} {emoji} !"]( + url=props["url"].deref[String]() , + emoji="🔥" + ), + CSS(color="blue"), + ), + H[Counter]("first instance",Title="first"), + H[Counter]("second instance",Title="second",inc=True), + H[Br](),H[Br](), + toggle_button + ) + + +@value +struct Counter(Component): + @staticmethod + fn component_name()->String: return "Counter" + + var x:Int + fn __init__(inout self): self.x=0 + + fn event(inout self, e:DomEvent): + if e.name == "increment_10": + self.x+=10 + + def render(inout self, props: PropsType) -> Element: + if props.find("inc"): + self.x+=1 + return + H[Span]( + H[H1]( + props["Title"].deref[StringLiteral]() + str(" ") + self.x, + ), + H[Button]( + "Increment", + On[DomEvent.Click]("increment_10") + ) + ) From 05ebe95a74e3d1b1950b420a172d92932b3c6bdb Mon Sep 17 00:00:00 2001 From: rd4com <144297616+rd4com@users.noreply.github.com> Date: Tue, 23 Apr 2024 18:54:18 +0200 Subject: [PATCH 2/8] Integration prototype --- integration prototype/README.md | 95 ++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/integration prototype/README.md b/integration prototype/README.md index 6c57f96..02af523 100644 --- a/integration prototype/README.md +++ b/integration prototype/README.md @@ -4,7 +4,7 @@ > This prototype was made to ameliorate and gauge the design of LSX. > It should not be used for anything in production. # ⚠️ -- Does start a socket based http server: ```ip: 127.0.0.1 port: 8080` +- Does start a socket based http server: ```ip: 127.0.0.1 port: 8080``` - At least a fixed amount of 5 unfreed pointers. - No current support for multiple clients/visitors (not a web-framework, yet). - A session system would be needed in order to further organize instances. @@ -34,3 +34,96 @@ - functions with same name and different signature ! # Possible feature request: - __name_of[App]() -> StringLiteral: "App" (struct name) + +# How it looks so far: +```python +from Helpers import * +from DomEvent import * +from DomTree import * +from Component import * +from ComponentManager import * +from Server import * + +def main(): + m = ComponentManager[ + App, + Counter + ]() + s = Server(m^) + s.Start[App]() + +@value +struct App(Component): + @staticmethod + fn component_name()->String: return "App" + + var some_state:Int + fn __init__(inout self): self.some_state = 1 + + fn event(inout self, e:DomEvent): + if e.name == "toggle_event": + print("Toggle!") + self.some_state *=-1 + + def render(inout self, props: PropsType) -> Element: + var color = "green" + if self.some_state == -1: color = "yellow" + var toggle_button = + H[A]( + CSS( + `font-size`="32px", + border=String("8px solid ")+color + ), + On[DomEvent.Click]("toggle_event"), + "ToggleButton", + H[Span]( + str(self.some_state), + CSS(`background-color`=color) + ), + ) + if self.some_state == -1: + return toggle_button + + return + H[Div]( + H[H1]( + f["{url} {emoji} !"]( + url=props["url"].deref[String]() , + emoji="🔥" + ), + CSS(color="blue"), + ), + H[Counter]("first instance",Title="first"), + H[Counter]("second instance",Title="second",inc=True), + H[Br](),H[Br](), + toggle_button + ) + + +@value +struct Counter(Component): + @staticmethod + fn component_name()->String: return "Counter" + + var x:Int + fn __init__(inout self): self.x=0 + + fn event(inout self, e:DomEvent): + if e.name == "increment_10": + self.x+=10 + + def render(inout self, props: PropsType) -> Element: + if props.find("inc"): + self.x+=1 + return + H[Span]( + H[H1]( + props["Title"].deref[StringLiteral]() + str(" ") + self.x, + ), + H[Button]( + "Increment", + On[DomEvent.Click]("increment_10") + ) + ) + +``` From 0db656e9630791c52ef1a58c9d0f973869f35446 Mon Sep 17 00:00:00 2001 From: rd4com <144297616+rd4com@users.noreply.github.com> Date: Mon, 6 May 2024 05:25:14 +0200 Subject: [PATCH 3/8] update --- integration prototype/README.md | 129 ------------- integration prototype/main.mojo | 88 --------- .../AnyThing.mojo | 15 +- .../Component.mojo | 4 +- .../ComponentManager.mojo | 63 +++---- .../DomEvent.mojo | 2 + .../DomTree.mojo | 15 +- .../Helpers.mojo | 0 struct composability prototype/README.md | 175 ++++++++++++++++++ .../Server.mojo | 17 +- struct composability prototype/example.png | Bin 0 -> 17351 bytes .../js_renderer.js | 2 +- struct composability prototype/main.mojo | 154 +++++++++++++++ struct composability prototype/todo | 2 + 14 files changed, 397 insertions(+), 269 deletions(-) delete mode 100644 integration prototype/README.md delete mode 100644 integration prototype/main.mojo rename {integration prototype => struct composability prototype}/AnyThing.mojo (87%) rename {integration prototype => struct composability prototype}/Component.mojo (91%) rename {integration prototype => struct composability prototype}/ComponentManager.mojo (83%) rename {integration prototype => struct composability prototype}/DomEvent.mojo (89%) rename {integration prototype => struct composability prototype}/DomTree.mojo (91%) rename {integration prototype => struct composability prototype}/Helpers.mojo (100%) create mode 100644 struct composability prototype/README.md rename {integration prototype => struct composability prototype}/Server.mojo (86%) create mode 100644 struct composability prototype/example.png rename {integration prototype => struct composability prototype}/js_renderer.js (96%) create mode 100644 struct composability prototype/main.mojo create mode 100644 struct composability prototype/todo diff --git a/integration prototype/README.md b/integration prototype/README.md deleted file mode 100644 index 02af523..0000000 --- a/integration prototype/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# Integration prototype - -> [!NOTE] -> This prototype was made to ameliorate and gauge the design of LSX. -> It should not be used for anything in production. -# ⚠️ -- Does start a socket based http server: ```ip: 127.0.0.1 port: 8080``` -- At least a fixed amount of 5 unfreed pointers. -- No current support for multiple clients/visitors (not a web-framework, yet). - - A session system would be needed in order to further organize instances. -# What is done so far: -- Storing states (instances of structs that implement the Component trait) -- Disposing of instances once they are "unmounted" -- Rendering from a tree on the client-side with JS -- Basic event distribution to individual instances of same Component type -- Instances have instance_name as a unique id -# Challenges -- Props should ideally become Parametrized (currently uses UnsafePointer) -- Probably more things -# Why -- By having another use case for the design, we can identify what could be done in order to ameliorate it. -# Hopes -- further the development of web-frameworks with mojo -# Current conclusions -- LSX is a nice and expressive way to build a DOM, deserves active attention and work - - the current design is capable of handling more advanced use cases - - great degree of composability - - in synergy with mojo -- Mojo is awesome and will become more awesome as the refinement loop unrolls - - great capabilities in the web/ui domain - - variadic arguments and keyword arguments brings a great level of expressiveness - - constrain function can be used on Components, they implement fn component_name()->StringLiteral -# Available features that could bring more to LSX: - - functions with same name and different signature ! -# Possible feature request: - - __name_of[App]() -> StringLiteral: "App" (struct name) - -# How it looks so far: -```python -from Helpers import * -from DomEvent import * -from DomTree import * -from Component import * -from ComponentManager import * -from Server import * - -def main(): - m = ComponentManager[ - App, - Counter - ]() - s = Server(m^) - s.Start[App]() - -@value -struct App(Component): - @staticmethod - fn component_name()->String: return "App" - - var some_state:Int - fn __init__(inout self): self.some_state = 1 - - fn event(inout self, e:DomEvent): - if e.name == "toggle_event": - print("Toggle!") - self.some_state *=-1 - - def render(inout self, props: PropsType) -> Element: - var color = "green" - if self.some_state == -1: color = "yellow" - var toggle_button = - H[A]( - CSS( - `font-size`="32px", - border=String("8px solid ")+color - ), - On[DomEvent.Click]("toggle_event"), - "ToggleButton", - H[Span]( - str(self.some_state), - CSS(`background-color`=color) - ), - ) - if self.some_state == -1: - return toggle_button - - return - H[Div]( - H[H1]( - f["{url} {emoji} !"]( - url=props["url"].deref[String]() , - emoji="🔥" - ), - CSS(color="blue"), - ), - H[Counter]("first instance",Title="first"), - H[Counter]("second instance",Title="second",inc=True), - H[Br](),H[Br](), - toggle_button - ) - - -@value -struct Counter(Component): - @staticmethod - fn component_name()->String: return "Counter" - - var x:Int - fn __init__(inout self): self.x=0 - - fn event(inout self, e:DomEvent): - if e.name == "increment_10": - self.x+=10 - - def render(inout self, props: PropsType) -> Element: - if props.find("inc"): - self.x+=1 - return - H[Span]( - H[H1]( - props["Title"].deref[StringLiteral]() + str(" ") + self.x, - ), - H[Button]( - "Increment", - On[DomEvent.Click]("increment_10") - ) - ) - -``` diff --git a/integration prototype/main.mojo b/integration prototype/main.mojo deleted file mode 100644 index 71fd723..0000000 --- a/integration prototype/main.mojo +++ /dev/null @@ -1,88 +0,0 @@ -from Helpers import * -from DomEvent import * -from DomTree import * -from Component import * -from ComponentManager import * -from Server import * - -def main(): - m = ComponentManager[ - App, - Counter - ]() - s = Server(m^) - s.Start[App]() - -@value -struct App(Component): - @staticmethod - fn component_name()->String: return "App" - - var some_state:Int - fn __init__(inout self): self.some_state = 1 - - fn event(inout self, e:DomEvent): - if e.name == "toggle_event": - print("Toggle!") - self.some_state *=-1 - - def render(inout self, props: PropsType) -> Element: - var color = "green" - if self.some_state == -1: color = "yellow" - var toggle_button = - H[A]( - CSS( - `font-size`="32px", - border=String("8px solid ")+color - ), - On[DomEvent.Click]("toggle_event"), - "ToggleButton", - H[Span]( - str(self.some_state), - CSS(`background-color`=color) - ), - ) - if self.some_state == -1: - return toggle_button - - return - H[Div]( - H[H1]( - f["{url} {emoji} !"]( - url=props["url"].deref[String]() , - emoji="🔥" - ), - CSS(color="blue"), - ), - H[Counter]("first instance",Title="first"), - H[Counter]("second instance",Title="second",inc=True), - H[Br](),H[Br](), - toggle_button - ) - - -@value -struct Counter(Component): - @staticmethod - fn component_name()->String: return "Counter" - - var x:Int - fn __init__(inout self): self.x=0 - - fn event(inout self, e:DomEvent): - if e.name == "increment_10": - self.x+=10 - - def render(inout self, props: PropsType) -> Element: - if props.find("inc"): - self.x+=1 - return - H[Span]( - H[H1]( - props["Title"].deref[StringLiteral]() + str(" ") + self.x, - ), - H[Button]( - "Increment", - On[DomEvent.Click]("increment_10") - ) - ) diff --git a/integration prototype/AnyThing.mojo b/struct composability prototype/AnyThing.mojo similarity index 87% rename from integration prototype/AnyThing.mojo rename to struct composability prototype/AnyThing.mojo index 0cd8fd7..37fc745 100644 --- a/integration prototype/AnyThing.mojo +++ b/struct composability prototype/AnyThing.mojo @@ -7,17 +7,16 @@ struct AnyThing: fn __init__[T:CollectionElement](inout self,arg:T): self.p = __type_of(self.p).alloc(1) var tmp = UnsafePointer[T].alloc(1) - __get_address_as_uninit_lvalue(tmp.value)=(arg) + initialize_pointee_move(tmp,arg) self.p[]=int(tmp) self.free_function = __type_of(self.free_function).alloc(1) fn f(to_free :Int)escaping->None: var tmp_ = UnsafePointer[T](address=to_free) destroy_pointee(tmp_) - #_=__get_address_as_owned_value(tmp.value)^ #<- T.__del__ ❤️🔥 tmp_.free() var ptr = UnsafePointer[fn(Int)escaping->None].alloc(1) - initialize_pointee[fn(Int)escaping->None](ptr,f) + initialize_pointee_move[fn(Int)escaping->None](ptr,f) self.free_function[]=int(ptr) self.ref_count=Pointer[Int].alloc(1) @@ -25,6 +24,9 @@ struct AnyThing: fn deref[T:CollectionElement](inout self)->T: return UnsafePointer[T](address=self.p[])[] + + fn __getitem__[T:CollectionElement](inout self)->T: + return UnsafePointer[T](address=self.p[])[] fn set[T:CollectionElement](inout self,arg:T): UnsafePointer[fn(Int)escaping->None](address=self.free_function[])[](self.p[]) @@ -33,16 +35,13 @@ struct AnyThing: fn f(to_free :Int)escaping->None: var tmp_ = UnsafePointer[T](address=to_free) destroy_pointee(tmp_) - #_=__get_address_as_owned_value(tmp_.value)^ tmp_.free() var ptr = UnsafePointer[fn(Int)escaping->None].alloc(1) - #__get_address_as_uninit_lvalue(ptr.value)=f - initialize_pointee[fn(Int)escaping->None](ptr,f) + initialize_pointee_move[fn(Int)escaping->None](ptr,f) self.free_function[]=int(ptr) var tmp = UnsafePointer[T].alloc(1) - initialize_pointee(tmp,arg) - #__get_address_as_uninit_lvalue(tmp.value)=arg + initialize_pointee_move(tmp,arg) self.p[]=int(tmp) fn __copyinit__(inout self, other:Self): diff --git a/integration prototype/Component.mojo b/struct composability prototype/Component.mojo similarity index 91% rename from integration prototype/Component.mojo rename to struct composability prototype/Component.mojo index 92bc381..c3edffc 100644 --- a/integration prototype/Component.mojo +++ b/struct composability prototype/Component.mojo @@ -8,10 +8,10 @@ trait Component(CollectionElement): fn component_name()->String:... fn __init__(inout self): ... fn event(inout self, e:DomEvent): ... - fn render( + def render( inout self, owned props:PropsType - ) raises ->Element: ... + ) ->Element: ... @value struct Component_T: diff --git a/integration prototype/ComponentManager.mojo b/struct composability prototype/ComponentManager.mojo similarity index 83% rename from integration prototype/ComponentManager.mojo rename to struct composability prototype/ComponentManager.mojo index db809f0..65e36cd 100644 --- a/integration prototype/ComponentManager.mojo +++ b/struct composability prototype/ComponentManager.mojo @@ -1,6 +1,23 @@ from Component import * from DomTree import * from python import Python +@value +struct Instances: + var states: Dict[ + String, #InstanceName + UnsafePointer[NoneType] + ] + + fn __getitem__[ + T:Component, + n:StringLiteral + ](self)->UnsafePointer[T]: + try: + return self.states[n].bitcast[T]() + except e: ... + return UnsafePointer[T]() + + @value struct ComponentManager[*Ts:Component]: var states: Dict[ @@ -8,7 +25,7 @@ struct ComponentManager[*Ts:Component]: UnsafePointer[NoneType] ] var types: Dict[String,String] #InstanceName,ComponentName - var previously_rendered_instances: List[String] #InstanceName + var previously_rendered_instances: List[String] #InstanceNames fn __init__(inout self): constrained[ @@ -21,31 +38,6 @@ struct ComponentManager[*Ts:Component]: self.previously_rendered_instances = __type_of(self.previously_rendered_instances)() - #only for debug, rendering is on the client-side from json - fn RenderIntoHtml( - inout self, - inout elements: Element, - indent:Int=0 - )->String: - var tmp_result: String = "" - var indent_ = String("") - for i in range(indent): indent_+="\t" - if elements.tag == "TextNode": - try: tmp_result+= indent_+ elements.attributes["value"] - except e: print(e) - else: - tmp_result += "\n"+indent_+"<"+elements.tag+" " - for a in elements.attributes: - try: tmp_result+=a[]+"=\""+elements.attributes[a[]]+"\" " - except e:print(e) - tmp_result += ">\n" - for i in elements.inner: - tmp_result += self.RenderIntoHtml( - i[]._get_ptr[Element]()[], - indent=indent+1 - ) - tmp_result += "\n"+indent_ +""+elements.tag+">" - return tmp_result fn RenderIntoJson[First:Bool=False]( inout self, @@ -81,9 +73,9 @@ struct ComponentManager[*Ts:Component]: #TODO: check not instance_name rendered twice @parameter if first: self.previously_rendered_instances.clear() - + if arg.component: - var tmp = arg.component.value() + var tmp = arg.component._value_copy() @parameter fn loop[I:Int](): if is_component[Ts[I]](tmp.component_name): @@ -97,13 +89,13 @@ struct ComponentManager[*Ts:Component]: var tmp = i[]._get_ptr[Element]()[] self.RenderComponentsIntoElements[False](tmp) i[]._get_ptr[Element]()[] = tmp - + fn CreateInstance[T:Component]( inout self, c:Component_T )->UnsafePointer[T]: var tmp=UnsafePointer[T].alloc(1) - initialize_pointee(tmp,T()) + initialize_pointee_move(tmp,T()) self.states[c.instance_name]=tmp.bitcast[NoneType]() self.types[c.instance_name]=c.component_name return tmp @@ -126,8 +118,6 @@ struct ComponentManager[*Ts:Component]: inout e:Element, c: Component_T ): - #if instance_name in dict InstanceManager - #else create instance var instance = self.GetInstance[T](c)#T() try: var tmp=instance[].render(c.props) @@ -140,7 +130,6 @@ struct ComponentManager[*Ts:Component]: ) fn delete_instances[only_unrendered:Bool=False](inout self): - var counter = 0 var deleted = Dict[String,Bool]() for instance_name in self.states: if self.types.find(instance_name[]): @@ -161,22 +150,22 @@ struct ComponentManager[*Ts:Component]: if found: return _=self.types.pop(instance_name[]) deleted[instance_name[]]=True - destroy_pointee(tmp_ptr.bitcast[Ts[I]]().value) + destroy_pointee(tmp_ptr.bitcast[Ts[I]]()) tmp_ptr.free() - counter+=1 except e:print("delete_instances/loop",e) unroll[loop,len(VariadicList(Ts))]() _=tmp_ptr except e: print("delete_instances",e) try: - print("__del__() :",counter) + print("__del__() :",len(deleted)) for i in deleted: var tmp_del = self.states.pop(i[]) print("\t",i[]) except e: print("delete_instances",e) @parameter if only_unrendered == False: - self.previously_rendered_instances.clear() + self.previously_rendered_instances= + __type_of(self.previously_rendered_instances)() _=deleted _=self.states _=self.types diff --git a/integration prototype/DomEvent.mojo b/struct composability prototype/DomEvent.mojo similarity index 89% rename from integration prototype/DomEvent.mojo rename to struct composability prototype/DomEvent.mojo index 84b8899..2dfd70f 100644 --- a/integration prototype/DomEvent.mojo +++ b/struct composability prototype/DomEvent.mojo @@ -1,5 +1,6 @@ from utils.variant import Variant from Component import * +from ComponentManager import * @value struct DomEvent: @@ -9,6 +10,7 @@ struct DomEvent: var type: String #TODO: "MouseEnter" var name:String # On[DomEvent.Click]("name") var data:String #TODO: json, depend on event type + var Instances: Instances @value struct EventHandler: diff --git a/integration prototype/DomTree.mojo b/struct composability prototype/DomTree.mojo similarity index 91% rename from integration prototype/DomTree.mojo rename to struct composability prototype/DomTree.mojo index 03e06e2..8b44f7d 100644 --- a/integration prototype/DomTree.mojo +++ b/struct composability prototype/DomTree.mojo @@ -24,7 +24,14 @@ trait Html: fn tag()->String: ... fn H[T:Html]( - *args:Variant[Element,String,StringLiteral,CSS_T,EventHandler], + *args: + Variant[ + Element, + String, + StringLiteral, + CSS_T, + EventHandler + ], **kwargs: String ) -> Element: var tmp = Element(T.tag()) @@ -61,7 +68,7 @@ struct Element: fn __init__(inout self,tag:String): self.component = None self.tag=tag - self.inner=__type_of(self.inner)() + self.inner=List[Variant[Self,NoneType]]() self.attributes = Dict[String,String]() fn __copyinit__(inout self,other:Self): @@ -85,6 +92,10 @@ struct H1: @staticmethod fn tag()->String: return "H1" @value +struct H2: + @staticmethod + fn tag()->String: return "H1" +@value struct A: @staticmethod fn tag()->String: return "a" diff --git a/integration prototype/Helpers.mojo b/struct composability prototype/Helpers.mojo similarity index 100% rename from integration prototype/Helpers.mojo rename to struct composability prototype/Helpers.mojo diff --git a/struct composability prototype/README.md b/struct composability prototype/README.md new file mode 100644 index 0000000..6844dbe --- /dev/null +++ b/struct composability prototype/README.md @@ -0,0 +1,175 @@ +# Integration prototype 🔥❤️🐍 + +> [!NOTE] +> This prototype was made to ameliorate design of LSX. +> It should not be used for anything in production (lots of UnsafePointer). + +Hope this will be useful to people that wan't to make web-frameworks too + +Lukas is working on lsx, the goal is to use 100% lsx with a wrapper on top if necessary + +Currently, it's just prototyping with the idea to see what to do + +# ⚠️ +- Start a socket, http-server: ```ip: 127.0.0.1 port: 8080``` +- Probably many unfreed pointers (need help) +- No current support for multiple clients/visitors, a session system would be neededdd + +# Why +- By having another use case for the design, we can identify what could be done in order to ameliorate it. +- Its fun to program in mojo +- further the development of web-frameworks + +# What is done so far: +- Reusable components +- Storing states (instances of structs that implement the Component trait) +- mount and ```T:Component.__del__()``` on unmount +- Rendering from a tree on the client-side with JS +- Basic event passing to instances of components +- Props as variadic keyword of anything (CollectionElement) (uses UnsafePointer) + +# Challenges +- Props should ideally become Parametrized (Variant at least?) +- Instance name cannot use underscores (don't know why) +- Probably more things + + +# Current conclusions +- LSX is a nice and expressive way to build a DOM, deserves attention and work + - great degree of composability + - very user-friendly (**kwargs and *args, for example) +- Mojo is awesome + + +# Available features that could bring more to LSX: + - functions with same name and different signature ! + +# Possible feature request: + - ```__struct_name_of[App]() -> StringLiteral: "App"``` (struct name) + +# Example: +There is a toggle components button, to mount/unmount some of them +```mojo +from Helpers import * +from DomEvent import * +from DomTree import * +from Component import * +from ComponentManager import * +from Server import * + +def main(): + spin_css=""" + body { + text-align: -moz-center; + font-family: monospace; + } + + @keyframes spin { + from { + transform:rotate(0deg); + } + to { + transform:rotate(360deg); + } + } + """ + m = ComponentManager[ + App, + Counter, + ]() + s = Server(m^) + s.extra_css = spin_css + s.Start[App]() + +@value +struct App(Component): + @staticmethod + fn component_name()->String: return "App" + + var some_state:Int + var title:String + + fn __init__(inout self): + self.some_state = 1 + self.title = "Default app title" + + fn event(inout self, e:DomEvent): + if e.name == "toggle_event": + print("Toggle!") + self.some_state *=-1 + + def render(inout self, props: PropsType) -> Element: + var color = "blue" + if self.some_state == -1: color = "blue" + var toggle_button = + H[A]( + On[DomEvent.Click]("toggle_event"), + "Toggle components ", + H[Span]( + str(self.some_state), + ), + CSS(`background-color`=color,`font-size`="16px") + ) + if self.some_state == -1: + return toggle_button + + return + H[Div]( + CSS( + width="fit-content", + ), + H[H1](self.title,CSS(`background-color`="deeppink")), + H[H1]( + "🐣", + CSS( + animation="spin 2s infinite", + width="fit-content", + `font-size`="128px", + margin="0" + ) + ), + H[H2]( + f["{url}{emoji}"]( + url="", #url=props["url"].deref[String]() , + emoji="🔥❤️🐍" + ), + CSS(color="blue"), + ), + H[Counter]("first instance",Title="first"), #🔥 components + H[Counter]("second instance",Title="second",inc=True), + H[Br](),H[Br](), + toggle_button, + ) + + + +@value +struct Counter(Component): + @staticmethod + fn component_name()->String: return "Counter" + + var x:Int + fn __init__(inout self): self.x=0 + + fn event(inout self, e:DomEvent): + if e.name == "increment_10": + self.x+=10 + #careful: return UnsafePointer[T]() if not found + e.Instances[App,"AppInstance"][].title=self.x + + def render(inout self, props: PropsType) -> Element: + if props.find("inc"): + self.x+=1 + return + H[Span]( + H[H1]( + props["Title"].deref[StringLiteral]() + str(" ") + self.x, + CSS(`background-color`="aliceblue") + ), + H[Button]( + "Increment", + On[DomEvent.Click]("increment_10"), + CSS(`background-color`="aqua") + ), + ) +``` diff --git a/integration prototype/Server.mojo b/struct composability prototype/Server.mojo similarity index 86% rename from integration prototype/Server.mojo rename to struct composability prototype/Server.mojo index bb938c6..f047132 100644 --- a/integration prototype/Server.mojo +++ b/struct composability prototype/Server.mojo @@ -6,6 +6,7 @@ struct Server[*Ts:Component]: var socket:PythonObject var component_manager:ComponentManager[Ts] var js_renderer: String + var extra_css:String fn __init__(inout self,owned arg:ComponentManager[Ts]): self.socket=None self.component_manager=arg @@ -16,6 +17,7 @@ struct Server[*Ts:Component]: print("Error importing js_renderer.js: " + str(e)) self.js_renderer = "" #TODO: set self.error = True + self.extra_css = " " fn Start[App_T:Component](inout self): constrained[ @@ -55,11 +57,17 @@ struct Server[*Ts:Component]: var response:String = "HTTP/1.0 200 OK\n\n" response += "" response += "" + response += "" response += "" response+="" client[0].sendall(PythonObject(response).encode()) client[0].close() - elif request[1]=="/event": #Event + elif request[1]=="/styles.css": + var response:String = "HTTP/1.0 200 OK\n\n" + response += self.extra_css + client[0].sendall(PythonObject(response).encode()) + client[0].close() + elif request[1]=="/event": var json_req = Python.import_module("json").loads( full_request[-1] ) @@ -82,11 +90,13 @@ struct Server[*Ts:Component]: "", "type", event_name, - "data" + "data", + self.component_manager.states ) ) except e: print('error component.event()',e) unroll[_loop,len(VariadicList(Self.Ts))]() + _=event_name _=instance_name @@ -103,6 +113,9 @@ struct Server[*Ts:Component]: only_unrendered=True ]() + print("self.states:") + for i in self.component_manager.states: print("\t",i[]) + client[0].sendall(PythonObject(response).encode()) client[0].close() diff --git a/struct composability prototype/example.png b/struct composability prototype/example.png new file mode 100644 index 0000000000000000000000000000000000000000..675a7f9f4b7beeaaf50260713d2bdf1932147f9e GIT binary patch literal 17351 zcmd74WpG?Em@R0>%n(D&6en#vX6Bd~VrFJ$W@ct)$IQ%}n3>wl%na@AOx5n2dNV)v z)y|LYs;AtG<$sq;rlwb-28&7z!djA_N2kiiEhZ0{A!z0Rj0J9u|By7*bLXK0rGP zN+`jDKVI-gVc=^#ClNI#MOzamSA7R#2vZwdYhzkRLkDAH8%Hx+r%R|FK5!zse~E+~ zjP;$&ZEZ-E%&m!K?w$xsG-m**u&%)HkXvUmy#cx~T_ za_pQS4d^}*S|4>INN5GujaQr#TE-rxP+09LhzEfp4o(St=85XwIzkeE;Y5_2b2+}3hovu6n5s&xp z>Ip%67!fxouhDLq1+nFM1(oS7I&|Bml*z3ccbF2)=Ey|x!@s_xn9Npl)-|^R0Wi@h zHaKrJN%S}=in^urm*wVh*_OM#1Cbn8VdPd102-W8q$Ym9Qi07}N(Av7cEV(W#Z82T zO<`OaDj#)~fyBqE{O*nD8(3XB8K{P#K{7FH`!~FSJ7eEh2uT4Ecjns*4UuEpX#D{* z{4*A^M5JnbDU%e|c4QW$s8&RWhiLyM@7z7}Lbr@Owst!G;yfcmiVx6|#fgPB8M!&i zu8X_+)1(TYTLTi1^$s2Uvc&1HmZ9X3>kq6nmhL;<3$5!j&A`rI{7#tZUoj$ZPgI51 zKQ6BUI$jP?(J0xmq8-{g8=lt8#+vQ&O^zM*m&-8~MijKCD!W>@NVEh?+p7uzE!}h{ zcb&vTVz##Ww m8*-+t7jrl45bTLTS#*Qhjc zn6ESvFEuNi6^ ?q}Sf0_X;C=pYhdUI6M zEQ$?xa$1lJu`Ex*8RO72-MRB!OH- GhiaTO=9uC#2UPVPB=XReY9sITy>thCS(MDc1zY_DA z5k<*O kDF-O$1Hc 3-;xGQ z=2>owb`H1a`8kr?tBv_mY{LCO`B+cSBU|QlmWNku$X(aT3#A_>4a4)iw`uLFKB`sX z5iP=rKLL`y?A $i+ zR6gTsv4(_q?CCL?lLgGM)T*(RoF`6=M~XVziu!ZE)a3|MaJGl?YHuzH&*?4yb^`I3 zn(}#7BvD3E|E{)z>@{I#v|qA9<=1Bz5G@JqSxM^GUT_>s^suP-MJ%^`ojlKil=@~) zsO#OGJ+-kpe6V>(oggvyg^w#6Di&bS!h74!e_qmaPVhMd#J?ove%It=nc~DvRik=L zFTU7j*MW_vuJQv1MFz>+pk=?tu3D1__#i==Av&70&QR<+9ggnTup(p_7}|)8#HJZV z{W$|qR11oe*&tDegpyMd%T`J{fiazh0pot&V*xYsjazned8=SufA}Kop3RAxN;2uI zS>eyM(mM|imwn8Ifn!(Mk4-2hgrI_<_iTg-%y$SqUCAEBAIH}dJ-Gj%vKVtHdJmgX zXv&UxetgIMm#EVFvbafY?jsoU61R*6#nDD}1i&?tln_Bh*)%eHnFU(MotON;jVL(X z1u*t!r3X{-Qam77;x7se)yc7IXuT+9fY$t1ZG&g`tonAZyLPuDZrLm|LcH(P7_R6f zSkfBm>#&B^0f=_W1xW=3 `eL_A1WbhM_h~4uom>z2=U|iOG~W;@aT@91X3DW}8=N z^8DaW@|#C{rMgRsI(Vx%jF!apm8@On8nX$eXx^l>x($rKmCUUcO@Fad>fO_92kkg` zzAeyBN8Zw6E9;8|J6b8HH4b`uP?A44Pt&=XMn+;=vVGcmaI=4rd=s`R{hB48rC+WZ zhy)vgl4%?J@zbzAPQPl%22s1- K2cuq+5?+T?& kaT#f_-Xvvdu3Mt|t(k4tI zkbtYy&ksbiahJxht7G1Vx7KDEqBDNexQD_3-ms2DkMrq*Pb{0S*Dw#(UHS>=KQoHS z%z}tXDQJ@$=ab@U>0;UQb)F>w`NGt{AQT8)VE0a>@^Cn!F44#bV?K#pL6w{7JuTHH zD`fLMy *JOs_7^($2xHB)bZ!`7d`qc3CMx(P-sfX%$D#8 z5iKwNpeJ0%ML07bpOpSSG;^)ccp+;6?;Re58NohoQg}YY5{)tBP8Kbh@A{`s`K89Y zRn&`Al-SEV7@T5e2+83lWQX;>2{$VNDzhwDNs1z) By`6s+=tx19f?@)K2$bz(<-byW?B?r&E)wIF!xm YE0g1fC}i(i zN)j}l IR>N=B7zP)zoQ1>V*S z8&l=oC$v@$DU?G ldbEM3&=0nW8PXBgY9jUe6nwT#oikhuF z?>Cz_A>8fwmGHUCh}Zwyh;WjLm^ltPFGpvIQwC8Yc?iPbtXN7AtDs;N*2)4B(11v= z!5bO%u*Of&oXvtsE{~2k>*uPSI^0YeQ+DHUjd+Up&(-PQ6C4SvQH7+tX5%>)3YjSh zt9P``DbaqoGVbu5bkS5D&x1y!&N2gQ!R1b!;(&ZLT5a!7=JTZ_K?800rPg1SIS;q$ zu?PUPEEdeMWKGAKEXiv0_|B;~r|8;WyF PweVuOE6c?#)~0o so+XbaCFJT7Gt)V4uQ6o-`^wJR zI=GSpfhYZ_%{Z=jBF5|mW!3vY0|E)-2Wh4g{;yTn1f)0(CNfF~<6wNn_t$4KxDy!0 zeLE^Xno%;%`6 H0YmFx^bfV)_3^#5mSxEjE z+qfLkK=CNpi~|Y`X~bJU`6{20PSoa`$7ij(cEF>HO~vM0Jf IkE=Fd%&c{4>%t=e7n!Z1SPCk3eaIT`r_c$Xup{$gF^Qz% zXtn;1St~6JxR?f(+gW}aEq?$Lk|GzBWV_sw4f5H}8tic5tS7T*1Lk?!v)Y6YZaio0 zV*LmrbG`bJVs%x&GVPPLORXcVzb>eM7aTmv2?1uwDL+UzV{J%izg&6|q2vQ5WJNZN zS&GjDTM63!)-g}SQ0c!%ZPlX|e7pVPv6~`(yvLi|E#8j+L(1Ckxy&05Ob`=V<#Tdb z(Ew7;DCzSYXqDGb=}B#CgJJGJyIQginCN?WqcL}o{+57VUN1WU^q(frXJJlWIdlTd zsu{4Q@7(2`5+hJyg-&3W_EC2!UAvI`jb^F0(n1}L&aE3x7I2*^Nk$$hWZEO!IP&d3 ztIk=GESY=QjQu=eLZxW$Q7)u=cjY)Q>@c!zEaI{PH7#L=2Vq5&m=|&GD0+2FxU&?U zONpgc)4j`Q5Oz2p*<#gD1)fxXUQuAdE%ingwcS~bqA0{H8`BWB9 v;Z#15|)O{Kcs#;9sDwd7J0MqX+qX{Q6j|3tjYl|U7HArkd0;$`V_~8 zQa>2njuPQ>)eXdsbSP!77;ev`@eu_&!2m^YNfoC~SOx1&|z%hOuOkZ%VMJ}grHDk#qx)XLeBQ5{9_N*NW z2*Qb&u;{}mtrm^t%zSBlhg2M2rK}q?rY)g+C+!Cs6)+uIb(TyIHF!phHzoyH4Ytg> zf>!7fl8Jlx^XT5~GKfBVohNjwJ}!DrI0#NI6_c!0oPJua!cjO2UkS6N+zvKqNZVaZ z++j4W ng*q->4g=hAkz}o8A-^&z3Rm{OWkYleS4>_}N`jkgvr(GX8>kX(0=N^D*2G z3xgdV&D)1m&E8_e*R@vwQNAD1!aWO@CKpO(l+j|BnD;8EOC`hXr;Z9&83I@Da*huL z)^rO7prTUdH;Tmz%`ugW1Jm4#i20}YqLYP)6(e$XX4?D?vR5dTNOjxZ5n$Yx*_xnF zFD&7eji6ujvcl5eqTed-f;8wB;NN8d5O`%VK8ozJUs+@**qN&cD)#TRUy9{fP{5hm zeZ5Kw7ZxI+mmp*9gTvu2&_#r~gXy}hpRK(gDAm*zf?}E_EGoU5@Ly7!whjK|vCGHL zU8t)9^MZ&Umvcf&LwhZ@NikCs{OKiv1WNb^Zvbpj@p7U;ZHsPnp>Y5CwZboLxk&D| z6wz}k4XK1P4q3@_nFLWl9-6qy{QRyJMx7I!OyKxD*5>@Po8}z%*JSyhn*kl|)$uRi zOgm*H#HBMtWlc2tyI(hy6J!B-m;KsH^U^Zjih}3P#I`?7KD( }liK3zrfl*4KN)6 RS+ z6ER;F71g6#N>Ckb*h(s+e&2rjZC$-nxKLB;b-JWvRa4NmC^U)gDb7)`GJKa0=Qy4A zS)#$%g{lSww$wjsZLTAYMmn!92pneW@}o}Kk{A-Zz{eQoz%>-!4tDCC==kPle00l> ztww|94j|o)2p_rYr+BdyJu-+Jt8-4C>3=;Qz`0RVzw29bN0XjLeg4A%VhA%xoNFV| z9gYcMg6Q^a%c`Qm=eQ}^P8;8LMVoZmvs-Mm{F1>uY)AZ#cT3x{<{;G>eN^Gb<{NVx zh{O=fhvV31BjB?!s_fR{6 #;MP#Y=S>{lve;uzP=k&y#niB=ae+ ztE644f7Ty(y7r*zcu>jc2JspAT-+cw>%5u%`|~PINp| z6AY(}=Rn^Xt<0l0X&R=7^CZkM=HTqwGu`$_+{KH9f-s|&qFW5qGaZ4L5NkBKBCIK| zQxzqMJf|Dxk)=GXDN~mWEA8z3dB+u(QR(uEp)Dno^2@_RQolVd1=RR~zqgHYe0U&o zPVgVz^3wH-`- I_&=}C#Eyr0ANdSLyHtO8giPj`3dSv`6;?|ARB+=L>s>y zTYq1hfcQP^kfF0oa$0oTYt+}}#W#wl<$YtLJ*(f|ME_PJb_Vm?8-&6X^Q!&Fed(Y| ziT~xtu^5r`1!Y4m7B<61(Y6!&%(8-9IW18KB_$vx!;P&5bO$fx`}+0ht!AIUe$J1I z_kjQt5+efbvYE2g$LkAlqv8Ef+I{!R>KPwGxg|ZN2DG8?wQqx*Ya>K4scdOzX$1j3 zT(hz1OQLiiR|Q6Xd043++dfZ8LicYh$gg`0qYw;Iz?0ATMx$_lGgJSB!{Uhp4enl{ z#6}$5I3 Z!w8eEql80b(gzH{%d?nqj+|tm*mh|9Edi)%gY+VO;(?;V@ z!TqJe@o9F0I-!>5mbVd?gU4VDC Mm+2~$^Auc%3IBlFqjPiDVjmoJvRai>>dQKT%{uKFJz8@jr&{B|ni&-h=| zhEdi~GKYHeexu`HAm*J&0ts_W)+{&R31l5P#POJkRm~B=KC-~pQvi;9 jrHt;T+lzRdL!m)aS2L~ zmN>wKZCC5HVsq+#ATaIbyZ_DX&0po;8PR;Pu(PN@lCiGXUAmgEvh|bz$f9=}eu(mn z=G(i3HlO%qTB ztst>Rcg?wv?O)Mog9GiXfdMT#B!qWXZn?uFA)zJN39%-1ao*eMN^35<(&v-iSJEsE zcz!LZA8@4|*6m|^0-i4&{sfvQZu_NTYHhSmm8J&U7g-$p Bwfs!4h`R z1OIm{z`3m1@`#+UBXlf#+H8RBHz`nWoLe8p4>PJ~2%{V)tQhTY!-F~yBQ}YDd9KAF zs0qddg&BBE2_a;vVTqp|T-4$rDwmss#CkeK+Woi%f 2oD z|AO4xud7?oY2!F}H+mweR{K(H16;@j*VF-q*h?&HB=#wK*MrH5s<@ zOlmU+R_JWG7E}B2L3J{EW7anO-{vtqhMegb`A@ayjx-Pl m#<|Zc)I9#k0e;^aEeHWp>uJ_JH_UCzA z=hffcD?vr} GE?`;8p(Z3~E`_g(pRyfHiC9 zkoZjgD}*6XCO3TW=~Gt(XZ1QkW_+4bW9WA6J2r+SI{rb|N8|`|o>U!gw8{@=9f3## z>3%K;zz{vJ;3Q?kG@-RJ;e`J8!vuA#9u~CIFfxp}G^JL!A$1Dw2}{9kmXQ4z7(L+& zHz5POZ2t6L&xu?pOPldSlw7?X7gPXWyu^t$_vHf|rA4x|@7hpK`SBM6irP}=QnvZ# z_;}w*qB((2)BxuR#;_R80%^ko!&Z-pTPS7wc&+`qGZ@($sNd?V+1m`-lcn2T+c0 za-X;`AbV@51H`HHEM~TGZe|i}!dA#boJ4@79@j#0kUh!Tj3|Y+`_TSmvir#g@E&k} zugPHkErKbO(fY{$Orw)TBQ1xm7;DoOlvPYg(oprl4gp()W{|prcgA?;nkJLY4@dw{ zCg9+i)&EyvH2;Tz2))zoI;@j9Y(nTeVk!Rj??uAk(1@@1lJWP>&Pi|0e=muf=Dt|5 zX$E}}tWTI1RKFWC&3bq>e7m?*t<>KG>~cdQ{T1r@?NO!vPJ=tnmH5{%L@{ok6qp}> zKK(@l-6!5kAS@+_{@2Or3CGRW`*L>|&qm4Jm!3+s$;_Ib9amkK{idQ~S6`t5yQI|A z>%U@1YH!b`KR`Roi&fUZT%XA`&(^1U8j9f$=LPz=(@Eg-*<5T{`I>+Z2m+PoNxr&k zyC&)jXStZw1qxVPl5{@G#)-qC;f>aAQ$UDc=4Y0*sJ9t#x~@I2_q?tl(VZ~n^LTym zu6aIYYe&2uk05cL4;q@=SOBFQq>fE;JU#eqJ0F;AJ3IW=OE2+g6g`uJa7RP#tv&rX z2vJi&orq$Rl0jdr34Qivl-S1v)9dn@OF|r{xLC!%`m@Y0IX;+wzaEHS4f)9J7VdBP zl{IPdVQ*rhA_zk8Mv$5N!tV14SI3KLs`H9kePwvnJ(mB4K{+|u=P|48vDQL}$~W7) z;$v-*wwUMj?kx?tMgaB~WO=vEd;`5NvVd=gV`F1)hujw9x$L)%c?|x!_os`Z!vv#0Z