Skip to content

Instantly share code, notes, and snippets.

@wojtekmach
Created July 18, 2024 13:53
Show Gist options
  • Select an option

  • Save wojtekmach/8310bf1d8725715a2801f334caa0c339 to your computer and use it in GitHub Desktop.

Select an option

Save wojtekmach/8310bf1d8725715a2801f334caa0c339 to your computer and use it in GitHub Desktop.
Mix.install([
{:phoenix_playground, "~> 0.1.4"},
{:req_s3, "~> 0.2.0"}
])
# Based on https://hexdocs.pm/phoenix_live_view/uploads-external.html#direct-to-s3
defmodule DemoLive do
use Phoenix.LiveView
@impl true
def mount(_params, _session, socket) do
{:ok,
socket
|> allow_upload(
:photo,
accept: ~w[.png .jpeg .jpg],
max_entries: 1,
auto_upload: true,
external: &presign_upload/2
)}
end
@impl true
def render(assigns) do
~H"""
<form id="upload-form" phx-change="validate">
<.live_file_input upload={@uploads.photo} />
</form>
<div phx-drop-target={@uploads.photo.ref} style="width: 20em; height: 10em; padding: 1em; border: 1px dashed">
<%= for entry <- @uploads.photo.entries do %>
<div>
<.live_img_preview entry={entry} height="100" />
<div><%= entry.progress %>%</div>
<%= if entry.done? do %>
<.link href={presign_url(entry)}>Uploaded</.link>
<% end %>
</div>
<% end %>
</div>
<script type="text/javascript">
window.uploaders.S3 = function(entries, onViewError) {
entries.forEach(entry => {
let formData = new FormData()
let {url, fields} = entry.meta
Object.entries(fields).forEach(([key, val]) => formData.append(key, val))
formData.append("file", entry.file)
let xhr = new XMLHttpRequest()
onViewError(() => xhr.abort())
xhr.onload = () => xhr.status === 204 ? entry.progress(100) : entry.error()
xhr.onerror = () => entry.error()
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
let percent = Math.round((event.loaded / event.total) * 100)
if (percent < 100) { entry.progress(percent) }
}
})
xhr.open("POST", url, true)
xhr.send(formData)
})
}
</script>
<style type="text/css">
body { padding: 1em; }
</style>
"""
end
@impl true
def handle_event("validate", _params, socket) do
{:noreply, socket}
end
defp presign_upload(entry, socket) do
s3_options = s3_options(entry)
form = ReqS3.presign_form(s3_options ++ [content_type: entry.client_type])
meta = %{
uploader: "S3",
key: s3_options[:key],
url: form.url,
fields: Map.new(form.fields)
}
{:ok, meta, socket}
end
defp presign_url(entry) do
ReqS3.presign_url(s3_options(entry))
end
defp s3_options(entry) do
[
access_key_id: System.fetch_env!("AWS_ACCESS_KEY_ID"),
secret_access_key: System.fetch_env!("AWS_SECRET_ACCESS_KEY"),
region: System.get_env("AWS_REGION", "us-east-1"),
bucket: System.fetch_env!("BUCKET_NAME"),
key: "uploads/#{entry.client_name}"
]
end
end
PhoenixPlayground.start(live: DemoLive)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment