# `Hakushi.Kit`
[🔗](https://github.com/hattoriai/hakushi/blob/v0.6.0/lib/hakushi/kit.ex#L1)

A kit: the things of one kind of canvas, which the canvas draws, makes
and edits by itself. A canvas with `kits` needs no `:node` slots and no
`new_node` for them:

    <.canvas id="model" graph={@graph} kits={[Hakushi.Kits.Schema]} on_change={&save/2} />
    <.kit_items for="model" kits={[Hakushi.Kits.Schema]} />

Kits are optional: a canvas draws anything with `:node` slots, and a
kit is a way to package a kind of canvas (its nodes, how people make and
edit them, its paper and its rules) so that it is used in one line, by
one app or many. Hakushi has six: `Hakushi.Kits.Schema` (tables,
their columns and foreign keys), `Hakushi.Kits.Flow` (triggers, steps
and conditions), `Hakushi.Kits.Notes` (sticky notes),
`Hakushi.Kits.Ink` (drawing with a pen), `Hakushi.Kits.Shapes`
(rectangles, ellipses, text, lines) and `Hakushi.Kits.Images` (image
files); an app makes
its own with the same callbacks. See the [Kits](kits.md) guide.

## A kit of the app's own

A kit is a module that `use`s `Hakushi.Kit` and implements its
callbacks:

    defmodule MyAppWeb.Kits.Roadmap do
      use Hakushi.Kit

      @impl true
      def name, do: "roadmap"
      @impl true
      def title(_messages), do: "Roadmap"
      @impl true
      def icon, do: "workflow"
      @impl true
      def types, do: ["roadmap-milestone"]
      @impl true
      def elements(_messages), do: [%{item: "milestone", label: "Milestone", icon: "zap"}]

      @impl true
      def new_node(_item, {x, y}, _messages),
        do: %{type: "roadmap-milestone", x: x, y: y, data: %{"title" => "Milestone"}}

      @impl true
      def node(assigns) do
        ~H"""
        <div class="milestone">
          <.handle kind={:target} side={:left} />
          <input
            :if={!@readonly}
            value={get(@node.data, "title")}
            phx-blur="hakushi:edit"
            phx-target={@target}
            phx-value-node={@node.id}
            phx-value-action="title"
          />
          <span :if={@readonly}>{get(@node.data, "title")}</span>
          <.handle kind={:source} side={:right} />
        </div>
        """
      end

      @impl true
      def edit("title", %{"value" => title}, data), do: {:ok, Map.put(data, "title", title)}
      def edit(_action, _params, _data), do: :error
    end

The node's controls send `"hakushi:edit"` to the canvas (`@target`)
with the node's id and an `action`; the canvas calls `edit/3` with the
node's data and puts the data it returns in the node, as a change of the
person's own: checked by the rules and `on_change`, undone with ⌘Z, and
shared with everyone on a shared canvas.

A kit's data has string keys, as a graph loaded from JSON has; `get/2`
reads a key of either kind.

# `element`

```elixir
@type element() :: %{item: String.t(), label: String.t(), icon: String.t()}
```

An element of a kit: an item of its bar (`Hakushi.Components.kit_items/1`).

# `canvas`
*optional* 

```elixir
@callback canvas() :: keyword()
```

The canvas the kit likes: its paper (`background`, `grid`, `snap`), the
way the tidy up goes (`layout`, as the canvas's attribute: `:right`,
`:down`, `:force`, `:grid`, or `{:layered, :down}`) and the rules of its edges (`rules`, a
keyword list of `Hakushi.Rules`). Defaults to `[]`.

The canvas's own attributes win; among kits, the first kit's paper and
layout, and the rules of all of them (the first kit's, for a rule two
kits set).

    def canvas, do: [background: :lines, grid: 24, rules: [loops: false]]

# `drawn`
*optional* 

```elixir
@callback drawn(tool :: String.t(), params :: map(), messages :: map()) :: map() | nil
```

A new node for a line drawn with a tool of the kit (`c:tools/1`), or
`nil`. `params` are the line's `"points"` on the canvas (`[x, y,
pressure]`), and the `"color"` and `"size"` chosen in the bar. A node
with no `id` gets one; drawn over a group, it is in it.

# `edit`

```elixir
@callback edit(action :: String.t(), params :: map(), data :: map()) ::
  {:ok, map()} | :error
```

The node's data after an `action` of its controls (`params` are the
event's), or `:error` for an action the kit refuses.

# `elements`

```elixir
@callback elements(messages :: map()) :: [element()]
```

The things people add, in the canvas's words.

# `from_markdown`
*optional* 

```elixir
@callback from_markdown(text :: String.t()) :: {:ok, Hakushi.Graph.t()} | :error
```

The canvas of a diagram that `to_markdown/1` wrote, or that a person
wrote in the kit's kind of diagram: `{:ok, graph}`, or `:error` for a
text the kit does not read.

# `group?`
*optional* 

```elixir
@callback group?(type :: String.t()) :: boolean()
```

Whether the nodes of a type are groups, which hold others (a lane, a
frame), as a `:node` slot's `group` makes them. Defaults to `false`.
See the [Groups](groups.md) guide.

# `icon`

```elixir
@callback icon() :: String.t()
```

The kit's icon, a name of `Hakushi.Icons`.

# `label`
*optional* 

```elixir
@callback label(node :: Hakushi.Node.t(), messages :: map()) :: String.t()
```

A node's name for screen readers, where the canvas's `node_label` is
its default: `"Drawing"`. Defaults to the default's.

# `name`

```elixir
@callback name() :: String.t()
```

The kit's name, unique among a canvas's kits.

# `new_node`

```elixir
@callback new_node(
  item :: String.t() | nil,
  at :: {number(), number()},
  messages :: map()
) :: map() | nil
```

A new node for an element's `item` dropped at `{x, y}`, or for a double
click on the paper (`item` is `nil`). A node with no `id` gets one.

# `node`

```elixir
@callback node(assigns :: map()) :: Phoenix.LiveView.Rendered.t()
```

Draws a node of the kit's types. The assigns are `node` (the
`Hakushi.Node`), `canvas` (the canvas's id, for the ids of forms),
`readonly`, `target` (the canvas, for `phx-target`) and `messages`
(the canvas's words).

# `pasted`
*optional* 

```elixir
@callback pasted(url :: String.t(), box :: [number()] | nil, messages :: map()) ::
  {:ok, map()} | {:error, term()}
```

A node of an address (a URL) pasted on the canvas, as `c:uploaded/4`.
It runs away from the LiveView (it may download), and the canvas adds
its node when it is done.

# `resizable?`
*optional* 

```elixir
@callback resizable?(type :: String.t()) :: boolean()
```

Whether people resize the nodes of a type by their corners. Defaults to `false`.

# `styles`
*optional* 

```elixir
@callback styles(messages :: map()) :: [map()]
```

The style of the kit's nodes that people change, in a panel over the
selection, for one node or many, as one change: a list of groups, each
with a `key` of the nodes' data, a `label`, a `kind` (`"color"`,
`"width"` or `"choice"`) and its `options` (maps of `value` and `label`);
or a field for a text (`"text"`, with a `placeholder`), which sends its
value as a choice does; or a button of the canvas's own for one node
(`"action"`, by its `key`: `"crop"` crops an image in place).
A node shows its style in an element of its own with
`data-hakushi-style`: JSON of the node's values, by the groups' keys.
The panel reads it to show the groups and press the choices the nodes
share; a group shows only when a selected node has its key there (an
`"action"` too), so that a node with no such element has an empty
panel:

    <div class="milestone" data-hakushi-style={JSON.encode!(%{status: get(@node.data, "status")})}>

A choice sends `"hakushi:edit"` with the nodes, the action `"style"`
and `%{"style" => %{key => value}}`, which `c:edit/3` gets for each
node. Defaults to none. See `Hakushi.Kits.Shapes` and the
[Kits](kits.md#a-panel-of-styles) guide.

# `title`

```elixir
@callback title(messages :: map()) :: String.t()
```

The kit's title, in the canvas's words (`Hakushi.Messages`).

# `to_markdown`
*optional* 

```elixir
@callback to_markdown(graph :: Hakushi.Graph.t()) :: String.t() | nil
```

The kit's nodes of `graph` as Markdown: a fenced diagram (Mermaid), so
that a document with the canvas in it (a Kotoba embed) exports as text
people read and Markdown viewers draw, and that `from_markdown/1` reads
back. `nil` when the graph has none of the kit's nodes.

Write the places of the nodes in it (`Hakushi.Mermaid.places/3`), so
that the canvas comes back as it was. See the [Kits](kits.md) guide.

# `tools`
*optional* 

```elixir
@callback tools(messages :: map()) :: [map()]
```

The tools of the kit, for a bar on the canvas: a way to make its nodes
by drawing them, as a pen draws ink (`Hakushi.Kits.Ink`). Defaults to
none.

A tool is a map with `tool` (its name in the kit), `label`, `icon` (a
name of `Hakushi.Icons`), `key` (a letter that takes it, or `nil`) and
`mode`, what the pointer does with it on the canvas:

  * `"draw"`: draws a line, which becomes a node (`c:drawn/3`) when the
    pen lifts. Its options are `colors` (the names of the colours the
    bar has, `--hakushi-ink-<name>` in the CSS), `color`, `sizes`, `size`
    and `flat` (one width, whatever the pressure).
  * `"erase"`: deletes the nodes of the kit's types that it passes over.
  * `"lasso"`: selects what a line drawn around encloses.
  * `"pick"`: opens the browser's picker of files, for a kit that takes
    them (`c:upload/0`); the pointer stays as it was.

The bar has the pointer first, which selects and drags as usual.

# `types`

```elixir
@callback types() :: [String.t()]
```

The node types the kit draws.

# `upload`
*optional* 

```elixir
@callback upload() :: keyword()
```

The files people drop, paste or pick on the canvas, for a kit whose
nodes are made of them (`Hakushi.Kits.Images`): the options of a
LiveView upload (`Phoenix.LiveView.allow_upload/3`'s `accept`,
`max_file_size` and `max_entries`), which the canvas allows for the
first such kit of its kits. Defaults to none.

# `uploaded`
*optional* 

```elixir
@callback uploaded(
  path :: Path.t(),
  meta :: map(),
  box :: [number()] | nil,
  messages :: map()
) ::
  {:ok, map()} | {:error, term()}
```

A node of an uploaded file, which the canvas adds where the person put
it: `{:ok, node}`, or `{:error, reason}` for a file the kit refuses.
`path` is the uploaded file, a temporary file; `meta` has the client's
`:name` and `:content_type` (a claim); `box` is the place the person
put it, `[x, y, width, height]`, or `nil`.

# `from_markdown`

```elixir
@spec from_markdown([module()], String.t()) ::
  {:ok, Hakushi.Graph.t(), module()} | :error
```

A canvas from Markdown (a diagram of `to_markdown/2`): read by the kit it
names (`%% hakushi kit=...`), or else by the first of `kits` that reads
it. Gives `{:ok, graph, kit}` or `:error`.

# `get`

```elixir
@spec get(map(), String.t()) :: term()
```

The value of `key` in a node's data, whose keys are strings (a graph
loaded from JSON) or atoms (a graph made in Elixir).

# `to_markdown`

```elixir
@spec to_markdown([module()], Hakushi.Graph.t()) :: String.t() | nil
```

A canvas as Markdown: the diagram of each of `kits` that has nodes in
`graph` (`c:to_markdown/1`), one after the other. `nil` when none has.

    Hakushi.Kit.to_markdown([Hakushi.Kits.Schema], graph)
    #=> "```mermaid
%% hakushi kit=schema
erDiagram
  ..."

# `word`

```elixir
@spec word(map(), String.t(), String.t()) :: String.t()
```

A word of `messages`, with its `{name}` filled.

---

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