# `MishkaCmsCore.Runtime.SiteIcon`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/site_icon.ex#L5)

A site's icons and its web manifest, each its own, answered on the site's own address
(`MishkaCmsCoreWeb.SeoController`):

  * `/favicon.ico` — the file the `site.favicon` setting names (`favicon/1`);
  * `/apple-touch-icon.png` and `/apple-touch-icon-precomposed.png` — 180×180, the icon a phone
    puts on its home screen;
  * `/site-icon-192.png` and `/site-icon-512.png` — the icons of the manifest;
  * `/site.webmanifest` — what a browser installs the site with (`manifest/1`).

## What the icons are drawn from

The site's `site.app_icon` setting, else its `site.favicon` — the first that names a picture of
the site's own (`MishkaCmsCore.Runtime.ShareCard.site_file/2`): a file of its media or under the
host's static folder that is a PNG, JPEG, WebP, GIF or SVG (`source/1`). An `http(s)://`
address is never fetched, so a site whose pictures are elsewhere has no icons but its favicon
link. Each icon is the picture fitted whole in its square and centered on transparency, never
stretched (`MishkaCmsCore.Runtime.ShareCard.squares/2`).

## When they are drawn

On the first request for one: every size at once, by libvips in a separate process, as a share
card is drawn. A site draws one at a time; the requests that come meanwhile wait for it and send
what it drew. The files are stored at `/uploads/<site_id>/icons/<hash>-<size>.png`, `hash` named
by the picture's bytes, and every later request sends the file. A picture libvips cannot draw is
not tried again until the setting or the file changes. Drawing them fires
`"runtime:site:after_icons"` with `%{site_id:, hash:, files: %{size => path}}`; a site's
older icons are deleted then.

## What a page reads

Which files the favicon and the picture are, and the picture's hash, are kept in ETS
(`MishkaCmsCore.Runtime.States.AssetsState`, under `{site_id, "site_icon"}`) beside the values of
the two settings they were read for. The head of every page reads them from there
(`url/2`, `manifest_url/1`); a setting that holds another value — on any node — has the files
read again. A media file replaced in place is caught by `MishkaCmsCore.Runtime.MediaSiteIcons`,
and saving either setting by `MishkaCmsCore.Runtime.SettingSiteIcons`: both `forget/1` it.

## The manifest

`name` is the site's name in its primary language (`MishkaCmsCore.Runtime.Seo.site_name/2`),
`start_url` is `/`, `display` is `browser` and `icons` are the 192×192 and 512×512 PNGs. Three
more keys are there only for a site that set them, on its "App & home screen" settings, and are
left out of the manifest of one that did not: `short_name` (`short_name/1`, which a browser
replaces with `name` when it is missing), `theme_color` (`theme_color/1`) and `background_color`
(`background_color/1`). A plugin on `"runtime:site:manifest"` is handed
`%{site: site, manifest: manifest}` and returns the map with its `manifest`: another `display`,
a `lang`.

The three settings are read from the settings cache, as every setting is, so a change shows on
the next request. A page's head names the theme color too, as a `theme-color` meta tag
(`MishkaCmsCore.Runtime.Seo`), for a site with no icons as well.

    SiteIcon.url(site.id, 180) #=> "/apple-touch-icon.png?v=4be1f0a3c2d9e8b7"

# `file`

```elixir
@type file() :: %{path: Path.t(), type: String.t()}
```

A file of the site's own: where it is on disk and its type, read off its first bytes.

# `source`

```elixir
@type source() :: %{
  path: Path.t(),
  type: String.t(),
  hash: String.t(),
  failed?: boolean()
}
```

The picture the icons are drawn from, and the hash their names carry.

# `background_color`

```elixir
@spec background_color(String.t() | nil) :: String.t() | nil
```

The color shown behind the icon while the installed app opens: the site's
`site.background_color` setting, `nil` while it is empty or holds anything but a hex color
(`color?/1`).

# `color?`

```elixir
@spec color?(term()) :: boolean()
```

Whether `value` is a hex color a manifest and a `theme-color` meta tag take: `#rgb` or `#rrggbb`,
in either case, and nothing around it.

    color?("#0b0a14") #=> true
    color?("red")     #=> false

# `color_or_empty?`

```elixir
@spec color_or_empty?(term()) :: boolean()
```

`color?/1`, or empty: how `site.theme_color` and `site.background_color` are validated.

# `dir`

```elixir
@spec dir(String.t()) :: String.t()
```

The folder the icons of the site `site_id` are written to.

# `drawn_event`

```elixir
@spec drawn_event() :: String.t()
```

The event fired after a site's icons are drawn: `"runtime:site:after_icons"`.

# `favicon`

```elixir
@spec favicon(String.t() | nil) :: file() | nil
```

The site's favicon as a file of its own: `%{path:, type:}`, `type` one of
`MishkaCmsCore.Runtime.ShareCard.picture_type/1`'s. `nil` for no favicon, and for one that is an
address elsewhere or a file the site does not own.

# `file`

```elixir
@spec file(String.t() | nil, pos_integer()) ::
  {:ok, %{path: Path.t(), hash: String.t()}} | :error
```

The PNG of the site's icon of `size` on disk, drawn now when it is not there yet:
`{:ok, %{path:, hash:}}`, `hash` the one its address carries (`url/2`). `:error` for a site with
no picture to draw it from, and for a picture libvips could not draw.

# `forget`

```elixir
@spec forget(String.t() | nil) :: :ok
```

Drops what is kept of the icons of the site `site_id` — of every site for `nil` — so the next
request reads its settings and files again, and draws its icons again when their picture
changed.

# `forget_file`

```elixir
@spec forget_file(String.t(), String.t()) :: :ok
```

`forget/1` for the site `site_id` when its favicon or the picture of its icons is the media file
`relative_path`, `/uploads/<site_id>/media/…`; anything else is left as it is.

# `manifest`

```elixir
@spec manifest(%{id: String.t()}) :: map() | nil
```

The site's web manifest: `%{"name", "start_url", "display", "icons"}`, and `"short_name"`,
`"theme_color"` and `"background_color"` when the site set them, as a plugin on `manifest_event/0`
leaves it, with string keys, ready to be encoded. `nil` for a site with no icons.

# `manifest_event`

```elixir
@spec manifest_event() :: String.t()
```

The event a plugin changes a site's manifest on: `"runtime:site:manifest"`.

# `manifest_url`

```elixir
@spec manifest_url(String.t() | nil) :: String.t() | nil
```

The site's `/site.webmanifest`, for a site with icons; `nil` for one with none.

# `path`

```elixir
@spec path(pos_integer()) :: String.t()
```

The path the icon of `size` is served at, with no hash.

# `settings`

```elixir
@spec settings() :: [String.t()]
```

The settings the icons are read from: `["site.app_icon", "site.favicon"]`.

# `short_name`

```elixir
@spec short_name(String.t() | nil) :: String.t() | nil
```

The name the site is given under its home-screen icon: its `site.app_short_name` setting, `nil`
while it is empty.

# `short_name?`

```elixir
@spec short_name?(term()) :: boolean()
```

Whether `value` fits `site.app_short_name`: at most `short_name_max/0` characters.

# `short_name_max`

```elixir
@spec short_name_max() :: pos_integer()
```

The longest `site.app_short_name` a site may save, in characters: `30`.

# `sizes`

```elixir
@spec sizes() :: [pos_integer()]
```

Every size an icon is drawn at, in pixels: `[180, 192, 512]`.

# `source`

```elixir
@spec source(String.t() | nil) :: source() | nil
```

The picture the site's icons are drawn from: its app icon, else its favicon, when it is a
drawable file of its own. `nil` when neither is.

# `theme_color`

```elixir
@spec theme_color(String.t() | nil) :: String.t() | nil
```

The color of the browser's toolbar on phones and of the installed app's title bar: the site's
`site.theme_color` setting, `nil` while it is empty or holds anything but a hex color
(`color?/1`).

# `url`

```elixir
@spec url(String.t() | nil, pos_integer()) :: String.t() | nil
```

The address the site's icon of `size` is served at, with the hash of the picture it is drawn
from: `"/apple-touch-icon.png?v=…"` for 180, `"/site-icon-192.png?v=…"`. `nil` for a site with
no picture to draw it from.

---

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