From 7a145f639ddbc18ba2f4810eeed2ea6e08edb29a Mon Sep 17 00:00:00 2001 From: Hayleigh Thompson Date: Tue, 16 Apr 2024 22:19:43 +0100 Subject: :sparkles: Add a new 'ForceModel' debug action to experiment with time-travel debugging. --- src/client-runtime.ffi.mjs | 32 +++++++++++++++++++++++++++++++- src/lustre/internals/runtime.gleam | 18 +++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/client-runtime.ffi.mjs b/src/client-runtime.ffi.mjs index 51c2eef..ecca48f 100644 --- a/src/client-runtime.ffi.mjs +++ b/src/client-runtime.ffi.mjs @@ -1,5 +1,10 @@ import { ElementNotFound, NotABrowser } from "./lustre.mjs"; -import { Dispatch, Shutdown } from "./lustre/internals/runtime.mjs"; +import { + Dispatch, + Shutdown, + Debug, + ForceModel, +} from "./lustre/internals/runtime.mjs"; import { morph } from "./vdom.ffi.mjs"; import { Ok, Error, isEqual } from "./gleam.mjs"; @@ -58,6 +63,11 @@ export class LustreClientApplication { return; } + case action instanceof Debug: { + this.#debug(action[0]); + return; + } + default: return; } @@ -114,6 +124,26 @@ export class LustreClientApplication { } } + #debug(action) { + switch (true) { + case action instanceof ForceModel: { + const vdom = this.#view(this.#model); + const dispatch = (handler) => (e) => { + const result = handler(e); + + if (result instanceof Ok) { + this.send(new Dispatch(result[0])); + } + }; + + this.#queue = []; + this.#effects = []; + this.#didUpdate = false; + this.#root = morph(this.#root, vdom, dispatch, this.#isComponent); + } + } + } + #shutdown() { this.#root.remove(); this.#root = null; diff --git a/src/lustre/internals/runtime.gleam b/src/lustre/internals/runtime.gleam index a2b5365..b2374b4 100644 --- a/src/lustre/internals/runtime.gleam +++ b/src/lustre/internals/runtime.gleam @@ -3,8 +3,8 @@ import gleam/dict.{type Dict} import gleam/dynamic.{type Decoder, type Dynamic} import gleam/erlang/process.{type Selector, type Subject} -import gleam/list import gleam/json.{type Json} +import gleam/list import gleam/option.{Some} import gleam/otp/actor.{type Next, type StartError, Spec} import gleam/result @@ -46,6 +46,7 @@ pub type Action(msg, runtime) { } pub type DebugAction { + ForceModel(Dynamic) Model(reply: fn(Dynamic) -> Nil) View(reply: fn(Element(Dynamic)) -> Nil) } @@ -133,6 +134,21 @@ fn loop( loop(Batch(rest, effect.batch([effects, other_effects])), next) } + Debug(ForceModel(model)) -> { + let model = dynamic.unsafe_coerce(model) + let html = state.view(model) + let diff = patch.elements(state.html, html) + let next = + State(..state, model: model, html: html, handlers: diff.handlers) + + case patch.is_empty_element_diff(diff) { + True -> Nil + False -> run_renderers(state.renderers, Diff(diff)) + } + + actor.continue(next) + } + Debug(Model(reply)) -> { reply(dynamic.from(state.model)) actor.continue(state) -- cgit v1.2.3