Hakushi.Kits.Images (Hakushi v0.6.0)

Copy Markdown View Source

The kit of images on the paper: for a moodboard, a screenshot to mark up, references beside a plan.

<.canvas id="board" graph={@graph} kits={[Hakushi.Kits.Images]} on_change={&save/2} />

People drop image files on the paper, paste them (or paste an image's address, from the hosts the app allows), or pick them with the kit's tool (I). Each is a node where it lands, at its own aspect ratio, sized to fit the view. While a file uploads, the canvas shows a placeholder with its progress (to the others too, on a shared canvas); the node is made only once the server has the file and has checked it (Hakushi.Images).

An image resizes from its corners and sides with its aspect ratio kept (Shift frees it), is cropped in place (the panel's Crop, then Enter), takes a caption on a double click, and alt text from the panel. Its edges, groups, copy and paste and export are those of any node.

The node

A node of type "image", whose data is the stored file and how it is shown:

%{
  "url" => "/uploads/2026/10/…-board.png",
  "key" => "2026/10/…-board.png",   # in the store (Storehouse)
  "thumb" => nil,                     # a smaller copy's URL, for small zooms
  "name" => "board.png",
  "pixels" => [1600, 1000],           # the file's width and height
  "crop" => [0, 0, 1, 1],             # x, y, width, height: fractions of the file
  "alt" => "The palette of the spring campaign",
  "caption" => "Spring"
}

The files

The files go to the app's store with Storehouse: the local disk, S3, or a store of the app's (config :storehouse, adapter: ...). It is an optional dependency of Hakushi: the kit is there when the app has {:storehouse, "~> 0.2"} in its deps. The canvas takes the files with a LiveView upload of its own: the canvas must not be in a form of the app's. The limits are the kit's config:

config :hakushi, Hakushi.Kits.Images,
  max_file_size: 10_000_000,     # bytes, the default
  max_entries: 10,               # files at once, the default
  max_pixels: 50_000_000,        # width × height, the default
  fetch: ["images.example.com", "*.cdn.example.com"],
  thumbnail: &MyApp.Images.thumbnail/2,
  files: &MyApp.Images.path/1
  • :fetch - the hosts whose images people paste by their address, and agents add by theirs: the server downloads them (Hakushi.Images.fetch/2) and stores them as uploads. None by default.
  • :thumbnail - a function of the stored image and its file that gives the URL of a smaller copy, which the canvas shows when it is zoomed out (see Hakushi.Images.store/2).
  • :files - a function of a name an agent gives (add_image's file) that gives {:ok, path} of one of the app's files, or :error.
  • :adapter - the store, for this kit only. The default is Storehouse.adapter/0.

Summary

Functions

The kit's config, with its defaults.

Whether the kit takes images from the address url: its host is one of the config's :fetch.

An image's node, for a stored image (Hakushi.Images.store/2), in a box of the canvas: at its x and y, as wide as it (at most the image's own width, and 480 with no width), and as high as its aspect ratio says.

Functions

config()

@spec config() :: keyword()

The kit's config, with its defaults.

fetchable?(url)

@spec fetchable?(String.t()) :: boolean()

Whether the kit takes images from the address url: its host is one of the config's :fetch.

image_node(image, box, opts \\ [])

@spec image_node(Hakushi.Images.image(), [number()] | nil, keyword()) :: map()

An image's node, for a stored image (Hakushi.Images.store/2), in a box of the canvas: at its x and y, as wide as it (at most the image's own width, and 480 with no width), and as high as its aspect ratio says.

{:ok, image} = Hakushi.Images.store("priv/seeds/palette.png")
Hakushi.Kits.Images.image_node(image, [0, 0, 320, 0], alt: "The palette")

Options

  • :alt and :caption - the image's.