# `PhoenixKitCatalogue.Attachments`
[🔗](https://github.com/BeamLabEU/phoenix_kit_catalogue/blob/0.1.14/lib/phoenix_kit_catalogue/attachments.ex#L1)

Folder-scoped file attachments + featured image for catalogue
resources (items and catalogues share the exact same pattern).

Each resource owns a `phoenix_kit_media_folders` row keyed by a
deterministic name derived from the resource struct and UUID.
Files belong to the resource via `phoenix_kit_files.folder_uuid`,
queried on mount and refreshed after uploads. An optional featured
image is a single UUID pointer on `resource.data["featured_image_uuid"]`.

## Usage

The owning LiveView calls `mount_attachments/2` in `mount/3` and
`allow_attachment_upload/1` in the same chain. Its event/info
clauses delegate to the matching functions here:

    # Mount
    socket
    |> Attachments.mount_attachments(item_or_catalogue)
    |> Attachments.allow_attachment_upload()

    # Events (one-liner bodies)
    def handle_event("open_featured_image_picker", _, s),
      do: Attachments.open_featured_image_picker(s)

    def handle_event("close_media_selector", _, s),
      do: {:noreply, Attachments.close_media_selector(s)}

    def handle_event("cancel_upload", %{"ref" => ref}, s),
      do: Attachments.cancel_attachment_upload(s, ref)

    def handle_event("clear_featured_image", _, s),
      do: Attachments.clear_featured_image(s)

    def handle_event("remove_file", %{"uuid" => uuid}, s),
      do: Attachments.trash_file(s, uuid)

    def handle_info({:media_selected, uuids}, s),
      do: Attachments.handle_media_selected(s, uuids)

    def handle_info({:media_selector_closed}, s),
      do: {:noreply, Attachments.close_media_selector(s)}

On save, weave attachment state into params:

    params = Attachments.inject_attachment_data(params, socket)

And after a `:new` save succeeds, rename the pending folder:

    :ok = Attachments.maybe_rename_pending_folder(socket, saved_resource)

## Resource shape

The module pattern-matches on the resource struct to derive the
folder name prefix. Add a new clause to `folder_name_for/1` to
support additional resource types.

# `allow_attachment_upload`

Registers the file input `:attachment_files` with a 20-file, 100MB
ceiling and auto-upload. Progress is consumed by `handle_progress/3`
which this module captures for the caller.

# `cancel_attachment_upload`

Cancels an in-flight upload entry by ref.

# `clear_featured_image`

Nulls the featured image pointer in socket state (save persists).

# `close_media_selector`

Clears the media-selector assigns; returns the plain socket.

# `file_icon`

Picks a heroicon name for a file based on its Storage type.

# `format_file_size`

Renders a byte count as a human string. Nil-safe.

# `handle_media_selected`

Routes the `:media_selected` reply by `:media_selector_target`.
Featured-image target promotes the first selected UUID; files
target is a no-op (modal already set folder_uuid). Both refresh
the grid from the folder.

# `inject_attachment_data`

Merges `files_folder_uuid` and `featured_image_uuid` into `params["data"]`.
Call right before passing params to your context's create/update.

# `maybe_rename_pending_folder`

After a `:new` save, renames the pending (random-named) folder to
the deterministic name now that the resource has a UUID. Non-fatal:
rename failures log and return `:ok` so the save flow isn't blocked.

# `mount_attachments`

Populates the attachment-related assigns on the socket. Accepts the
owning resource (Item or Catalogue or Category). Stashes the resource
at `:attachments_resource` so later callbacks (progress, events) can
reach it without plumbing.

## Options

  * `:files_grid` (default `true`) — set to `false` to skip the
    `assign_files_state/1` work (and the per-mount DB query that
    enumerates the folder's files). The CategoryFormLive uses this
    because its UI only renders the featured-image card; it has no
    files grid, so the file list query was wasted.

# `open_featured_image_picker`

Opens the media selector modal scoped to the resource's folder.

# `trash_file`

Removes the file from this resource. Three cases:

1. File's home folder is this resource AND it's only here → trash it
   (single-owner case; same effect as before folder-links).
2. File's home folder is this resource AND it's also linked elsewhere
   → promote one link to home, delete the promoted link. The file
   stays alive under its new owner.
3. File was here via a `FolderLink` (home is another resource) →
   delete the link. Other owners keep their reference untouched.

Also clears the featured pointer if the removed file was featured.

# `upload_error_message`

Translates LiveView upload error atoms to user-facing text.

# `upload_name`

Returns the upload ref name used for the inline files dropzone.

---

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