# `PhoenixDevToolbar.Panel`
[🔗](https://github.com/zoten/phoenix_dev_toolbar/blob/0.0.2/lib/phoenix_dev_toolbar/panel.ex#L1)

> #### Experimental {: .warning}
>
> This API may change in any 0.0.x release. Changes are listed in the CHANGELOG.

A plugin's tab in the toolbar panel: a `Phoenix.LiveComponent` ([ADR 05](https://github.com/zoten/phoenix_dev_toolbar/blob/main/adr/05-plugin-behaviour.md)).

A plugin returns its panel module from `c:PhoenixDevToolbar.Plugin.panel/0`.
The toolbar renders it inside its own iframe, never in the host page, and
hands it the plugin's events as they arrive:

    defmodule MyApp.Toolbar.JobsPanel do
      use PhoenixDevToolbar.Panel

      # Events have no :id field: give streams a DOM id from the unique seq.
      @impl Phoenix.LiveComponent
      def mount(socket), do: {:ok, stream_configure(socket, :jobs, dom_id: &"jobs-#{&1.seq}")}

      @impl PhoenixDevToolbar.Panel
      def handle_reset(events, socket),
        do: stream(socket, :jobs, events, reset: true, limit: -500)

      @impl PhoenixDevToolbar.Panel
      def handle_events(events, socket),
        do: Enum.reduce(events, socket, &stream_insert(&2, :jobs, &1, limit: -500))

      @impl Phoenix.LiveComponent
      def render(assigns) do
        ~H"""
        <ul id="jobs" class="pdt-list" phx-update="stream">
          <li id="jobs-empty" class="pdt-list__empty">No jobs yet.</li>
          <li :for={{id, job} <- @streams.jobs} id={id} class="pdt-row">{job.data.worker}</li>
        </ul>
        """
      end
    end

Events are the plugin's own, oldest first. Keep them in streams, never in
plain assigns: a page can produce thousands. `handle_reset/2` gets every
stored event of a new page (the panel opened, live navigation), and runs
once with `[]` before the first render; `handle_events/2` gets the new ones
since. DOM ids must be unique in the whole panel: prefix them with your
plugin's name and use the event's `seq`.

The toolbar assigns, before calling either callback:

  * `@config`: the plugin's config, as returned by its `init/1`
  * `@page`: the page shown, a struct whose module is internal. Rely only on
    `@page.root` (the sid of its HTTP request) and `@page.sids` (the sids of
    the sessions that make up the page: the request, then its LiveView once
    connected; empty when the page's events have expired). Its other fields
    (`transport`, `path`, `owner`) are internal and may change
  * `@shown?`: the tab is selected
  * `@visible?`: the panel is open and the browser tab visible

`c:handle_props/1` runs when only those change, for example to start or stop
polling. A panel can use every LiveComponent feature except `update/2` and
`update_many/1`, which the toolbar defines: `handle_event/3`
(with `phx-target={@myself}`), `start_async/3` and `handle_async/3`. For
timers, `send_after/4` delivers a message to `c:handle_info/2`. Style it with the panel's `pdt-`
classes (the stable set is listed in the plugin guide): the panel
stylesheet is the only one loaded.

The behaviour is unstable before 1.0 (see the note at the top).

# `handle_events`

```elixir
@callback handle_events(
  events :: [PhoenixDevToolbar.Event.t()],
  Phoenix.LiveView.Socket.t()
) ::
  Phoenix.LiveView.Socket.t()
```

New events of the plugin since the last call, oldest first.

# `handle_info`
*optional* 

```elixir
@callback handle_info(message :: term(), Phoenix.LiveView.Socket.t()) ::
  Phoenix.LiveView.Socket.t()
```

A message scheduled with `send_after/4`, or `{:navigate, params}` from
`navigate/3`. Defaults to ignoring it.

# `handle_props`
*optional* 

```elixir
@callback handle_props(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
```

`@shown?`, `@visible?` or `@page` changed. Defaults to doing nothing.

# `handle_reset`

```elixir
@callback handle_reset(
  events :: [PhoenixDevToolbar.Event.t()],
  Phoenix.LiveView.Socket.t()
) ::
  Phoenix.LiveView.Socket.t()
```

The page changed: replace everything shown with `events`.

# `duration`

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

Formats a duration in microseconds (`Event.duration`) the way the toolbar
does: `"0.04 ms"`, `"12.3 ms"`, `"2.50 s"`, `"n/a"` for nil.

# `navigate`

```elixir
@spec navigate(Phoenix.LiveView.Socket.t(), atom(), map()) ::
  Phoenix.LiveView.Socket.t()
```

Switches the panel to the tab of the plugin `plugin_id` and delivers
`{:navigate, params}` to that tab's `c:handle_info/2`. Returns `socket`
unchanged, for pipelines. Call it from the panel's own callbacks.

Use it to link to another tab, for example to inspect a process in the System
tab. Keep `params` small (a few strings): they go through the panel process
as they are. A plugin that is not attached or has no tab is ignored, and so is
a call from anything but a toolbar panel (a host LiveView's socket).

    def handle_event("inspect", %{"pid" => pid}, socket),
      do: {:noreply, Panel.navigate(socket, :system, %{"pid" => pid})}

# `send_after`

```elixir
@spec send_after(module(), String.t(), term(), non_neg_integer()) :: reference()
```

Delivers `message` to the panel `module` with DOM id `id` (its
`@id`) after `ms` milliseconds, through `c:handle_info/2`. Returns the
timer reference. Call it from the panel's own callbacks.

---

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