# Drawing App Recipes

These recipes show common drawing-app patterns. They are app-level patterns:
LivePanels owns runtime drawing behavior, and your app owns product meaning.

## Add A Viewport Toolbar Item

Toolbars can be ordinary viewport items:

```elixir
[
  drawing_tools:
    {MyAppWeb.DrawingToolsLive,
     label: "Drawing tools",
     placement_space: :viewport,
     placement_anchor: :top,
     placement_lock: true,
     reserved_area: %{edge: :top, policy: :avoid},
     frame: :content,
     persistent: false,
     render: %{frame_overflow: :visible, stream_update: :canvas_state},
     interaction: %{presentable: false, removable: false}}
]
```

This keeps the toolbar inside the canvas runtime while excluding it from saved
board content.

## Build A Tool Button

```heex
<button
  type="button"
  class={["tool-button", @cursor_mode == :draw and @draw_tool == :arrow and "is-active"]}
  phx-click={
    set_cursor_mode_js(@livepanels.canvas_id, :draw,
      tool: :arrow,
      style: @draw_style,
      push: "set_cursor_mode"
    )
  }
>
  Arrow
</button>
```

Mirror mode when the toolbar needs active state. Skip `push:` for controls that
do not need LiveView assign state.

## Apply Style To Selected Vector Items

```elixir
def handle_event("set_draw_style", params, socket) do
  style = normalize_draw_style(params, socket.assigns.draw_style)
  selection = LivePanels.Canvas.Read.selection(socket)

  selected_vector_items =
    socket
    |> LivePanels.Canvas.Read.items()
    |> Enum.filter(&LivePanels.Canvas.Selection.selected_item?(selection, &1.id))
    |> Enum.filter(fn item ->
      match?({:ok, _draw}, LivePanels.Drawing.vector_state(item))
    end)

  socket =
    Enum.reduce(selected_vector_items, assign(socket, :draw_style, style), fn item, acc ->
      LivePanels.Drawing.update_style!(acc, item, style)
    end)

  {:noreply, socket}
end
```

Use this behavior when palette changes should restyle selected vector items and also
set the style for the next draft.

## Create A Callout Stamp

```elixir
def handle_event("stamp-callout", _params, socket) do
  box = %{x: 160, y: 120, w: 260, h: 120}
  arrow = %{x: 60, y: 160, w: 120, h: 80}

  socket =
    LivePanels.Canvas.transaction!(socket, fn tx ->
      tx
      |> LivePanels.Drawing.vector_item!(:rectangle, box,
        id: "callout-box",
        style: %{color: "#f97316", width: 3, fill_color: "#ffedd5", stroke_style: "solid", opacity: 100, roundness: 10}
      )
      |> LivePanels.Drawing.vector_item!(:arrow, arrow,
        id: "callout-arrow",
        points: [%{x: 0, y: 0}, %{x: 120, y: 80}],
        style: %{color: "#f97316", width: 3, fill_color: "none", stroke_style: "solid", opacity: 100, roundness: 0}
      )
      |> LivePanels.Canvas.Selection.select!(["callout-box", "callout-arrow"])
    end)

  {:noreply, socket}
end
```

Use deterministic ids only when the stamp can appear once. Generate unique ids
for repeatable stamps.

## Add A Vector Item Inspector

```elixir
defp assign_vector_item_inspector(socket) do
  selection = LivePanels.Canvas.Read.selection(socket)

  selected_vector_items =
    socket
    |> LivePanels.Canvas.Read.items()
    |> Enum.filter(&LivePanels.Canvas.Selection.selected_item?(selection, &1.id))
    |> Enum.flat_map(fn item ->
      case LivePanels.Drawing.vector_state(item) do
        {:ok, draw} -> [%{item: item, draw: draw}]
        :error -> []
      end
    end)

  assign(socket, :selected_vector_items, selected_vector_items)
end
```

Render controls from the inspector:

```heex
<aside :if={@selected_vector_items != []} class="vector-item-inspector">
  <section :for={selected <- @selected_vector_items}>
    <p>{selected.draw.kind}</p>
    <button {erase_attrs(selected.item)}>Remove</button>
  </section>
</aside>
```

## Import An Excalidraw File

```elixir
def handle_event("import_excalidraw", %{"content" => content}, socket) do
  with {:ok, document} <- Jason.decode(content),
       {:ok, [_ | _]} <- LivePanels.Drawing.excalidraw_commands(document) do
    {:noreply, LivePanels.Drawing.import_excalidraw!(socket, document)}
  else
    _error ->
      {:noreply, put_flash(socket, :error, "No supported drawing elements found.")}
  end
end
```

In a real upload flow, read the uploaded file with LiveView uploads and keep
file size limits tight.

## Export The Current Drawing

```elixir
def handle_event("export_excalidraw", _params, socket) do
  document =
    socket
    |> LivePanels.Canvas.Read.state()
    |> LivePanels.Drawing.to_excalidraw()

  {:noreply,
   push_event(socket, "download-file", %{
     filename: "board.excalidraw",
     mime_type: "application/vnd.excalidraw+json",
     content: Jason.encode!(document, pretty: true)
   })}
end
```

The browser event that actually downloads the file belongs to your application.

## Combine Drawing And Graph

Use the graph toolkit when the canvas is primarily a graph. Use the drawing
toolkit when it is primarily a drawing surface. For hybrid products, import the
toolkit helpers you need explicitly:

```elixir
use LivePanels.Canvas,
  pubsub: MyApp.PubSub

use LivePanels.Toolkit.Canvas
import LivePanels.Toolkit.Controls.Drawing
import LivePanels.Toolkit.Controls.Viewport
import LivePanels.Graph.Attrs
```

Then render both capabilities:

```heex
<.canvas_runtime {canvas_runtime_attrs(assigns, cursor_mode: @cursor_mode)}>
  <:item :let={context}>
    <.node_or_content_item context={context} />
  </:item>
</.canvas_runtime>
```

Committed vector items and graph connector items both render through the runtime
world SVG item projection. Product UI stays in node markup, controls, and CSS.

## Annotate A Document

Use one item for the document and vector items for annotations:

```heex
<:item
  id="page-1"
  type="document_page"
  at={{0, 0}}
  size={{960, 1240}}
  props={%{document_id: @document.id, page: 1}}
>
  <.document_page document={@document} page={1} />
</:item>
```

Keep annotation ids stable when they mirror database rows:

```elixir
LivePanels.Drawing.vector_item!(socket, :rectangle, placement,
  id: "annotation-#{annotation.id}",
  style: highlight_style
)
```

The document remains app content. The vector item remains canvas annotation.

## Debug A Missing Vector Item

Check these in order:

- the command created a committed vector item, not just a browser draft;
- the vector item kind is supported;
- the style map includes every required field;
- the item id is unique;
- the placement has positive width and height;
- point-backed vector items have valid points;
- the standard runtime is rendering the world vector layer;
- custom renderers call `LivePanels.Drawing.vector_state/1`;
- erase or removal controls are not immediately removing the vector item;
- layout restore has the item type and vector state expected by the current
  code.

Use `LivePanels.Drawing.vector_item/4` when creating vector items from
server code and show the returned reason on rejection. Browser-authored drawing
commands also record their result on `LivePanels.Canvas.Read.last_command/1`.
