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 guide.
A kit of the app's own
A kit is a module that uses 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
endThe 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.
Summary
Types
An element of a kit: an item of its bar (Hakushi.Components.kit_items/1).
Callbacks
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 [].
A new node for a line drawn with a tool of the kit (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.
The node's data after an action of its controls (params are the
event's), or :error for an action the kit refuses.
The things people add, in the canvas's words.
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.
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 guide.
The kit's icon, a name of Hakushi.Icons.
A node's name for screen readers, where the canvas's node_label is
its default: "Drawing". Defaults to the default's.
The kit's name, unique among a canvas's kits.
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.
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).
A node of an address (a URL) pasted on the canvas, as uploaded/4.
It runs away from the LiveView (it may download), and the canvas adds
its node when it is done.
Whether people resize the nodes of a type by their corners. Defaults to false.
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
The kit's title, in the canvas's words (Hakushi.Messages).
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.
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.
The node types the kit draws.
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.
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.
Functions
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.
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).
A canvas as Markdown: the diagram of each of kits that has nodes in
graph (to_markdown/1), one after the other. nil when none has.
A word of messages, with its {name} filled.
Types
An element of a kit: an item of its bar (Hakushi.Components.kit_items/1).
Callbacks
@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]]
A new node for a line drawn with a tool of the kit (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.
The node's data after an action of its controls (params are the
event's), or :error for an action the kit refuses.
The things people add, in the canvas's words.
@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.
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 guide.
@callback icon() :: String.t()
The kit's icon, a name of Hakushi.Icons.
@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.
@callback name() :: String.t()
The kit's name, unique among a canvas's kits.
@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.
@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).
@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 uploaded/4.
It runs away from the LiveView (it may download), and the canvas adds
its node when it is done.
Whether people resize the nodes of a type by their corners. Defaults to false.
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 edit/3 gets for each
node. Defaults to none. See Hakushi.Kits.Shapes and the
Kits guide.
The kit's title, in the canvas's words (Hakushi.Messages).
@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 guide.
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 (drawn/3) when the pen lifts. Its options arecolors(the names of the colours the bar has,--hakushi-ink-<name>in the CSS),color,sizes,sizeandflat(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 (upload/0); the pointer stays as it was.
The bar has the pointer first, which selects and drags as usual.
@callback types() :: [String.t()]
The node types the kit draws.
@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.
@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.
Functions
@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.
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).
@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 (to_markdown/1), one after the other. nil when none has.
Hakushi.Kit.to_markdown([Hakushi.Kits.Schema], graph)
#=> "```mermaid%% hakushi kit=schema erDiagram ..."
A word of messages, with its {name} filled.