Fix JS and JS-based image upload

This commit is contained in:
Christoph Haas 2025-05-02 00:38:42 +02:00
parent 36984ff1d4
commit fa8a7fc77a
6 changed files with 95 additions and 51 deletions

View file

@ -1,21 +1,23 @@
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails" // import "@hotwired/turbo-rails"
import "controllers" // import "controllers"
// Initialize the upload form. // Initialize the upload form.
function upload_form_init() { function upload_form_init() {
// Disable the submit button if Javascript is supported. // Disable the submit button if Javascript is supported.
// Upload will start right after selecting images. // Upload will start right after selecting images.
// $("#file-submit").hide(); const fileSubmit = document.getElementById("file-submit");
document.getElementById("file-submit").style.display = "none"; if (fileSubmit)
{
fileSubmit.style.display = "none";
}
// Install event handler to notice when files were selected. // Install event handler to notice when files were selected.
// $("#file").on("change", upload_form_files_selected); document.getElementById("file")?.addEventListener("change", upload_form_files_selected);
document.getElementById("file").addEventListener("change", upload_form_files_selected);
} }
function upload_form_files_selected() { function upload_form_files_selected() {
no_files = this.files.length; var no_files = this.files.length;
if (no_files > 0) { if (no_files > 0) {
document.getElementById("file-label").innerHTML = `${no_files} files selected - uploading…`; document.getElementById("file-label").innerHTML = `${no_files} files selected - uploading…`;
document.getElementById("file-select-button").disabled = true; document.getElementById("file-select-button").disabled = true;
@ -28,7 +30,7 @@ function upload_form_files_selected() {
} }
// Handle Ctrl-V to paste an image directly // Handle Ctrl-V to paste an image directly
export function upload_paste_handler(event) { function upload_paste_handler(event, uploadUrl) {
console.log("paste!"); console.log("paste!");
var items = (event.clipboardData || event.originalEvent.clipboardData).items; var items = (event.clipboardData || event.originalEvent.clipboardData).items;
@ -48,45 +50,48 @@ export function upload_paste_handler(event) {
console.log(item.type); console.log(item.type);
var file = item.getAsFile(); var file = item.getAsFile();
console.log(file); console.log(file);
upload_file_with_ajax(file); upload_file_with_ajax(file, uploadUrl);
} }
} }
function upload_file_with_ajax(file) { function upload_file_with_ajax(file, uploadUrl) {
if (file == null) { if (file == null) {
console.log("File was null. Strange."); console.log("File was null. Strange.");
display_error("Maybe the image was too large."); display_error("Maybe the image was too large.");
return; return;
} }
var formData = new FormData(); var formData = new FormData();
formData.append("file", file); formData.append("file", file);
console.log(formData);
fetch(ajax_upload_url, { fetch(uploadUrl, {
method: "POST", method: "POST",
body: formData, body: formData,
headers: { headers: {
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content,
// 'Content-Type' is omitted to let the browser set it automatically // 'Content-Type' is omitted to let the browser set it automatically
// with the proper boundary for FormData // with the proper boundary for FormData
}, },
}) })
.then(async (response) => { .then(async (response) => {
if (!response.ok) { if (!response.ok) {
const errorData = await response.json(); const errorData = await response.json();
console.log(`AJAX file upload returned error: ${response.statusText}`); console.log(`AJAX file upload returned error: ${response.statusText}`);
display_error(errorData); display_error(errorData);
throw new Error("Upload failed"); throw new Error("Upload failed");
} }
return response.json(); return response.json();
}) })
.then((data) => { .then((data) => {
console.log("AJAX file upload was ok"); console.log("AJAX file upload was ok");
window.location.replace(after_upload_url); window.location.replace(after_upload_url);
}) })
.catch((error) => { .catch((error) => {
if (error.message !== "Upload failed") { if (error.message !== "Upload failed") {
console.log(`AJAX file upload error: ${error}`); console.log(`AJAX file upload error: ${error}`);
} }
}); });
// $.ajax(ajax_upload_url, { // $.ajax(ajax_upload_url, {
// type: "POST", // type: "POST",
@ -107,7 +112,8 @@ function upload_file_with_ajax(file) {
// }); // });
} }
function display_error(msg) { function display_error(msg) { console.log("foo");
// $("#messages").html( // $("#messages").html(
// '<div class="callout alert alert-callout-subtle radius">Sorry, pasting did not work. ' + msg + "</div>" // '<div class="callout alert alert-callout-subtle radius">Sorry, pasting did not work. ' + msg + "</div>"
// ); // );
@ -117,3 +123,21 @@ function display_error(msg) {
</div> </div>
`; `;
} }
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM loaded");
// Handle uploads on specific views/pages. The DIV with ID
// #data-js-upload-form carries data attributes that tell us where
// uploads will be sent from Javascript.
let element = document.getElementById("data-js-upload-form");
if (element) {
console.log("upload_form_init");
// console.log(element.dataset);
// console.log(element.dataset.uploadUrl);
upload_form_init(element.dataset.uploadUrl);
// document.onpaste = upload_paste_handler(element.dataset.uploadUrl);
document.onpaste = function(event) { upload_paste_handler(event, element.dataset.uploadUrl); }
}
});

View file

@ -1,5 +1,9 @@
/ Render this callout on top of the package's details page / Render this callout on top of the package's details page
/ if images have just been uploaded. / if images have just been uploaded.
// Make variables accessible by the JS that handles uploads
#data-js-upload-form data-upload-url=upload_receive_json_path
- if (@valid_images and @valid_images.any?) or (@invalid_images and @invalid_images.any?) - if (@valid_images and @valid_images.any?) or (@invalid_images and @invalid_images.any?)
.callout.warning .callout.warning

View file

@ -66,13 +66,8 @@
= render(partial: 'details_rightbox', locals: {pkg: @package}) = render(partial: 'details_rightbox', locals: {pkg: @package})
javascript: javascript:
import {upload_paste_handler} from 'aplication';
// Handle Ctrl-V to paste an image directly
document.onpaste = upload_paste_handler;
// Where to send POST requests for uploads // Where to send POST requests for uploads
var ajax_upload_url = '#{{upload_receive_json_path}}'; let ajax_upload_url = '#{{upload_receive_json_path}}';
// Where to go to after a successful image upload // Where to go to after a successful image upload
var after_upload_url = '#{{package_path(@package)}}'; let after_upload_url = '#{{package_path(@package)}}';

View file

@ -64,18 +64,11 @@ h1 = "Upload screenshots for #{@package.name}"
' from a shell using after setting "export LANG=C". ' from a shell using after setting "export LANG=C".
li can be pasted here from your clipboard (Ctrl-V) li can be pasted here from your clipboard (Ctrl-V)
/ javascript: javascript:
/ // Hide the submit button unless images are selected for upload // todo: use data attributes to pass this information to javascript
/ // $(document).ready(upload_form_init); // https://chat.deepseek.com/a/chat/s/41a4350b-7799-42bf-aec9-3c205b22ab24
/ import upload_form_init from '../../assets/javascripts/upload.js'; // Where to send POST requests for uploads
/ document.addEventListener('DOMContentLoaded', upload_form_init); let ajax_upload_url = '#{{upload_receive_json_path}}';
/ // import upload_paste_handler from '../../assets/javascripts/debshots.js'; // Where to go to after a successful image upload
/ // Handle Ctrl-V to paste an image directly let after_upload_url = '#{{package_path(@package)}}';
/ document.onpaste = upload_paste_handler;
/ // Where to send POST requests for uploads
/ var ajax_upload_url = '#{{upload_receive_json_path}}';
/ // Where to go to after a successful image upload
/ var after_upload_url = '#{{package_path(@package)}}';

7
config/importmap.rb Normal file
View file

@ -0,0 +1,7 @@
# Pin npm packages by running ./bin/importmap
pin 'application'
pin '@hotwired/turbo-rails', to: 'turbo.min.js'
pin '@hotwired/stimulus', to: 'stimulus.min.js'
pin '@hotwired/stimulus-loading', to: 'stimulus-loading.js'
pin_all_from 'app/javascript/controllers', under: 'controllers'

View file

@ -0,0 +1,21 @@
const path = require("path")
const webpack = require("webpack")
module.exports = {
mode: "production",
devtool: "source-map",
entry: {
application: "./app/javascript/application.js"
},
output: {
filename: "[name].js",
sourceMapFilename: "[file].map",
chunkFormat: "module",
path: path.resolve(__dirname, '..', '..', 'app/assets/builds')
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
})
]
}