Skip to content

Instantly share code, notes, and snippets.

@TwistingTwists
Created September 4, 2023 04:10
Show Gist options
  • Save TwistingTwists/87d2aecd5ef7f0f8af6f1a7ab2c9b15b to your computer and use it in GitHub Desktop.
Save TwistingTwists/87d2aecd5ef7f0f8af6f1a7ab2c9b15b to your computer and use it in GitHub Desktop.
defmodule RazorNewWeb.RemoteUploadLive do
use RazorNewWeb, :live_view
@defaults %{
# default params for upload in liveview
# 4000 MB
max_file_size: 4000 * 1000 * 1000,
chunk_size: 640 * 1000 * 3,
accept_upload_types: ~w( .mp4 ),
max_entries: 1
}
@impl Phoenix.LiveView
def render(assigns) do
~H"""
<form id="video-upload-form" phx-submit="save" phx-change="validate">
<.upload_component {assigns} />
</form>
"""
end
def upload_component(assigns) do
~H"""
<div class="sm:grid sm:border-t sm:border-gray-200 sm:pt-5 ">
<%!-- render progress bar for upload --%>
<%= for entry <- @uploads.video.entries do %>
<div class=" text-center text-gray-600">
<div
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow={entry.progress}
style={"transition: width 0.5s ease-in-out; width: #{entry.progress}%; min-width: 1px;"}
class="col-span-full bg-purple-500 h-1.5 w-0 p-0"
>
</div>
<%= entry.progress %> %
<button
type="button"
phx-click="cancel-upload"
phx-value-ref={entry.ref}
aria-label="cancel"
>
&times;
</button>
</div>
<%= for err <- upload_errors(@uploads.video, entry) do %>
<p class="alert alert-danger"><%= error_to_string(err) %></p>
<% end %>
<% end %>
<%!-- upload drop zone --%>
<div class="mt-1 sm:mt-0" phx-drop-target={@uploads.video.ref}>
<div class="max-w-lg flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md hover:bg-gray-100">
<div class="space-y-1 text-center">
<svg
class="mx-auto h-12 w-12 text-gray-400"
stroke="currentColor"
fill="none"
viewBox="0 0 48 48"
aria-hidden="true"
>
<path
d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
</path>
</svg>
<div class="flex text-sm text-gray-600">
<label
for={@uploads.video.ref}
class="relative cursor-pointer bg-white rounded-md font-medium text-yellow-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
>
<span phx-click={js_exec("##{@uploads.video.ref}", "click", [])}>
Upload files
</span>
<.live_file_input
upload={@uploads.video}
class=" hover:bg-blue-200 sr-only "
tabindex="0"
/>
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p class="text-xs ">
<span class="text-red-400 font-bold">
<%= Enum.join(assigns[:accepted_file_types] || [], ", ") %>
</span>
up to <%= trunc((assigns[:max_file_size] || 1) / 1_000_000) %> MB
</p>
</div>
</div>
</div>
</div>
"""
end
@impl Phoenix.LiveView
def mount(params, _session, %{assigns: assigns} = socket) do
{
:ok,
socket
|> allow_upload(:video,
auto_upload: true,
progress: &handle_progress/3,
accept: @defaults[:accept_upload_types],
max_entries: @defaults[:max_entries],
max_file_size: @defaults[:max_file_size],
chunk_size: @defaults[:chunk_size],
writer: &s3_writer/3
)
|> assign(:accepted_file_types, @defaults[:accept_upload_types])
}
end
def s3_writer(name, entry, socket) do
{RazorNewWeb.S3Writer, [s3_config: ExAws.Config.new(:s3), key: entry.client_name]}
end
def handle_progress(:video, entry, socket) do
{:noreply, socket}
end
@impl Phoenix.LiveView
def handle_event("cancel-upload", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :video, ref)}
end
def handle_event("validate", params, socket) do
{:noreply, socket}
end
def error_to_string(:too_large), do: "Too large"
def error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
def error_to_string(:too_many_files), do: "You have selected too many files"
defp max_uploaded(uploads), do: length(uploads.video.entries) < uploads.video.max_entries
end
defmodule RazorNewWeb.S3Writer do
@moduledoc """
Module to stream video directly to S3 bucket.
But not via presigned url.
"""
@behaviour Phoenix.LiveView.UploadWriter
require Logger
@impl true
def init(opts) do
{s3_config, rest} = Keyword.pop(opts, :s3_config, ExAws.Config.new(:s3))
bucket = s3_config.bucket
file_name = Keyword.fetch!(rest, :key)
with s3_op <- ExAws.S3.initiate_multipart_upload(bucket, file_name) do
{:ok, %{key: file_name, chunk: 1, s3_config: s3_config, s3_op: s3_op, parts: []}}
end
end
@impl true
def meta(state) do
state
end
@impl true
def write_chunk(data, state) do
part =
ExAws.S3.Upload.upload_chunk!({data, state.chunk}, state.s3_op, state.s3_config)
{:ok, %{state | chunk: state.chunk + 1, parts: [part | state.parts]}}
end
@impl true
def close(state, _reason) do
case ExAws.S3.Upload.complete(state.parts, state.s3_op, state.s3_config) do
{:ok, _} ->
{:ok, state}
{:error, reason} ->
{:error, reason}
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment