#lustre, message chaining?

1 messages · Page 1 of 1 (latest)

celest topaz
#

In lustre, I understand that a msg hits the update function which then updates the model if necessary and then the view function is recalled with the updated state.... I want a particular message to update state and then after the view is rendered I want a secondary message triggered which will update the view again, how could I achieve this, would this be with an effect?

ionic depot
#

What is your use case exactly? Why do you need to launch another message after the first one? 🙂 Is it for animation purpose?

celest topaz
#

Yes, I want to add some classes then wait 1 frame and switch the classes

#

When the state of a menu item is toggled I will set entering to true, remove display:none, and add the necessary opacity-0 transition duration etc... then one frame later remove opacity-0 and add opacity-100

ionic depot
#

Yup, that was my intuition. I’d build my own Effect using effect.from, and dispatching the message you’re interested in. I’d wrap the dispatching in a setTimeout though, to make sure 100ms pass before repainting. 🙂

celest topaz
#

I don't think that is necessary, I'm considering copying the way alpine js does it it does it on th3 very next frame

main barn
#

I want a particular message to update state and then after the view is rendered I want a secondary message triggered which will update the view again
dispatch a message after an animation frame

ionic depot
celest topaz
#

I'll try when I get home

main barn
celest topaz
#

I still need a little help on this unfortunately. Do i need that plinth package, or is there any example on how i would hook into that requestAnimationFrame() using lustre, it's on the window right, not on the element.

celest topaz
#

ok, so i can ffi into javascript to call window.requestAnimationFrame() but when do i call that, in my update function when my transition should start... then the thing i want to do in the request animation frame is to fire a message with effect.from?

celest topaz
#

was thinking something like this:

pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
  case msg {
    UserClickedProducts -> {
      window.request_animation_frame(start_transition)
      #(Model(..model, is_entering: True), effect.none())
    }
    TransitionStarted -> #(Model(..model, entered: True), effect.none())
  }
}

fn start_transition(_timestamp: Float) {
  effect.from(fn(dispatch) {
    TransitionStarted
    |> dispatch
  })
  Nil
}
main barn
#

That would do nothing because you’re not returning the effect

#

But yes right track

celest topaz
#

Where would I return the effect from

main barn
#

What is the return type of your update function ^.^

celest topaz
#

#(Model, Effect(Msg))

#

Don't I want the effect to happen in the requestAnimationFrame callback

main barn
#

Did you read the guide in side effects?

#

(Im not being obtuse)

celest topaz
#

A couple times... I'll go read it again, sorry if I'm coming off really dumb here, it's completely new at of doing things, and something is not clicking for me

main barn
#

No all good just good to know what youve read or not

#

So in lustre, side effects (eg things that affect the outside world) dont happen in your lustre program

#

instead, we essentially construct a description of a side effect for the runtime to perform, and then return those side effects from our update function

#
pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
  case msg {
    UserClickedProducts -> #(Model(..model, is_entering: True), start_transition())
    TransitionStarted -> #(Model(..model, entered: True), effect.none())
  }
}

fn start_transition() {
  use dispatch <- effect.from
  use _timestamp <- window.request_animation_frame

  dispatch(TransitionStarted
}

you would write that like this

#

"construct an effect that after one animation frame dispatches a TransitionStarted message back to our program"

celest topaz
#

I was gonna say ah OK I got it, but that vastly overestimates my understanding on the topic. I'll try this approach... I think I just need to get some repitition in

main barn
#

Its quite a different mental model

glacial mist
#

@celest topaz I might look for some of richard feldman's talks on elm. Lustre has some differences from Elm but they follow a similar architecture.

It helped me clarify some of the concepts around model-view-update

celest topaz
#

So I added some print statements to see when stuff happens, i click the button, and the first message gets set i see "User clicked products" in the console, but the print statements in start_transition never fire:

pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
  case msg {
    UserClickedProducts -> {
      io.println("User clicked products")
      #(Model(..model, is_entering: True), start_transition())
    }
    TransitionStarted -> {
      io.println("Transition started")
      #(Model(..model, entered: True), effect.none())
    }
  }
}

fn start_transition() -> Effect(Msg) {
  io.println("start_transition()")
  use dispatch <- effect.from
  use _ts <- window.request_animation_frame
  io.println("Request animation_frame")
  dispatch(TransitionStarted)
}
celest topaz
#

I got this working now, I'm just working on refactoring to make a better api for adding transitions on everything

celest topaz
#

This is what I've landed on so far for typing of transitions and a helper function to get the classes:

pub type Transition {
  Transition(
    direction: TransitionDirection,
    complete: Bool,
    transitioning: Bool,
    enter_classes: TransitionClasses,
    leave_classes: TransitionClasses,
  )
}

pub type TransitionDirection {
  Entering
  Leaving
}

pub type TransitionClasses {
  TransitionClasses(all: String, from: String, to: String)
}

pub fn get_classes(transition: Transition) {
  case transition {
    Transition(direction: Entering, ..) ->
      case transition.complete {
        False ->
          transition.enter_classes.all <> " " <> transition.enter_classes.from
        True ->
          transition.enter_classes.all <> " " <> transition.enter_classes.to
      }
    Transition(direction: Leaving, ..) ->
      case transition.complete {
        False ->
          transition.leave_classes.all <> " " <> transition.leave_classes.from
        True ->
          transition.leave_classes.all <> " " <> transition.leave_classes.to
      }
  }
}

Then i apply the classes to my component like this:

pub fn product_flyout(transition: types.Transition) {
  let classes = types.get_classes(transition)
  div(
    [
      class(classes),
      class(
        "absolute -left-8 top-full z-10 mt-3 w-screen max-w-md overflow-hidden rounded-3xl bg-white shadow-lg ring-1 ring-gray-900/5",
      ),
    ],
    [div([class("p-4")], [analytics_menu_item()]), flyout_footer()],
  )
}