PhoenixKitCatalogue.Attachments (PhoenixKitCatalogue v0.19.1)

Copy Markdown View Source

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.

Summary

Functions

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.

Cancels an in-flight upload entry by ref.

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

Clears the media-selector assigns and re-reads the files grid; returns the plain socket. The core MediaSelectorModal stores its uploads into this resource's folder as they land — closing without confirming still leaves them there, so the grid (and, if the folder's contents changed, every other surface counting them) must catch up on close.

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

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

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.

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

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.

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.

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

Re-reads the resource's folder into the files grid. For hosts that hear over PubSub that the resource changed in another session (an upload or removal there) — the form's own pointers (featured image) are untouched.

Removes the file from this resource. Three cases

Translates LiveView upload error atoms to user-facing text.

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

Functions

allow_attachment_upload(socket)

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(socket, ref)

Cancels an in-flight upload entry by ref.

close_media_selector(socket)

Clears the media-selector assigns and re-reads the files grid; returns the plain socket. The core MediaSelectorModal stores its uploads into this resource's folder as they land — closing without confirming still leaves them there, so the grid (and, if the folder's contents changed, every other surface counting them) must catch up on close.

file_icon(arg1)

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

format_file_size(bytes)

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

handle_media_selected(socket, file_uuids)

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(params, socket)

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(socket, resource)

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(socket, resource, opts \\ [])

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.

refresh_files(socket)

Re-reads the resource's folder into the files grid. For hosts that hear over PubSub that the resource changed in another session (an upload or removal there) — the form's own pointers (featured image) are untouched.

trash_file(socket, uuid)

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(other)

Translates LiveView upload error atoms to user-facing text.

upload_name()

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