Skip to content

Commit

Permalink
refactor top-level app definition; tag 0.0.12
Browse files Browse the repository at this point in the history
  • Loading branch information
tiye committed Oct 30, 2024
1 parent 2faf8ed commit e654313
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 155 deletions.
2 changes: 1 addition & 1 deletion moon.mod.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tiye/respo",
"version": "0.0.11",
"version": "0.0.12",
"deps": {
"tiye/dom-ffi": "0.0.4",
"tiye/cirru-parser": "0.0.7"
Expand Down
93 changes: 69 additions & 24 deletions src/lib/app.mbt
Original file line number Diff line number Diff line change
@@ -1,28 +1,73 @@
// pub trait RespoApp {
// // TODO better use RespoAction instead of Show
// dispatch(Self, (Show) -> UInt) -> Unit!@node.RespoCommonError
// pick_storage_key(Self) -> String
// get_mount_target(Self) -> @dom_ffi.Node
// get_store(Self) -> @json.FromJson
// // get_loop_delay() -> UInt?
// // view(Self) -> @node.RespoNode[T]!@node.RespoCommonError
// // render_loop(Self) -> Unit!@node.RespoCommonError
// backup_model_beforeunload(Self) -> Unit!@node.RespoCommonError
// try_load_storage(Self) -> Unit!@node.RespoCommonError
// }
/// get basic App structure
pub struct RespoApp[Model] {
store : Ref[Model]
storage_key : String
mount_target : @dom_ffi.Node
}

// impl RespoApp with try_load_storage(self) {
// let window = @dom_ffi.window()
// let storage = window.local_storage()
// let key = self.pick_storage_key()
// match storage.get_item(key) {
// Some(s) => {
// let store = self.get_store()
// store.val = try_from_string!(s)
// }
// None => @dom_ffi.log("no storage")
// }
// }
/// backup store to local storage before unload

pub fn backup_model_beforeunload[Model : ToJson](
self : RespoApp[Model]
) -> Unit {
let window = @dom_ffi.window()
let storage = window.local_storage()
let p = self.storage_key
let store = self.store.val
let beforeunload = fn(_e : @dom_ffi.BeforeUnloadEvent) {
let content = store.to_json().stringify()
// util::log!("before unload {} {}", p, content);
storage.set_item(p, content)
}
window.set_onbeforeunload(beforeunload)
}

pub fn try_load_storage[Model : @json.FromJson + Default](
key : String
) -> Model {
let window = @dom_ffi.window()
let storage = window.local_storage()
match storage.get_item(key) {
Some(s) =>
match @json.parse?(s) {
Ok(j) =>
match @json.from_json?(j) {
Ok(s) => s
Err(e) => {
@dom_ffi.log("failed to parse storage: \{e}")
Model::default()
}
}
Err(e) => {
@dom_ffi.log("failed to parse storage: \{e}")
Model::default()
}
}
None => {
@dom_ffi.log("no storage")
Model::default()
}
}
}

pub fn RespoApp::render_loop[Model, ActionOp](
self : RespoApp[Model],
renderer : () -> @node.RespoNode[ActionOp]!@node.RespoCommonError,
dispatch_action : (ActionOp) -> Unit!@node.RespoCommonError
) -> Unit {
let mount_target = self.mount_target
let ret = render_node?(
mount_target,
self.store,
renderer,
dispatch_action,
Some(100),
)
match ret {
Ok(_) => ()
Err(e) => @dom_ffi.error_log(e.to_string())
}
}

// pub trait RespoStore: ToJson + @json.FromJson {
// // TODO better use RespoAction instead of Show
Expand Down
2 changes: 1 addition & 1 deletion src/lib/dialog/drawer.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type DrawerRenderer[T] ((@node.DispatchFn[T]) -> Unit!@node.RespoCommonError) ->
]!@node.RespoCommonError

fn to_string[T](self : DrawerRenderer[T]) -> String {
"(DrawerRenderer)"
"(DrawerRenderer \{self})"
}

pub fn DrawerRenderer::default[T]() -> DrawerRenderer[T] {
Expand Down
10 changes: 5 additions & 5 deletions src/lib/dialog/prompt.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub struct PromptOptions {
pub type PromptValidator (String) -> Result[Unit, String]

pub fn to_string(self : PromptValidator) -> String {
"(PromptValidator)"
"(PromptValidator \{self})"
}

impl Show for PromptValidator with output(self, logger) {
Expand Down Expand Up @@ -73,10 +73,10 @@ fn comp_prompt_modal[T : @node.RespoAction](
// @dom_ffi.log("Prompt state: " + state.to_json().stringify(indent=2))
let submit = on_submit
let close = on_close
@dom_ffi.warn_log(
"recreate prompt modal with states: " +
states.data.to_json().stringify(indent=2),
)
// @dom_ffi.warn_log(
// "recreate prompt modal with states: " +
// states.data.to_json().stringify(indent=2),
// )
let on_text_input = fn(
e : @node.RespoEvent,
dispatch : @node.DispatchFn[T]
Expand Down
15 changes: 12 additions & 3 deletions src/lib/node/dom-change.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ pub fn to_cirru[T](self : DomChange[T]) -> @cirru.Cirru {
effect_type.to_cirru(),
coord_path_to_cirru(coord),
dom_path_to_cirru(dom_path),
// skip_indexes.to_cirru(), // TODO
indexes_to_cirru(skip_indexes),
],
)
ReplaceElement(~coord, ~dom_path, ~node) =>
Expand Down Expand Up @@ -279,7 +279,7 @@ pub fn to_cirru[T](self : ChildDomOp[T]) -> @cirru.Cirru {
coord_path_to_cirru(nested_coord),
dom_path_to_cirru(nested_dom_path),
effect_type.to_cirru(),
// skip_indexes.to_cirru(), // TODO
indexes_to_cirru(skip_indexes),
],
)
}
Expand All @@ -296,7 +296,7 @@ pub fn get_dom_path[T](self : DomChange[T]) -> Array[UInt] {
}
}

pub fn to_cirru(self : RespoCoord) -> @cirru.Cirru {
pub fn RespoCoord::to_cirru(self : RespoCoord) -> @cirru.Cirru {
match self {
Key(key) => Leaf(key.to_string())
Comp(comp) => List([@cirru.Leaf("::Comp"), @cirru.Leaf(comp)])
Expand All @@ -312,3 +312,12 @@ pub fn dom_path_to_cirru(dom_path : Array[UInt]) -> @cirru.Cirru {
let items = dom_path.map(fn(x) { @cirru.Leaf(x.to_string()) })
@cirru.List(items)
}

// turn @hashset.T[Int] to Cirru
pub fn indexes_to_cirru(xs : @hashset.T[Int]) -> @cirru.Cirru {
let items = []
for x in xs {
items.push(@cirru.Leaf(x.to_string()))
}
@cirru.List(items)
}
6 changes: 3 additions & 3 deletions src/lib/renderer.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ pub fn mark_need_rerender() -> Unit {
pub fn render_node[T, U](
mount_target : @dom_ffi.Node,
// TODO it copies the whole store, need to optimize
get_store : () -> U,
store : Ref[U],
renderer : () -> @node.RespoNode[T]!@node.RespoCommonError,
dispatch_action : (T) -> Unit!@node.RespoCommonError,
_interval : Float?
) -> Unit!@node.RespoCommonError {
let prev_store = @ref.new(get_store())
let prev_store = @ref.new(store.val)
let tree0 : @node.RespoNode[T] = renderer!()
let prev_tree = @ref.new(tree0)
let handle_event = fn(
Expand Down Expand Up @@ -65,7 +65,7 @@ pub fn render_node[T, U](
@node.raf_loop(
fn(_t : Float) -> Unit!@node.RespoCommonError {
if drain_rerender_status() {
prev_store.val = get_store()
prev_store.val = store.val
let new_tree = renderer!()
let changes : Ref[Array[@node.DomChange[T]]] = Ref::new([])
@node.diff_tree!(new_tree, prev_tree.val, [], [], changes)
Expand Down
116 changes: 16 additions & 100 deletions src/main/main.mbt
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
let app_store_key : String = "demo_respo_store"

struct App {
store : Ref[Store]
mount_target : @dom_ffi.Node
}

fn get_store(self : App) -> Ref[Store] {
self.store
}

fn get_mount_target(self : App) -> @dom_ffi.Node {
self.mount_target
}

fn dispatch(self : App, op : ActionOp) -> Unit {
// TODO intent
@dom_ffi.log("Action: " + op.to_string())
self.store.val.update(op)
}

fn view(
self : App
store : Store
) -> @respo_node.RespoNode[ActionOp]!@respo_node.RespoCommonError {
let store = self.store.val
// @dom_ffi.log("Store to render: " + store.to_json().stringify(indent=2))
let states = store.get_states()
@respo_node.div(
Expand All @@ -38,92 +18,28 @@ fn view(
)
}

fn pick_storage_key() -> String {
app_store_key
}

fn try_load_storage(self : App) -> Unit!@respo_node.RespoCommonError {
let window = @dom_ffi.window()
let storage = window.local_storage()
let key = pick_storage_key()
match storage.get_item(key) {
Some(s) => {
let store = self.get_store()
store.val = try_from_string!(s)
}
None => @dom_ffi.log("no storage")
}
}

/// backup store to local storage before unload

fn backup_model_beforeunload(self : App) -> Unit {
let window = @dom_ffi.window()
let storage = window.local_storage()
let p = pick_storage_key()
let store = self.get_store().val
let beforeunload = fn(_e : @dom_ffi.BeforeUnloadEvent) {
let content = store.to_string()
// util::log!("before unload {} {}", p, content);
storage.set_item(p, content)
}
window.set_onbeforeunload(beforeunload)
}

fn main {
let app : App = {
mount_target: @dom_ffi.window()
let window = @dom_ffi.window()
let mount_target = window
.document()
.query_selector(".app")
.reinterpret_as_node(),
store: Ref::new(Store::default()),
}
let ret = app.try_load_storage?()
match ret {
Ok(_) => ()
Err(e) => @dom_ffi.error_log(e.to_string())
.reinterpret_as_node()
let app : @respo.RespoApp[Store] = {
store: Ref::new(@respo.try_load_storage(app_store_key)),
mount_target,
storage_key: app_store_key,
}
app.backup_model_beforeunload()
@dom_ffi.log("store: " + app.store.val.to_json().stringify(indent=2))
app.render_loop()
let window = @dom_ffi.window()
// @dom_ffi.log("store: " + app.store.val.to_json().stringify(indent=2))
app.render_loop(
fn() { view!(app.store.val) },
fn(op) {
@dom_ffi.log("Action: " + op.to_string())
app.store.val.update(op)
},
)
let dev_mode = @dom_ffi.get_url_search_params(
window.get_location().get_search(),
).get("mode")
@dom_ffi.log("dev mode: " + dev_mode.to_string())
}

fn App::render_loop(self : App) -> Unit {
let mount_target = self.mount_target
// let store_to_action = global_store.to_owned()

let dispatch_action = fn(op : ActionOp) -> Unit!@respo_node.RespoCommonError {
// @dom_ffi.log("Action: \{op.to_string()}")
// @dom_ffi.log("Global store: \{global_store.val.to_json()}")

// Self::dispatch(store_to_action.to_owned(), op)?;
self.store.val.update(op)
match op {
Noop => raise @respo_node.RespoCommonError("TODO")
_ => ()
}
}
let renderer = fn() -> @respo_node.RespoNode[_]!@respo_node.RespoCommonError {
let node = self.view!()
// @dom_ffi.log(
// @cirru.format?([node.to_cirru()], { use_inline: false }).unwrap(),
// )
node
}
let ret = @respo.render_node?(
mount_target,
fn() { self.store },
renderer,
dispatch_action,
Some(100),
)
match ret {
Ok(_) => ()
Err(e) => @dom_ffi.error_log(e.to_string())
}
}
24 changes: 6 additions & 18 deletions src/main/store.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ struct Task {
time : Double
} derive(Default, Eq, Hash, ToJson, @json.FromJson)

enum IndentOp {
Noop
IncTwice
}
// enum IndentOp {
// Noop
// IncTwice
// }

enum ActionOp {
Noop
StatesChange(@respo.RespoUpdateState)
Intent(IndentOp)
// Intent(IndentOp)
Increment
Decrement
IncTwice
Expand All @@ -46,7 +46,7 @@ fn ActionOp::to_string(self : ActionOp) -> String {
Noop => "Noop"
StatesChange(states) =>
"StatesChange(\{states.cursor} \{states.data.to_json()})"
Intent(intent) => "Intent(...)"
// Intent(intent) => "Intent(...)"
Increment => "Increment"
Decrement => "Decrement"
IncTwice => "IncTwice"
Expand Down Expand Up @@ -100,15 +100,3 @@ fn update(self : Store, op : ActionOp) -> Unit {
fn to_string(self : Store) -> String {
self.to_json().stringify(indent=2)
}

fn try_from_string(s : String) -> Store!@respo_node.RespoCommonError {
let j = match @json.parse?(s) {
Ok(j) => j
Err(e) => raise @respo_node.RespoCommonError(e.to_string())
}
let store : Store = match @json.from_json?(j) {
Ok(s) => s
Err(e) => raise @respo_node.RespoCommonError(e.to_string())
}
store
}

0 comments on commit e654313

Please sign in to comment.