Skip to content

Commit

Permalink
Add client-side resize demo
Browse files Browse the repository at this point in the history
  • Loading branch information
mcrumm committed Apr 12, 2023
1 parent b4ac234 commit 5c797aa
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 1 deletion.
4 changes: 3 additions & 1 deletion assets/js/app.js
Expand Up @@ -7,10 +7,12 @@ import { LiveSocket } from "phoenix_live_view"
import Uploaders from "./uploaders"
import MediaRecorderDemo from "./media_recorder_demo"
import Croppr from "./croppr"
import ResizeInput from "./resize_input"

let hooks = {
Croppr,
MediaRecorderDemo
MediaRecorderDemo,
ResizeInput
}

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
Expand Down
37 changes: 37 additions & 0 deletions assets/js/image_utils.js
@@ -0,0 +1,37 @@
// Adapted from: https://codesalad.dev/blog/how-to-resize-an-image-in-10-lines-of-javascript-29
function resizeImage(imgEl, wantedWidth, callback) {
const canvas = document.createElement("canvas")
const ctx = canvas.getContext("2d")

const aspect = imgEl.width / imgEl.height

canvas.width = wantedWidth
canvas.height = wantedWidth / aspect

ctx.drawImage(imgEl, 0, 0, canvas.width, canvas.height)
return canvas.toBlob(callback, "image/jpeg", 0.9)
}

export function convertResizeImageFiles(fileList, wantedWidth, callback) {
Array.from(fileList).forEach(file => {
let reader = new FileReader();

reader.addEventListener("load", () => {
let imgEl = document.createElement("img")

imgEl.addEventListener("load", () => {

resizeImage(imgEl, wantedWidth, (blob) => {

callback(blob)

})

})

imgEl.src = reader.result
})

reader.readAsDataURL(file)
})
}
19 changes: 19 additions & 0 deletions assets/js/resize_input.js
@@ -0,0 +1,19 @@
import { convertResizeImageFiles } from './image_utils'

// This hook attaches to a custom input element to resize selected images.
export default ResizeInput = {
getUploadTarget() { return this.el.dataset.uploadTarget },
mounted() {
this.el.addEventListener("change", (e) => {
e.preventDefault()
e.stopImmediatePropagation()

if (this.el.files && this.el.files.length > 0) {
convertResizeImageFiles(this.el.files, 300, (resizedImageBlob) => {
// Enqueues the resized blob on the <.live_file_input />.
this.upload(this.getUploadTarget(), [resizedImageBlob])
})
}
})
}
}
4 changes: 4 additions & 0 deletions lib/drops_web/live/page_live.html.heex
Expand Up @@ -26,6 +26,10 @@
<dd>Record and upload an audio sample directly in the browser.</dd>
<dt><%= link("Croppr.js Demo", to: Routes.croppr_path(@socket, :index)) %></dt>
<dd>Crop and manipulate images before uploading.</dd>
<dt>
<.link navigate={Routes.uploads_resize_path(@socket, :index)}>Resize Demo</.link>
</dt>
<dd>Demonstrates resizing images before uploading.</dd>
<dt>
<%= link("Issue 2037", to: Routes.issues_external_upload_single_entry_path(@socket, :index)) %>
</dt>
Expand Down
78 changes: 78 additions & 0 deletions lib/drops_web/live/uploads_live/resize.ex
@@ -0,0 +1,78 @@
defmodule DropsWeb.UploadsLive.Resize do
use DropsWeb, :live_view

@impl Phoenix.LiveView
def render(assigns) do
~H"""
<h1>Resize Example</h1>
<p>This example illustrates how to resize an image before uploading it to the web server.</p>
<!-- Users select files with this file input. -->
<input type="file" id="resize-target" phx-hook="ResizeInput" data-upload-target="images" />
<form phx-change="change" phx-submit="submit">
<!-- The live file input is hidden because users will not interact with it directly. -->
<.live_file_input upload={@uploads.images} style="display:none;" />
<button type="submit">Upload</button>
</form>
<section class="row upload-demo">
<section class="column">
<h3>Pending Uploads</h3>
<div :for={entry <- @uploads.images.entries} class="upload-entry" id={"entry-#{entry.ref}"}>
<% # render in-flight progress using the reactive `entry.progress` %>
<DropsWeb.Uploads.progress entry={entry} />
<div class="upload-entry__details">
<% # <.live_img_preview> uses an internal hook to render a client-side image preview %>
<.live_img_preview entry={entry} />
<% # review the handle_event("cancel-upload") callback in the LiveView %>
<a
href="#"
phx-click="cancel-upload"
phx-value-ref={entry.ref}
class="upload-entry__cancel"
>
&times;
</a>
</div>
<% # upload_errors/2 returns error atoms per upload entry %>
<p :for={error <- upload_errors(@uploads.images, entry)} class="alert alert-danger">
<%= upload_error_to_string(error) %>
</p>
</div>
</section>
<section class="column">
<h3>Uploaded Images</h3>
<DropsWeb.Uploads.figure_group paths={@uploaded_files} />
</section>
</section>
"""
end

@impl Phoenix.LiveView
def handle_event("change", _, socket) do
{:noreply, socket}
end

@impl true
def handle_event("cancel-upload", %{"ref" => ref}, socket) do
{:noreply, cancel_upload(socket, :images, ref)}
end

@impl true
def handle_event("submit", _params, socket) do
{:noreply, DropsWeb.Uploads.consume_prepend_entries(socket, :images, :uploaded_files)}
end

@impl Phoenix.LiveView
def mount(_, _, socket) do
{:ok,
socket
|> assign(:uploaded_files, [])
|> allow_upload(:images, accept: ~w(image/*), max_entries: 25)}
end
end
1 change: 1 addition & 0 deletions lib/drops_web/router.ex
Expand Up @@ -32,6 +32,7 @@ defmodule DropsWeb.Router do
live "/uploads/component", ComponentUploadsLive, :index
live "/uploads/multi", MultiInputUploadsLive, :index
live "/uploads/external/auto", ExternalLive.Auto, :index
live "/uploads/resize", UploadsLive.Resize, :index

live "/issues/2037", IssuesLive.ExternalUploadSingleEntry, :index
live "/issues/2271", IssuesLive.Issue2271, :index
Expand Down

0 comments on commit 5c797aa

Please sign in to comment.