diff --git a/app/controllers/ocr_controller.rb b/app/controllers/ocr_controller.rb
index cd4ac44a..97a84760 100644
--- a/app/controllers/ocr_controller.rb
+++ b/app/controllers/ocr_controller.rb
@@ -33,6 +33,10 @@ def scan
ocrresult.image.attach(file)
ocrresult.save
+ if params[:files].length > 1
+ params[:files][1..].each { |f| ocrresult.extra_images.attach(f) }
+ end
+
logger.debug "OCR data id stored in flash: #{ocrresult.id}"
# If multiple recipes detected, redirect to selection page
diff --git a/app/controllers/recipes_controller.rb b/app/controllers/recipes_controller.rb
index b8b171ac..a638ea07 100644
--- a/app/controllers/recipes_controller.rb
+++ b/app/controllers/recipes_controller.rb
@@ -61,6 +61,7 @@ def create
ocrresult = OcrResult.find_by(id: params[:ocrresult_id])
if ocrresult.present?
@recipe.recipe_images.attach(ocrresult.image.blob) if ocrresult.image.attached?
+ ocrresult.extra_images.each { |img| @recipe.recipe_images.attach(img.blob) }
end
end
diff --git a/app/javascript/controllers/dropzone_controller.js b/app/javascript/controllers/dropzone_controller.js
index 5c6063e7..0e552029 100644
--- a/app/javascript/controllers/dropzone_controller.js
+++ b/app/javascript/controllers/dropzone_controller.js
@@ -2,13 +2,20 @@ import { Controller } from "@hotwired/stimulus"
import { Utils } from "../src/utils"
export default class extends Controller {
- static targets = ["dropzone", "fileInput", "spinner", "successMessage", "errorMessage", "resetButton"]
- static values = { url: String }
+ static targets = ["dropzone", "fileInput", "spinner", "successMessage", "errorMessage", "resetButton", "previewContainer", "uploadButton"]
+ static values = { url: String, previewMode: Boolean }
connect() {
this.maxFiles = 10
this.acceptedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/heic', 'image/heif']
this.acceptedExtensions = ['.jpg', '.jpeg', '.png', '.webp', '.heic', '.heif']
+ this.pendingFiles = []
+ this.pendingObjectUrls = []
+
+ // Initialize button state in preview mode
+ if (this.previewModeValue && this.hasUploadButtonTarget) {
+ this.uploadButtonTarget.disabled = true
+ }
}
// Drag and drop handlers
@@ -58,8 +65,9 @@ export default class extends Controller {
// Hide any previous messages
this.hideMessages()
- // Validate file count
- if (files.length > this.maxFiles) {
+ // Validate file count (account for already-queued files in preview mode)
+ const totalCount = this.previewModeValue ? this.pendingFiles.length + files.length : files.length
+ if (totalCount > this.maxFiles) {
this.showError(`Maximum ${this.maxFiles} files allowed. You selected ${files.length} files.`)
return
}
@@ -81,8 +89,84 @@ export default class extends Controller {
return
}
- // All validations passed, upload files
- this.uploadFiles(files)
+ if (this.previewModeValue) {
+ for (let i = 0; i < files.length; i++) {
+ this.pendingFiles.push(files[i])
+ }
+ this.renderPreviews()
+ this.uploadButtonTarget.disabled = false
+ } else {
+ // All validations passed, upload files
+ this.uploadFiles(files)
+ }
+ }
+
+ renderPreviews() {
+ // Revoke old object URLs before rebuilding
+ this.pendingObjectUrls.forEach(url => URL.revokeObjectURL(url))
+ this.pendingObjectUrls = []
+
+ if (this.pendingFiles.length === 0) {
+ this.previewContainerTarget.classList.add('hidden')
+ this.previewContainerTarget.innerHTML = ''
+ return
+ }
+
+ this.previewContainerTarget.classList.remove('hidden')
+
+ const grid = document.createElement('div')
+ grid.className = 'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 mt-4'
+
+ this.pendingFiles.forEach((file, index) => {
+ const objectUrl = URL.createObjectURL(file)
+ this.pendingObjectUrls.push(objectUrl)
+
+ const card = document.createElement('div')
+ card.className = 'relative rounded-lg overflow-hidden border border-gray-200 bg-gray-50'
+ card.innerHTML = `
+
+