diff --git a/README.md b/README.md index 8b1b2e0..4287a6d 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ pub fn main() { let assert Ok(main) = tardis.single("main") lustre.application(init, update, view) - |> tardis.wrap(main) + |> tardis.wrap(with: main) |> lustre.start("#app", Nil) - |> tardis.activate(main) + |> tardis.activate(with: main) } fn init(_) { @@ -61,9 +61,9 @@ You're good to go! ## Multiple apps setup -While it's easy to setup a single application with tardis, it can also be used to debug multiple applications in the same page. Tardis exposes two additional functions: [`setup`](https://hexdocs.pm/tardis/tardis.html#setup) and [`application`](https://hexdocs.pm/tardis/tardis.html#application). The first one initialize the instance of the debugger, while the second one allows to setup an application on the debugger! +While it's easy to setup a single application with tardis, it can also be used to debug multiple applications in the same page. Tardis exposes two additional functions: [`setup`](https://hexdocs.pm/tardis/tardis.html#setup) and [`application`](https://hexdocs.pm/tardis/tardis.html#application). The first one initialize the debugger, while the second one allows to setup an application on the debugger! -In case you're developping a independant package, you can even send the debugger instance directly to your application, and it will nicely integrate in it! +In case you're developping a independant package, you can even send the tardis or the debugger instance directly to your application, and it will nicely integrate in it! ```gleam import gleam/int @@ -78,14 +78,14 @@ pub fn main() { let mod = tardis.application(instance, "module") lustre.application(init_1, update_1, view_1) - |> tardis.wrap(main) + |> tardis.wrap(with: main) |> lustre.start("#app", Nil) - |> tardis.activate(main) + |> tardis.activate(with: main) lustre.application(init_2, update_2, view_2) - |> tardis.wrap(mod) + |> tardis.wrap(with: mod) |> lustre.start("#mod", Nil) - |> tardis.activate(mod) + |> tardis.activate(with: mod) } ``` diff --git a/src/tardis.gleam b/src/tardis.gleam index aef3228..880bbe7 100644 --- a/src/tardis.gleam +++ b/src/tardis.gleam @@ -1,3 +1,43 @@ +//// Tardis is in charge of being your good debugger friend, able to navigate +//// accross time and space! Just instanciate it, and use it in your application. +//// +//// ```gleam +//// import gleam/int +//// import lustre +//// import lustre/element/html +//// import lustre/event +//// import tardis +//// +//// pub fn main() { +//// let assert Ok(main) = tardis.single("main") +//// +//// lustre.application(init, update, view) +//// |> tardis.wrap(with: main) +//// |> lustre.start("#app", Nil) +//// |> tardis.activate(with: main) +//// } +//// +//// fn init(_) { +//// 0 +//// } +//// +//// fn update(model, msg) { +//// case msg { +//// Incr -> model + 1 +//// Decr -> model - 1 +//// } +//// } +//// +//// fn view(model) { +//// let count = int.to_string(model) +//// html.div([], [ +//// html.button([event.on_click(Incr)], [html.text(" + ")]), +//// html.p([], [html.text(count)]), +//// html.button([event.on_click(Decr)], [html.text(" - ")]) +//// ]) +//// } +//// ``` + import gleam/dynamic.{type Dynamic} import gleam/function import gleam/int @@ -23,16 +63,32 @@ import tardis/internals/setup.{type Middleware} import tardis/internals/styles as s import tardis/internals/view as v -pub opaque type Instance { - Instance(dispatch: fn(Action(Msg, lustre.ClientSpa)) -> Nil) +/// Represents the running Tardis. Should be used with lustre applications +/// only. One tardis is enough for multiple lustre applications running on the +/// same page, with their own update loop. +pub opaque type Tardis { + Tardis(dispatch: fn(Action(Msg, lustre.ClientSpa)) -> Nil) } -pub opaque type Tardis { - Tardis(#(fn(Dynamic) -> Nil, Middleware)) +/// Represents the instance for an application. It should be used within one and +/// only one application. You can get the instance by using [`application`](#application) +/// or [`single`](#single). +pub opaque type Instance { + Instance(#(fn(Dynamic) -> Nil, Middleware)) } -pub fn wrap(application: App(a, b, c), tardis: Tardis) { - let Tardis(#(_, middleware)) = tardis +/// Wrap a lustre application with the debugger. The debugger will never interfere +/// with your application by itself. You can directly chain your application with +/// this function. +/// ```gleam +/// fn main() { +/// let assert Ok(instance) = tardis.single("main") +/// lustre.application(init, update, view) +/// |> tardis.wrap(with: instance) +/// } +/// ``` +pub fn wrap(application: App(a, b, c), with instance: Instance) { + let Instance(#(_, middleware)) = instance setup.update_lustre( application, setup.wrap_init(middleware), @@ -40,9 +96,24 @@ pub fn wrap(application: App(a, b, c), tardis: Tardis) { ) } -pub fn activate(result: Result(fn(Action(a, b)) -> Nil, c), tardis: Tardis) { +/// Activate the debugger of the application. `activate` should be run to let +/// the debugger being able to rewind time. It should be executed on the exact +/// same instance that has been wrapped. +/// ```gleam +/// fn main() { +/// let assert Ok(instance) = tardis.single("main") +/// lustre.application(init, update, view) +/// |> tardis.wrap(with: instance) +/// |> lustre.start() +/// |> tardis.activate(with: instance) +/// } +/// ``` +pub fn activate( + result: Result(fn(Action(a, b)) -> Nil, c), + with instance: Instance, +) { use dispatch <- result.map(result) - let Tardis(#(dispatcher, _)) = tardis + let Instance(#(dispatcher, _)) = instance dispatcher(dynamic.from(dispatch)) dispatch } @@ -62,25 +133,31 @@ fn start_sketch(root) { error.SketchError(error) } +/// Creates the tardis. Should be run once, at the start of the application. +/// It can be skipped when using [`single`](#single). pub fn setup() { let #(shadow_root, lustre_root) = setup.mount_shadow_node() start_sketch(shadow_root) |> result.map(sketch.compose(view, _)) |> result.map(lustre.application(init, update, _)) |> result.try(start_lustre(lustre_root, _)) - |> result.map(fn(dispatch) { Instance(dispatch) }) + |> result.map(fn(dispatch) { Tardis(dispatch) }) } +/// Directly creates a tardis instance for a single application. +/// Replaces `setup` and `application` in a single application context. pub fn single(name: String) { setup() |> result.map(application(_, name)) } -pub fn application(instance: Instance, name: String) { +/// Creates the application debugger from the tardis. Should be run once, +/// at the start of the application. It can be skipped when using [`single`](#single). +pub fn application(instance: Tardis, name: String) -> Instance { let dispatch = instance.dispatch let updater = setup.create_model_updater(dispatch, name) let adder = setup.step_adder(dispatch, name) - Tardis(#(updater, adder)) + Instance(#(updater, adder)) } fn init(_) { diff --git a/src/tardis/error.gleam b/src/tardis/error.gleam index a4f5b4b..074e6fd 100644 --- a/src/tardis/error.gleam +++ b/src/tardis/error.gleam @@ -1,3 +1,6 @@ +//// Manages error for Tardis. +//// Used only during initialization, to identify what is failing. + import lustre import sketch/error as sketch