Antoine du Hamel 2 лет назад
Родитель
Сommit
2e8f79787b
48 измененных файлов с 680 добавлено и 195 удалено
  1. 47 0
      .github/ISSUE_TEMPLATE/1-bug.yml
  2. 34 0
      .github/ISSUE_TEMPLATE/2-feature.yml
  3. 0 5
      .github/ISSUE_TEMPLATE/bug_report.md
  4. 5 0
      .github/ISSUE_TEMPLATE/config.yml
  5. 0 5
      .github/ISSUE_TEMPLATE/feature_request.md
  6. 0 14
      .github/ISSUE_TEMPLATE/integration_help.md
  7. 6 5
      BACKLOG.md
  8. 52 0
      CHANGELOG.md
  9. 46 42
      README.md
  10. 23 0
      e2e/clients/dashboard-ui/app.js
  11. 16 0
      e2e/cypress.config.mjs
  12. 0 5
      e2e/cypress.json
  13. 2 2
      e2e/cypress/integration/dashboard-compressor.spec.ts
  14. 73 3
      e2e/cypress/integration/dashboard-transloadit.spec.ts
  15. 25 17
      e2e/cypress/integration/dashboard-tus.spec.ts
  16. 10 2
      e2e/cypress/integration/dashboard-ui.spec.ts
  17. 1 1
      e2e/cypress/integration/dashboard-vue.spec.ts
  18. 5 5
      e2e/cypress/integration/react.spec.ts
  19. 0 22
      e2e/cypress/plugins/index.js
  20. 0 3
      e2e/cypress/support/commands.ts
  21. 20 0
      e2e/cypress/support/e2e.ts
  22. 2 3
      e2e/package.json
  23. 1 1
      e2e/tsconfig.json
  24. 6 0
      packages/@uppy/companion/CHANGELOG.md
  25. 7 0
      packages/@uppy/compressor/CHANGELOG.md
  26. 1 1
      packages/@uppy/compressor/src/index.js
  27. 6 0
      packages/@uppy/core/CHANGELOG.md
  28. 6 0
      packages/@uppy/core/src/Uppy.js
  29. 112 0
      packages/@uppy/core/src/Uppy.test.js
  30. 3 0
      packages/@uppy/core/types/index.d.ts
  31. 6 0
      packages/@uppy/dashboard/CHANGELOG.md
  32. 2 0
      packages/@uppy/dashboard/src/components/FileCard/index.jsx
  33. 15 0
      packages/@uppy/image-editor/CHANGELOG.md
  34. 6 0
      packages/@uppy/image-editor/src/ImageEditor.jsx
  35. 2 0
      packages/@uppy/image-editor/types/index.d.ts
  36. 4 0
      packages/@uppy/image-editor/types/index.test-d.ts
  37. 7 0
      packages/@uppy/remote-sources/CHANGELOG.md
  38. 3 1
      packages/@uppy/remote-sources/src/index.js
  39. 7 0
      packages/@uppy/robodog/CHANGELOG.md
  40. 4 9
      packages/@uppy/robodog/src/TransloaditFormResult.js
  41. 21 0
      packages/@uppy/transloadit/CHANGELOG.md
  42. 9 2
      packages/@uppy/transloadit/src/AssemblyOptions.js
  43. 24 0
      packages/@uppy/transloadit/src/Client.js
  44. 43 27
      packages/@uppy/transloadit/src/index.js
  45. 1 1
      website/src/_posts/2017-03-0.15.md
  46. 2 2
      website/src/_posts/2019-08-1.3.md
  47. 10 2
      website/src/docs/image-editor.md
  48. 5 15
      yarn.lock

+ 47 - 0
.github/ISSUE_TEMPLATE/1-bug.yml

@@ -0,0 +1,47 @@
+name: 🐛 Bug report
+labels: Bug, Triage
+description: Describe a bug with a project
+body:
+  - type: checkboxes
+    id: initial-checklist
+    attributes:
+      label: Initial checklist
+      options:
+        - label: I understand this is a bug report and questions should be posted in the [Community Forum](https://community.transloadit.com/)
+          required: true
+        - label: I searched [issues](https://github.com/transloadit/uppy/issues?q=is%3Aissue) and couldn’t find anything (or linked relevant results below)
+          required: true
+  - type: input
+    id: runnable-example
+    attributes:
+      label: Link to runnable example
+      description: |
+        Link to repository or sandbox with runnable example of the issue.
+        Alternatively, use the next section *Steps to reproduce*.
+
+        Starters:
+
+        - [Uppy Dashboard](https://codesandbox.io/s/uppy-dashboard-xpxuhd?file=/src/index.js)
+    validations:
+      required: false
+  - type: textarea
+    id: steps-to-reproduce
+    attributes:
+      label: Steps to reproduce
+      description: How did this happen? Please provide a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example).
+    validations:
+      required: true
+  - type: textarea
+    id: expected-behavior
+    attributes:
+      label: Expected behavior
+      description: What should happen?
+    validations:
+      required: true
+  - type: textarea
+    id: actual-behavior
+    attributes:
+      label: Actual behavior
+      description: What happens instead?
+    validations:
+      required: true

+ 34 - 0
.github/ISSUE_TEMPLATE/2-feature.yml

@@ -0,0 +1,34 @@
+name: 🚀 Feature request
+labels: Feature, Triage
+description: Suggest an idea
+body:
+  - type: checkboxes
+    id: initial-checklist
+    attributes:
+      label: Initial checklist
+      options:
+        - label: I understand this is a feature request and questions should be posted in the [Community Forum](https://community.transloadit.com/)
+          required: true
+        - label: I searched [issues](https://github.com/transloadit/uppy/issues?q=is%3Aissue) and couldn’t find anything (or linked relevant results below)
+          required: true
+  - type: textarea
+    id: problem
+    attributes:
+      label: Problem
+      description: Please describe the problem you are trying to solve here.
+    validations:
+      required: true
+  - type: textarea
+    id: solution
+    attributes:
+      label: Solution
+      description: What should happen? Please describe the desired behavior.
+    validations:
+      required: true
+  - type: textarea
+    id: alternatives
+    attributes:
+      label: Alternatives
+      description: What are the alternative solutions? Can this be solved in a different way?
+    validations:
+      required: true

+ 0 - 5
.github/ISSUE_TEMPLATE/bug_report.md

@@ -1,5 +0,0 @@
----
-name: Bug report
-about: Let us know about an unexpected error, a crash, or an incorrect behavior.
-labels: Bug, Triage
----

+ 5 - 0
.github/ISSUE_TEMPLATE/config.yml

@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+  - name: 🙋 Ask a question
+    url: https://community.transloadit.com
+    about: Ask questions and discuss with other community members

+ 0 - 5
.github/ISSUE_TEMPLATE/feature_request.md

@@ -1,5 +0,0 @@
----
-name: Feature request
-about: Suggest a new feature or other enhancement.
-labels: Feature, Triage
----

+ 0 - 14
.github/ISSUE_TEMPLATE/integration_help.md

@@ -1,14 +0,0 @@
----
-name: Integration help
-title: Please use the community forum (or paid support) instead
-about: Do you need assistance with building the Uppy client in your bundler, or running Companion on your own preferred server platform?
-labels: Not Accepted, Triage
----
-
-Transloadit is providing Uppy free of charge. If you want, you can self-host all its components and never pay us a dime. You can access docs and tests, and your Bug Reports and Feature Requests are always welcome on GitHub.
-
-We offer also a different category of support that we like to call Integration Help: help to make things work for your environment, that have already been reported as working for the larger community.
-
-As much as we at Transloadit would like to provide detailed Integration Help to every non-paying user, Uppy has reached a point where this is no longer sustainable for our small crew. If we end up investing our time in a million different apps that use Uppy, as long as no money is flowing back, we won’t be able to ramp up our team to meet the demand. This would spread the team ever thinner and eventually grind development to a halt.
-
-That is not where we want to be. So, to offer enthusiasts, businesses, and enterprises help in a sustainable way, we’re providing community-based Integration Help for free at <https://community.transloadit.com/c/uppy>. If you are unable to solve your problem with help of the Uppy community, we offer paid Integration Help via <https://uppy.io/support>.

+ 6 - 5
BACKLOG.md

@@ -9,10 +9,13 @@ PRs are welcome! Please do open an issue to discuss first if it's a big feature,
 
 - [x] Switch to ES Modules (ESM)
 - [x] @uppy/image-editor: Remove silly hack to work around non-ESM.
-- [ ] Some not too breaking breaking changes. Go through TODOs
-- [ ] Companion breaking changes, like S3 keys
+- [ ] Some not too breaking breaking changes. Go through TODOs (@arturi, @aduh95, @Murderlon)
+- [ ] Companion breaking changes, like S3 keys (@mifi)
 - [x] New remote-sources preset
 - [ ] Deprecate Robodog
+  - [ ] Remove from 3.x branch (@aduh95)
+  - [ ] Update docs that refer to Robodog (@arturi)
+  - [ ] Update Transloadit.com examples and docs to use @uppy/transloadit + @uppy/remote-sources plugins instead of @uppy/robodog (@arturi)
 
 ## `4.0.0`
 
@@ -27,7 +30,6 @@ PRs are welcome! Please do open an issue to discuss first if it's a big feature,
 
 - [ ] Make sure Uppy works well in VR
 - [ ] normalize file names when uploading from iOS? Can we do it with meta data? date? `image-${index}`? #678
-- [ ] robodog: Simplify Robodog — yes to easy few lines of code Transloadit experience, but currently it’s hard to choose between modes, decide what you need.
 - [ ] Can Uppy upload a lot of files at once? Seems to fail now: https://github.com/transloadit/uppy/issues/3313 (@aduh95, @Murderlon)
 - [ ] Consider how we can make Uppy smaller. Replace some packages with smaller alternatives. Talk about Socket.io again (@aduh95)
 - [ ] Better events — more data, consistency, naming (@Murderlon)
@@ -57,14 +59,13 @@ PRs are welcome! Please do open an issue to discuss first if it's a big feature,
 - [ ] Giphy image search (on top of Unsplash plugin) ()
 - [ ] Image search (via Google or Bing or DuckDuckGo): use duckduckgo-images-api or Google Search API (@arturi)
 - [ ] Vimeo #2872
-- [ ] box: add to https://uppy.io/examples/dashboard/ (@mifi)
 
 ### Miscellaneous
 
 - [ ] goldenretriever: make it work with aws multipart https://community.transloadit.com/t/resumable-aws-s3-multipart-integration/14888 (@goto-bus-stop)
 - [ ] provider: add sorting (by date) #254
 - [ ] qa: add one integration test (or add to existing test) that uses more exotic (tus) options such as `useFastRemoteRetry` or `removeFingerprintOnSuccess` https://github.com/transloadit/uppy/issues/1327 (@arturi, @ifedapoolarewaju)
-- [ ] react: Add a React Hook to manage an Uppy instance https://github.com/transloadit/uppy/pull/1247#issuecomment-458063951 (@goto-bus-stop)
+- [x] react: Add a React Hook to manage an Uppy instance https://github.com/transloadit/uppy/pull/1247#issuecomment-458063951 (@goto-bus-stop)
 - [ ] rn: Uppy React Native works with Expo, now let's make it work without
 - [ ] rn: Uppy React Native works with Url Plugin, now let's make it work with Instagram
 - [ ] security: consider iframe / more security for Transloadit/Uppy integration widget and Uppy itself. Page can’t get files from Google Drive if its an iframe

+ 52 - 0
CHANGELOG.md

@@ -105,6 +105,58 @@ Released: 2022-05-30
 - @uppy/companion: remove support for EOL versions of Node.js (Antoine du Hamel / #3784)
 - @uppy/react: refactor to ESM (Antoine du Hamel / #3780)
 - @uppy/transloadit: remove IE 10 hack (Antoine du Hamel / #3777)
+## 2.13.1
+
+Released: 2022-07-27
+
+| Package              | Version | Package              | Version |
+| -------------------- | ------- | -------------------- | ------- |
+| @uppy/companion      |   3.7.1 | @uppy/remote-sources |   0.1.1 |
+| @uppy/compressor     |   0.3.1 | @uppy/transloadit    |   2.3.5 |
+| @uppy/core           |   2.3.2 | @uppy/robodog        |   2.9.1 |
+| @uppy/dashboard      |   2.4.1 | uppy                 |  2.13.1 |
+| @uppy/image-editor   |   1.4.1 |                      |         |
+
+- @uppy/compressor: fix upload causing meta name to reset (Justin / #3890)
+- @uppy/transloadit: cancel assemblies when all its files have been removed (Antoine du Hamel / #3893)
+- e2e: Add retries for flaky e2e test (Merlijn Vos / #3915)
+- @uppy/dashboard,@uppy/image-editor,@uppy/remote-sources: Fix `uppy.close()` crashes when remote-sources or image-editor is installed (Merlijn Vos / #3914)
+- @uppy/core: Add missing type for retry-all event (Luc Boissaye / #3901)
+- @uppy/companion: Companion app type (Mikael Finstad / #3899)
+- e2e: upgrade to Cypress 10 (Antoine du Hamel / #3896)
+- meta: Fix website build (Murderlon)
+- meta: Create new issue templates (Merlijn Vos / #3879)
+
+
+## 2.13.0
+
+Released: 2022-07-18
+
+| Package            | Version | Package            | Version |
+| ------------------ | ------- | ------------------ | ------- |
+| @uppy/dashboard    |   2.4.0 | @uppy/robodog      |   2.9.0 |
+| @uppy/image-editor |   1.4.0 | uppy               |  2.13.0 |
+| @uppy/transloadit  |   2.3.4 |                    |         |
+
+- @uppy/transloadit: fix outdated file ids and incorrect usage of files (Merlijn Vos / #3886)
+- @uppy/image-editor: remove beta notice (Merlijn Vos / #3877)
+- meta: Fix broken links in _posts/2019-08-1.3.md (YukeshShr / #3884)
+- meta: Fix broken link in _posts/2017-03-0.15.md (YukeshShr / #3883)
+- @uppy/image-editor: Add image editor cancel event (James R T / #3875)
+
+
+## 2.12.3
+
+Released: 2022-07-11
+
+| Package           | Version | Package           | Version |
+| ----------------- | ------- | ----------------- | ------- |
+| @uppy/transloadit |   2.3.3 | uppy              |  2.12.3 |
+| @uppy/robodog     |   2.8.3 |                   |         |
+
+- @uppy/transloadit: fix TypeError when file is cancelled asynchronously (Antoine du Hamel / #3872)
+- @uppy/robodog,@uppy/transloadit: use modern syntax to simplify code (Antoine du Hamel / #3873)
+- meta: fix `release-beta` automation (Antoine du Hamel)
 
 
 ## 2.12.2

+ 46 - 42
README.md

@@ -254,9 +254,9 @@ Use Uppy in your project? [Let us know](https://github.com/transloadit/uppy/issu
 :---: |:---: |:---: |:---: |:---: |:---: |
 [sadovnychyi](https://github.com/sadovnychyi) |[samuelayo](https://github.com/samuelayo) |[richardwillars](https://github.com/richardwillars) |[ajkachnic](https://github.com/ajkachnic) |[dependabot\[bot\]](https://github.com/apps/dependabot) |[github-actions\[bot\]](https://github.com/apps/github-actions) |
 
-[<img alt="zcallan" src="https://avatars.githubusercontent.com/u/13760738?v=4&s=117" width="117">](https://github.com/zcallan) |[<img alt="tim-kos" src="https://avatars.githubusercontent.com/u/15005?v=4&s=117" width="117">](https://github.com/tim-kos) |[<img alt="janko" src="https://avatars.githubusercontent.com/u/795488?v=4&s=117" width="117">](https://github.com/janko) |[<img alt="wilkoklak" src="https://avatars.githubusercontent.com/u/17553085?v=4&s=117" width="117">](https://github.com/wilkoklak) |[<img alt="YukeshShr" src="https://avatars.githubusercontent.com/u/71844521?v=4&s=117" width="117">](https://github.com/YukeshShr) |[<img alt="oliverpool" src="https://avatars.githubusercontent.com/u/3864879?v=4&s=117" width="117">](https://github.com/oliverpool) |
+[<img alt="zcallan" src="https://avatars.githubusercontent.com/u/13760738?v=4&s=117" width="117">](https://github.com/zcallan) |[<img alt="tim-kos" src="https://avatars.githubusercontent.com/u/15005?v=4&s=117" width="117">](https://github.com/tim-kos) |[<img alt="YukeshShr" src="https://avatars.githubusercontent.com/u/71844521?v=4&s=117" width="117">](https://github.com/YukeshShr) |[<img alt="janko" src="https://avatars.githubusercontent.com/u/795488?v=4&s=117" width="117">](https://github.com/janko) |[<img alt="wilkoklak" src="https://avatars.githubusercontent.com/u/17553085?v=4&s=117" width="117">](https://github.com/wilkoklak) |[<img alt="oliverpool" src="https://avatars.githubusercontent.com/u/3864879?v=4&s=117" width="117">](https://github.com/oliverpool) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[zcallan](https://github.com/zcallan) |[tim-kos](https://github.com/tim-kos) |[janko](https://github.com/janko) |[wilkoklak](https://github.com/wilkoklak) |[YukeshShr](https://github.com/YukeshShr) |[oliverpool](https://github.com/oliverpool) |
+[zcallan](https://github.com/zcallan) |[tim-kos](https://github.com/tim-kos) |[YukeshShr](https://github.com/YukeshShr) |[janko](https://github.com/janko) |[wilkoklak](https://github.com/wilkoklak) |[oliverpool](https://github.com/oliverpool) |
 
 [<img alt="Botz" src="https://avatars.githubusercontent.com/u/2706678?v=4&s=117" width="117">](https://github.com/Botz) |[<img alt="mcallistertyler" src="https://avatars.githubusercontent.com/u/14939210?v=4&s=117" width="117">](https://github.com/mcallistertyler) |[<img alt="mokutsu-coursera" src="https://avatars.githubusercontent.com/u/65177495?v=4&s=117" width="117">](https://github.com/mokutsu-coursera) |[<img alt="DJWassink" src="https://avatars.githubusercontent.com/u/1822404?v=4&s=117" width="117">](https://github.com/DJWassink) |[<img alt="taoqf" src="https://avatars.githubusercontent.com/u/15901911?v=4&s=117" width="117">](https://github.com/taoqf) |[<img alt="mrbatista" src="https://avatars.githubusercontent.com/u/6544817?v=4&s=117" width="117">](https://github.com/mrbatista) |
 :---: |:---: |:---: |:---: |:---: |:---: |
@@ -378,85 +378,89 @@ Use Uppy in your project? [Let us know](https://github.com/transloadit/uppy/issu
 :---: |:---: |:---: |:---: |:---: |:---: |
 [jorgeepc](https://github.com/jorgeepc) |[jszobody](https://github.com/jszobody) |[jcalonso](https://github.com/jcalonso) |[jmontoyaa](https://github.com/jmontoyaa) |[tykarol](https://github.com/tykarol) |[firesharkstudios](https://github.com/firesharkstudios) |
 
-[<img alt="kaspermeinema" src="https://avatars.githubusercontent.com/u/73821331?v=4&s=117" width="117">](https://github.com/kaspermeinema) |[<img alt="firesharkstudios" src="https://avatars.githubusercontent.com/u/17069637?v=4&s=117" width="117">](https://github.com/firesharkstudios) |[<img alt="kevin-west-10x" src="https://avatars.githubusercontent.com/u/65194914?v=4&s=117" width="117">](https://github.com/kevin-west-10x) |[<img alt="elkebab" src="https://avatars.githubusercontent.com/u/6313468?v=4&s=117" width="117">](https://github.com/elkebab) |[<img alt="kyleparisi" src="https://avatars.githubusercontent.com/u/1286753?v=4&s=117" width="117">](https://github.com/kyleparisi) |[<img alt="leaanthony" src="https://avatars.githubusercontent.com/u/1943904?v=4&s=117" width="117">](https://github.com/leaanthony) |
+[<img alt="justinjurenka" src="https://avatars.githubusercontent.com/u/19280122?v=4&s=117" width="117">](https://github.com/justinjurenka) |[<img alt="tykarol" src="https://avatars.githubusercontent.com/u/9386320?v=4&s=117" width="117">](https://github.com/tykarol) |[<img alt="kaspermeinema" src="https://avatars.githubusercontent.com/u/73821331?v=4&s=117" width="117">](https://github.com/kaspermeinema) |[<img alt="firesharkstudios" src="https://avatars.githubusercontent.com/u/17069637?v=4&s=117" width="117">](https://github.com/firesharkstudios) |[<img alt="kevin-west-10x" src="https://avatars.githubusercontent.com/u/65194914?v=4&s=117" width="117">](https://github.com/kevin-west-10x) |[<img alt="elkebab" src="https://avatars.githubusercontent.com/u/6313468?v=4&s=117" width="117">](https://github.com/elkebab) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[kaspermeinema](https://github.com/kaspermeinema) |[firesharkstudios](https://github.com/firesharkstudios) |[kevin-west-10x](https://github.com/kevin-west-10x) |[elkebab](https://github.com/elkebab) |[kyleparisi](https://github.com/kyleparisi) |[leaanthony](https://github.com/leaanthony) |
+[justinjurenka](https://github.com/justinjurenka) |[tykarol](https://github.com/tykarol) |[kaspermeinema](https://github.com/kaspermeinema) |[firesharkstudios](https://github.com/firesharkstudios) |[kevin-west-10x](https://github.com/kevin-west-10x) |[elkebab](https://github.com/elkebab) |
 
-[<img alt="larowlan" src="https://avatars.githubusercontent.com/u/555254?v=4&s=117" width="117">](https://github.com/larowlan) |[<img alt="dviry" src="https://avatars.githubusercontent.com/u/1230260?v=4&s=117" width="117">](https://github.com/dviry) |[<img alt="galli-leo" src="https://avatars.githubusercontent.com/u/5339762?v=4&s=117" width="117">](https://github.com/galli-leo) |[<img alt="leods92" src="https://avatars.githubusercontent.com/u/879395?v=4&s=117" width="117">](https://github.com/leods92) |[<img alt="louim" src="https://avatars.githubusercontent.com/u/923718?v=4&s=117" width="117">](https://github.com/louim) |[<img alt="lucaperret" src="https://avatars.githubusercontent.com/u/1887122?v=4&s=117" width="117">](https://github.com/lucaperret) |
+[<img alt="kyleparisi" src="https://avatars.githubusercontent.com/u/1286753?v=4&s=117" width="117">](https://github.com/kyleparisi) |[<img alt="leaanthony" src="https://avatars.githubusercontent.com/u/1943904?v=4&s=117" width="117">](https://github.com/leaanthony) |[<img alt="larowlan" src="https://avatars.githubusercontent.com/u/555254?v=4&s=117" width="117">](https://github.com/larowlan) |[<img alt="dviry" src="https://avatars.githubusercontent.com/u/1230260?v=4&s=117" width="117">](https://github.com/dviry) |[<img alt="galli-leo" src="https://avatars.githubusercontent.com/u/5339762?v=4&s=117" width="117">](https://github.com/galli-leo) |[<img alt="leods92" src="https://avatars.githubusercontent.com/u/879395?v=4&s=117" width="117">](https://github.com/leods92) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[larowlan](https://github.com/larowlan) |[dviry](https://github.com/dviry) |[galli-leo](https://github.com/galli-leo) |[leods92](https://github.com/leods92) |[louim](https://github.com/louim) |[lucaperret](https://github.com/lucaperret) |
+[kyleparisi](https://github.com/kyleparisi) |[leaanthony](https://github.com/leaanthony) |[larowlan](https://github.com/larowlan) |[dviry](https://github.com/dviry) |[galli-leo](https://github.com/galli-leo) |[leods92](https://github.com/leods92) |
 
-[<img alt="lucax88x" src="https://avatars.githubusercontent.com/u/6294464?v=4&s=117" width="117">](https://github.com/lucax88x) |[<img alt="onhate" src="https://avatars.githubusercontent.com/u/980905?v=4&s=117" width="117">](https://github.com/onhate) |[<img alt="mperrando" src="https://avatars.githubusercontent.com/u/525572?v=4&s=117" width="117">](https://github.com/mperrando) |[<img alt="marcosthejew" src="https://avatars.githubusercontent.com/u/1500967?v=4&s=117" width="117">](https://github.com/marcosthejew) |[<img alt="marcusforsberg" src="https://avatars.githubusercontent.com/u/1009069?v=4&s=117" width="117">](https://github.com/marcusforsberg) |[<img alt="Acconut" src="https://avatars.githubusercontent.com/u/1375043?v=4&s=117" width="117">](https://github.com/Acconut) |
+[<img alt="louim" src="https://avatars.githubusercontent.com/u/923718?v=4&s=117" width="117">](https://github.com/louim) |[<img alt="ombr" src="https://avatars.githubusercontent.com/u/857339?v=4&s=117" width="117">](https://github.com/ombr) |[<img alt="lucaperret" src="https://avatars.githubusercontent.com/u/1887122?v=4&s=117" width="117">](https://github.com/lucaperret) |[<img alt="lucax88x" src="https://avatars.githubusercontent.com/u/6294464?v=4&s=117" width="117">](https://github.com/lucax88x) |[<img alt="onhate" src="https://avatars.githubusercontent.com/u/980905?v=4&s=117" width="117">](https://github.com/onhate) |[<img alt="mperrando" src="https://avatars.githubusercontent.com/u/525572?v=4&s=117" width="117">](https://github.com/mperrando) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[lucax88x](https://github.com/lucax88x) |[onhate](https://github.com/onhate) |[mperrando](https://github.com/mperrando) |[marcosthejew](https://github.com/marcosthejew) |[marcusforsberg](https://github.com/marcusforsberg) |[Acconut](https://github.com/Acconut) |
+[louim](https://github.com/louim) |[ombr](https://github.com/ombr) |[lucaperret](https://github.com/lucaperret) |[lucax88x](https://github.com/lucax88x) |[onhate](https://github.com/onhate) |[mperrando](https://github.com/mperrando) |
 
-[<img alt="martin-brennan" src="https://avatars.githubusercontent.com/u/920448?v=4&s=117" width="117">](https://github.com/martin-brennan) |[<img alt="masaok" src="https://avatars.githubusercontent.com/u/1320083?v=4&s=117" width="117">](https://github.com/masaok) |[<img alt="mattfik" src="https://avatars.githubusercontent.com/u/1638028?v=4&s=117" width="117">](https://github.com/mattfik) |[<img alt="matthewhartstonge" src="https://avatars.githubusercontent.com/u/6119549?v=4&s=117" width="117">](https://github.com/matthewhartstonge) |[<img alt="mauricioribeiro" src="https://avatars.githubusercontent.com/u/2589856?v=4&s=117" width="117">](https://github.com/mauricioribeiro) |[<img alt="hrsh" src="https://avatars.githubusercontent.com/u/1929359?v=4&s=117" width="117">](https://github.com/hrsh) |
+[<img alt="marcosthejew" src="https://avatars.githubusercontent.com/u/1500967?v=4&s=117" width="117">](https://github.com/marcosthejew) |[<img alt="marcusforsberg" src="https://avatars.githubusercontent.com/u/1009069?v=4&s=117" width="117">](https://github.com/marcusforsberg) |[<img alt="Acconut" src="https://avatars.githubusercontent.com/u/1375043?v=4&s=117" width="117">](https://github.com/Acconut) |[<img alt="martin-brennan" src="https://avatars.githubusercontent.com/u/920448?v=4&s=117" width="117">](https://github.com/martin-brennan) |[<img alt="masaok" src="https://avatars.githubusercontent.com/u/1320083?v=4&s=117" width="117">](https://github.com/masaok) |[<img alt="mattfik" src="https://avatars.githubusercontent.com/u/1638028?v=4&s=117" width="117">](https://github.com/mattfik) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[martin-brennan](https://github.com/martin-brennan) |[masaok](https://github.com/masaok) |[mattfik](https://github.com/mattfik) |[matthewhartstonge](https://github.com/matthewhartstonge) |[mauricioribeiro](https://github.com/mauricioribeiro) |[hrsh](https://github.com/hrsh) |
+[marcosthejew](https://github.com/marcosthejew) |[marcusforsberg](https://github.com/marcusforsberg) |[Acconut](https://github.com/Acconut) |[martin-brennan](https://github.com/martin-brennan) |[masaok](https://github.com/masaok) |[mattfik](https://github.com/mattfik) |
 
-[<img alt="mhulet" src="https://avatars.githubusercontent.com/u/293355?v=4&s=117" width="117">](https://github.com/mhulet) |[<img alt="mkopinsky" src="https://avatars.githubusercontent.com/u/591435?v=4&s=117" width="117">](https://github.com/mkopinsky) |[<img alt="achmiral" src="https://avatars.githubusercontent.com/u/10906059?v=4&s=117" width="117">](https://github.com/achmiral) |[<img alt="boudra" src="https://avatars.githubusercontent.com/u/711886?v=4&s=117" width="117">](https://github.com/boudra) |[<img alt="mnafees" src="https://avatars.githubusercontent.com/u/1763885?v=4&s=117" width="117">](https://github.com/mnafees) |[<img alt="shahimclt" src="https://avatars.githubusercontent.com/u/8318002?v=4&s=117" width="117">](https://github.com/shahimclt) |
+[<img alt="matthewhartstonge" src="https://avatars.githubusercontent.com/u/6119549?v=4&s=117" width="117">](https://github.com/matthewhartstonge) |[<img alt="mauricioribeiro" src="https://avatars.githubusercontent.com/u/2589856?v=4&s=117" width="117">](https://github.com/mauricioribeiro) |[<img alt="hrsh" src="https://avatars.githubusercontent.com/u/1929359?v=4&s=117" width="117">](https://github.com/hrsh) |[<img alt="mhulet" src="https://avatars.githubusercontent.com/u/293355?v=4&s=117" width="117">](https://github.com/mhulet) |[<img alt="mkopinsky" src="https://avatars.githubusercontent.com/u/591435?v=4&s=117" width="117">](https://github.com/mkopinsky) |[<img alt="achmiral" src="https://avatars.githubusercontent.com/u/10906059?v=4&s=117" width="117">](https://github.com/achmiral) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[mhulet](https://github.com/mhulet) |[mkopinsky](https://github.com/mkopinsky) |[achmiral](https://github.com/achmiral) |[boudra](https://github.com/boudra) |[mnafees](https://github.com/mnafees) |[shahimclt](https://github.com/shahimclt) |
+[matthewhartstonge](https://github.com/matthewhartstonge) |[mauricioribeiro](https://github.com/mauricioribeiro) |[hrsh](https://github.com/hrsh) |[mhulet](https://github.com/mhulet) |[mkopinsky](https://github.com/mkopinsky) |[achmiral](https://github.com/achmiral) |
 
-[<img alt="navruzm" src="https://avatars.githubusercontent.com/u/168341?v=4&s=117" width="117">](https://github.com/navruzm) |[<img alt="marton-laszlo-attila" src="https://avatars.githubusercontent.com/u/73295321?v=4&s=117" width="117">](https://github.com/marton-laszlo-attila) |[<img alt="pleasespammelater" src="https://avatars.githubusercontent.com/u/11870394?v=4&s=117" width="117">](https://github.com/pleasespammelater) |[<img alt="naveed-ahmad" src="https://avatars.githubusercontent.com/u/701567?v=4&s=117" width="117">](https://github.com/naveed-ahmad) |[<img alt="nicojones" src="https://avatars.githubusercontent.com/u/6078915?v=4&s=117" width="117">](https://github.com/nicojones) |[<img alt="coreprocess" src="https://avatars.githubusercontent.com/u/1226918?v=4&s=117" width="117">](https://github.com/coreprocess) |
+[<img alt="boudra" src="https://avatars.githubusercontent.com/u/711886?v=4&s=117" width="117">](https://github.com/boudra) |[<img alt="mnafees" src="https://avatars.githubusercontent.com/u/1763885?v=4&s=117" width="117">](https://github.com/mnafees) |[<img alt="shahimclt" src="https://avatars.githubusercontent.com/u/8318002?v=4&s=117" width="117">](https://github.com/shahimclt) |[<img alt="navruzm" src="https://avatars.githubusercontent.com/u/168341?v=4&s=117" width="117">](https://github.com/navruzm) |[<img alt="marton-laszlo-attila" src="https://avatars.githubusercontent.com/u/73295321?v=4&s=117" width="117">](https://github.com/marton-laszlo-attila) |[<img alt="pleasespammelater" src="https://avatars.githubusercontent.com/u/11870394?v=4&s=117" width="117">](https://github.com/pleasespammelater) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[navruzm](https://github.com/navruzm) |[marton-laszlo-attila](https://github.com/marton-laszlo-attila) |[pleasespammelater](https://github.com/pleasespammelater) |[naveed-ahmad](https://github.com/naveed-ahmad) |[nicojones](https://github.com/nicojones) |[coreprocess](https://github.com/coreprocess) |
+[boudra](https://github.com/boudra) |[mnafees](https://github.com/mnafees) |[shahimclt](https://github.com/shahimclt) |[navruzm](https://github.com/navruzm) |[marton-laszlo-attila](https://github.com/marton-laszlo-attila) |[pleasespammelater](https://github.com/pleasespammelater) |
 
-[<img alt="nil1511" src="https://avatars.githubusercontent.com/u/2058170?v=4&s=117" width="117">](https://github.com/nil1511) |[<img alt="leftdevel" src="https://avatars.githubusercontent.com/u/843337?v=4&s=117" width="117">](https://github.com/leftdevel) |[<img alt="cryptic022" src="https://avatars.githubusercontent.com/u/18145703?v=4&s=117" width="117">](https://github.com/cryptic022) |[<img alt="patricklindsay" src="https://avatars.githubusercontent.com/u/7923681?v=4&s=117" width="117">](https://github.com/patricklindsay) |[<img alt="plneto" src="https://avatars.githubusercontent.com/u/5697434?v=4&s=117" width="117">](https://github.com/plneto) |[<img alt="pedrofs" src="https://avatars.githubusercontent.com/u/56484?v=4&s=117" width="117">](https://github.com/pedrofs) |
+[<img alt="naveed-ahmad" src="https://avatars.githubusercontent.com/u/701567?v=4&s=117" width="117">](https://github.com/naveed-ahmad) |[<img alt="nicojones" src="https://avatars.githubusercontent.com/u/6078915?v=4&s=117" width="117">](https://github.com/nicojones) |[<img alt="coreprocess" src="https://avatars.githubusercontent.com/u/1226918?v=4&s=117" width="117">](https://github.com/coreprocess) |[<img alt="nil1511" src="https://avatars.githubusercontent.com/u/2058170?v=4&s=117" width="117">](https://github.com/nil1511) |[<img alt="leftdevel" src="https://avatars.githubusercontent.com/u/843337?v=4&s=117" width="117">](https://github.com/leftdevel) |[<img alt="cryptic022" src="https://avatars.githubusercontent.com/u/18145703?v=4&s=117" width="117">](https://github.com/cryptic022) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[nil1511](https://github.com/nil1511) |[leftdevel](https://github.com/leftdevel) |[cryptic022](https://github.com/cryptic022) |[patricklindsay](https://github.com/patricklindsay) |[plneto](https://github.com/plneto) |[pedrofs](https://github.com/pedrofs) |
+[naveed-ahmad](https://github.com/naveed-ahmad) |[nicojones](https://github.com/nicojones) |[coreprocess](https://github.com/coreprocess) |[nil1511](https://github.com/nil1511) |[leftdevel](https://github.com/leftdevel) |[cryptic022](https://github.com/cryptic022) |
 
-[<img alt="pmusaraj" src="https://avatars.githubusercontent.com/u/368961?v=4&s=117" width="117">](https://github.com/pmusaraj) |[<img alt="phillipalexander" src="https://avatars.githubusercontent.com/u/1577682?v=4&s=117" width="117">](https://github.com/phillipalexander) |[<img alt="ppadmavilasom" src="https://avatars.githubusercontent.com/u/11167452?v=4&s=117" width="117">](https://github.com/ppadmavilasom) |[<img alt="Pzoco" src="https://avatars.githubusercontent.com/u/3101348?v=4&s=117" width="117">](https://github.com/Pzoco) |[<img alt="eman8519" src="https://avatars.githubusercontent.com/u/2380804?v=4&s=117" width="117">](https://github.com/eman8519) |[<img alt="luarmr" src="https://avatars.githubusercontent.com/u/817416?v=4&s=117" width="117">](https://github.com/luarmr) |
+[<img alt="patricklindsay" src="https://avatars.githubusercontent.com/u/7923681?v=4&s=117" width="117">](https://github.com/patricklindsay) |[<img alt="plneto" src="https://avatars.githubusercontent.com/u/5697434?v=4&s=117" width="117">](https://github.com/plneto) |[<img alt="pedrofs" src="https://avatars.githubusercontent.com/u/56484?v=4&s=117" width="117">](https://github.com/pedrofs) |[<img alt="pmusaraj" src="https://avatars.githubusercontent.com/u/368961?v=4&s=117" width="117">](https://github.com/pmusaraj) |[<img alt="phillipalexander" src="https://avatars.githubusercontent.com/u/1577682?v=4&s=117" width="117">](https://github.com/phillipalexander) |[<img alt="ppadmavilasom" src="https://avatars.githubusercontent.com/u/11167452?v=4&s=117" width="117">](https://github.com/ppadmavilasom) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[pmusaraj](https://github.com/pmusaraj) |[phillipalexander](https://github.com/phillipalexander) |[ppadmavilasom](https://github.com/ppadmavilasom) |[Pzoco](https://github.com/Pzoco) |[eman8519](https://github.com/eman8519) |[luarmr](https://github.com/luarmr) |
+[patricklindsay](https://github.com/patricklindsay) |[plneto](https://github.com/plneto) |[pedrofs](https://github.com/pedrofs) |[pmusaraj](https://github.com/pmusaraj) |[phillipalexander](https://github.com/phillipalexander) |[ppadmavilasom](https://github.com/ppadmavilasom) |
 
-[<img alt="refo" src="https://avatars.githubusercontent.com/u/1114116?v=4&s=117" width="117">](https://github.com/refo) |[<img alt="SxDx" src="https://avatars.githubusercontent.com/u/2004247?v=4&s=117" width="117">](https://github.com/SxDx) |[<img alt="robwilson1" src="https://avatars.githubusercontent.com/u/7114944?v=4&s=117" width="117">](https://github.com/robwilson1) |[<img alt="romain-preston" src="https://avatars.githubusercontent.com/u/1517040?v=4&s=117" width="117">](https://github.com/romain-preston) |[<img alt="scherroman" src="https://avatars.githubusercontent.com/u/7923938?v=4&s=117" width="117">](https://github.com/scherroman) |[<img alt="rossng" src="https://avatars.githubusercontent.com/u/565371?v=4&s=117" width="117">](https://github.com/rossng) |
+[<img alt="Pzoco" src="https://avatars.githubusercontent.com/u/3101348?v=4&s=117" width="117">](https://github.com/Pzoco) |[<img alt="eman8519" src="https://avatars.githubusercontent.com/u/2380804?v=4&s=117" width="117">](https://github.com/eman8519) |[<img alt="luarmr" src="https://avatars.githubusercontent.com/u/817416?v=4&s=117" width="117">](https://github.com/luarmr) |[<img alt="refo" src="https://avatars.githubusercontent.com/u/1114116?v=4&s=117" width="117">](https://github.com/refo) |[<img alt="SxDx" src="https://avatars.githubusercontent.com/u/2004247?v=4&s=117" width="117">](https://github.com/SxDx) |[<img alt="robwilson1" src="https://avatars.githubusercontent.com/u/7114944?v=4&s=117" width="117">](https://github.com/robwilson1) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[refo](https://github.com/refo) |[SxDx](https://github.com/SxDx) |[robwilson1](https://github.com/robwilson1) |[romain-preston](https://github.com/romain-preston) |[scherroman](https://github.com/scherroman) |[rossng](https://github.com/rossng) |
+[Pzoco](https://github.com/Pzoco) |[eman8519](https://github.com/eman8519) |[luarmr](https://github.com/luarmr) |[refo](https://github.com/refo) |[SxDx](https://github.com/SxDx) |[robwilson1](https://github.com/robwilson1) |
 
-[<img alt="rart" src="https://avatars.githubusercontent.com/u/3928341?v=4&s=117" width="117">](https://github.com/rart) |[<img alt="fortunto2" src="https://avatars.githubusercontent.com/u/1236751?v=4&s=117" width="117">](https://github.com/fortunto2) |[<img alt="samuelcolburn" src="https://avatars.githubusercontent.com/u/9741902?v=4&s=117" width="117">](https://github.com/samuelcolburn) |[<img alt="sebasegovia01" src="https://avatars.githubusercontent.com/u/35777287?v=4&s=117" width="117">](https://github.com/sebasegovia01) |[<img alt="sergei-zelinsky" src="https://avatars.githubusercontent.com/u/19428086?v=4&s=117" width="117">](https://github.com/sergei-zelinsky) |[<img alt="szh" src="https://avatars.githubusercontent.com/u/546965?v=4&s=117" width="117">](https://github.com/szh) |
+[<img alt="romain-preston" src="https://avatars.githubusercontent.com/u/1517040?v=4&s=117" width="117">](https://github.com/romain-preston) |[<img alt="scherroman" src="https://avatars.githubusercontent.com/u/7923938?v=4&s=117" width="117">](https://github.com/scherroman) |[<img alt="rossng" src="https://avatars.githubusercontent.com/u/565371?v=4&s=117" width="117">](https://github.com/rossng) |[<img alt="rart" src="https://avatars.githubusercontent.com/u/3928341?v=4&s=117" width="117">](https://github.com/rart) |[<img alt="fortunto2" src="https://avatars.githubusercontent.com/u/1236751?v=4&s=117" width="117">](https://github.com/fortunto2) |[<img alt="samuelcolburn" src="https://avatars.githubusercontent.com/u/9741902?v=4&s=117" width="117">](https://github.com/samuelcolburn) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[rart](https://github.com/rart) |[fortunto2](https://github.com/fortunto2) |[samuelcolburn](https://github.com/samuelcolburn) |[sebasegovia01](https://github.com/sebasegovia01) |[sergei-zelinsky](https://github.com/sergei-zelinsky) |[szh](https://github.com/szh) |
+[romain-preston](https://github.com/romain-preston) |[scherroman](https://github.com/scherroman) |[rossng](https://github.com/rossng) |[rart](https://github.com/rart) |[fortunto2](https://github.com/fortunto2) |[samuelcolburn](https://github.com/samuelcolburn) |
 
-[<img alt="SpazzMarticus" src="https://avatars.githubusercontent.com/u/5716457?v=4&s=117" width="117">](https://github.com/SpazzMarticus) |[<img alt="waptik" src="https://avatars.githubusercontent.com/u/1687551?v=4&s=117" width="117">](https://github.com/waptik) |[<img alt="amaitu" src="https://avatars.githubusercontent.com/u/15688439?v=4&s=117" width="117">](https://github.com/amaitu) |[<img alt="steverob" src="https://avatars.githubusercontent.com/u/1220480?v=4&s=117" width="117">](https://github.com/steverob) |[<img alt="sjauld" src="https://avatars.githubusercontent.com/u/8232503?v=4&s=117" width="117">](https://github.com/sjauld) |[<img alt="taj" src="https://avatars.githubusercontent.com/u/16062635?v=4&s=117" width="117">](https://github.com/taj) |
+[<img alt="sebasegovia01" src="https://avatars.githubusercontent.com/u/35777287?v=4&s=117" width="117">](https://github.com/sebasegovia01) |[<img alt="sergei-zelinsky" src="https://avatars.githubusercontent.com/u/19428086?v=4&s=117" width="117">](https://github.com/sergei-zelinsky) |[<img alt="szh" src="https://avatars.githubusercontent.com/u/546965?v=4&s=117" width="117">](https://github.com/szh) |[<img alt="SpazzMarticus" src="https://avatars.githubusercontent.com/u/5716457?v=4&s=117" width="117">](https://github.com/SpazzMarticus) |[<img alt="waptik" src="https://avatars.githubusercontent.com/u/1687551?v=4&s=117" width="117">](https://github.com/waptik) |[<img alt="amaitu" src="https://avatars.githubusercontent.com/u/15688439?v=4&s=117" width="117">](https://github.com/amaitu) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[SpazzMarticus](https://github.com/SpazzMarticus) |[waptik](https://github.com/waptik) |[amaitu](https://github.com/amaitu) |[steverob](https://github.com/steverob) |[sjauld](https://github.com/sjauld) |[taj](https://github.com/taj) |
+[sebasegovia01](https://github.com/sebasegovia01) |[sergei-zelinsky](https://github.com/sergei-zelinsky) |[szh](https://github.com/szh) |[SpazzMarticus](https://github.com/SpazzMarticus) |[waptik](https://github.com/waptik) |[amaitu](https://github.com/amaitu) |
 
-[<img alt="Tashows" src="https://avatars.githubusercontent.com/u/16656928?v=4&s=117" width="117">](https://github.com/Tashows) |[<img alt="twarlop" src="https://avatars.githubusercontent.com/u/2856082?v=4&s=117" width="117">](https://github.com/twarlop) |[<img alt="tmaier" src="https://avatars.githubusercontent.com/u/350038?v=4&s=117" width="117">](https://github.com/tmaier) |[<img alt="WIStudent" src="https://avatars.githubusercontent.com/u/2707930?v=4&s=117" width="117">](https://github.com/WIStudent) |[<img alt="tomsaleeba" src="https://avatars.githubusercontent.com/u/1773838?v=4&s=117" width="117">](https://github.com/tomsaleeba) |[<img alt="tomekp" src="https://avatars.githubusercontent.com/u/1856393?v=4&s=117" width="117">](https://github.com/tomekp) |
+[<img alt="steverob" src="https://avatars.githubusercontent.com/u/1220480?v=4&s=117" width="117">](https://github.com/steverob) |[<img alt="sjauld" src="https://avatars.githubusercontent.com/u/8232503?v=4&s=117" width="117">](https://github.com/sjauld) |[<img alt="taj" src="https://avatars.githubusercontent.com/u/16062635?v=4&s=117" width="117">](https://github.com/taj) |[<img alt="Tashows" src="https://avatars.githubusercontent.com/u/16656928?v=4&s=117" width="117">](https://github.com/Tashows) |[<img alt="twarlop" src="https://avatars.githubusercontent.com/u/2856082?v=4&s=117" width="117">](https://github.com/twarlop) |[<img alt="tmaier" src="https://avatars.githubusercontent.com/u/350038?v=4&s=117" width="117">](https://github.com/tmaier) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[Tashows](https://github.com/Tashows) |[twarlop](https://github.com/twarlop) |[tmaier](https://github.com/tmaier) |[WIStudent](https://github.com/WIStudent) |[tomsaleeba](https://github.com/tomsaleeba) |[tomekp](https://github.com/tomekp) |
+[steverob](https://github.com/steverob) |[sjauld](https://github.com/sjauld) |[taj](https://github.com/taj) |[Tashows](https://github.com/Tashows) |[twarlop](https://github.com/twarlop) |[tmaier](https://github.com/tmaier) |
 
-[<img alt="tvaliasek" src="https://avatars.githubusercontent.com/u/8644946?v=4&s=117" width="117">](https://github.com/tvaliasek) |[<img alt="vially" src="https://avatars.githubusercontent.com/u/433598?v=4&s=117" width="117">](https://github.com/vially) |[<img alt="valentinoli" src="https://avatars.githubusercontent.com/u/23453691?v=4&s=117" width="117">](https://github.com/valentinoli) |[<img alt="nagyv" src="https://avatars.githubusercontent.com/u/126671?v=4&s=117" width="117">](https://github.com/nagyv) |[<img alt="dwnste" src="https://avatars.githubusercontent.com/u/17119722?v=4&s=117" width="117">](https://github.com/dwnste) |[<img alt="weston-sankey-mark43" src="https://avatars.githubusercontent.com/u/97678695?v=4&s=117" width="117">](https://github.com/weston-sankey-mark43) |
+[<img alt="WIStudent" src="https://avatars.githubusercontent.com/u/2707930?v=4&s=117" width="117">](https://github.com/WIStudent) |[<img alt="tomsaleeba" src="https://avatars.githubusercontent.com/u/1773838?v=4&s=117" width="117">](https://github.com/tomsaleeba) |[<img alt="tomekp" src="https://avatars.githubusercontent.com/u/1856393?v=4&s=117" width="117">](https://github.com/tomekp) |[<img alt="tvaliasek" src="https://avatars.githubusercontent.com/u/8644946?v=4&s=117" width="117">](https://github.com/tvaliasek) |[<img alt="vially" src="https://avatars.githubusercontent.com/u/433598?v=4&s=117" width="117">](https://github.com/vially) |[<img alt="valentinoli" src="https://avatars.githubusercontent.com/u/23453691?v=4&s=117" width="117">](https://github.com/valentinoli) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[tvaliasek](https://github.com/tvaliasek) |[vially](https://github.com/vially) |[valentinoli](https://github.com/valentinoli) |[nagyv](https://github.com/nagyv) |[dwnste](https://github.com/dwnste) |[weston-sankey-mark43](https://github.com/weston-sankey-mark43) |
+[WIStudent](https://github.com/WIStudent) |[tomsaleeba](https://github.com/tomsaleeba) |[tomekp](https://github.com/tomekp) |[tvaliasek](https://github.com/tvaliasek) |[vially](https://github.com/vially) |[valentinoli](https://github.com/valentinoli) |
 
-[<img alt="willycamargo" src="https://avatars.githubusercontent.com/u/5041887?v=4&s=117" width="117">](https://github.com/willycamargo) |[<img alt="xhocquet" src="https://avatars.githubusercontent.com/u/8116516?v=4&s=117" width="117">](https://github.com/xhocquet) |[<img alt="YehudaKremer" src="https://avatars.githubusercontent.com/u/946652?v=4&s=117" width="117">](https://github.com/YehudaKremer) |[<img alt="zachconner" src="https://avatars.githubusercontent.com/u/11339326?v=4&s=117" width="117">](https://github.com/zachconner) |[<img alt="zacharylawson" src="https://avatars.githubusercontent.com/u/7375444?v=4&s=117" width="117">](https://github.com/zacharylawson) |[<img alt="zackbloom" src="https://avatars.githubusercontent.com/u/55347?v=4&s=117" width="117">](https://github.com/zackbloom) |
+[<img alt="nagyv" src="https://avatars.githubusercontent.com/u/126671?v=4&s=117" width="117">](https://github.com/nagyv) |[<img alt="dwnste" src="https://avatars.githubusercontent.com/u/17119722?v=4&s=117" width="117">](https://github.com/dwnste) |[<img alt="weston-sankey-mark43" src="https://avatars.githubusercontent.com/u/97678695?v=4&s=117" width="117">](https://github.com/weston-sankey-mark43) |[<img alt="willycamargo" src="https://avatars.githubusercontent.com/u/5041887?v=4&s=117" width="117">](https://github.com/willycamargo) |[<img alt="xhocquet" src="https://avatars.githubusercontent.com/u/8116516?v=4&s=117" width="117">](https://github.com/xhocquet) |[<img alt="YehudaKremer" src="https://avatars.githubusercontent.com/u/946652?v=4&s=117" width="117">](https://github.com/YehudaKremer) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[willycamargo](https://github.com/willycamargo) |[xhocquet](https://github.com/xhocquet) |[YehudaKremer](https://github.com/YehudaKremer) |[zachconner](https://github.com/zachconner) |[zacharylawson](https://github.com/zacharylawson) |[zackbloom](https://github.com/zackbloom) |
+[nagyv](https://github.com/nagyv) |[dwnste](https://github.com/dwnste) |[weston-sankey-mark43](https://github.com/weston-sankey-mark43) |[willycamargo](https://github.com/willycamargo) |[xhocquet](https://github.com/xhocquet) |[YehudaKremer](https://github.com/YehudaKremer) |
 
-[<img alt="sartoshi-foot-dao" src="https://avatars.githubusercontent.com/u/99770068?v=4&s=117" width="117">](https://github.com/sartoshi-foot-dao) |[<img alt="agreene-coursera" src="https://avatars.githubusercontent.com/u/30501355?v=4&s=117" width="117">](https://github.com/agreene-coursera) |[<img alt="alfatv" src="https://avatars.githubusercontent.com/u/62238673?v=4&s=117" width="117">](https://github.com/alfatv) |[<img alt="arggh" src="https://avatars.githubusercontent.com/u/17210302?v=4&s=117" width="117">](https://github.com/arggh) |[<img alt="avalla" src="https://avatars.githubusercontent.com/u/986614?v=4&s=117" width="117">](https://github.com/avalla) |[<img alt="bdirito" src="https://avatars.githubusercontent.com/u/8117238?v=4&s=117" width="117">](https://github.com/bdirito) |
+[<img alt="zachconner" src="https://avatars.githubusercontent.com/u/11339326?v=4&s=117" width="117">](https://github.com/zachconner) |[<img alt="zacharylawson" src="https://avatars.githubusercontent.com/u/7375444?v=4&s=117" width="117">](https://github.com/zacharylawson) |[<img alt="zackbloom" src="https://avatars.githubusercontent.com/u/55347?v=4&s=117" width="117">](https://github.com/zackbloom) |[<img alt="sartoshi-foot-dao" src="https://avatars.githubusercontent.com/u/99770068?v=4&s=117" width="117">](https://github.com/sartoshi-foot-dao) |[<img alt="agreene-coursera" src="https://avatars.githubusercontent.com/u/30501355?v=4&s=117" width="117">](https://github.com/agreene-coursera) |[<img alt="alfatv" src="https://avatars.githubusercontent.com/u/62238673?v=4&s=117" width="117">](https://github.com/alfatv) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[sartoshi-foot-dao](https://github.com/sartoshi-foot-dao) |[agreene-coursera](https://github.com/agreene-coursera) |[alfatv](https://github.com/alfatv) |[arggh](https://github.com/arggh) |[avalla](https://github.com/avalla) |[bdirito](https://github.com/bdirito) |
+[zachconner](https://github.com/zachconner) |[zacharylawson](https://github.com/zacharylawson) |[zackbloom](https://github.com/zackbloom) |[sartoshi-foot-dao](https://github.com/sartoshi-foot-dao) |[agreene-coursera](https://github.com/agreene-coursera) |[alfatv](https://github.com/alfatv) |
 
-[<img alt="c0b41" src="https://avatars.githubusercontent.com/u/2834954?v=4&s=117" width="117">](https://github.com/c0b41) |[<img alt="canvasbh" src="https://avatars.githubusercontent.com/u/44477734?v=4&s=117" width="117">](https://github.com/canvasbh) |[<img alt="christianwengert" src="https://avatars.githubusercontent.com/u/12936057?v=4&s=117" width="117">](https://github.com/christianwengert) |[<img alt="craigcbrunner" src="https://avatars.githubusercontent.com/u/2780521?v=4&s=117" width="117">](https://github.com/craigcbrunner) |[<img alt="darthf1" src="https://avatars.githubusercontent.com/u/17253332?v=4&s=117" width="117">](https://github.com/darthf1) |[<img alt="dkisic" src="https://avatars.githubusercontent.com/u/32257921?v=4&s=117" width="117">](https://github.com/dkisic) |
+[<img alt="arggh" src="https://avatars.githubusercontent.com/u/17210302?v=4&s=117" width="117">](https://github.com/arggh) |[<img alt="avalla" src="https://avatars.githubusercontent.com/u/986614?v=4&s=117" width="117">](https://github.com/avalla) |[<img alt="bdirito" src="https://avatars.githubusercontent.com/u/8117238?v=4&s=117" width="117">](https://github.com/bdirito) |[<img alt="c0b41" src="https://avatars.githubusercontent.com/u/2834954?v=4&s=117" width="117">](https://github.com/c0b41) |[<img alt="canvasbh" src="https://avatars.githubusercontent.com/u/44477734?v=4&s=117" width="117">](https://github.com/canvasbh) |[<img alt="christianwengert" src="https://avatars.githubusercontent.com/u/12936057?v=4&s=117" width="117">](https://github.com/christianwengert) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[c0b41](https://github.com/c0b41) |[canvasbh](https://github.com/canvasbh) |[christianwengert](https://github.com/christianwengert) |[craigcbrunner](https://github.com/craigcbrunner) |[darthf1](https://github.com/darthf1) |[dkisic](https://github.com/dkisic) |
+[arggh](https://github.com/arggh) |[avalla](https://github.com/avalla) |[bdirito](https://github.com/bdirito) |[c0b41](https://github.com/c0b41) |[canvasbh](https://github.com/canvasbh) |[christianwengert](https://github.com/christianwengert) |
 
-[<img alt="fingul" src="https://avatars.githubusercontent.com/u/894739?v=4&s=117" width="117">](https://github.com/fingul) |[<img alt="franckl" src="https://avatars.githubusercontent.com/u/3875803?v=4&s=117" width="117">](https://github.com/franckl) |[<img alt="gaelicwinter" src="https://avatars.githubusercontent.com/u/6510266?v=4&s=117" width="117">](https://github.com/gaelicwinter) |[<img alt="green-mike" src="https://avatars.githubusercontent.com/u/5584225?v=4&s=117" width="117">](https://github.com/green-mike) |[<img alt="heocoi" src="https://avatars.githubusercontent.com/u/13751011?v=4&s=117" width="117">](https://github.com/heocoi) |[<img alt="hxgf" src="https://avatars.githubusercontent.com/u/56104?v=4&s=117" width="117">](https://github.com/hxgf) |
+[<img alt="craigcbrunner" src="https://avatars.githubusercontent.com/u/2780521?v=4&s=117" width="117">](https://github.com/craigcbrunner) |[<img alt="darthf1" src="https://avatars.githubusercontent.com/u/17253332?v=4&s=117" width="117">](https://github.com/darthf1) |[<img alt="dkisic" src="https://avatars.githubusercontent.com/u/32257921?v=4&s=117" width="117">](https://github.com/dkisic) |[<img alt="fingul" src="https://avatars.githubusercontent.com/u/894739?v=4&s=117" width="117">](https://github.com/fingul) |[<img alt="franckl" src="https://avatars.githubusercontent.com/u/3875803?v=4&s=117" width="117">](https://github.com/franckl) |[<img alt="gaelicwinter" src="https://avatars.githubusercontent.com/u/6510266?v=4&s=117" width="117">](https://github.com/gaelicwinter) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[fingul](https://github.com/fingul) |[franckl](https://github.com/franckl) |[gaelicwinter](https://github.com/gaelicwinter) |[green-mike](https://github.com/green-mike) |[heocoi](https://github.com/heocoi) |[hxgf](https://github.com/hxgf) |
+[craigcbrunner](https://github.com/craigcbrunner) |[darthf1](https://github.com/darthf1) |[dkisic](https://github.com/dkisic) |[fingul](https://github.com/fingul) |[franckl](https://github.com/franckl) |[gaelicwinter](https://github.com/gaelicwinter) |
 
-[<img alt="johnmanjiro13" src="https://avatars.githubusercontent.com/u/28798279?v=4&s=117" width="117">](https://github.com/johnmanjiro13) |[<img alt="kode-ninja" src="https://avatars.githubusercontent.com/u/7857611?v=4&s=117" width="117">](https://github.com/kode-ninja) |[<img alt="jx-zyf" src="https://avatars.githubusercontent.com/u/26456842?v=4&s=117" width="117">](https://github.com/jx-zyf) |[<img alt="magumbo" src="https://avatars.githubusercontent.com/u/6683765?v=4&s=117" width="117">](https://github.com/magumbo) |[<img alt="ninesalt" src="https://avatars.githubusercontent.com/u/7952255?v=4&s=117" width="117">](https://github.com/ninesalt) |[<img alt="phil714" src="https://avatars.githubusercontent.com/u/7584581?v=4&s=117" width="117">](https://github.com/phil714) |
+[<img alt="green-mike" src="https://avatars.githubusercontent.com/u/5584225?v=4&s=117" width="117">](https://github.com/green-mike) |[<img alt="heocoi" src="https://avatars.githubusercontent.com/u/13751011?v=4&s=117" width="117">](https://github.com/heocoi) |[<img alt="hxgf" src="https://avatars.githubusercontent.com/u/56104?v=4&s=117" width="117">](https://github.com/hxgf) |[<img alt="johnmanjiro13" src="https://avatars.githubusercontent.com/u/28798279?v=4&s=117" width="117">](https://github.com/johnmanjiro13) |[<img alt="kode-ninja" src="https://avatars.githubusercontent.com/u/7857611?v=4&s=117" width="117">](https://github.com/kode-ninja) |[<img alt="jx-zyf" src="https://avatars.githubusercontent.com/u/26456842?v=4&s=117" width="117">](https://github.com/jx-zyf) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[johnmanjiro13](https://github.com/johnmanjiro13) |[kode-ninja](https://github.com/kode-ninja) |[jx-zyf](https://github.com/jx-zyf) |[magumbo](https://github.com/magumbo) |[ninesalt](https://github.com/ninesalt) |[phil714](https://github.com/phil714) |
+[green-mike](https://github.com/green-mike) |[heocoi](https://github.com/heocoi) |[hxgf](https://github.com/hxgf) |[johnmanjiro13](https://github.com/johnmanjiro13) |[kode-ninja](https://github.com/kode-ninja) |[jx-zyf](https://github.com/jx-zyf) |
 
-[<img alt="luntta" src="https://avatars.githubusercontent.com/u/14221637?v=4&s=117" width="117">](https://github.com/luntta) |[<img alt="rhymes" src="https://avatars.githubusercontent.com/u/146201?v=4&s=117" width="117">](https://github.com/rhymes) |[<img alt="rlebosse" src="https://avatars.githubusercontent.com/u/2794137?v=4&s=117" width="117">](https://github.com/rlebosse) |[<img alt="rtaieb" src="https://avatars.githubusercontent.com/u/35224301?v=4&s=117" width="117">](https://github.com/rtaieb) |[<img alt="slawexxx44" src="https://avatars.githubusercontent.com/u/11180644?v=4&s=117" width="117">](https://github.com/slawexxx44) |[<img alt="thanhthot" src="https://avatars.githubusercontent.com/u/50633205?v=4&s=117" width="117">](https://github.com/thanhthot) |
+[<img alt="magumbo" src="https://avatars.githubusercontent.com/u/6683765?v=4&s=117" width="117">](https://github.com/magumbo) |[<img alt="ninesalt" src="https://avatars.githubusercontent.com/u/7952255?v=4&s=117" width="117">](https://github.com/ninesalt) |[<img alt="phil714" src="https://avatars.githubusercontent.com/u/7584581?v=4&s=117" width="117">](https://github.com/phil714) |[<img alt="luntta" src="https://avatars.githubusercontent.com/u/14221637?v=4&s=117" width="117">](https://github.com/luntta) |[<img alt="rhymes" src="https://avatars.githubusercontent.com/u/146201?v=4&s=117" width="117">](https://github.com/rhymes) |[<img alt="rlebosse" src="https://avatars.githubusercontent.com/u/2794137?v=4&s=117" width="117">](https://github.com/rlebosse) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[luntta](https://github.com/luntta) |[rhymes](https://github.com/rhymes) |[rlebosse](https://github.com/rlebosse) |[rtaieb](https://github.com/rtaieb) |[slawexxx44](https://github.com/slawexxx44) |[thanhthot](https://github.com/thanhthot) |
+[magumbo](https://github.com/magumbo) |[ninesalt](https://github.com/ninesalt) |[phil714](https://github.com/phil714) |[luntta](https://github.com/luntta) |[rhymes](https://github.com/rhymes) |[rlebosse](https://github.com/rlebosse) |
 
-[<img alt="tinny77" src="https://avatars.githubusercontent.com/u/1872936?v=4&s=117" width="117">](https://github.com/tinny77) |[<img alt="tusharjkhunt" src="https://avatars.githubusercontent.com/u/31904234?v=4&s=117" width="117">](https://github.com/tusharjkhunt) |[<img alt="vedran555" src="https://avatars.githubusercontent.com/u/38395951?v=4&s=117" width="117">](https://github.com/vedran555) |[<img alt="yoann-hellopret" src="https://avatars.githubusercontent.com/u/46525558?v=4&s=117" width="117">](https://github.com/yoann-hellopret) |[<img alt="olitomas" src="https://avatars.githubusercontent.com/u/6918659?v=4&s=117" width="117">](https://github.com/olitomas) |[<img alt="JimmyLv" src="https://avatars.githubusercontent.com/u/4997466?v=4&s=117" width="117">](https://github.com/JimmyLv) |
+[<img alt="rtaieb" src="https://avatars.githubusercontent.com/u/35224301?v=4&s=117" width="117">](https://github.com/rtaieb) |[<img alt="slawexxx44" src="https://avatars.githubusercontent.com/u/11180644?v=4&s=117" width="117">](https://github.com/slawexxx44) |[<img alt="thanhthot" src="https://avatars.githubusercontent.com/u/50633205?v=4&s=117" width="117">](https://github.com/thanhthot) |[<img alt="tinny77" src="https://avatars.githubusercontent.com/u/1872936?v=4&s=117" width="117">](https://github.com/tinny77) |[<img alt="tusharjkhunt" src="https://avatars.githubusercontent.com/u/31904234?v=4&s=117" width="117">](https://github.com/tusharjkhunt) |[<img alt="vedran555" src="https://avatars.githubusercontent.com/u/38395951?v=4&s=117" width="117">](https://github.com/vedran555) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[tinny77](https://github.com/tinny77) |[tusharjkhunt](https://github.com/tusharjkhunt) |[vedran555](https://github.com/vedran555) |[yoann-hellopret](https://github.com/yoann-hellopret) |[olitomas](https://github.com/olitomas) |[JimmyLv](https://github.com/JimmyLv) |
+[rtaieb](https://github.com/rtaieb) |[slawexxx44](https://github.com/slawexxx44) |[thanhthot](https://github.com/thanhthot) |[tinny77](https://github.com/tinny77) |[tusharjkhunt](https://github.com/tusharjkhunt) |[vedran555](https://github.com/vedran555) |
+
+[<img alt="yoann-hellopret" src="https://avatars.githubusercontent.com/u/46525558?v=4&s=117" width="117">](https://github.com/yoann-hellopret) |[<img alt="olitomas" src="https://avatars.githubusercontent.com/u/6918659?v=4&s=117" width="117">](https://github.com/olitomas) |[<img alt="JimmyLv" src="https://avatars.githubusercontent.com/u/4997466?v=4&s=117" width="117">](https://github.com/JimmyLv) |
+:---: |:---: |:---: |
+[yoann-hellopret](https://github.com/yoann-hellopret) |[olitomas](https://github.com/olitomas) |[JimmyLv](https://github.com/JimmyLv) |
 
 <!--/contributors-->
 

+ 23 - 0
e2e/clients/dashboard-ui/app.js

@@ -1,13 +1,36 @@
 import Uppy from '@uppy/core'
 import Dashboard from '@uppy/dashboard'
+import RemoteSources from '@uppy/remote-sources'
+import Webcam from '@uppy/webcam'
+import ScreenCapture from '@uppy/screen-capture'
+import GoldenRetriever from '@uppy/golden-retriever'
 import ImageEditor from '@uppy/image-editor'
+import DropTarget from '@uppy/drop-target'
+import Audio from '@uppy/audio'
+import Compressor from '@uppy/compressor'
 
 import '@uppy/core/dist/style.css'
 import '@uppy/dashboard/dist/style.css'
 
+const COMPANION_URL = 'http://companion.uppy.io'
+
 const uppy = new Uppy()
   .use(Dashboard, { target: '#app', inline: true })
+  .use(RemoteSources, { companionUrl: COMPANION_URL })
+  .use(Webcam, {
+    target: Dashboard,
+    showVideoSourceDropdown: true,
+    showRecordingLength: true,
+  })
+  .use(Audio, {
+    target: Dashboard,
+    showRecordingLength: true,
+  })
+  .use(ScreenCapture, { target: Dashboard })
   .use(ImageEditor, { target: Dashboard })
+  .use(DropTarget, { target: document.body })
+  .use(Compressor)
+  .use(GoldenRetriever, { serviceWorker: true })
 
 // Keep this here to access uppy in tests
 window.uppy = uppy

+ 16 - 0
e2e/cypress.config.mjs

@@ -0,0 +1,16 @@
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { defineConfig } from 'cypress'
+
+export default defineConfig({
+  defaultCommandTimeout: 16000,
+
+  e2e: {
+    baseUrl: 'http://localhost:1234',
+    specPattern: 'cypress/integration/*.spec.ts',
+
+    // eslint-disable-next-line no-unused-vars
+    setupNodeEvents (on, config) {
+      // implement node event listeners here
+    },
+  },
+})

+ 0 - 5
e2e/cypress.json

@@ -1,5 +0,0 @@
-{
-  "projectId": "mcijsj",
-  "baseUrl": "http://localhost:1234",
-  "defaultCommandTimeout": 16000
-}

+ 2 - 2
e2e/cypress/integration/dashboard-compressor.spec.ts

@@ -22,13 +22,13 @@ function uglierBytes (text) {
 describe('dashboard-compressor', () => {
   beforeEach(() => {
     cy.visit('/dashboard-compressor')
-    cy.get('.uppy-Dashboard-input').as('file-input')
+    cy.get('.uppy-Dashboard-input:first').as('file-input')
   })
 
   it('should compress images', () => {
     const sizeBeforeCompression = []
 
-    cy.get('@file-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
+    cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg'], { force:true })
 
     cy.get('.uppy-Dashboard-Item-statusSize').each((element) => {
       const text = element.text()

+ 73 - 3
e2e/cypress/integration/dashboard-transloadit.spec.ts

@@ -1,13 +1,14 @@
 describe('Dashboard with Transloadit', () => {
   beforeEach(() => {
     cy.visit('/dashboard-transloadit')
-    cy.get('.uppy-Dashboard-input').as('file-input')
+    cy.get('.uppy-Dashboard-input:first').as('file-input')
+    cy.intercept('/assemblies').as('createAssemblies')
     cy.intercept('/assemblies/*').as('assemblies')
     cy.intercept('/resumable/*').as('resumable')
   })
 
   it('should upload cat image successfully', () => {
-    cy.get('@file-input').attachFile('images/cat.jpg')
+    cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { force:true })
     cy.get('.uppy-StatusBar-actionBtn--upload').click()
 
     cy.wait('@assemblies')
@@ -17,7 +18,7 @@ describe('Dashboard with Transloadit', () => {
   })
 
   it('should close assembly polling when cancelled', () => {
-    cy.get('@file-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
+    cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg'], { force:true })
     cy.get('.uppy-StatusBar-actionBtn--upload').click()
 
     cy.intercept({
@@ -42,4 +43,73 @@ describe('Dashboard with Transloadit', () => {
       expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).some((a: any) => a.pollInterval)).to.equal(false)
     })
   })
+
+  it('should not create assembly when all individual files have been cancelled', () => {
+    cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg'], { force:true })
+    cy.get('.uppy-StatusBar-actionBtn--upload').click()
+
+    cy.window().then(({ uppy }) => {
+      expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).length).to.equal(0)
+
+      const { files } = uppy.getState()
+      uppy.removeFiles(Object.keys(files))
+
+      cy.wait('@createAssemblies').then(() => {
+        expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).some((a: any) => a.pollInterval)).to.equal(false)
+      })
+    })
+  })
+
+  // Not working, the upstream changes have not landed yet.
+  it.skip('should create assembly if there is still one file to upload', () => {
+    cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg'], { force:true })
+    cy.get('.uppy-StatusBar-actionBtn--upload').click()
+
+    cy.window().then(({ uppy }) => {
+      expect(Object.values(uppy.getPlugin('Transloadit').activeAssemblies).length).to.equal(0)
+
+      const { files } = uppy.getState()
+      const [fileID] = Object.keys(files)
+      uppy.removeFile(fileID)
+
+      cy.wait('@createAssemblies').then(() => {
+        cy.wait('@resumable')
+        cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
+      })
+    })
+  })
+
+  // Not working, the upstream changes have not landed yet.
+  it.skip('should complete upload if one gets cancelled mid-flight', () => {
+    cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg'], { force:true })
+    cy.get('.uppy-StatusBar-actionBtn--upload').click()
+
+    cy.wait('@createAssemblies')
+    cy.wait('@resumable')
+
+    cy.window().then(({ uppy }) => {
+      const { files } = uppy.getState()
+      const [fileID] = Object.keys(files)
+      uppy.removeFile(fileID)
+
+      cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
+    })
+  })
+
+  it('should not emit error if upload is cancelled right away', () => {
+    cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { force:true })
+    cy.get('.uppy-StatusBar-actionBtn--upload').click()
+
+    const handler = cy.spy()
+
+    cy.window().then(({ uppy }) => {
+      const { files } = uppy.getState()
+      uppy.on('upload-error', handler)
+
+      const [fileID] = Object.keys(files)
+      uppy.removeFile(fileID)
+      uppy.removeFile(fileID)
+      cy.wait('@createAssemblies').then(() => expect(handler).not.to.be.called)
+    })
+  })
 })

+ 25 - 17
e2e/cypress/integration/dashboard-tus.spec.ts

@@ -10,14 +10,14 @@ type Tus = BaseTus & {
 describe('Dashboard with Tus', () => {
   beforeEach(() => {
     cy.visit('/dashboard-tus')
-    cy.get('.uppy-Dashboard-input').as('file-input')
+    cy.get('.uppy-Dashboard-input:first').as('file-input')
     cy.intercept('/files/*').as('tus')
     cy.intercept('http://localhost:3020/url/*').as('url')
     cy.intercept('http://localhost:3020/search/unsplash/*').as('unsplash')
   })
 
   it('should upload cat image successfully', () => {
-    cy.get('@file-input').attachFile('images/cat.jpg')
+    cy.get('@file-input').selectFile('cypress/fixtures/images/cat.jpg', { force:true })
     cy.get('.uppy-StatusBar-actionBtn--upload').click()
 
     cy.wait('@tus')
@@ -25,24 +25,32 @@ describe('Dashboard with Tus', () => {
     cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
   })
 
-  it('should start exponential backoff when receiving HTTP 429', () => {
-    cy.get('@file-input').attachFile(['images/baboon.png'])
-    cy.get('.uppy-StatusBar-actionBtn--upload').click()
+  it(
+    'should start exponential backoff when receiving HTTP 429',
+    {
+      retries: {
+        runMode: 3, // retry flaky test
+      },
+    },
+    () => {
+      cy.get('@file-input').selectFile('cypress/fixtures/images/baboon.png', { force:true })
+      cy.get('.uppy-StatusBar-actionBtn--upload').click()
 
-    cy.intercept(
-      { method: 'PATCH', pathname: '/files/*', times: 2 },
-      { statusCode: 429, body: {} },
-    ).as('patch')
+      cy.intercept(
+        { method: 'PATCH', pathname: '/files/*', times: 2 },
+        { statusCode: 429, body: {} },
+      ).as('patch')
 
-    cy.wait('@patch')
-    cy.wait('@patch')
+      cy.wait('@patch')
+      cy.wait('@patch')
 
-    cy.window().then(({ uppy }) => {
-      expect(uppy.getPlugin<Tus>('Tus').requests.isPaused).to.equal(true)
-      cy.wait('@tus')
-      cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
-    })
-  })
+      cy.window().then(({ uppy }) => {
+        expect(uppy.getPlugin<Tus>('Tus').requests.isPaused).to.equal(true)
+        cy.wait('@tus')
+        cy.get('.uppy-StatusBar-statusPrimary').should('contain', 'Complete')
+      })
+    },
+  )
 
   it('should upload remote image with URL plugin', () => {
     cy.get('[data-cy="Url"]').click()

+ 10 - 2
e2e/cypress/integration/dashboard-ui.spec.ts

@@ -1,11 +1,19 @@
 describe('dashboard-ui', () => {
   beforeEach(() => {
     cy.visit('/dashboard-ui')
-    cy.get('.uppy-Dashboard-input').as('file-input')
+    cy.get('.uppy-Dashboard-input:first').as('file-input')
+  })
+
+  it('should not throw when calling uppy.close()', () => {
+    cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg'], { force:true })
+
+    cy.window().then(({ uppy }) => {
+      expect(uppy.close()).to.not.throw
+    })
   })
 
   it('should render thumbnails', () => {
-    cy.get('@file-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
+    cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg'], { force:true })
     cy.get('.uppy-Dashboard-Item-previewImg')
       .should('have.length', 2)
       .each((element) => expect(element).attr('src').to.include('blob:'))

+ 1 - 1
e2e/cypress/integration/dashboard-vue.spec.ts

@@ -6,7 +6,7 @@ describe('dashboard-vue', () => {
   // Only Vue 3 works in Parcel if you use SFC's but Vue 3 is broken in Uppy:
   // https://github.com/transloadit/uppy/issues/2877
   xit('should render in Vue 3 and show thumbnails', () => {
-    cy.get('@file-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
+    cy.get('@file-input').selectFile(['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg'], { force:true })
     cy.get('.uppy-Dashboard-Item-previewImg')
       .should('have.length', 2)
       .each((element) => expect(element).attr('src').to.include('blob:'))

+ 5 - 5
e2e/cypress/integration/react.spec.ts

@@ -1,13 +1,13 @@
 describe('@uppy/react', () => {
   beforeEach(() => {
     cy.visit('/react')
-    cy.get('#dashboard .uppy-Dashboard-input').as('dashboard-input')
-    cy.get('#modal .uppy-Dashboard-input').as('modal-input')
+    cy.get('#dashboard .uppy-Dashboard-input:first').as('dashboard-input')
+    cy.get('#modal .uppy-Dashboard-input:first').as('modal-input')
     cy.get('#drag-drop .uppy-DragDrop-input').as('dragdrop-input')
   })
 
   it('should render Dashboard in React and show thumbnails', () => {
-    cy.get('@dashboard-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
+    cy.get('@dashboard-input').selectFile(['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg'], { force:true })
     cy.get('#dashboard .uppy-Dashboard-Item-previewImg')
       .should('have.length', 2)
       .each((element) => expect(element).attr('src').to.include('blob:'))
@@ -15,7 +15,7 @@ describe('@uppy/react', () => {
 
   it('should render Modal in React and show thumbnails', () => {
     cy.get('#open').click()
-    cy.get('@modal-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
+    cy.get('@modal-input').selectFile(['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg'], { force:true })
     cy.get('#modal .uppy-Dashboard-Item-previewImg')
       .should('have.length', 2)
       .each((element) => expect(element).attr('src').to.include('blob:'))
@@ -25,7 +25,7 @@ describe('@uppy/react', () => {
     const spy = cy.spy()
 
     cy.window().then(({ uppy }) => uppy.on('thumbnail:generated', spy))
-    cy.get('@dragdrop-input').attachFile(['images/cat.jpg', 'images/traffic.jpg'])
+    cy.get('@dragdrop-input').selectFile(['cypress/fixtures/images/cat.jpg', 'cypress/fixtures/images/traffic.jpg'], { force:true })
     // not sure how I can accurately wait for the thumbnail
     // eslint-disable-next-line cypress/no-unnecessary-waiting
     cy.wait(1000).then(() => expect(spy).to.be.called)

+ 0 - 22
e2e/cypress/plugins/index.js

@@ -1,22 +0,0 @@
-/// <reference types="cypress" />
-// ***********************************************************
-// This example plugins/index.js can be used to load plugins
-//
-// You can change the location of this file or turn off loading
-// the plugins file with the 'pluginsFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/plugins-guide
-// ***********************************************************
-
-// This function is called when a project is opened or re-opened (e.g. due to
-// the project's config changing)
-
-/**
- * @type {Cypress.PluginConfig}
- */
-// eslint-disable-next-line no-unused-vars
-module.exports = (on, config) => {
-  // `on` is used to hook into various events Cypress emits
-  // `config` is the resolved Cypress config
-}

+ 0 - 3
e2e/cypress/support/commands.ts

@@ -25,9 +25,6 @@
 // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
 //
 
-/* eslint-disable-next-line import/no-extraneous-dependencies */
-import 'cypress-file-upload'
-
 import { createFakeFile } from './createFakeFile'
 
 Cypress.Commands.add('createFakeFile', createFakeFile)

+ 20 - 0
e2e/cypress/support/e2e.ts

@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/e2e.ts is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')

+ 2 - 3
e2e/package.json

@@ -6,7 +6,7 @@
   "scripts": {
     "client:start": "parcel clients/index.html",
     "cypress:open": "cypress open",
-    "cypress:headless": "cypress run --record",
+    "cypress:headless": "cypress run",
     "generate-test": "yarn node generate-test.mjs"
   },
   "dependencies": {
@@ -45,8 +45,7 @@
     "@uppy/zoom": "workspace:^"
   },
   "devDependencies": {
-    "cypress": "^9.0.0",
-    "cypress-file-upload": "^5.0.8",
+    "cypress": "^10.0.0",
     "deep-freeze": "^0.0.1",
     "parcel": "^2.0.1",
     "prompts": "^2.4.2",

+ 1 - 1
e2e/tsconfig.json

@@ -2,7 +2,7 @@
   "compilerOptions": {
     "target": "es2020",
     "lib": ["es2020", "dom"],
-    "types": ["cypress", "cypress-file-upload"]
+    "types": ["cypress"]
   },
   "include": ["cypress/**/*.ts"]
 }

+ 6 - 0
packages/@uppy/companion/CHANGELOG.md

@@ -21,6 +21,12 @@ Included in: Uppy v3.0.0-beta
 
 - @uppy/companion: remove `searchProviders` wrapper & move `s3` options (Merlijn Vos / #3781)
 - @uppy/companion: remove support for EOL versions of Node.js (Antoine du Hamel / #3784)
+## 3.7.1
+
+Released: 2022-07-27
+Included in: Uppy v2.13.1
+
+- @uppy/companion: Companion app type (Mikael Finstad / #3899)
 
 ## 3.7.0
 

+ 7 - 0
packages/@uppy/compressor/CHANGELOG.md

@@ -1,5 +1,12 @@
 # @uppy/compressor
 
+## 0.3.1
+
+Released: 2022-07-27
+Included in: Uppy v2.13.1
+
+- @uppy/compressor: fix upload causing meta name to reset (Justin / #3890)
+
 ## 0.3.0
 
 Released: 2022-05-30

+ 1 - 1
packages/@uppy/compressor/src/index.js

@@ -60,7 +60,7 @@ export default class Compressor extends BasePlugin {
             size,
             data: compressedBlob,
           })
-          this.uppy.setFileMeta(file.id, { name, type })
+          this.uppy.setFileMeta(file.id, { type })
           compressedFiles.push(file)
         } catch (err) {
           this.uppy.log(`[Image Compressor] Failed to compress ${file.id}:`, 'warning')

+ 6 - 0
packages/@uppy/core/CHANGELOG.md

@@ -6,6 +6,12 @@ Released: 2022-06-09
 Included in: Uppy v3.0.0-beta.1
 
 - @uppy/core,@uppy/dashboard: fix types for some events (Antoine du Hamel / #3812)
+## 2.3.2
+
+Released: 2022-07-27
+Included in: Uppy v2.13.1
+
+- @uppy/core: Add missing type for retry-all event (Luc Boissaye / #3901)
 
 ## 2.3.1
 

+ 6 - 0
packages/@uppy/core/src/Uppy.js

@@ -677,6 +677,12 @@ class Uppy {
         return
       }
 
+      const { capabilities } = this.getState()
+      if (newFileIDs.length !== currentUploads[uploadID].fileIDs.length
+          && !capabilities.individualCancellation) {
+        throw new Error('individualCancellation is disabled')
+      }
+
       updatedUploads[uploadID] = {
         ...currentUploads[uploadID],
         fileIDs: newFileIDs,

+ 112 - 0
packages/@uppy/core/src/Uppy.test.js

@@ -1,6 +1,7 @@
 /* eslint no-console: "off", no-restricted-syntax: "off" */
 import { afterEach, beforeEach, describe, expect, it, jest, xit } from '@jest/globals'
 
+import assert from 'node:assert'
 import fs from 'node:fs'
 import prettierBytes from '@transloadit/prettier-bytes'
 import Core from '../lib/index.js'
@@ -263,6 +264,117 @@ describe('src/Core', () => {
     expect(Object.keys(core.getState().files).length).toEqual(0)
   })
 
+  it('should allow remove all uploads when individualCancellation is disabled', () => {
+    const core = new Core()
+
+    const { capabilities } = core.getState()
+    core.setState({
+      capabilities: {
+        ...capabilities,
+        individualCancellation: false,
+      },
+    })
+
+    core.addFile({
+      source: 'jest',
+      name: 'foo1.jpg',
+      type: 'image/jpeg',
+      data: new File([sampleImage], { type: 'image/jpeg' }),
+    })
+
+    core.addFile({
+      source: 'jest',
+      name: 'foo2.jpg',
+      type: 'image/jpeg',
+      data: new File([sampleImage], { type: 'image/jpeg' }),
+    })
+
+    const fileIDs = Object.keys(core.getState().files)
+    const id = core[Symbol.for('uppy test: createUpload')](fileIDs)
+
+    expect(core.getState().currentUploads[id]).toBeDefined()
+    expect(Object.keys(core.getState().files).length).toEqual(2)
+
+    core.removeFiles(fileIDs)
+
+    expect(core.getState().currentUploads[id]).toBeUndefined()
+    expect(Object.keys(core.getState().files).length).toEqual(0)
+  })
+
+  it('should disallow remove one upload when individualCancellation is disabled', () => {
+    const core = new Core()
+
+    const { capabilities } = core.getState()
+    core.setState({
+      capabilities: {
+        ...capabilities,
+        individualCancellation: false,
+      },
+    })
+
+    core.addFile({
+      source: 'jest',
+      name: 'foo1.jpg',
+      type: 'image/jpeg',
+      data: new File([sampleImage], { type: 'image/jpeg' }),
+    })
+
+    core.addFile({
+      source: 'jest',
+      name: 'foo2.jpg',
+      type: 'image/jpeg',
+      data: new File([sampleImage], { type: 'image/jpeg' }),
+    })
+
+    const fileIDs = Object.keys(core.getState().files)
+    const id = core[Symbol.for('uppy test: createUpload')](fileIDs)
+
+    expect(core.getState().currentUploads[id]).toBeDefined()
+    expect(Object.keys(core.getState().files).length).toEqual(2)
+
+    assert.throws(() => core.removeFile(fileIDs[0]), /individualCancellation is disabled/)
+
+    expect(core.getState().currentUploads[id]).toBeDefined()
+    expect(Object.keys(core.getState().files).length).toEqual(2)
+  })
+
+  it('should allow remove one upload when individualCancellation is enabled', () => {
+    const core = new Core()
+
+    const { capabilities } = core.getState()
+    core.setState({
+      capabilities: {
+        ...capabilities,
+        individualCancellation: true,
+      },
+    })
+
+    core.addFile({
+      source: 'jest',
+      name: 'foo1.jpg',
+      type: 'image/jpeg',
+      data: new File([sampleImage], { type: 'image/jpeg' }),
+    })
+
+    core.addFile({
+      source: 'jest',
+      name: 'foo2.jpg',
+      type: 'image/jpeg',
+      data: new File([sampleImage], { type: 'image/jpeg' }),
+    })
+
+    const fileIDs = Object.keys(core.getState().files)
+    const id = core[Symbol.for('uppy test: createUpload')](fileIDs)
+
+    expect(core.getState().currentUploads[id]).toBeDefined()
+    expect(Object.keys(core.getState().files).length).toEqual(2)
+
+    core.removeFile(fileIDs[0])
+
+    expect(core.getState().currentUploads[id]).toBeDefined()
+    expect(Object.keys(core.getState().files).length).toEqual(1)
+  })
+
   it('should close, reset and uninstall when the close method is called', () => {
     // use DeepFrozenStore in some tests to make sure we are not mutating things
     const core = new Core({

+ 3 - 0
packages/@uppy/core/types/index.d.ts

@@ -216,6 +216,8 @@ export type UploadCompleteCallback<TMeta> = (result: UploadResult<TMeta>) => voi
 export type ErrorCallback = (error: Error) => void;
 export type UploadErrorCallback<TMeta> = (file: UppyFile<TMeta> | undefined, error: Error, response?: ErrorResponse) => void;
 export type UploadRetryCallback = (fileID: string) => void;
+export type RetryAllCallback = (fileIDs: string[]) => void;
+
 // TODO: reverse the order in the next major version
 export type RestrictionFailedCallback<TMeta> = (file: UppyFile<TMeta> | undefined, error: Error) => void;
 
@@ -232,6 +234,7 @@ export interface UppyEventMap<TMeta = Record<string, unknown>> {
   'error': ErrorCallback
   'upload-error': UploadErrorCallback<TMeta>
   'upload-retry': UploadRetryCallback
+  'retry-all': RetryAllCallback
   'info-visible': GenericEventCallback
   'info-hidden': GenericEventCallback
   'cancel-all': GenericEventCallback

+ 6 - 0
packages/@uppy/dashboard/CHANGELOG.md

@@ -6,6 +6,12 @@ Released: 2022-06-09
 Included in: Uppy v3.0.0-beta.1
 
 - @uppy/core,@uppy/dashboard: fix types for some events (Antoine du Hamel / #3812)
+## 2.4.1
+
+Released: 2022-07-27
+Included in: Uppy v2.13.1
+
+- @uppy/dashboard,@uppy/image-editor,@uppy/remote-sources: Fix `uppy.close()` crashes when remote-sources or image-editor is installed (Merlijn Vos / #3914)
 
 ## 2.3.0
 

+ 2 - 0
packages/@uppy/dashboard/src/components/FileCard/index.jsx

@@ -59,6 +59,8 @@ class FileCard extends Component {
   }
 
   handleCancel = () => {
+    const file = this.props.files[this.props.fileCardFor]
+    this.props.uppy.emit('file-editor:cancel', file)
     this.props.toggleFileCard(false)
   }
 

+ 15 - 0
packages/@uppy/image-editor/CHANGELOG.md

@@ -1,5 +1,20 @@
 # @uppy/image-editor
 
+## 1.4.1
+
+Released: 2022-07-27
+Included in: Uppy v2.13.1
+
+- @uppy/dashboard,@uppy/image-editor,@uppy/remote-sources: Fix `uppy.close()` crashes when remote-sources or image-editor is installed (Merlijn Vos / #3914)
+
+## 1.4.0
+
+Released: 2022-07-18
+Included in: Uppy v2.13.0
+
+- @uppy/image-editor: remove beta notice (Merlijn Vos / #3877)
+- @uppy/image-editor: Add image editor cancel event (James R T / #3875)
+
 ## 1.3.0
 
 Released: 2022-05-30

+ 6 - 0
packages/@uppy/image-editor/src/ImageEditor.jsx

@@ -121,6 +121,12 @@ export default class ImageEditor extends UIPlugin {
   }
 
   uninstall () {
+    const { currentImage } = this.getPluginState()
+
+    if (currentImage) {
+      const file = this.uppy.getFile(currentImage.id)
+      this.uppy.emit('file-editor:cancel', file)
+    }
     this.unmount()
   }
 

+ 2 - 0
packages/@uppy/image-editor/types/index.d.ts

@@ -34,10 +34,12 @@ export default ImageEditor
 
 export type FileEditorStartCallback<TMeta> = (file: UppyFile<TMeta>) => void;
 export type FileEditorCompleteCallback<TMeta> = (updatedFile: UppyFile<TMeta>) => void;
+export type FileEditorCancelCallback<TMeta> = (file: UppyFile<TMeta>) => void;
 
 declare module '@uppy/core' {
   export interface UppyEventMap<TMeta> {
     'file-editor:start' : FileEditorStartCallback<TMeta>
     'file-editor:complete': FileEditorCompleteCallback<TMeta>
+    'file-editor:cancel': FileEditorCancelCallback<TMeta>
   }
 }

+ 4 - 0
packages/@uppy/image-editor/types/index.test-d.ts

@@ -16,4 +16,8 @@ import ImageEditor from '..'
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
     const fileName = file.name
   })
+  uppy.on('file-editor:cancel', (file) => {
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    const fileName = file.name
+  })
 }

+ 7 - 0
packages/@uppy/remote-sources/CHANGELOG.md

@@ -1,5 +1,12 @@
 # @uppy/remote-sources
 
+## 0.1.1
+
+Released: 2022-07-27
+Included in: Uppy v2.13.1
+
+- @uppy/dashboard,@uppy/image-editor,@uppy/remote-sources: Fix `uppy.close()` crashes when remote-sources or image-editor is installed (Merlijn Vos / #3914)
+
 ## 0.1.0
 
 Released: 2022-06-07

+ 3 - 1
packages/@uppy/remote-sources/src/index.js

@@ -70,7 +70,9 @@ export default class RemoteSources extends BasePlugin {
         throw new Error(`Invalid plugin: "${pluginId}" is not one of: ${formatter.format(pluginNames)}.`)
       }
       this.uppy.use(plugin, optsForRemoteSourcePlugin)
-      this.#installedPlugins.add(plugin)
+      // `plugin` is a class, but we want to track the instance object
+      // so we have to do `getPlugin` here.
+      this.#installedPlugins.add(this.uppy.getPlugin(pluginId))
     })
   }
 

+ 7 - 0
packages/@uppy/robodog/CHANGELOG.md

@@ -1,5 +1,12 @@
 # @uppy/robodog
 
+## 2.8.3
+
+Released: 2022-07-11
+Included in: Uppy v2.12.3
+
+- @uppy/robodog,@uppy/transloadit: use modern syntax to simplify code (Antoine du Hamel / #3873)
+
 ## 2.8.0
 
 Released: 2022-06-07

+ 4 - 9
packages/@uppy/robodog/src/TransloaditFormResult.js

@@ -17,17 +17,12 @@ class TransloaditFormResult extends BasePlugin {
   }
 
   getAssemblyStatuses (fileIDs) {
-    const assemblyIds = []
-    fileIDs.forEach((fileID) => {
-      const file = this.uppy.getFile(fileID)
-      const assembly = file.transloadit && file.transloadit.assembly
-      if (assembly && assemblyIds.indexOf(assembly) === -1) {
-        assemblyIds.push(assembly)
-      }
-    })
+    const assemblyIds = new Set(
+      fileIDs.map(fileID => this.uppy.getFile(fileID)?.transloadit?.assembly).filter(Boolean),
+    )
 
     const tl = this.uppy.getPlugin(this.opts.transloaditPluginId || 'Transloadit')
-    return assemblyIds.map((id) => tl.getAssembly(id))
+    return Array.from(assemblyIds, (id) => tl.getAssembly(id))
   }
 
   handleUpload (fileIDs) {

+ 21 - 0
packages/@uppy/transloadit/CHANGELOG.md

@@ -6,6 +6,27 @@ Released: 2022-05-30
 Included in: Uppy v3.0.0-beta
 
 - @uppy/transloadit: remove IE 10 hack (Antoine du Hamel / #3777)
+## 2.3.5
+
+Released: 2022-07-27
+Included in: Uppy v2.13.1
+
+- @uppy/transloadit: cancel assemblies when all its files have been removed (Antoine du Hamel / #3893)
+
+## 2.3.4
+
+Released: 2022-07-18
+Included in: Uppy v2.13.0
+
+- @uppy/transloadit: fix outdated file ids and incorrect usage of files (Merlijn Vos / #3886)
+
+## 2.3.3
+
+Released: 2022-07-11
+Included in: Uppy v2.12.3
+
+- @uppy/transloadit: fix TypeError when file is cancelled asynchronously (Antoine du Hamel / #3872)
+- @uppy/robodog,@uppy/transloadit: use modern syntax to simplify code (Antoine du Hamel / #3873)
 
 ## 2.3.2
 

+ 9 - 2
packages/@uppy/transloadit/src/AssemblyOptions.js

@@ -30,7 +30,7 @@ function validateParams (params) {
  */
 function dedupe (list) {
   const dedupeMap = Object.create(null)
-  for (const { fileIDs, options } of list) {
+  for (const { fileIDs, options } of list.filter(Boolean)) {
     const id = JSON.stringify(options)
     if (id in dedupeMap) {
       dedupeMap[id].fileIDArrays.push(fileIDs)
@@ -62,9 +62,16 @@ class AssemblyOptions {
    * Get Assembly options for a file.
    */
   async #getAssemblyOptions (file) {
-    const options = this.opts
+    if (file == null) return undefined
 
+    const options = this.opts
     const assemblyOptions = await options.getAssemblyOptions(file, options)
+
+    // We check if the file is present here again, because it could had been
+    // removed during the await, e.g. if the user hit cancel while we were
+    // waiting for the options.
+    if (file == null) return undefined
+
     if (Array.isArray(assemblyOptions.fields)) {
       assemblyOptions.fields = Object.fromEntries(
         assemblyOptions.fields.map((fieldName) => [fieldName, file.meta[fieldName]]),

+ 24 - 0
packages/@uppy/transloadit/src/Client.js

@@ -20,6 +20,11 @@ export default class Client {
     this.#fetchWithNetworkError = this.opts.rateLimitedQueue.wrapPromiseFunction(fetchWithNetworkError)
   }
 
+  /**
+   * @param  {RequestInfo | URL} input
+   * @param  {RequestInit} init
+   * @returns {Promise<any>}
+   */
   #fetchJSON (...args) {
     return this.#fetchWithNetworkError(...args).then(response => {
       if (response.status === 429) {
@@ -126,6 +131,25 @@ export default class Client {
       .catch((err) => this.#reportError(err, { assembly, file, url, type: 'API_ERROR' }))
   }
 
+  /**
+   * Update the number of expected files in an already created assembly.
+   *
+   * @param {object} assembly
+   * @param {number} num_expected_upload_files
+   */
+  updateNumberOfFilesInAssembly (assembly, num_expected_upload_files) {
+    const url = new URL(assembly.assembly_ssl_url)
+    url.pathname = '/update_assemblies'
+    const body = JSON.stringify({
+      assembly_updates: [{
+        assembly_id: assembly.assembly_id,
+        num_expected_upload_files,
+      }],
+    })
+    return this.#fetchJSON(url, { method: 'post', headers: this.#headers, body })
+      .catch((err) => this.#reportError(err, { url, type: 'API_ERROR' }))
+  }
+
   /**
    * Cancel a running Assembly.
    *

+ 43 - 27
packages/@uppy/transloadit/src/index.js

@@ -190,7 +190,18 @@ export default class Transloadit extends BasePlugin {
       fields: options.fields,
       expectedFiles: fileIDs.length,
       signature: options.signature,
-    }).then((newAssembly) => {
+    }).then(async (newAssembly) => {
+      const files = this.uppy.getFiles().filter(({ id }) => fileIDs.includes(id))
+      if (files.length !== fileIDs.length) {
+        if (files.length === 0) {
+          // All files have been removed, cancelling.
+          await this.client.cancelAssembly(newAssembly)
+          return null
+        }
+        // At least one file has been removed.
+        await this.client.updateNumberOfFilesInAssembly(newAssembly, files.length)
+      }
+
       const assembly = new Assembly(newAssembly, this.#rateLimitedQueue)
       const { status } = assembly
       const assemblyID = status.assembly_id
@@ -212,26 +223,33 @@ export default class Transloadit extends BasePlugin {
         },
       })
 
-      const { files } = this.uppy.getState()
       const updatedFiles = {}
-      fileIDs.forEach((id) => {
-        updatedFiles[id] = this.#attachAssemblyMetadata(this.uppy.getFile(id), status)
+      files.forEach((file) => {
+        updatedFiles[file.id] = this.#attachAssemblyMetadata(file, status)
       })
+
       this.uppy.setState({
         files: {
-          ...files,
+          ...this.uppy.getState().files,
           ...updatedFiles,
         },
       })
+
       const fileRemovedHandler = (fileRemoved, reason) => {
         if (reason === 'cancel-all') {
           assembly.close()
+          this.client.cancelAssembly(newAssembly).catch(() => { /* ignore potential errors */ })
           this.uppy.off(fileRemovedHandler)
         } else if (fileRemoved.id in updatedFiles) {
           delete updatedFiles[fileRemoved.id]
-          if (Object.keys(updatedFiles).length === 0) {
+          const nbOfRemainingFiles = Object.keys(updatedFiles).length
+          if (nbOfRemainingFiles === 0) {
             assembly.close()
+            this.client.cancelAssembly(newAssembly).catch(() => { /* ignore potential errors */ })
             this.uppy.off(fileRemovedHandler)
+          } else {
+            this.client.updateNumberOfFilesInAssembly(newAssembly, nbOfRemainingFiles)
+              .catch(() => { /* ignore potential errors */ })
           }
         }
       }
@@ -253,7 +271,7 @@ export default class Transloadit extends BasePlugin {
     })
   }
 
-  #createAssemblyWatcher (assemblyID, fileIDs, uploadID) {
+  #createAssemblyWatcher (assemblyID, uploadID) {
   // AssemblyWatcher tracks completion states of all Assemblies in this upload.
     const watcher = new AssemblyWatcher(this.uppy, assemblyID)
 
@@ -300,7 +318,7 @@ export default class Transloadit extends BasePlugin {
    */
   #onFileUploadURLAvailable = (rawFile) => {
     const file = this.uppy.getFile(rawFile.id)
-    if (!file || !file.transloadit || !file.transloadit.assembly) {
+    if (!file?.transloadit?.assembly) {
       return
     }
 
@@ -497,10 +515,7 @@ export default class Transloadit extends BasePlugin {
       // Set up the assembly watchers again for all the ongoing uploads.
       Object.keys(uploadsAssemblies).forEach((uploadID) => {
         const assemblyIDs = uploadsAssemblies[uploadID]
-        const fileIDsInUpload = assemblyIDs.flatMap((assemblyID) => {
-          return this.getAssemblyFiles(assemblyID).map((file) => file.id)
-        })
-        this.#createAssemblyWatcher(assemblyIDs, fileIDsInUpload, uploadID)
+        this.#createAssemblyWatcher(assemblyIDs, uploadID)
       })
 
       const allAssemblyIDs = Object.keys(assemblies)
@@ -586,16 +601,16 @@ export default class Transloadit extends BasePlugin {
   }
 
   #prepareUpload = (fileIDs, uploadID) => {
-    // Only use files without errors
-    const filteredFileIDs = fileIDs.filter((file) => !file.error)
-
-    const files = filteredFileIDs.map(fileID => {
-      const file = this.uppy.getFile(fileID)
-      this.uppy.emit('preprocess-progress', file, {
-        mode: 'indeterminate',
-        message: this.i18n('creatingAssembly'),
-      })
-      return file
+    const files = fileIDs.map(id => this.uppy.getFile(id))
+    const filesWithoutErrors = files.filter((file) => {
+      if (!file.error) {
+        this.uppy.emit('preprocess-progress', file, {
+          mode: 'indeterminate',
+          message: this.i18n('creatingAssembly'),
+        })
+        return true
+      }
+      return false
     })
 
     // eslint-disable-next-line no-shadow
@@ -630,19 +645,20 @@ export default class Transloadit extends BasePlugin {
       },
     })
 
-    const assemblyOptions = new AssemblyOptions(files, this.opts)
+    const assemblyOptions = new AssemblyOptions(filesWithoutErrors, this.opts)
 
     return assemblyOptions.build()
       .then((assemblies) => Promise.all(assemblies.map(createAssembly)))
-      .then((createdAssemblies) => {
+      .then((maybeCreatedAssemblies) => {
+        const createdAssemblies = maybeCreatedAssemblies.filter(Boolean)
         const assemblyIDs = createdAssemblies.map(assembly => assembly.status.assembly_id)
-        this.#createAssemblyWatcher(assemblyIDs, filteredFileIDs, uploadID)
+        this.#createAssemblyWatcher(assemblyIDs, uploadID)
         return Promise.all(createdAssemblies.map(assembly => this.#connectAssembly(assembly)))
       })
       // If something went wrong before any Assemblies could be created,
       // clear all processing state.
       .catch((err) => {
-        files.forEach((file) => {
+        filesWithoutErrors.forEach((file) => {
           this.uppy.emit('preprocess-complete', file)
           this.uppy.emit('upload-error', file, err)
         })
@@ -829,7 +845,7 @@ export default class Transloadit extends BasePlugin {
 
   getAssemblyFiles (assemblyID) {
     return this.uppy.getFiles().filter((file) => {
-      return file && file.transloadit && file.transloadit.assembly === assemblyID
+      return file?.transloadit?.assembly === assemblyID
     })
   }
 }

+ 1 - 1
website/src/_posts/2017-03-0.15.md

@@ -11,7 +11,7 @@ Spring is in the air and Uppy is feeling particularly full of life. In this post
 
 ## Yo-yoify for NPM-installed Uppy
 
-In [`0.14`](http://localhost:4000/blog/2017/02/0.14/), we added `yo-yoify` transform to give Uppy some extra speed and eliminate `Function.caller` issues. As it turned out, we [forgot](https://github.com/transloadit/uppy/issues/158) about our Babel-transpiled `lib` version of Uppy that gets published to NPM 🙀. We then spent some time creating a standalone version of `yo-yoify` that would be able to parse `yo-yo` template strings before Babel-transpilation, only to discover that there already is a [`babel-plugin-yo-yoify`](https://www.npmjs.com/package/babel-plugin-yo-yoify) that not only does precisely that, but also frees us from jumping through a lot of unnecessary hoops. It did [have](https://github.com/goto-bus-stop/babel-plugin-yo-yoify/issues/9) [a few](https://github.com/goto-bus-stop/babel-plugin-yo-yoify/pull/8) [issues](https://github.com/goto-bus-stop/babel-plugin-yo-yoify/issues/11) at first, but luckily our friend [Renée](https://github.com/goto-bus-stop) was available to colaborate with us on this. Renée has also agreed to join Uppy for a while to help us with other pressing issues. So, silver linings — bugs can sometimes lead to new friends and wonderful beginnings. We are very excited about what this all means for Uppy in the months to come.
+In [`0.14`](https://uppy.io/blog/2017/02/0.14/), we added `yo-yoify` transform to give Uppy some extra speed and eliminate `Function.caller` issues. As it turned out, we [forgot](https://github.com/transloadit/uppy/issues/158) about our Babel-transpiled `lib` version of Uppy that gets published to NPM 🙀. We then spent some time creating a standalone version of `yo-yoify` that would be able to parse `yo-yo` template strings before Babel-transpilation, only to discover that there already is a [`babel-plugin-yo-yoify`](https://www.npmjs.com/package/babel-plugin-yo-yoify) that not only does precisely that, but also frees us from jumping through a lot of unnecessary hoops. It did [have](https://github.com/goto-bus-stop/babel-plugin-yo-yoify/issues/9) [a few](https://github.com/goto-bus-stop/babel-plugin-yo-yoify/pull/8) [issues](https://github.com/goto-bus-stop/babel-plugin-yo-yoify/issues/11) at first, but luckily our friend [Renée](https://github.com/goto-bus-stop) was available to colaborate with us on this. Renée has also agreed to join Uppy for a while to help us with other pressing issues. So, silver linings — bugs can sometimes lead to new friends and wonderful beginnings. We are very excited about what this all means for Uppy in the months to come.
 
 Uppy from NPM is now good to go and the issue has been completely resolved. You can update at: <https://www.npmjs.com/package/uppy>. And yeah, if you use `yo-yo`, give [`babel-plugin-yo-yoify`](https://www.npmjs.com/package/babel-plugin-yo-yoify) a try.
 

+ 2 - 2
website/src/_posts/2019-08-1.3.md

@@ -94,7 +94,7 @@ See [#1440](https://github.com/transloadit/uppy/pull/1440), [#1565](https://gith
 
 Thanks to our amazing contributors, Uppy now speaks: Arabic, Chinese, Dutch, English, Finnish, French, German, Hungarian, Italian, Japanese, Persian, Portuguese, Russian, Serbian, Spanish an Turkish!
 
-New translations are rolling in every month, but there are plenty more languages out there that Uppy would love to learn. Please consider [contributing your language](http://localhost:4000/docs/locales/#Contributing-a-new-language) as well!
+New translations are rolling in every month, but there are plenty more languages out there that Uppy would love to learn. Please consider [contributing your language](http://uppy.io/docs/locales/#Contributing-a-new-language) as well!
 
 We’ve also added docs for locales — how to use from NPM and CDN, auto-generated list of languages that are supported already and an invitation to add more [#1553](https://github.com/transloadit/uppy/pull/1553).
 
@@ -119,7 +119,7 @@ Robodog is an Uppy-based library that helps you talk to the Transloadit API. Sin
 ## Website and docs
 
 * Added Community projects — a page with Uppy plugins and integrations developed by the community [#1620](https://github.com/transloadit/uppy/pull/1620). [Add yours](https://github.com/transloadit/uppy/blob/master/website/src/docs/community-projects.md)!
-* Custom plugin [example](http://localhost:4000/docs/writing-plugins/#Example-of-a-custom-plugin) — now you have a reference to look at when writing your own plugin for Uppy [#1623](https://github.com/transloadit/uppy/pull/1623).
+* Custom plugin [example](http://uppy.io/docs/writing-plugins/#Example-of-a-custom-plugin) — now you have a reference to look at when writing your own plugin for Uppy [#1623](https://github.com/transloadit/uppy/pull/1623).
 * Added signature authentication to Transloadit example [#1705](https://github.com/transloadit/uppy/pull/1705).
 * Documented Companion’s Auth and Token mechanism [#1540](https://github.com/transloadit/uppy/pull/1540).
 * Updated AWS S3 Multipart documentation wrt CORS settings [#1539](https://github.com/transloadit/uppy/pull/1539).

+ 10 - 2
website/src/docs/image-editor.md

@@ -12,8 +12,6 @@ tagline: "allows users to crop, rotate, zoom and flip images that are added to U
 
 Designed to be used with the Dashboard UI (can in theory work without it).
 
-⚠ In beta.
-
 ![Screenshor of the Image Editor plugin UI in Dashboard](https://user-images.githubusercontent.com/1199054/87208710-654db400-c307-11ea-9471-6e3c6582d2a5.png)
 
 ```js
@@ -121,6 +119,16 @@ uppy.on('file-editor:complete', (updatedFile) => {
 })
 ```
 
+### file-editor:cancel
+
+Emitted when `uninstall` is called or when the current image editing changes are discarded.
+
+```js
+uppy.on('file-editor:cancel', (file) => {
+  console.log(file)
+})
+```
+
 ### `locale: {}`
 
 ```js

+ 5 - 15
yarn.lock

@@ -16437,18 +16437,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"cypress-file-upload@npm:^5.0.8":
-  version: 5.0.8
-  resolution: "cypress-file-upload@npm:5.0.8"
-  peerDependencies:
-    cypress: ">3.0.0"
-  checksum: 9c70ca7e0bb137d0ec0b8d38987219ce15b26ac3a40e3ed4e78e6ad4690392eab905586848eec6ad8edd42ee480e68ccc63007b2ebd0a02f4b3eca116ff017e3
-  languageName: node
-  linkType: hard
-
-"cypress@npm:^9.0.0":
-  version: 9.7.0
-  resolution: "cypress@npm:9.7.0"
+"cypress@npm:^10.0.0":
+  version: 10.3.1
+  resolution: "cypress@npm:10.3.1"
   dependencies:
     "@cypress/request": ^2.88.10
     "@cypress/xvfb": ^1.2.4
@@ -16494,7 +16485,7 @@ __metadata:
     yauzl: ^2.10.0
   bin:
     cypress: bin/cypress
-  checksum: 45df7c85bc7ec2e187153ff2b98bf5106d2313d70e2367a5742b5269a9e82d3fdd730d5bbc32ac8da72aeb120a52f9384c2ba4e2fc86b532f68440f22d700fc9
+  checksum: 7c76157195ec9409b9665aa9f7698ffd221c74c17f5026769fa20f90a60869cc8274282fa5b9b65e495429839f7a0ba05d69cf12a8af7a318ebcd704f96156c2
   languageName: node
   linkType: hard
 
@@ -17557,8 +17548,7 @@ __metadata:
     "@uppy/webcam": "workspace:^"
     "@uppy/xhr-upload": "workspace:^"
     "@uppy/zoom": "workspace:^"
-    cypress: ^9.0.0
-    cypress-file-upload: ^5.0.8
+    cypress: ^10.0.0
     deep-freeze: ^0.0.1
     parcel: ^2.0.1
     prompts: ^2.4.2