<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="284786" data-post-id="284786">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="GazeIntoTheAbyss" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  GazeIntoTheAbyss
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Are you using the default S3Uploader?</p>
<p>I was asking about this yesterday or the day before and am currently at this point:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">def presign_upload_banner(entry, socket) do
    uploads = socket.assigns.uploads
    key = s3_key(entry)

    config = %{
      region: "eu-west-2",
      access_key_id: System.fetch_env!("AWS_ACCESS_KEY_ID"),
      secret_access_key: System.fetch_env!("AWS_SECRET_ACCESS_KEY")
    }
  
    {:ok, fields} =
      SimpleS3Upload.sign_form_upload(config, @bucket,
        key: key,
        content_type: entry.client_type,
        max_file_size: uploads[entry.upload_config].max_file_size,
        expires_in: :timer.hours(1)
      )
  
    meta = %{uploader: "S3Banner", key: key, url: s3_host(), fields: fields, canvas_height: 360}
    {:ok, meta, socket}
  end
</code></pre>
<p>360 is a placeholder for a variable value. You can add values to the meta and access them in the Uploader JS using entry.meta.YOURVALUE</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">Uploaders.S3Banner = function(entries, onViewError) {
  entries.forEach(entry =&gt; {
    let formData = new FormData();
    let {url, fields} = entry.meta;
    Object.entries(fields).forEach(([key, val]) =&gt; formData.append(key, val));

    let img = new Image();
    img.onload = function() {
 
      const sliceHeight = entry.meta.canvas_height;
      let canvas = document.createElement('canvas');
      canvas.width = screen.width;
      canvas.height = sliceHeight;

      let ctx = canvas.getContext('2d');
      const cropStart = (img.height - sliceHeight) / 2;
      const cropEnd = cropStart + sliceHeight;

      ctx.drawImage(img, 0, cropStart, img.width, cropEnd - cropStart, 0, 0, canvas.width, canvas.height);

      // convert the canvas content to a Blob object and add it to the form data
      canvas.toBlob(function(blob) {
        formData.append("file", blob);

        // send the form data via XHR
        let xhr = new XMLHttpRequest();
        onViewError(() =&gt; xhr.abort());
        xhr.onload = () =&gt; xhr.status === 204 ? entry.progress(100) : entry.error();
        xhr.onerror = () =&gt; entry.error();
        xhr.upload.addEventListener("progress", (event) =&gt; {
          if(event.lengthComputable){
            let percent = Math.round((event.loaded / event.total) * 100);
            if(percent &lt; 100){ entry.progress(percent); }
          }
        });

        xhr.open("POST", url, true);
        xhr.send(formData);
      });
    };
    img.src = URL.createObjectURL(entry.file);
  });
}
</code></pre>
<p>The above will take the uploaded image and create a screen width banner that is 360px in height. It will crop the image above and below to leave a “slice” of the original image.</p>
<p>You can obviously just change the JS to change the dimensions and resize as well though.</p>
<p>What I haven’t figured out how to do is validate the upladed image size. I can add an IF statement to check the dimensions and make the above return an error, but no idea how to send an event to the server yet as still learning.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="284786" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/how-do-i-modify-the-upload-content-before-the-upload/44290/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-284786" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="284786"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #11"></div>
  </section>
</div>
    <div class="postbit" id="284787" data-post-id="284787">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="sreyansjain" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/sreyansjain/120/16464_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  sreyansjain
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="GazeIntoTheAbyss" data-post="12" data-topic="44290">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/g/eb8c5e/48.png" class="avatar"> GazeIntoTheAbyss:</div>
<blockquote>
<p>he dimensions and make the above return an err</p>
</blockquote>
</aside>
<p>I am trying to upload to Digital Ocean Spaces which is similar to S3.<br>
Will post the code once I am done.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="284787" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/how-do-i-modify-the-upload-content-before-the-upload/44290/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-284787" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="284787"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #12"></div>
  </section>
</div>
    <div class="postbit" id="284849" data-post-id="284849">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="mcrumm" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/mcrumm/120/23032_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  mcrumm
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Phoenix Core Team</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>One option is to use a hidden <code>&lt;.live_file_input /&gt;</code> and then use <a href="https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook" rel="noopener nofollow ugc"><code>this.upload()</code></a> on a client-side hook to enqueue the resized file. Here is a brief example:</p>
<p>In your HEEx template:</p>
<pre data-code-wrap="html"><code class="lang-html">&lt;!-- Users select files with this file input. --&gt;
&lt;input type="file" id="resize-target" phx-hook="ResizeInput" data-upload-target="images" /&gt;

&lt;form phx-change="change" phx-submit="submit"&gt;
  &lt;!-- The live file input is hidden because users will not interact with it directly. --&gt;
  &lt;.live_file_input upload={@uploads.images} style="display:none;" /&gt;
  &lt;button type="submit"&gt;Upload&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<p>…and the <code>ResizeInput</code> hook:</p>
<pre data-code-wrap="js"><code class="lang-js">// assets/js/resize_input.js
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) =&gt; {
      e.preventDefault()
      e.stopImmediatePropagation()

      if (this.el.files &amp;&amp; this.el.files.length &gt; 0) {
        convertResizeImageFiles(this.el.files, 300, (resizedImageBlob) =&gt; {
          // Enqueues the resized blob on the &lt;.live_file_input /&gt;.
          this.upload(this.getUploadTarget(), [resizedImageBlob])
        })
      }
    })
  }
}
</code></pre>
<p>I put the visible file input outside of the live form so that its change events don’t trigger additional messages to the server.</p>
<p>You can find a working demo in my live_upload_example repo: <a href="https://github.com/mcrumm/live_upload_example/commit/5c797aabf7b6d89a4dd622b4f937876bfbd3f975" class="inline-onebox" rel="noopener nofollow ugc">Add client-side resize demo · mcrumm/live_upload_example@5c797aa · GitHub</a></p>
<p>Hope that helps!</p>
<p>PS– Please forgive my JavaScript. The <code>convertResizeImageFiles</code> callback should really receive a list of files, that way the upload function would only need to be invoked once for each batch of selected files, however I am currently on vacation so this is the best I could do on short notice <img src="https://forum.elixirforum.com/images/emoji/apple/smiley.png?v=15" title=":smiley:" class="emoji" alt=":smiley:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="284849" data-batch-url="/posts/batch_likers">
                        4
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/how-do-i-modify-the-upload-content-before-the-upload/44290/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-284849" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="284849"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #13"></div>
  </section>
</div>
    <div class="postbit" id="285019" data-post-id="285019">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="sreyansjain" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/sreyansjain/120/16464_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  sreyansjain
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote group-Phoenix-Core-Team" data-username="mcrumm" data-post="14" data-topic="44290">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/mcrumm/48/23032_2.png" class="avatar"> mcrumm:</div>
<blockquote>
<p>d files, however I am currently on vacation so this i</p>
</blockquote>
</aside>
<p>Here is the code that I am using now to upload to Digital Ocean Spaces</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defp presign_upload(entry, socket) do
    filename = "#{entry.uuid}_#{entry.client_name}"

    # user_id = socket.assigns.user_id
    config = ExAws.Config.new(:s3)
    query_params = [{"x-amz-acl", "public-read"}]

    {:ok, url} =
      ExAws.S3.presigned_url(
        config,
        :put,
        "documents",
        "#{socket.assigns.current_student.username}/#{filename}",
        expires_in: 300,
        query_params: query_params
      )

    meta = %{uploader: "Document", url: url}
    {:ok, meta, socket}
  end
</code></pre>
<p>Now the JS code in the app.js file</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">Uploaders.Document = function(entries, onViewError) {
  entries.forEach(entry =&gt; {
    let file = entry.file
    let url = entry.meta.url
    if (file.type.indexOf("image") &gt;= 0) {
      var reader = new FileReader();
      reader.onload = function(readerEvent) {
        var image = new Image();
        image.onload = function(imageEvent) {
          var canvas = document.createElement('canvas'),
            max_width = 1200,// TODO : pull max size from a site config
            max_height = 800,// TODO : pull max size from a site config
            width = image.width,
            height = image.height;
          if (width &gt; max_width) {
            height *= max_width / width;
            width = max_width;
          }
          if (height &gt; max_height) {
            width *= max_height / height;
            height = max_height;
          }
          canvas.width = width;
          canvas.height = height;
          canvas.getContext('2d').drawImage(image, 0, 0, width, height);
          var dataUrl = canvas.toDataURL(file.type);
          let blob = dataURItoBlob(dataUrl);
          var xhr = new XMLHttpRequest()
          onViewError(() =&gt; xhr.abort())
          xhr.upload.addEventListener("loadstart", function() {
            // console.log("Load started")
          });

          xhr.upload.addEventListener("progress", function(e) {
            if (e.lengthComputable) {
              let percent = Math.round((e.loaded / e.total) * 100)
              entry.progress(percent)
            }
          })

          xhr.addEventListener('readystatechange', function(e) {
            if (xhr.readyState == 4 &amp;&amp; xhr.status == 200) {
            }
            else if (xhr.readyState == 4 &amp;&amp; xhr.status != 200) {
              entry.error("Upload Failed")
            }
          })
          xhr.open('PUT', url, true)
          xhr.setRequestHeader("x-amz-acl", "public-read")
          xhr.setRequestHeader("Content-Type", blob.type)
          xhr.send(blob)
        }
        image.src = readerEvent.target.result;
      }
      reader.readAsDataURL(file);
    } else {
      var xhr = new XMLHttpRequest()
      onViewError(() =&gt; xhr.abort())
      xhr.onload = () =&gt; xhr.status === 204 ? entry.progress(100) : entry.error()
      xhr.upload.addEventListener("loadstart", function() {
        console.log("Load started")
      });

      xhr.upload.addEventListener("progress", function(e) {
        if (e.lengthComputable) {
          let percent = Math.round((e.loaded / e.total) * 100)
          entry.progress(percent)
        }
      })

      xhr.addEventListener('readystatechange', function(e) {
        if (xhr.readyState == 4 &amp;&amp; xhr.status == 200) {
          // console.log("Done")
        }
        else if (xhr.readyState == 4 &amp;&amp; xhr.status != 200) {
          // console.log("Error")
          entry.error("Upload Failed")
        }
      })
      xhr.open('PUT', url, true)
      xhr.setRequestHeader("x-amz-acl", "public-read")
      xhr.setRequestHeader("Content-Type", file.type)
      xhr.send(file)
    }
  })
}
</code></pre>
<p>The dataURItoBlob function</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">function dataURItoBlob(dataURI) {
  var byteString = atob(dataURI.split(',')[1]);
  var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
  var ab = new ArrayBuffer(byteString.length);
  var ia = new Uint8Array(ab);


  for (var i = 0; i &lt; byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i);
  }

  var blob = new Blob([ab], { type: mimeString });
  return blob;

}
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="285019" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/how-do-i-modify-the-upload-content-before-the-upload/44290/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-285019" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="285019"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #14"></div>
  </section>
</div>
    <div class="postbit" id="285020" data-post-id="285020">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="sreyansjain" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/sreyansjain/120/16464_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  sreyansjain
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>This is very nice and can be used to consume files directly on the server instead of loading to an external storage.<br>
Thank you so much for sharing (while being on vacation, really appreciate it.).<br>
Enjoy your vacation.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="285020" data-batch-url="/posts/batch_likers">
                        2
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/how-do-i-modify-the-upload-content-before-the-upload/44290/16">Post #15</a>
	                </div>
	            </div>
              <div id="likers-container-285020" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="285020"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>