Skip to content

Instantly share code, notes, and snippets.

@dankentfield
Created October 2, 2023 10:20
Show Gist options
  • Save dankentfield/719666ede4370ab7a9a6c511a82b0983 to your computer and use it in GitHub Desktop.
Save dankentfield/719666ede4370ab7a9a6c511a82b0983 to your computer and use it in GitHub Desktop.
Application.put_env(:sample, Example.Endpoint,
http: [ip: {127, 0, 0, 1}, port: 5001],
server: true,
live_view: [signing_salt: "aaaaaaaa"],
secret_key_base: String.duplicate("a", 64),
pubsub_server: Example.PubSub
)
Mix.install([
{:plug_cowboy, "~> 2.5"},
{:jason, "~> 1.0"},
{:phoenix, "~> 1.7", override: true},
{:phoenix_live_view, "~> 0.20"}
])
defmodule Example.ErrorView do
def render(template, _), do: Phoenix.Controller.status_message_from_template(template)
end
defmodule Example.HomeLive do
use Phoenix.LiveView, layout: {__MODULE__, :live}
import Phoenix.Component
def render("live.html", assigns) do
~H"""
<style>body{margin: 5rem 10rem}blockquote,pre>code{padding:1rem 1.5rem}dl,ol,p,ul{margin-top:0}.row .column,img{max-width:100%}#media-recorder-demo,html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}html{font-size:62.5%}body{color:#000;font-family:Helvetica,Arial,sans-serif;font-size:1.6em;font-weight:300;line-height:1.6}b,label,legend,strong{font-weight:700}blockquote{border-left:.3rem solid #d1d1d1;margin-left:0;margin-right:0}.alert p,blockquote :last-child{margin-bottom:0}.button,button,input[type=button],input[type=reset],input[type=submit]{background-color:#0069d9;border:.1rem solid #0069d9;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}code,pre{background:#f4f5f6}dl,ol,td:first-child,th:first-child,ul{padding-left:0}.row,.row.row-no-padding,.row.row-no-padding>.column,fieldset{padding:0}.button:focus,.button:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type=button][disabled]:focus,input[type=button][disabled]:hover,input[type=reset][disabled]:focus,input[type=reset][disabled]:hover,input[type=submit][disabled]:focus,input[type=submit][disabled]:hover{background-color:#0069d9;border-color:#0069d9}.button.button-outline,button.button-outline,input[type=button].button-outline,input[type=reset].button-outline,input[type=submit].button-outline{background-color:transparent;color:#0069d9}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type=button].button-outline:focus,input[type=button].button-outline:hover,input[type=reset].button-outline:focus,input[type=reset].button-outline:hover,input[type=submit].button-outline:focus,input[type=submit].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type=button].button-outline[disabled]:focus,input[type=button].button-outline[disabled]:hover,input[type=reset].button-outline[disabled]:focus,input[type=reset].button-outline[disabled]:hover,input[type=submit].button-outline[disabled]:focus,input[type=submit].button-outline[disabled]:hover{border-color:inherit;color:#0069d9}.button.button-clear,button.button-clear,input[type=button].button-clear,input[type=reset].button-clear,input[type=submit].button-clear{background-color:transparent;border-color:transparent;color:#0069d9}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type=button].button-clear:focus,input[type=button].button-clear:hover,input[type=reset].button-clear:focus,input[type=reset].button-clear:hover,input[type=submit].button-clear:focus,input[type=submit].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,a,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type=button].button-clear[disabled]:focus,input[type=button].button-clear[disabled]:hover,input[type=reset].button-clear[disabled]:focus,input[type=reset].button-clear[disabled]:hover,input[type=submit].button-clear[disabled]:focus,input[type=submit].button-clear[disabled]:hover{color:#0069d9}code{border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{border-left:.3rem solid #0069d9;overflow-y:hidden;padding:1em}pre>code{border-radius:0;display:block;white-space:pre}hr{border:0;border-top:.1rem solid #f4f5f6;margin:3rem 0}input[type=email],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=url],select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;border:.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1rem;width:100%}.phx-logo img,select{width:auto}input[type=email]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=url]:focus,select:focus,textarea:focus{border-color:#0069d9;outline:0}select{background:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 29 14" width="29"><path fill="%23d1d1d1" d="M9.37727 3.625l5.08154 6.93523L19.54036 3.625"/></svg>') center right no-repeat;padding-right:3rem}select:focus{background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 29 14" width="29"><path fill="%230069d9" d="M9.37727 3.625l5.08154 6.93523L19.54036 3.625"/></svg>')}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;margin-bottom:.5rem}fieldset{border-width:0}input[type=checkbox],input[type=radio]{display:inline}.label-inline{display:inline-block;font-weight:400;margin-left:.5rem}.row{display:flex;flex-direction:column;width:100%}.phx-logo,.phx-logo img,.row .column,header nav a{display:block}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{flex:1 1 auto;margin-left:0;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{-ms-grid-row-align:center;align-self:center}@media (min-width:40rem){.row{flex-direction:row;margin-left:-1rem;width:calc(100% + 2rem)}.row .column{margin-bottom:inherit;padding:0 1rem}}a{text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;width:100%}td,th{border-bottom:.1rem solid #e1e1e1;padding:1.2rem 1.5rem;text-align:left}td:last-child,th:last-child{padding-right:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2rem;margin-top:0}h3,h4{letter-spacing:-.08rem}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right}h1{font-size:3.6rem;line-height:1.25}h2{font-size:2.8rem;line-height:1.3}h3{font-size:2.2rem;line-height:1.35}h4{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h5{letter-spacing:-.05rem;font-size:1.6rem;letter-spacing:0;line-height:1.4}h6{font-size:1.4rem;letter-spacing:0;line-height:1.2}.container{margin:0 auto;max-width:80rem;padding:0 2rem;position:relative;width:100%}.phx-hero{text-align:center;border-bottom:1px solid #e3e3e3;background:#eee;border-radius:6px;padding:3em 3em 1em;margin-bottom:3rem;font-weight:200;font-size:120%}.phx-logo,.upload-component>.preview img{margin:1rem}.phx-hero input{background:#fff}.phx-logo{min-width:300px}header{width:100%;background:#fdfdfd;border-bottom:1px solid #eaeaea;margin-bottom:2rem}header section{align-items:center;display:flex;flex-direction:column;justify-content:space-between}header section :first-child{order:2}header section :last-child{order:1}header nav li,header nav ul{margin:0;padding:0;display:block;text-align:right;white-space:nowrap}header nav ul{margin:0 1rem 1rem}@media (min-width:40.0rem){header section{flex-direction:row}header nav ul{margin:1rem}.phx-logo{flex-basis:527px;margin:2rem 1rem}}details{margin:1rem 0 2rem}details:not([open]) summary::after{content:" (click to expand)"}.upload-demo{margin-bottom:2rem}.upload-entry{border-bottom:1px dashed #ccc;padding:.87rem 0}.upload-entry__progress{height:.87rem;width:100%;min-width:100%}.upload-entry__details{display:flex;padding-bottom:1rem}.upload-entry__details figure{display:flex;flex-basis:100%;margin:1rem 1.5rem 0 0}.upload-entry__details figure img{width:96px;object-fit:contain;align-self:flex-start;padding-right:.87rem}.upload-entry__details figure figcaption{flex:1 1 auto;align-self:center}.avatar>img,output>figure img{margin:1rem 1.5rem 0 0}output>figure figcaption{font-family:monospace;font-size:smaller}#media-recorder-demo .button{border-color:#f26f40}#media-recorder-demo .button-outline{font-size:2rem;transition:.4s;cursor:pointer;color:#f26f40}#media-recorder-demo .button-outline:focus,#media-recorder-demo .button-outline:hover{background:#f26f40;color:#fff}.alert:empty,.phx-no-feedback .invalid-feedback,.phx-no-feedback.invalid-feedback{display:none}.phx-click-loading{opacity:.5;transition:opacity 1s ease-out}.phx-disconnected{cursor:wait}.phx-disconnected *{pointer-events:none}.phx-modal{opacity:1!important;position:fixed;z-index:1;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:rgba(0,0,0,.4)}.phx-modal-content{background-color:#fefefe;margin:15% auto;padding:20px;border:1px solid #888;width:80%}.phx-modal-close{color:#aaa;float:right;font-size:28px;font-weight:700}.phx-modal-close:focus,.phx-modal-close:hover{color:#000;text-decoration:none;cursor:pointer}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.invalid-feedback{color:#a94442;display:block;margin:-1rem 0 2rem}.visually-hidden{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}</style>
<script src="https://cdn.jsdelivr.net/npm/phoenix@1.7.2/priv/static/phoenix.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/phoenix_live_view@0.20.0/priv/static/phoenix_live_view.min.js"></script>
<script>
let liveSocket = new window.LiveView.LiveSocket("/live", window.Phoenix.Socket)
liveSocket.connect()
</script>
<%= @inner_content %>
"""
end
def render(assigns) do
~H"""
<h1>Automatic Uploads Example</h1>
<p>
Uploads begin automatically. In this example, the <code>chunk_size</code>
has been set very low so you can more easily observe the progress state changes.
</p>
<section class="row upload-demo">
<section class="column">
<h2>Upload</h2>
<p>You may add up to <%= @uploads.exhibit.max_entries %> exhibits at a time.</p>
<%= for error <- upload_errors(@uploads.exhibit) do %>
<p class="alert alert-danger"><%= upload_error_to_string(error) %></p>
<% end %>
<form id="auto-form" phx-change="validate">
<.live_file_input upload={@uploads.exhibit} />
</form>
<section
class="pending-uploads"
phx-drop-target={@uploads.exhibit.ref}
style="min-height: 100%;"
>
<h3>Pending Uploads (<%= length(@uploads.exhibit.entries) %>)</h3>
<%= for entry <- @uploads[:exhibit].entries do %>
<div>
<div>
<%= entry.uuid %><br />
<a
href="#"
phx-click="cancel-upload"
phx-value-ref={entry.ref}
class="upload-entry__cancel"
>
Cancel Upload
</a>
<%= for error <- upload_errors(@uploads.exhibit, entry) do %>
<p class="alert alert-danger"><%= upload_error_to_string(error) %></p>
<% end %>
</div>
</div>
<% end %>
</section>
</section>
<section class="column">
<h2>Uploaded Files (<%= length(@uploaded_files) %>)</h2>
<.figure_group paths={@uploaded_files} />
</section>
</section>
"""
end
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(:uploaded_files, [])
|> allow_upload(:exhibit,
accept: :any,
max_entries: 2,
max_file_size: 100 * 1_024 * 1_024,
chunk_size: 256,
auto_upload: true,
progress: &handle_progress/3
)}
end
# with auto_upload: true we can consume files here
defp handle_progress(:exhibit, entry, socket) do
if entry.done? do
uuid =
consume_uploaded_entry(socket, entry, fn _meta ->
{:ok, entry.uuid}
end)
{:noreply, update(socket, :uploaded_files, &[uuid | &1])}
else
{:noreply, socket}
end
end
def handle_event("validate", _params, socket) do
{:noreply, socket}
end
def handle_event("cancel-upload", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :exhibit, ref)}
end
attr :paths, :list, required: true, doc: "A list of URI paths"
def figure_group(assigns), do: ~H"""
<div data-figure-group>
<.figure :for={path <- @paths} src={path} caption={path} />
</div>
"""
attr :src, :string, required: true, doc: "The absolute URI path to the image"
attr :caption, :string, default: ""
slot :inner_block
def figure(assigns), do: ~H"""
<figure>
<img src={@src} />
<figcaption :if={@caption != ""}><%= @caption %></figcaption>
</figure>
"""
@doc """
Returns an error message for an upload error.
See [`upload_errors/2`](`Phoenix.Component.upload_errors/2`).
"""
def upload_error_to_string(:too_large), do: "The file is too large"
def upload_error_to_string(:too_many_files), do: "You have selected too many files"
def upload_error_to_string(:not_accepted), do: "You have selected an unacceptable file type"
def upload_error_to_string(:external_client_failure), do: "Something went terribly wrong"
end
defmodule Example.Router do
use Phoenix.Router
import Phoenix.LiveView.Router
pipeline :browser do
plug(:accepts, ["html"])
end
scope "/", Example do
pipe_through(:browser)
live("/", HomeLive, :index)
end
end
defmodule Example.Endpoint do
use Phoenix.Endpoint, otp_app: :sample
socket("/live", Phoenix.LiveView.Socket)
plug(Example.Router)
end
{:ok, _} = Supervisor.start_link([Example.Endpoint,
{Phoenix.PubSub, name: Example.PubSub},
], strategy: :one_for_one)
Process.sleep(:infinity)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment