-
![]()
-
-
diff --git a/app/components/file_upload_component/file_upload_component.scss b/app/components/file_upload_component/file_upload_component.scss
index cede94190..9ad96b006 100644
--- a/app/components/file_upload_component/file_upload_component.scss
+++ b/app/components/file_upload_component/file_upload_component.scss
@@ -31,4 +31,8 @@
background-color: var(--uploads-dropzone-progress-bar-foreground-color);
}
}
+
+ &.dz-error .dz-error-message {
+ display: block;
+ }
}
diff --git a/app/javascript/src/javascripts/file_upload_component.js b/app/javascript/src/javascripts/file_upload_component.js
index 66981c1c0..f4f356d0d 100644
--- a/app/javascript/src/javascripts/file_upload_component.js
+++ b/app/javascript/src/javascripts/file_upload_component.js
@@ -33,16 +33,19 @@ export default class FileUploadComponent {
let dropzone = new Dropzone(this.$dropTarget.get(0), {
url: "/uploads.json",
- paramName: "upload[file]",
+ paramName: "upload[files]",
clickable: this.$dropzone.get(0),
previewsContainer: this.$dropzone.get(0),
thumbnailHeight: null,
thumbnailWidth: null,
addRemoveLinks: false,
- maxFiles: 1,
+ parallelUploads: this.maxFiles,
+ maxFiles: this.maxFiles,
maxFilesize: this.maxFileSize,
maxThumbnailFilesize: this.maxFileSize,
timeout: 0,
+ uploadMultiple: true,
+ createImageThumbnails: false,
acceptedFiles: "image/jpeg,image/png,image/gif,video/mp4,video/webm",
previewTemplate: this.$component.find(".dropzone-preview-template").html(),
});
@@ -54,13 +57,6 @@ export default class FileUploadComponent {
dropzone.on("addedfile", file => {
this.$dropzone.removeClass("error");
this.$dropzone.find(".dropzone-hint").hide();
-
- // Remove all files except the file just added.
- dropzone.files.forEach(f => {
- if (f !== file) {
- dropzone.removeFile(f);
- }
- });
});
dropzone.on("success", file => {
@@ -70,7 +66,9 @@ export default class FileUploadComponent {
});
dropzone.on("error", (file, msg) => {
- this.$dropzone.addClass("error");
+ this.$dropzone.find(".dropzone-hint").show();
+ dropzone.removeFile(file);
+ Utility.error(msg);
});
return dropzone;
@@ -164,6 +162,10 @@ export default class FileUploadComponent {
return Number(this.$component.attr("data-max-file-size")) / (1024 * 1024);
}
+ get maxFiles() {
+ return Number(this.$component.attr("data-max-files-per-upload"));
+ }
+
// The element to listen for drag and drop events and paste events. By default,
// it's the `.file-upload-component` element. If `data-drop-target` is the `body`
// element, then you can drop images or paste URLs anywhere on the page.
diff --git a/app/models/upload.rb b/app/models/upload.rb
index 539e1e7aa..21581cb5c 100644
--- a/app/models/upload.rb
+++ b/app/models/upload.rb
@@ -4,7 +4,9 @@ class Upload < ApplicationRecord
extend Memoist
class Error < StandardError; end
- attr_accessor :file
+ MAX_FILES_PER_UPLOAD = 100
+
+ attr_accessor :files
belongs_to :uploader, class_name: "User"
has_many :upload_media_assets, dependent: :destroy
@@ -13,6 +15,7 @@ class Upload < ApplicationRecord
normalize :source, :normalize_source
+ validates :files, length: { maximum: MAX_FILES_PER_UPLOAD, message: "can't have more than #{MAX_FILES_PER_UPLOAD} files per upload" }
validates :source, format: { with: %r{\Ahttps?://}i, message: "is not a valid URL" }, if: -> { source.present? }
validates :referer_url, format: { with: %r{\Ahttps?://}i, message: "is not a valid URL" }, if: -> { referer_url.present? }
validate :validate_file_and_source, on: :create
@@ -55,9 +58,9 @@ class Upload < ApplicationRecord
concerning :ValidationMethods do
def validate_file_and_source
- if file.present? && source.present?
+ if files.present? && source.present?
errors.add(:base, "Can't give both a file and a source")
- elsif file.blank? && source.blank?
+ elsif files.blank? && source.blank?
errors.add(:base, "No file or source given")
end
end
@@ -86,8 +89,8 @@ class Upload < ApplicationRecord
end
def async_process_upload!
- if file.present?
- ProcessUploadJob.perform_now(self)
+ if files.present?
+ process_upload!
elsif source.present?
ProcessUploadJob.perform_later(self)
else
@@ -98,12 +101,10 @@ class Upload < ApplicationRecord
def process_upload!
update!(status: "processing")
- if file.present?
- media_file = MediaFile.open(file.tempfile)
- media_asset = MediaAsset.upload!(media_file)
- upload_media_asset = UploadMediaAsset.new(media_asset: media_asset, source_url: "file://#{file.original_filename}", status: "active")
-
- update!(upload_media_assets: [upload_media_asset], status: "completed", media_asset_count: 1)
+ if files.present?
+ upload_media_assets = files.map do |_index, file|
+ UploadMediaAsset.new(file: file.tempfile, source_url: "file://#{file.original_filename}")
+ end
elsif source.present?
page_url = source_strategy.page_url
image_urls = source_strategy.image_urls
@@ -115,11 +116,11 @@ class Upload < ApplicationRecord
upload_media_assets = image_urls.map do |image_url|
UploadMediaAsset.new(source_url: image_url, page_url: page_url, media_asset: nil)
end
-
- update!(upload_media_assets: upload_media_assets, media_asset_count: upload_media_assets.size)
else
raise Error, "No file or source given" # Should never happen
end
+
+ update!(upload_media_assets: upload_media_assets, media_asset_count: upload_media_assets.size)
rescue Exception => e
update!(status: "error", error: e.message)
end
diff --git a/app/models/upload_media_asset.rb b/app/models/upload_media_asset.rb
index 3521dd2b7..3cdbc591f 100644
--- a/app/models/upload_media_asset.rb
+++ b/app/models/upload_media_asset.rb
@@ -3,6 +3,8 @@
class UploadMediaAsset < ApplicationRecord
extend Memoist
+ attr_accessor :file
+
belongs_to :upload
belongs_to :media_asset, optional: true
has_one :post, through: :media_asset
@@ -62,16 +64,22 @@ class UploadMediaAsset < ApplicationRecord
end
def async_process_upload!
- return if file_upload?
- ProcessUploadMediaAssetJob.perform_later(self)
+ if file.present?
+ process_upload!
+ else
+ ProcessUploadMediaAssetJob.perform_later(self)
+ end
end
def process_upload!
- return if file_upload?
update!(status: :processing)
- strategy = Sources::Strategies.find(source_url)
- media_file = strategy.download_file!(source_url)
+ if file.present?
+ media_file = MediaFile.open(file)
+ else
+ media_file = source_strategy.download_file!(source_url)
+ end
+
MediaAsset.upload!(media_file) do |media_asset|
update!(media_asset: media_asset)
end
diff --git a/app/policies/upload_policy.rb b/app/policies/upload_policy.rb
index 70f787092..8c70c1606 100644
--- a/app/policies/upload_policy.rb
+++ b/app/policies/upload_policy.rb
@@ -18,6 +18,6 @@ class UploadPolicy < ApplicationPolicy
end
def permitted_attributes
- %i[file source referer_url]
+ [:source, :referer_url, files: {}]
end
end
diff --git a/test/factories/upload.rb b/test/factories/upload.rb
index 31940aa64..e2c040c14 100644
--- a/test/factories/upload.rb
+++ b/test/factories/upload.rb
@@ -17,7 +17,7 @@ FactoryBot.define do
status { "completed" }
source { nil }
media_asset_count { 1 }
- file { Rack::Test::UploadedFile.new("#{Rails.root}/test/files/test.jpg") }
+ files { { "0" => Rack::Test::UploadedFile.new("#{Rails.root}/test/files/test.jpg") } }
upload_media_assets do
[build(:upload_media_asset, media_asset: build(:media_asset, file: "test/files/test.jpg"), source_url: "file://test.jpg", status: "active")]
diff --git a/test/functional/uploads_controller_test.rb b/test/functional/uploads_controller_test.rb
index 66dd62999..00b251620 100644
--- a/test/functional/uploads_controller_test.rb
+++ b/test/functional/uploads_controller_test.rb
@@ -162,7 +162,7 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
assert_no_difference("Upload.count") do
file = File.open("test/files/test.jpg")
source = "https://files.catbox.moe/om3tcw.webm"
- post_auth uploads_path(format: :json), @user, params: { upload: { file: file, source: source }}
+ post_auth uploads_path(format: :json), @user, params: { upload: { files: { "0" => file }, source: source }}
end
assert_response 422
@@ -171,7 +171,7 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
should "fail if given an unsupported filetype" do
file = Rack::Test::UploadedFile.new("test/files/ugoira.json")
- post_auth uploads_path(format: :json), @user, params: { upload: { file: file }}
+ post_auth uploads_path(format: :json), @user, params: { upload: { files: { "0" => file } }}
assert_response 201
assert_match("File is not an image or video", Upload.last.error)
@@ -247,12 +247,9 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
context "when re-uploading a media asset stuck in the 'processing' state" do
should "mark the asset as failed" do
asset = create(:media_asset, file: File.open("test/files/test.jpg"), status: "processing")
- file = Rack::Test::UploadedFile.new("test/files/test.jpg")
+ create_upload!("test/files/test.jpg", user: @user)
- post_auth uploads_path, @user, params: { upload: { file: file }}
upload = Upload.last
-
- assert_redirected_to upload
assert_match("Upload failed, try again", upload.reload.error)
assert_equal("failed", asset.reload.status)
end
@@ -287,6 +284,24 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
# should_upload_successfully("test/files/compressed.swf")
end
+ context "uploading multiple files from your computer" do
+ should "work" do
+ files = {
+ "0" => Rack::Test::UploadedFile.new("test/files/test.jpg"),
+ "1" => Rack::Test::UploadedFile.new("test/files/test.png"),
+ "2" => Rack::Test::UploadedFile.new("test/files/test.gif"),
+ }
+
+ post_auth uploads_path(format: :json), @user, params: { upload: { files: files }}
+
+ upload = Upload.last
+ assert_response 201
+ assert_equal("", upload.error.to_s)
+ assert_equal("completed", upload.status)
+ assert_equal(3, upload.media_asset_count)
+ end
+ end
+
context "uploading a file from a source" do
should_upload_successfully("https://www.artstation.com/artwork/04XA4")
should_upload_successfully("https://dantewontdie.artstation.com/projects/YZK5q")
diff --git a/test/test_helpers/upload_test_helper.rb b/test/test_helpers/upload_test_helper.rb
index ddab1ce11..775a38513 100644
--- a/test/test_helpers/upload_test_helper.rb
+++ b/test/test_helpers/upload_test_helper.rb
@@ -7,7 +7,7 @@ module UploadTestHelper
source = { source: source_or_file_path }
else
file = Rack::Test::UploadedFile.new(Rails.root.join(source_or_file_path))
- source = { file: file }
+ source = { files: { "0" => file } }
end
perform_enqueued_jobs do