Livebook: How do folks display progress?

It’s easy to create a Kino.frame and render some status text like “Loading” or “Idle”. Ideally I’d like to disable the submit button in my form and display a spinner inside it.

I’m wondering what elixirforum friendly folks do in their Livebooks! :slight_smile:

Hey, looks like you’re really lucky: Elixir Blog Posts - #1129 by lccezinha

I’m not sure - that blog post was written for Liveview. Is it applicable to Livebook? :thinking:

Oh, you’re right… I think you could do something similar with Kino.HTML?

Yeah, I think Kino.HTML might be it. I could create a reusable spinner component, then render it into a Frame whenever there is some async task.

There is a little bit of prior art in the form of hex.pm/packages/kino_progress_bar.

Even if it doesn’t meet your needs, it may serve as inspiration for a new solution.

You can build your own spinner UI component based on Kino.HTML like that:

defmodule KinoSpinner do
  def new(dimensions \\ "30px") do
    Kino.HTML.new("""
    <div class="loader"></div>

    <style>
      .loader {
        border: 16px solid #f3f3f3; /* Light grey */
        border-top: 16px solid #3498db; /* Blue */
        border-radius: 50%;
        width: #{dimensions};
        height: #{dimensions};
        animation: spin 2s linear infinite;
      }

      @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
      }
    </style>
    """)
  end
end

And use like this in a form:

form =
  Kino.Control.form(
    [
      name: Kino.Input.text("Data", default: "some data to process")
    ],
    submit: "Submit"
  )

output_frame = frame()

Kino.listen(form, fn _event ->
  Kino.Frame.render(output_frame, grid([text("Processing..."), KinoSpinner.new()]))
  Process.sleep(2_000)
  Kino.Frame.render(output_frame, "Processing is done. ✅")
end)

grid([form, output_frame])

Here’s a quick video demo: CleanShot 2024-09-18 at 13.47.38 · CleanShot Cloud

Or you can use a custom kino from the community, like this one from Andrés Alejos:

form =
  Kino.Control.form(
    [
      name: Kino.Input.text("Data", default: "some data to process")
    ],
    submit: "Submit"
  )

output_frame = frame()

Kino.listen(form, fn _event ->
  progress_bar = KinoProgressBar.new(max: 100)
  Kino.Frame.render(output_frame, progress_bar)

  Enum.each(1..100, fn index ->
    KinoProgressBar.update(progress_bar, index, 100)
    Process.sleep(50)
  end)

  Kino.Frame.render(output_frame, "Processing is done. ✅")
end)

grid([form, output_frame])

Here’s a quick video demo: CleanShot 2024-09-18 at 13.49.15 · CleanShot Cloud

Here’s the notebook with both demos: How to display progress using Kino · GitHub

4 Likes

Wow, thanks for the high effort post Hugo!

1 Like

~btw I think you meant to write Kino.Frame.new() instead of frame()? Where does frame() come from?~

Nevermind, import Kino.Shorts is the answer.

1 Like

It seems that grid() breaks the <enter> key behavior in the form’s text input. Where should I report that?

You can report on Issues · livebook-dev/livebook · GitHub

1 Like