# `Storehouse.Responsive`
[🔗](https://github.com/hattoriai/storehouse/blob/v0.2.0/lib/storehouse/responsive.ex#L1)

What a page needs to show a photograph at every screen's size, from a
preset of variants with a ladder of widths (`Storehouse.Variants`): its
`src`, its `srcset`, its size, its placeholder. As data, for a JSON API,
or as an `<img>` (`Storehouse.Components.img/1`).

    Storehouse.Responsive.resolve(file, :photo, sizes: "(min-width: 60rem) 60rem, 100vw")
    #=> %Storehouse.Responsive{
    #     src: "/variants/…/photo-2560-e04b77d1",
    #     srcset: "/variants/…/photo-640-… 640w, …, /variants/…/photo-2560-… 2400w",
    #     sizes: "(min-width: 60rem) 60rem, 100vw",
    #     width: 2400, height: 1600,
    #     placeholder: "data:image/webp;base64,…", color: "#9a7b76", …}

The `srcset` has the ladder's widths below the original's, then the
original's own (up to the widest step): never a size larger than the
photograph. Each is asked for at its step of the ladder, or the next one
up, so every photograph's sizes are the ladder's, and a CDN keeps few
of them; a step past the original's width gives the original's. Its
`width` and `height` are those of the largest, for the page to keep the
room before the image arrives.

It reads no store: the original's width and height are the file's
(`Storehouse.store/2` keeps them), its placeholder and colour its
analysis's (`Storehouse.Analyzer.Placeholder`, `ThumbHash`, `BlurHash`).
It encodes as JSON (`JSON.Encoder`, `Jason.Encoder`).

## Warming

A variant is made the first time it is asked for, and a large original
takes seconds: `warm/3` asks for each size once, before the first
visitor does, and `warm_later/3` does it out of the request, on the app's
runner. Off unless the app turns it on, in production:

    config :storehouse, Storehouse.Responsive, warm: true

# `t`

```elixir
@type t() :: %Storehouse.Responsive{
  blurhash: String.t() | nil,
  color: String.t() | nil,
  height: pos_integer() | nil,
  placeholder: String.t() | nil,
  sizes: String.t() | nil,
  src: String.t(),
  srcset: String.t(),
  thumbhash: String.t() | nil,
  width: pos_integer() | nil
}
```

# `enabled?`

```elixir
@spec enabled?(keyword()) :: boolean()
```

Whether warming is on: `:enabled`, else the configured `:warm`, `false`
by default, so that development and tests ask no image service.

# `resolve`

```elixir
@spec resolve(Storehouse.File.t(), atom(), keyword()) :: t()
```

The `src`, `srcset`, size and placeholder of `file` with `preset`.

## Options

  * `:src` - the width of `src`, for a browser that takes no `srcset`
    (the step of the ladder as wide, or the next one up). The default is
    the largest.
  * `:sizes` - the `sizes` attribute, as given: what the layout's slot
    for the photograph is, at each screen's width. See the Variants
    guide.
  * `Storehouse.Variants.url/3`'s, for each URL.

# `urls`

```elixir
@spec urls(Storehouse.File.t(), atom(), keyword()) :: [String.t()]
```

Every URL `resolve/3` gives for `file` with `preset`, once each: the
sizes to warm.

## Options

  * `:up_to` - the widest step: larger ones are left to be made when
    asked for (a lightbox's, zoomed in). The default is every one.
  * `:src`, and `Storehouse.Variants.url/3`'s, as for `resolve/3`.

# `warm`

```elixir
@spec warm(Storehouse.File.t(), atom() | [atom()], keyword()) ::
  {:ok, non_neg_integer()} | {:error, {:not_warmed, [{term(), term()}]}}
```

Makes `file`'s sizes with each of `presets` before a visitor asks for
them: `{:ok, count}`, or `{:error, {:not_warmed, failed}}` with what was
not made and why.

With a renderer that renders in the app (`Storehouse.Renderer.Vix`),
each is made with `Storehouse.variant/3`, in the format a browser of
today is given. With one of URLs (`Storehouse.Renderer.Imgproxy`), each
URL is asked for, as a browser of today asks: the service renders it and
the CDN in front keeps it. One at a time, so the app's visitors never
wait behind a batch.

It warms when it is called, turned on or not.

## Options

  * `:up_to` and `:src` - as for `urls/2`.
  * `:through` - `:render` or `:http`. The default is the renderer's:
    `:http` asks a variant's URL, through the plug or a CDN, for a
    renderer in the app that is behind one.
  * `:accept` - the `Accept` header. The default is a browser's that
    takes AVIF and WebP.
  * `:base_url` - put before a URL that is a path, to ask it over HTTP:
    `"https://example.com"`.
  * `:timeout` - for each, in milliseconds. The default is 90 seconds.
  * `:adapter`, and `Storehouse.Variants`'s configuration.

# `warm_later`

```elixir
@spec warm_later(Storehouse.File.t(), atom() | [atom()], keyword()) ::
  :ok | {:error, term()}
```

`warm/3` out of the request, as a job of the app's runner, when warming
is on (`enabled?/1`), and nothing when it is off. The original is not
read: the file's width is all it needs. A file's job waiting is not
queued again.

On a queue of its own, so that the app's other work never waits behind
it, with Oban:

    Storehouse.Responsive.warm_later(file, [:photo],
      up_to: 3840,
      runner: {Storehouse.Runner.Oban, queue: :warm}
    )

## Options

  * `:runner` - the runner, in place of the app's (`Storehouse.Runner`).
  * `:enabled` - in place of the configured `:warm`.
  * `warm/3`'s.

# `widths`

```elixir
@spec widths(Storehouse.File.t(), atom(), keyword()) :: [
  {pos_integer(), pos_integer()}
]
```

The widths of `file`'s `srcset` with `preset`, each with the step of
the ladder it is asked for at: `[{width, step}]`, narrowest first.

    # A photograph 1500 wide, a ladder of 640, 1280 and 1920:
    Storehouse.Responsive.widths(file, :photo)
    #=> [{640, 640}, {1280, 1280}, {1500, 1920}]

For a file whose width is not known, the ladder's.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
