Drafter.Widget.ProgressBar (drafter v0.3.1)

Copy Markdown View Source

Renders a horizontal progress bar with optional percentage, value, and ETA display.

Supports both a determinate mode (showing progress toward a known maximum) and an indeterminate mode that animates a sliding block when the total is unknown.

Component tag

Tag :progress_bar, built by Drafter.App as {:progress_bar, opts}:

progress_bar(opts)

There is no positional argument; every prop comes from opts.

Options

  • :progress - number/0 current progress. Default 0.0. The filled fraction is progress / max_value, clamped into 0.0..1.0.
  • :max_value - number/0 value representing 100%. Default 100.0. A max_value of 0 or less renders as 0%.
  • :show_percentage - boolean/0, append the rounded percentage. Default true.
  • :show_eta - boolean/0, append an estimated time remaining. Default true. Shows "..." until at least one second of wall clock has passed since mount and progress is above zero, then "12s", "3m 4s" or "1h 2m", and "∞" when the computed rate is not positive.
  • :indeterminate - boolean/0. Default false. Animates a sliding block whose position advances by one on each update/2 and ignores :progress, :show_percentage and :show_eta.
  • :label - String.t/0 or nil. Default nil. Held on the state and never drawn.
  • :show_value - boolean/0. Default false. Held on the state and never drawn; the status text is built from :show_percentage and :show_eta only.
  • :width - pos_integer/0. Default 50 when mounting directly, and the width of opts[:__rect__] through the element. Held on the state and never read by render/2, which uses the rect it is given.
  • :height - pos_integer/0. Default 1 when mounting directly, and the height of opts[:__rect__] through the element. Held on the state and never read by render/2.
  • :pulse - read by from_component_opts/2 with default false and dropped by mount/1; the state has no such field.
  • :class - theme class atom or list of them, normalised into :classes by from_component_opts/2 with default [] and dropped by mount/1.

update/2 accepts :progress, :max_value, :label, :show_percentage, :show_value, :show_eta, :width, :height and :indeterminate, and refreshes the animation clock on every call. Through the component tree, update_props_from_mount/3 narrows that to :progress, :max_value, :label, :show_percentage, :show_value, :indeterminate and :classes:show_eta, :width and :height are mount-only.

Usage

progress_bar(progress: 42.0, max_value: 100.0)
progress_bar(progress: 7, max_value: 20, show_percentage: false, show_value: true)
progress_bar(indeterminate: true)

Summary

Functions

Sets :progress from the newest entry of a Drafter.RingBuffer.

The component tag this widget registers under.

Builds the props map for a {:progress_bar, opts} element.

Ignores every event and returns {:noreply, state}. The bar is not focusable.

Builds the widget state from props.

The number of rows the element asks for: 1, or 8 when opts[:orientation] is :vertical.

Draws the bar into rect.

Callback implementation for Drafter.Widget.unmount/1.

Replaces the state fields named in props, keeping the current value for any key that is absent.

Narrows a re-render to the props that may change after mount.

Types

t()

@type t() :: %Drafter.Widget.ProgressBar{
  height: pos_integer(),
  indeterminate: boolean(),
  label: String.t() | nil,
  last_progress: number(),
  last_update_time: integer(),
  max_value: number(),
  progress: number(),
  show_eta: boolean(),
  show_percentage: boolean(),
  show_value: boolean(),
  spin_position: non_neg_integer(),
  start_time: integer(),
  width: pos_integer()
}

Functions

apply_data_buffer(state, buffer, rect)

@spec apply_data_buffer(t(), Drafter.RingBuffer.t(), Drafter.Widget.rect()) :: t()

Sets :progress from the newest entry of a Drafter.RingBuffer.

Returns state unchanged when the buffer is empty. The rect is ignored.

component_tag()

@spec component_tag() :: :progress_bar

The component tag this widget registers under.

iex> Drafter.Widget.ProgressBar.component_tag()
:progress_bar

focused(state)

from_component_opts(args, opts)

@spec from_component_opts(
  term(),
  keyword()
) :: Drafter.Widget.props()

Builds the props map for a {:progress_bar, opts} element.

The positional argument is ignored. :width and :height fall back to the width and height of opts[:__rect__], itself defaulting to %{width: 50, height: 1}. :class is normalised into :classes. The result also carries :pulse and :classes, which mount/1 drops.

iex> props = Drafter.Widget.ProgressBar.from_component_opts(nil, progress: 3, max_value: 6)
iex> {props.progress, props.max_value, props.width, props.height}
{3, 6, 50, 1}

iex> props = Drafter.Widget.ProgressBar.from_component_opts(nil, __rect__: %{width: 80, height: 2})
iex> {props.width, props.height}
{80, 2}

handle_event(event, state)

@spec handle_event(Drafter.Event.t(), t()) :: {:noreply, t()}

Ignores every event and returns {:noreply, state}. The bar is not focusable.

mount(props)

@spec mount(Drafter.Widget.props()) :: t()

Builds the widget state from props.

Every option listed in the module doc is read here with the default stated there. :start_time and :last_update_time are set to the current monotonic millisecond, which is what the ETA is measured against, and :spin_position starts at 0.

iex> state = Drafter.Widget.ProgressBar.mount(%{})
iex> {state.progress, state.max_value, state.indeterminate, state.spin_position}
{0.0, 100.0, false, 0}

iex> state = Drafter.Widget.ProgressBar.mount(%{progress: 7, max_value: 20})
iex> {state.progress, state.last_progress, state.show_percentage, state.show_eta}
{7, 7, true, true}

preferred_height(args, opts)

@spec preferred_height(
  term(),
  keyword()
) :: pos_integer()

The number of rows the element asks for: 1, or 8 when opts[:orientation] is :vertical.

:orientation is not otherwise an option of this widget — render/2 always draws horizontally — so a progress bar built from the component tree asks for one row.

iex> Drafter.Widget.ProgressBar.preferred_height(nil, [])
1

iex> Drafter.Widget.ProgressBar.preferred_height(nil, orientation: :vertical)
8

render(state, rect)

Draws the bar into rect.

state may be a plain props map, in which case it is passed through mount/1 first. The bar always fills rect.width; the status text is drawn at the right and the track takes what is left. Returns rect.height strips, the first the bar and the rest blank.

unmount(state)

Callback implementation for Drafter.Widget.unmount/1.

update(props, state)

@spec update(Drafter.Widget.props(), t()) :: t()

Replaces the state fields named in props, keeping the current value for any key that is absent.

Also refreshes :last_update_time and :last_progress, and advances :spin_position by one modulo 40 while the bar is indeterminate.

iex> state = Drafter.Widget.ProgressBar.mount(%{indeterminate: true})
iex> Drafter.Widget.ProgressBar.update(%{}, state).spin_position
1

iex> state = Drafter.Widget.ProgressBar.mount(%{progress: 1})
iex> updated = Drafter.Widget.ProgressBar.update(%{progress: 40}, state)
iex> {updated.progress, updated.last_progress, updated.spin_position}
{40, 40, 0}

update_props_from_mount(mount_props, existing_state, opts)

@spec update_props_from_mount(Drafter.Widget.props(), term(), keyword()) ::
  Drafter.Widget.props()

Narrows a re-render to the props that may change after mount.

Returns :progress, :max_value, :label, :show_percentage, :show_value, :indeterminate and :classes. :show_eta, :width and :height are dropped, so they are mount-only through the component tree.

iex> props = Drafter.Widget.ProgressBar.from_component_opts(nil, progress: 3)
iex> Drafter.Widget.ProgressBar.update_props_from_mount(props, %{}, []) |> Map.keys() |> Enum.sort()
[:classes, :indeterminate, :label, :max_value, :progress, :show_percentage, :show_value]