kino icon indicating copy to clipboard operation
kino copied to clipboard

[Feature Request] Stream of custom events

Open arathunku opened this issue 1 year ago • 16 comments

:wave: Hi!

I believe this would be a feature request, and I'm unclear whether issue tracker is the correct place to raise this?

Context

Let's say we've 2-3 heavy DB queries and I want to create a report that's available as app. Ideally, automatically refetch the data at some interval or when user clicks a button.

In edit mode, I can select most of the cells as "evaluate automatically" and it's works nicely, but it doesn't work for apps.

Actual solution as workaround

At this moment, I get around this by creating "UI" module that's appending data info in a single frame, but this mode of writing a notebook that also works as an app leaves a lot to be desired. This is also very difficult to explain to people that are just starting with Elixir and want to publish their notebook as an app.

Example:

# one cell, it might also be a more complex form, other type of user input

load_btn = Kino.Control.button("Load data") |> Kino.render()

# another cell
frame = Kino.Frame.new() |> Kino.render()

defmodule UI do
  def show(frame) do
    # very heavy db query 
    results = [1, 2, 3]
    Kino.Frame.render(
      frame, Kino.Markdown.new("#{Enum.join(results, ", ")} #{DateTime.utc_now()}")
    )
  end
end

load_btn
|> Kino.listen(fn _event -> 
  UI.show(frame)
end)
UI.show(frame)
Kino.nothing()

## another cell !

# another cell, wants to access "results" too!
# another cell, it must be reloaded "load_btn" is clicked!

defmodule AnotherUI do
  def show(frame, results) do
    Kino.Frame.render(
      frame, Kino.Markdown.new("Different view #{Enum.join(results, ", ")} #{DateTime.utc_now()}")
    )
  end
end

another_frame = Kino.Frame.new() |> Kino.render()

load_btn
|> Kino.listen(fn _event ->
  # no way to get "results"?
  AnotherUI.show(another_frame, [4, 5, 6])
end)
AnotherUI.show(another_frame, [4, 5, 6])
Kino.nothing()

Expected solution

Kino already has API for streams via Kino.Control.stream/1. I would love to simplify the code above to something like:

load_btn = Kino.Control.button("Load data") |> Kino.render()
event = Kino.Control.event(:results)

load_btn 
|> Kino.listen(fn _event -> 
  # very heavy db query 
  results = [1, 2, 3]
  Kino.send(event, data: [results: results]) 
end)

# another cell

event
# or  [event, ...other events] |> Kino.Control.stream()
|> Kino.animate(fn event -> 
  Kino.Markdown.new("Some view #{Enum.join(event.data.results, ", ")} #{DateTime.utc_now()}")
end)

## another cell !

event
|> Kino.animate(fn event -> 
  Kino.Markdown.new("Different view #{Enum.join(event.data.results, ", ")} #{DateTime.utc_now()}")
end)

It's also very likely that I'm doing something wrong and this is already possible but given the validation here I don't think it's easy to do nor obvious from the documentation.

arathunku avatar Sep 16 '24 12:09 arathunku