The pieces every module's file form shares, so each upload behaves the same wherever it happens: the upload config, storing a finished upload into a record's folder, the messages people see, and the files grid's featured-image and order rules.
socket = Attachments.allow(socket, :attachment_files, &handle_progress/3)
def handle_progress(:attachment_files, %{done?: true} = entry, socket) do
stored =
consume_uploaded_entry(socket, entry, fn %{path: path} ->
{:ok, Attachments.store(path, entry, Actor.uuid(socket), folder_uuid)}
end)
case stored do
{:ok, _file} -> …
{:already_attached, existing} -> put_flash(socket, :info, Attachments.duplicate_notice(entry.client_name, existing))
{:error, reason} -> put_flash(socket, :error, Attachments.failed_message(entry.client_name, reason))
end
endWhat each form keeps — which folder, when its pointers are written, who
hears about a change — is its own; the folder rules underneath are
PhoenixKit.Modules.Storage.ResourceFolders.
Summary
Functions
Registers upload name with the attachment defaults — any file type,
20 files, 100 MB each, uploaded as soon as they are chosen — and
progress as its progress callback. opts override the defaults.
Files sorted by a saved order of uuids: files the order doesn't know keep their relative place after the ordered ones (new uploads land last), and an empty order changes nothing.
What to tell a person whose upload is byte-identical to a file already in the folder: storage de-duplicates by content, so nothing new appears and the file keeps the earlier upload's name — without this it reads as a lost file.
What to tell a person about an upload error (LiveView's or store/4's).
What to tell a person when their upload client_name could not be stored.
What to tell a person when a record's files folder cannot be prepared.
Stores a finished upload (the temp path of entry) for user_uuid
and files it into folder_uuid by core's rules
(ResourceFolders.place_stored/2): a content duplicate already in the
folder is {:already_attached, file}, a trashed one is restored. The
browser's file name is reduced to its base name before it reaches
storage, and the type comes from Storage.determine_file_type/2.
nil for the folder stores the file without placing it (a form that
files it on save); a trashed duplicate is restored then too, so the
file returned is always live. Never raises — call it outside a
transaction, which a database error would abort.
A folder's files with the record's featured image first when it lives elsewhere — a featured image moved out of the folder is still what the record shows, so the grid shows it too.
Functions
@spec allow(Phoenix.LiveView.Socket.t(), atom(), function(), keyword()) :: Phoenix.LiveView.Socket.t()
Registers upload name with the attachment defaults — any file type,
20 files, 100 MB each, uploaded as soon as they are chosen — and
progress as its progress callback. opts override the defaults.
Files sorted by a saved order of uuids: files the order doesn't know keep their relative place after the ordered ones (new uploads land last), and an empty order changes nothing.
What to tell a person whose upload is byte-identical to a file already in the folder: storage de-duplicates by content, so nothing new appears and the file keeps the earlier upload's name — without this it reads as a lost file.
What to tell a person about an upload error (LiveView's or store/4's).
What to tell a person when their upload client_name could not be stored.
@spec folder_error_message() :: String.t()
What to tell a person when a record's files folder cannot be prepared.
@spec store(String.t(), map(), String.t() | nil, String.t() | nil) :: {:ok, PhoenixKit.Modules.Storage.File.t()} | {:already_attached, PhoenixKit.Modules.Storage.File.t()} | {:error, term()}
Stores a finished upload (the temp path of entry) for user_uuid
and files it into folder_uuid by core's rules
(ResourceFolders.place_stored/2): a content duplicate already in the
folder is {:already_attached, file}, a trashed one is restored. The
browser's file name is reduced to its base name before it reaches
storage, and the type comes from Storage.determine_file_type/2.
nil for the folder stores the file without placing it (a form that
files it on save); a trashed duplicate is restored then too, so the
file returned is always live. Never raises — call it outside a
transaction, which a database error would abort.
A folder's files with the record's featured image first when it lives elsewhere — a featured image moved out of the folder is still what the record shows, so the grid shows it too.