DEV: Add addComposerUploadPreProcessor to plugin-api (#14222)

DEV: Add addComposerUploadPreProcessor to plugin-api (#14222)

This new interface will be used explicitly to add upload preprocessors in the form of uppy plugins. These will be run for each upload in the composer (dependent on the logic of the plugin itself), before the UppyChecksum plugin is finally run.

Since discourse-encrypt uses the existing addComposerUploadHandler API for essentially preprocessing an upload and not uploading it to a different place, it will be the first plugin to use this interface, along with the register-media-optimization-upload-processor initializer in core.

Related https://github.com/discourse/discourse-encrypt/pull/131.

diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js
index eb2d079..bc351f6 100644
--- a/app/assets/javascripts/discourse/app/components/composer-editor.js
+++ b/app/assets/javascripts/discourse/app/components/composer-editor.js
@@ -3,6 +3,7 @@ import {
   authorizesAllExtensions,
   authorizesOneOrMoreImageExtensions,
 } from "discourse/lib/uploads";
+import { BasePlugin } from "@uppy/core";
 import { resolveAllShortUrls } from "pretty-text/upload-short-url";
 import {
   caretPosition,
@@ -61,6 +62,23 @@ export function cleanUpComposerUploadProcessor() {
   uploadProcessorActions = {};
 }
 
+let uploadPreProcessors = [];
+export function addComposerUploadPreProcessor(pluginClass, optionsResolverFn) {
+  if (!(pluginClass.prototype instanceof BasePlugin)) {
+    throw new Error(
+      "Composer upload preprocessors must inherit from the Uppy BasePlugin class."
+    );
+  }
+
+  uploadPreProcessors.push({
+    pluginClass,
+    optionsResolverFn,
+  });
+}
+export function cleanUpComposerUploadPreProcessor() {
+  uploadPreProcessors = [];
+}
+
 let uploadMarkdownResolvers = [];
 export function addComposerUploadMarkdownResolver(resolver) {
   uploadMarkdownResolvers.push(resolver);
@@ -79,6 +97,7 @@ export default Component.extend(ComposerUpload, {
   uploadMarkdownResolvers,
   uploadProcessorActions,
   uploadProcessorQueue,
+  uploadPreProcessors,
   uploadHandlers,
 
   @discourseComputed("composer.requiredCategoryMissing")
diff --git a/app/assets/javascripts/discourse/app/controllers/composer.js b/app/assets/javascripts/discourse/app/controllers/composer.js
index 43dad34..3941865 100644
--- a/app/assets/javascripts/discourse/app/controllers/composer.js
+++ b/app/assets/javascripts/discourse/app/controllers/composer.js
@@ -1,5 +1,4 @@
 import Composer, { SAVE_ICONS, SAVE_LABELS } from "discourse/models/composer";
-import { warn } from "@ember/debug";
 import Controller, { inject as controller } from "@ember/controller";
 import EmberObject, { action, computed } from "@ember/object";
 import { alias, and, or, reads } from "@ember/object/computed";
@@ -285,17 +284,10 @@ export default Controller.extend({
     return option;
   },
 
-  @discourseComputed("model.isEncrypted")
-  composerComponent(isEncrypted) {
+  @discourseComputed()
+  composerComponent() {
     const defaultComposer = "composer-editor";
     if (this.siteSettings.enable_experimental_composer_uploader) {
-      if (isEncrypted) {
-        warn(
-          "Uppy cannot be used for composer uploads until upload handlers are developed, falling back to composer-editor.",
-          { id: "composer" }
-        );
-        return defaultComposer;
-      }
       return "composer-editor-uppy";
     }
     return defaultComposer;
diff --git a/app/assets/javascripts/discourse/app/initializers/register-media-optimization-upload-processor.js b/app/assets/javascripts/discourse/app/initializers/register-media-optimization-upload-processor.js
index 1a2e427..7b6bfbf 100644
--- a/app/assets/javascripts/discourse/app/initializers/register-media-optimization-upload-processor.js
+++ b/app/assets/javascripts/discourse/app/initializers/register-media-optimization-upload-processor.js
@@ -1,4 +1,8 @@
-import { addComposerUploadProcessor } from "discourse/components/composer-editor";
+import {
+  addComposerUploadPreProcessor,
+  addComposerUploadProcessor,
+} from "discourse/components/composer-editor";
+import UppyMediaOptimization from "discourse/lib/uppy-media-optimization-plugin";
 
 export default {
   name: "register-media-optimization-upload-processor",
@@ -6,15 +10,30 @@ export default {
   initialize(container) {
     let siteSettings = container.lookup("site-settings:main");
     if (siteSettings.composer_media_optimization_image_enabled) {
-      addComposerUploadProcessor(
-        { action: "optimizeJPEG" },
-        {
-          optimizeJPEG: (data, opts) =>
-            container
-              .lookup("service:media-optimization-worker")
-              .optimizeImage(data, opts),
-        }
-      );
+      if (!siteSettings.enable_experimental_composer_uploader) {
+        addComposerUploadProcessor(
+          { action: "optimizeJPEG" },
+          {
+            optimizeJPEG: (data, opts) =>
+              container
+                .lookup("service:media-optimization-worker")
+                .optimizeImage(data, opts),
+          }
+        );
+      } else {
+        addComposerUploadPreProcessor(
+          UppyMediaOptimization,
+          ({ isMobileDevice }) => {
+            return {
+              optimizeFn: (data, opts) =>
+                container
+                  .lookup("service:media-optimization-worker")
+                  .optimizeImage(data, opts),
+              runParallel: !isMobileDevice,
+            };
+          }
+        );
+      }
     }
   },
 };
diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js
index 64299bc..f4aa789 100644
--- a/app/assets/javascripts/discourse/app/lib/plugin-api.js
+++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js
@@ -1,6 +1,7 @@
 import ComposerEditor, {
   addComposerUploadHandler,
   addComposerUploadMarkdownResolver,
+  addComposerUploadPreProcessor,
   addComposerUploadProcessor,
 } from "discourse/components/composer-editor";
 import {
@@ -1028,6 +1029,39 @@ class PluginApi {
   }
 
   /**
+   * Registers a pre-processor for file uploads in the form
+   * of an Uppy preprocessor plugin.
+   *
+   * See https://uppy.io/docs/writing-plugins/ for the Uppy
+   * documentation, but other examples of preprocessors in core
+   * can be found in UppyMediaOptimization and UppyChecksum.
+   *
+   * Useful for transforming to-be uploaded files client-side.
+   *
+   * Example:
+   *
+   * api.addComposerUploadPreProcessor(UppyMediaOptimization, ({ composerModel, composerElement, capabilities, isMobileDevice }) => {
+   *   return {
+   *     composerModel,
+   *     composerElement,
+   *     capabilities,
+   *     isMobileDevice,
+   *     someOption: true,
+   *     someFn: () => {},
+   *   };
+   * });
+   *
+   * @param {BasePlugin} pluginClass The uppy plugin class to use for the preprocessor.
+   * @param {Function} optionsResolverFn This function should return an object which is passed into the constructor
+   *                                     of the uppy plugin as the options argument. The object passed to the function
+   *                                     contains references to the composer model, element, the capabilities of the
+   *                                     browser, and isMobileDevice.
+   */
+  addComposerUploadPreProcessor(pluginClass, optionsResolverFn) {
+    addComposerUploadPreProcessor(pluginClass, optionsResolverFn);
+  }
+
+  /**
    * Registers a function to generate Markdown after a file has been uploaded.
    *
    * Example:
diff --git a/app/assets/javascripts/discourse/app/mixins/composer-upload-uppy.js b/app/assets/javascripts/discourse/app/mixins/composer-upload-uppy.js
index 788a441..d081f20 100644
--- a/app/assets/javascripts/discourse/app/mixins/composer-upload-uppy.js
+++ b/app/assets/javascripts/discourse/app/mixins/composer-upload-uppy.js
@@ -2,7 +2,6 @@ import Mixin from "@ember/object/mixin";
 import { ajax } from "discourse/lib/ajax";
 import { deepMerge } from "discourse-common/lib/object";
 import UppyChecksum from "discourse/lib/uppy-checksum-plugin";
-import UppyMediaOptimization from "discourse/lib/uppy-media-optimization-plugin";
 import Uppy from "@uppy/core";
 import DropTarget from "@uppy/drop-target";
 import XHRUpload from "@uppy/xhr-upload";
@@ -228,14 +227,8 @@ export default Mixin.create({
       }
     });
 
-    this._setupPreprocessing();
-
-    // It is important that the UppyChecksum preprocessor is the last one to

[... diff too long, it was truncated ...]

GitHub sha: 9873a942e3d2df367717f119c591492c690d291d

This commit appears in #14222 which was approved by eviltrout. It was merged by martin.