livebook-dev / kino

Client-driven interactive widgets for Livebook
Apache License 2.0
372 stars 65 forks source link

Support specifying column ratios in `Kino.Layout.grid` #459

Closed elepedus closed 3 months ago

elepedus commented 4 months ago

This PR allows the behaviour of the grid to be controlled using grid-template-columns

Example usage

Screenshot 2024-07-23 at 13 12 37

Requires Livebook PR #2718

jonatanklosko commented 4 months ago

Hey @elepedus, is there a use case you run into, and if so, which grid feature do you want to use (fixed values, fractions)?

Ideally I would like to avoid tying options so directly to the grid implementation details, so I'm trying to get more context :)

elepedus commented 4 months ago

Hi @jonatanklosko, I'd like to use it almost exactly as in the first example, to build a tug-of-war game. Eg:

defmodule Game do
  use GenServer
  def start_link(_), do: GenServer.start_link(__MODULE__, [])
  def init(_), do: {:ok, self(), {:continue, :setup}}
  def handle_continue(_,_) do
    state = %{
      bar: KinoProgressBar.new(value: 50, max: 100, style: "width: 100%; height: 100%"),
      value: 50
    }

    [&(&1 - 1), state.bar, &(&1 + 1)]
    |> Enum.map(fn 
      op when is_function(op) ->
          Kino.Control.button("Pull")
          |> tap(&Kino.Control.subscribe(&1,op))
      t -> t
    end)
    |> Kino.Layout.grid(columns: 3, template: "1fr 20fr 1fr")
    |> Kino.render()
    {:noreply, state}
  end
end
Kino.start_child!(Game)
Screenshot 2024-07-25 at 09 55 08

Without the ability to influence the width of the grid columns, the best I can do is a lot less compelling: Screenshot 2024-07-25 at 09 56 39

Beyond that, I am (ab-)using Livebook as a rapid prototyping platform, and being able to control the grid template would give me more power to build e.g navbars etc

elepedus commented 4 months ago

P.S.: here's a working example 🙂

https://elepedus-livebook-pr-2718.hf.space/apps/lb-tow

jonatanklosko commented 4 months ago

Sorry for the late reply.

One more reason why I want to avoid direct CSS is to prevent from CSS injection. As opposed to other custom elements, the grid is not in an iframe, so if we simply interpolate the CSS someone could make the grid take the whole screen and whatnot. It is not necessarily critical, but I would prefer to not worry about those cases.

Instead of the template, I think we could support columns: {1, 10, 1}, meaning 3 columns in these proportions. Would that work for your case?

elepedus commented 4 months ago

No worries, yeah, the ability to just pass ratios would be fine. Presumably we can interpolate the integer values into the grid-template-columns directive, which will achieve the same thing without opening up arbitrary CSS injection.

We would need to make sure we're not overly strict in validation, since the numbers don't strictly have to add up to a 12-column grid. E.g: in my example, I use 1 - 20 - 1, to make sure the end columns are as tight as possible around the buttons, and the middle column expands to fill all remaining space.

jonatanklosko commented 4 months ago

We would need to make sure we're not overly strict in validation

Oh, I meant proportions, not n-based grid, so no need for validations. In Livebook we can do this:

cond do
  is_integer(columns) ->
    "repeat(#{columns}, minmax(0, 1fr))"

  is_tuple(columns) and Enum.all?(columns, &is_integer/1) ->
    columns
    |> Tuple.to_list()
    |> Enum.map_join(", ", fn n -> "minmax(0, #{n}fr)" end)

  true ->
    ""
end

If that works for you, feel free to update both PRs in this direction :)

elepedus commented 3 months ago

Ahh.. I had misunderstood your initial suggestion. It's actually much more elegant than what I had in mind! Thanks for taking the time to help me out with this!

I've updated both PRs accordingly! :)