///|
pub(all) enum RespoNode[T, G] {
Component(RespoComponent[T, G])
Element(RespoElement[T, G])
}
///|
pub impl[T, G] Eq for RespoNode[T, G] with equal(
self : RespoNode[T, G],
other : RespoNode[T, G],
) -> Bool {
match (self, other) {
(Component(left), Component(right)) => left == right
(Element(left), Element(right)) => left == right
_ => false
}
}
///|
impl[T, G] Show for RespoNode[T, G] with output(self, logger) {
let ret = match self {
Component(component) => component.to_string()
Element(element) => element.to_string()
}
logger.write_string(ret)
}
///|
pub fn[T, G] RespoNode::to_cirru(self : RespoNode[T, G]) -> Cirru {
match self {
Component(component) => component.to_cirru()
Element(element) => element.to_cirru()
}
}
///|
/// currently it's commonly used for all errors in Respo
pub(all) suberror RespoCommonError {
RespoCommonError(String)
}
///|
pub impl Show for RespoCommonError with output(self, logger) {
let ret = match self {
RespoCommonError(msg) => "(RespoError \{msg})"
}
logger.write_string(ret)
}
///|
pub fn[T, G] load_coord_target_tree(
tree : RespoNode[T, G],
coord : ArrayView[RespoCoord],
) -> RespoNode[T, G] raise RespoCommonError {
// @dom_ffi.log("looking for " + coord.to_string() + tree.to_string())
if coord.length() == 0 {
tree
} else {
let branch = coord[0]
match (tree, branch) {
(Component(left), Comp(target_name)) => {
let { name, tree, .. } = left
if name == target_name {
load_coord_target_tree(tree, coord[1:])
} else {
raise RespoCommonError(
"Mismatch in expected component name: expected \{target_name}, found \{name}",
)
}
}
(Element(left), Key(idx)) => {
let { children, .. } = left
match children.search_by(fn(x) { x.0 == idx }) {
Some(i) => {
let child = children
.get(i)
.unwrap_or_error(RespoCommonError("to get child \{idx} \{i}")).1
load_coord_target_tree(child, coord[1:])
}
None => raise RespoCommonError("no child at index key \{idx}")
}
}
// match children.get(*idx as usize) {
// Some((_k, child)) => load_coord_target_tree(child, &coord[1..]),
// None => Err(format!("no child at index key {:?}", idx)),
// },
(Component(_), Key(_)) =>
raise RespoCommonError(
"Type mismatch: expected a DOM element, but found a component",
)
(Element(_), Comp(_)) =>
raise RespoCommonError(
"expected component at " +
coord_path_to_cirru(@immut/vector.from_array(coord.to_owned())).to_string() +
", found target being an element",
)
}
}
}
///|
/// creates a DOM tree from virtual DOM with proxied event handler attached
pub fn[T, G] build_dom_tree(
tree : RespoNode[T, G],
coord : @immut/vector.Vector[RespoCoord],
handle_event : (RespoEventMark) -> Unit raise RespoCommonError,
) -> @dom_ffi.Node raise RespoCommonError {
let window = @dom_ffi.window()
let document = window.document()
match tree {
Component({ name, tree: child, .. }) => {
let next_coord = coord.push(Comp(name))
build_dom_tree(child, next_coord, handle_event)
}
Element({ name, attrs, style, event, children }) => {
let element = document.create_element(name)
let mut inner_set = false
for pair in attrs {
let (key, value) = pair
match key {
"style" => @dom_ffi.warn_log("style is handled outside attrs")
"innerText" => {
inner_set = true
element.set_inner_text(value)
}
"innerHTML" => {
inner_set = true
element.set_inner_html(value)
}
"htmlFor" => element.set_html_for(value)
"value" =>
if name == "input" {
element.reinterpret_as_html_input_element().set_value(value)
} else if name == "textarea" {
element.reinterpret_as_html_textarea_element().set_value(value)
} else {
element.set_attribute(key, value)
}
_ =>
if key.has_prefix("data-") {
element.set_data_attribute(key[5:].to_owned(), value)
} else {
element.set_attribute(key, value)
}
}
}
if !style.is_empty() {
element.set_attribute("style", style.to_string())
}
if inner_set && !children.is_empty() {
@dom_ffi.warn_log(
"innerText or innerHTML is set, it's conflicted with children: \{inner_set} {TODO children}",
)
}
for pair in children {
let (k, child) = pair
let next_coord = coord.push(Key(k))
element
.reinterpret_as_node()
.append_child(build_dom_tree(child, next_coord, handle_event))
}
// util::log!("create handler for element: {} {:?}", name, event);
for pair in event {
let (key, _) = pair
attach_event(element, key, coord, handle_event)
}
element.reinterpret_as_node()
}
}
}
///|
pub(all) struct DispatchFn[T]((T) -> Unit raise RespoCommonError)
///|
pub impl[T] Show for DispatchFn[T] with output(self, logger) {
let ret = match self {
DispatchFn(_f) => "(DispatchFn ..)"
}
logger.write_string(ret)
}
///|
pub(open) trait RespoAction {
/// a function for building action to update states is required
build_states_action(Array[String], @dom_ffi.JsObscure?, Json?) -> Self
// detect_intent(Self) -> ActonOp? // TODO
// build_intent_action(ActonOp) -> Self
}
///|
pub fn[T] DispatchFn::run(
self : DispatchFn[T],
op : T,
) -> Unit raise RespoCommonError {
let f = self.0
f(op)
}
///|
/// abstract over cursor array with state type
pub struct RespoCursor[T] {
cursor : Array[String]
_phantom : T?
}
///|
pub fn[T] RespoCursor::new(cursor : Array[String]) -> RespoCursor[T] {
{ cursor, _phantom: None }
}
///|
/// dispatch an action to update states, to empty the state, pass None
pub fn[T : RespoAction, S : ToJson] DispatchFn::set_state(
self : DispatchFn[T],
cursor : RespoCursor[S],
state : S,
) -> Unit raise RespoCommonError {
let op = T::build_states_action(
cursor.cursor,
Some(@dom_ffi.v_to_js_obscure(state)),
Some(state.to_json()),
)
self.run(op)
}
// pub fn DispatchFn::run_intent[T : RespoAction](
// self : DispatchFn[T],
// op : Json
// ) -> Unit!RespoCommonError {
// let op = T::build_intent_action(op)
// self.run!(op)
// }
///|
/// dispatch an action to update states, to empty the state, pass None
pub fn[T : RespoAction, S] DispatchFn::empty_state(
self : DispatchFn[T],
cursor : RespoCursor[S],
) -> Unit raise RespoCommonError {
let op = T::build_states_action(cursor.cursor, None, None)
self.run(op)
}
///|
#deprecated("use `empty_state` instead")
pub fn[T : RespoAction] DispatchFn::run_empty_state(
self : DispatchFn[T],
cursor : Array[String],
) -> Unit raise RespoCommonError {
let op = T::build_states_action(cursor, None, None)
self.run(op)
}
///|
pub fn[T] DispatchFn::new(
f : (T) -> Unit raise RespoCommonError,
) -> DispatchFn[T] {
f
}