1.9 KiB · text History b868907
defmodule GitGudWeb.CiWallLive.Layout do
  @moduledoc """
  How the live CI wall tiles `n` cards across at most `max_cols`
  columns.

  Up to `max_cols` cards, each is a column of its own, full height. Past
  that the columns fill up from the right: the 7th card (of 6 columns)
  pairs the rightmost column, the 8th the next one in, until every
  column holds two; then the rightmost becomes three, and so on. So the
  first cards in order keep the most room, and the columns to their
  right hold one more card than them, or as many.

  Laid out as a CSS grid that flows down its columns: `rows` is a
  multiple of every column's card count (k and k + 1 cards need
  k·(k + 1) rows), and each card spans `rows / cards in its column`.
  """

  @type t :: %{cols: non_neg_integer(), rows: non_neg_integer(), spans: [pos_integer()]}

  @spec tile(non_neg_integer(), pos_integer()) :: t()
  def tile(0, _max_cols), do: %{cols: 0, rows: 0, spans: []}

  def tile(n, max_cols) when is_integer(n) and n > 0 and is_integer(max_cols) and max_cols > 0 do
    cols = min(n, max_cols)
    base = div(n, cols)
    # The rightmost `extra` columns hold one card more than the rest.
    extra = rem(n, cols)
    rows = if extra == 0, do: base, else: base * (base + 1)

    # The leftmost `cols - extra` columns, `base` cards each, come first.
    short = (cols - extra) * base

    spans = for i <- 0..(n - 1), do: if(i < short, do: div(rows, base), else: div(rows, base + 1))

    %{cols: cols, rows: rows, spans: spans}
  end

  @doc "How many cards each column holds, left to right (for the toolbar)."
  @spec column_counts(non_neg_integer(), pos_integer()) :: [pos_integer()]
  def column_counts(0, _max_cols), do: []

  def column_counts(n, max_cols) do
    cols = min(n, max_cols)
    base = div(n, cols)
    extra = rem(n, cols)
    List.duplicate(base, cols - extra) ++ List.duplicate(base + 1, extra)
  end
end