FEATURE: Change all core to use uppy-image-uploader (#14428)

Instead of using image-uploader, which relies on the old
UploadMixin, we can now use the uppy-image-uploader which
uses the new UppyUploadMixin which is stable enough and
supports both regular XHR uploads and direct S3 uploads,
controlled by a site setting (default to XHR).

At some point it may make sense to rename uppy-image-uploader
back to image-uploader, once we have gone through plugins
etc. and given a bit of deprecation time period.

This commit also fixes `for_private_message`, `for_site_setting`,
and `pasted` flags not being sent via uppy uploads onto the
UploadCreator, both via regular XHR uploads and also through
external/multipart uploads.

The uploaders changed are:

* site setting images
* badge images
* category logo
* category background
* group flair
* profile background
* profile card background
This commit is contained in:
Martin Brennan 2021-10-19 13:25:42 +10:00 committed by GitHub
parent 92afa74d92
commit 2364626ded
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 87 additions and 57 deletions

View File

@ -1,6 +0,0 @@
import ImageUploader from "discourse/components/image-uploader";
export default ImageUploader.extend({
layoutName: "components/image-uploader",
uploadUrlParams: "&for_site_setting=true",
});

View File

@ -40,12 +40,14 @@
</label> </label>
</div> </div>
{{#if imageUploaderSelected}} {{#if imageUploaderSelected}}
{{image-uploader {{uppy-image-uploader
id="badge-image-uploader"
imageUrl=buffered.image_url imageUrl=buffered.image_url
type="badge_image"
onUploadDone=(action "setImage") onUploadDone=(action "setImage")
onUploadDeleted=(action "removeImage") onUploadDeleted=(action "removeImage")
type="badge_image" class="no-repeat contain-image"
class="no-repeat contain-image"}} }}
<div class="control-instructions"> <div class="control-instructions">
<p class="help">{{i18n "admin.badges.image_help"}}</p> <p class="help">{{i18n "admin.badges.image_help"}}</p>
</div> </div>

View File

@ -1,2 +1,8 @@
{{site-settings-image-uploader imageUrl=value placeholderUrl=setting.placeholder type="site_setting"}} {{uppy-image-uploader
imageUrl=value
placeholderUrl=setting.placeholder
additionalParams=(hash for_site_setting=true)
type="site_setting"
id=(concat "site-setting-image-uploader-" setting.setting)
}}
<div class="desc">{{html-safe setting.description}}</div> <div class="desc">{{html-safe setting.description}}</div>

View File

@ -1,4 +1,5 @@
import Component from "@ember/component"; import Component from "@ember/component";
import deprecated from "discourse-common/lib/deprecated";
import UploadMixin from "discourse/mixins/upload"; import UploadMixin from "discourse/mixins/upload";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
@ -14,6 +15,10 @@ export default Component.extend(UploadMixin, {
init() { init() {
this._super(...arguments); this._super(...arguments);
// TODO (martin) (2022-01-22) Remove this component.
deprecated(
"image-uploader will be removed in a future version, use uppy-image-uploader instead (the API is the same)"
);
this._applyLightbox(); this._applyLightbox();
}, },

View File

@ -69,8 +69,6 @@ export default Component.extend(UppyUploadMixin, {
uploadDone(upload) { uploadDone(upload) {
this.setProperties({ this.setProperties({
imageUrl: upload.url,
imageId: upload.id,
imageFilesize: upload.human_filesize, imageFilesize: upload.human_filesize,
imageFilename: upload.original_filename, imageFilename: upload.original_filename,
imageWidth: upload.width, imageWidth: upload.width,
@ -79,8 +77,13 @@ export default Component.extend(UppyUploadMixin, {
this._applyLightbox(); this._applyLightbox();
// the value of the property used for imageUrl should be set
// in this callback. this should be done in cases where imageUrl
// is bound to a computed property of the parent component.
if (this.onUploadDone) { if (this.onUploadDone) {
this.onUploadDone(upload); this.onUploadDone(upload);
} else {
this.set("imageUrl", upload.url);
} }
}, },
@ -123,13 +126,16 @@ export default Component.extend(UppyUploadMixin, {
}, },
trash() { trash() {
this.setProperties({ imageUrl: null, imageId: null });
// uppy needs to be reset to allow for more uploads // uppy needs to be reset to allow for more uploads
this._reset(); this._reset();
// the value of the property used for imageUrl should be cleared
// in this callback. this should be done in cases where imageUrl
// is bound to a computed property of the parent component.
if (this.onUploadDeleted) { if (this.onUploadDeleted) {
this.onUploadDeleted(); this.onUploadDeleted();
} else {
this.setProperties({ imageUrl: null });
} }
}, },
}, },

View File

@ -68,10 +68,6 @@ export default Controller.extend({
"model.can_upload_user_card_background" "model.can_upload_user_card_background"
), ),
experimentalUserCardImageUpload: readOnly(
"siteSettings.enable_experimental_image_uploader"
),
actions: { actions: {
showFeaturedTopicModal() { showFeaturedTopicModal() {
showModal("feature-topic-on-profile", { showModal("feature-topic-on-profile", {

View File

@ -136,7 +136,6 @@ export default Mixin.create(ExtendableUploader, {
this._instrumentUploadTimings(); this._instrumentUploadTimings();
} }
// hidden setting like enable_experimental_image_uploader
if (this.siteSettings.enable_direct_s3_uploads) { if (this.siteSettings.enable_direct_s3_uploads) {
this._useS3MultipartUploads(); this._useS3MultipartUploads();
} else { } else {
@ -482,6 +481,8 @@ export default Mixin.create(ExtendableUploader, {
data: JSON.stringify({ data: JSON.stringify({
parts, parts,
unique_identifier: file.meta.unique_identifier, unique_identifier: file.meta.unique_identifier,
pasted: file.meta.pasted,
for_private_message: file.meta.for_private_message,
}), }),
// uppy is inconsistent, an error here fires the upload-error event // uppy is inconsistent, an error here fires the upload-error event
}).then((responseData) => { }).then((responseData) => {
@ -570,14 +571,14 @@ export default Mixin.create(ExtendableUploader, {
} }
if (event && event.clipboardData && event.clipboardData.files) { if (event && event.clipboardData && event.clipboardData.files) {
this._addFiles([...event.clipboardData.files]); this._addFiles([...event.clipboardData.files], { pasted: true });
} }
}.bind(this); }.bind(this);
this.element.addEventListener("paste", this.pasteEventListener); this.element.addEventListener("paste", this.pasteEventListener);
}, },
_addFiles(files) { _addFiles(files, opts = {}) {
files = Array.isArray(files) ? files : [files]; files = Array.isArray(files) ? files : [files];
try { try {
this._uppyInstance.addFiles( this._uppyInstance.addFiles(
@ -587,6 +588,7 @@ export default Mixin.create(ExtendableUploader, {
name: file.name, name: file.name,
type: file.type, type: file.type,
data: file, data: file,
meta: { pasted: opts.pasted },
}; };
}) })
); );

View File

@ -25,10 +25,6 @@ export default Mixin.create({
autoStartUploads: true, autoStartUploads: true,
id: null, id: null,
// TODO (martin): this is only used in one place, consider just using
// form data/meta instead uploadUrlParams: "&for_site_setting=true",
uploadUrlParams: "",
// TODO (martin): currently used for backups to turn on auto upload and PUT/XML requests // TODO (martin): currently used for backups to turn on auto upload and PUT/XML requests
// and for emojis to do sequential uploads, when we get to replacing those // and for emojis to do sequential uploads, when we get to replacing those
// with uppy make sure this is used when initializing uppy // with uppy make sure this is used when initializing uppy
@ -83,7 +79,11 @@ export default Mixin.create({
// need to use upload_type because uppy overrides type with the // need to use upload_type because uppy overrides type with the
// actual file type // actual file type
meta: deepMerge({ upload_type: this.type }, this.data || {}), meta: deepMerge(
{ upload_type: this.type },
this.additionalParams || {},
this.data || {}
),
onBeforeFileAdded: (currentFile) => { onBeforeFileAdded: (currentFile) => {
const validationOpts = deepMerge( const validationOpts = deepMerge(
@ -160,7 +160,6 @@ export default Mixin.create({
this._reset(); this._reset();
}); });
// hidden setting like enable_experimental_image_uploader
if (this.siteSettings.enable_direct_s3_uploads) { if (this.siteSettings.enable_direct_s3_uploads) {
this._useS3Uploads(); this._useS3Uploads();
} else { } else {
@ -224,8 +223,7 @@ export default Mixin.create({
return ( return (
getUrl(this.getWithDefault("uploadUrl", "/uploads")) + getUrl(this.getWithDefault("uploadUrl", "/uploads")) +
".json?client_id=" + ".json?client_id=" +
(this.messageBus && this.messageBus.clientId) + this.messageBus?.clientId
this.uploadUrlParams
); );
}, },
@ -252,9 +250,10 @@ export default Mixin.create({
_completeExternalUpload(file) { _completeExternalUpload(file) {
return ajax(getUrl("/uploads/complete-external-upload"), { return ajax(getUrl("/uploads/complete-external-upload"), {
type: "POST", type: "POST",
data: { data: deepMerge(
unique_identifier: file.meta.uniqueUploadIdentifier, { unique_identifier: file.meta.uniqueUploadIdentifier },
}, this.additionalParams || {}
),
}); });
}, },

View File

@ -1,18 +1,22 @@
<section class="field category-logo"> <section class="field category-logo">
<label>{{i18n "category.logo"}}</label> <label>{{i18n "category.logo"}}</label>
{{image-uploader {{uppy-image-uploader
imageUrl=logoImageUrl imageUrl=logoImageUrl
onUploadDone=(action "logoUploadDone") onUploadDone=(action "logoUploadDone")
onUploadDeleted=(action "logoUploadDeleted") onUploadDeleted=(action "logoUploadDeleted")
type="category_logo" type="category_logo"
class="no-repeat contain-image"}} class="no-repeat contain-image"
id="category-logo-uploader"
}}
</section> </section>
<section class="field category-background-image"> <section class="field category-background-image">
<label>{{i18n "category.background_image"}}</label> <label>{{i18n "category.background_image"}}</label>
{{image-uploader {{uppy-image-uploader
imageUrl=backgroundImageUrl imageUrl=backgroundImageUrl
onUploadDone=(action "backgroundUploadDone") onUploadDone=(action "backgroundUploadDone")
onUploadDeleted=(action "backgroundUploadDeleted") onUploadDeleted=(action "backgroundUploadDeleted")
type="category_background"}} type="category_background"
id="category-background-uploader"
}}
</section> </section>

View File

@ -21,11 +21,12 @@
onChange=(action (mut model.flair_icon)) onChange=(action (mut model.flair_icon))
}} }}
{{else if flairPreviewImage}} {{else if flairPreviewImage}}
{{image-uploader {{uppy-image-uploader
imageUrl=flairImageUrl imageUrl=flairImageUrl
onUploadDone=(action "setFlairImage") onUploadDone=(action "setFlairImage")
onUploadDeleted=(action "removeFlairImage") onUploadDeleted=(action "removeFlairImage")
type="group_flair" type="group_flair"
id="group-flair-uploader"
class="no-repeat contain-image"}} class="no-repeat contain-image"}}
<div class="control-instructions"> <div class="control-instructions">
{{i18n "groups.flair_upload_description"}} {{i18n "groups.flair_upload_description"}}

View File

@ -47,8 +47,11 @@
<div class="control-group pref-profile-bg"> <div class="control-group pref-profile-bg">
<label class="control-label">{{i18n "user.change_profile_background.title"}}</label> <label class="control-label">{{i18n "user.change_profile_background.title"}}</label>
<div class="controls"> <div class="controls">
{{image-uploader imageUrl=model.profile_background_upload_url {{uppy-image-uploader
type="profile_background"}} imageUrl=model.profile_background_upload_url
type="profile_background"
id="profile-background-uploader"
}}
</div> </div>
<div class="instructions"> <div class="instructions">
{{i18n "user.change_profile_background.instructions"}} {{i18n "user.change_profile_background.instructions"}}
@ -59,15 +62,11 @@
<div class="control-group pref-profile-bg"> <div class="control-group pref-profile-bg">
<label class="control-label">{{i18n "user.change_card_background.title"}}</label> <label class="control-label">{{i18n "user.change_card_background.title"}}</label>
<div class="controls"> <div class="controls">
{{#if experimentalUserCardImageUpload}}
{{uppy-image-uploader {{uppy-image-uploader
imageUrl=model.card_background_upload_url imageUrl=model.card_background_upload_url
type="card_background" type="card_background"
id="profile-card-background" id="profile-card-background-uploader"
}} }}
{{else}}
{{image-uploader imageUrl=model.card_background_upload_url type="card_background"}}
{{/if}}
</div> </div>
<div class="instructions"> <div class="instructions">
{{i18n "user.change_card_background.instructions"}} {{i18n "user.change_card_background.instructions"}}

View File

@ -253,7 +253,13 @@ class UploadsController < ApplicationController
end end
def complete_external_upload_via_manager(external_upload_stub) def complete_external_upload_via_manager(external_upload_stub)
external_upload_manager = ExternalUploadManager.new(external_upload_stub) opts = {
for_private_message: params[:for_private_message]&.to_s == "true",
for_site_setting: params[:for_site_setting]&.to_s == "true",
pasted: params[:pasted]&.to_s == "true",
}
external_upload_manager = ExternalUploadManager.new(external_upload_stub, opts)
hijack do hijack do
begin begin
upload = external_upload_manager.promote_to_upload! upload = external_upload_manager.promote_to_upload!

View File

@ -20,8 +20,9 @@ class ExternalUploadManager
Discourse.redis.get("#{BAN_USER_REDIS_PREFIX}#{user.id}") == "1" Discourse.redis.get("#{BAN_USER_REDIS_PREFIX}#{user.id}") == "1"
end end
def initialize(external_upload_stub) def initialize(external_upload_stub, upload_create_opts = {})
@external_upload_stub = external_upload_stub @external_upload_stub = external_upload_stub
@upload_create_opts = upload_create_opts
end end
def can_promote? def can_promote?
@ -67,18 +68,13 @@ class ExternalUploadManager
end end
# TODO (martin): See if these additional opts will be needed # TODO (martin): See if these additional opts will be needed
# # - check if retain_hours is needed
# for_private_message: for_private_message,
# for_site_setting: for_site_setting,
# pasted: pasted,
#
# also check if retain_hours is needed
opts = { opts = {
type: external_upload_stub.upload_type, type: external_upload_stub.upload_type,
existing_external_upload_key: external_upload_stub.key, existing_external_upload_key: external_upload_stub.key,
external_upload_too_big: external_size > DOWNLOAD_LIMIT, external_upload_too_big: external_size > DOWNLOAD_LIMIT,
filesize: external_size filesize: external_size
} }.merge(@upload_create_opts)
UploadCreator.new(tempfile, external_upload_stub.original_filename, opts).create_for( UploadCreator.new(tempfile, external_upload_stub.original_filename, opts).create_for(
external_upload_stub.created_by_id external_upload_stub.created_by_id

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class RemoveEnableExperimentalImageUploaderSiteSetting < ActiveRecord::Migration[6.1]
def up
execute <<~SQL
DELETE FROM site_settings
WHERE name = 'enable_experimental_image_uploader'
SQL
end
def down
raise ActiveRecord::IrreversibleMigration
end
end