فهرست منبع

Merge branch 'master' into dashboard-image-cropping

Artur Paikin 4 سال پیش
والد
کامیت
1423b4c839
100فایلهای تغییر یافته به همراه1633 افزوده شده و 1228 حذف شده
  1. 11 3
      .travis.yml
  2. 155 9
      CHANGELOG.md
  3. 62 58
      README.md
  4. 2 0
      bin/disc.js
  5. 3 2
      bin/make-new-versions-table
  6. 2 2
      examples/cdn-example/index.html
  7. 3 0
      examples/dev/Dashboard.js
  8. 1 1
      examples/react-native-expo/package.json
  9. 1 1
      examples/transloadit-textarea/index.html
  10. 2 2
      examples/uppy-with-companion/client/index.html
  11. 267 280
      package-lock.json
  12. 6 3
      package.json
  13. 1 1
      packages/@uppy/aws-s3-multipart/package.json
  14. 13 2
      packages/@uppy/aws-s3-multipart/src/MultipartUploader.js
  15. 2 12
      packages/@uppy/aws-s3-multipart/src/index.js
  16. 1 0
      packages/@uppy/aws-s3-multipart/types/index.d.ts
  17. 9 1
      packages/@uppy/aws-s3-multipart/types/index.test-d.ts
  18. 1 1
      packages/@uppy/aws-s3/package.json
  19. 5 3
      packages/@uppy/aws-s3/src/index.js
  20. 1 1
      packages/@uppy/aws-s3/types/index.d.ts
  21. 2 1
      packages/@uppy/companion-client/package.json
  22. 4 25
      packages/@uppy/companion-client/src/RequestClient.js
  23. 1 1
      packages/@uppy/companion/Dockerfile
  24. 9 9
      packages/@uppy/companion/package.json
  25. 38 2
      packages/@uppy/companion/src/companion.js
  26. 2 33
      packages/@uppy/companion/src/server/Uploader.js
  27. 2 2
      packages/@uppy/companion/src/server/controllers/oauth-redirect.js
  28. 2 2
      packages/@uppy/companion/src/server/controllers/send-token.js
  29. 21 9
      packages/@uppy/companion/src/server/controllers/url.js
  30. 20 0
      packages/@uppy/companion/src/server/helpers/request.js
  31. 9 17
      packages/@uppy/companion/src/server/helpers/utils.js
  32. 1 1
      packages/@uppy/companion/src/server/provider/dropbox/index.js
  33. 8 2
      packages/@uppy/companion/src/server/provider/index.js
  34. 1 15
      packages/@uppy/companion/src/server/provider/instagram/graph/adapter.js
  35. 1 1
      packages/@uppy/companion/src/server/provider/onedrive/index.js
  36. 3 39
      packages/@uppy/companion/src/standalone/helper.js
  37. 14 6
      packages/@uppy/companion/src/standalone/index.js
  38. 31 1
      packages/@uppy/companion/test/__tests__/http-agent.js
  39. 1 1
      packages/@uppy/core/package.json
  40. 14 3
      packages/@uppy/core/src/Plugin.js
  41. 12 11
      packages/@uppy/core/src/_common.scss
  42. 5 5
      packages/@uppy/core/src/index.js
  43. 1 1
      packages/@uppy/dashboard/package.json
  44. 17 13
      packages/@uppy/dashboard/src/components/AddFiles.js
  45. 1 1
      packages/@uppy/dashboard/src/components/FileCard/index.scss
  46. 30 13
      packages/@uppy/dashboard/src/components/FileItem/Buttons/index.js
  47. 7 7
      packages/@uppy/dashboard/src/components/FileItem/Buttons/index.scss
  48. 29 5
      packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.js
  49. 7 7
      packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.scss
  50. 2 2
      packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.js
  51. 5 5
      packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.scss
  52. 0 37
      packages/@uppy/dashboard/src/components/FileItem/FileProgress/PauseResumeCancelIcon.js
  53. 118 39
      packages/@uppy/dashboard/src/components/FileItem/FileProgress/index.js
  54. 59 106
      packages/@uppy/dashboard/src/components/FileItem/FileProgress/index.scss
  55. 12 8
      packages/@uppy/dashboard/src/components/FileItem/index.js
  56. 40 17
      packages/@uppy/dashboard/src/components/FileItem/index.scss
  57. 3 1
      packages/@uppy/dashboard/src/components/FileList.js
  58. 3 2
      packages/@uppy/dashboard/src/components/PickerPanelTopBar.js
  59. 0 201
      packages/@uppy/dashboard/src/components/icons.js
  60. 14 2
      packages/@uppy/dashboard/src/index.js
  61. 4 4
      packages/@uppy/dashboard/src/style.scss
  62. 64 1
      packages/@uppy/dashboard/src/utils/getFileTypeIcon.js
  63. 1 0
      packages/@uppy/dashboard/types/index.d.ts
  64. 1 1
      packages/@uppy/drag-drop/package.json
  65. 4 6
      packages/@uppy/drag-drop/src/index.js
  66. 6 16
      packages/@uppy/drag-drop/src/style.scss
  67. 1 1
      packages/@uppy/dropbox/package.json
  68. 1 11
      packages/@uppy/dropbox/src/index.js
  69. 1 1
      packages/@uppy/facebook/package.json
  70. 1 11
      packages/@uppy/facebook/src/index.js
  71. 1 1
      packages/@uppy/file-input/package.json
  72. 1 1
      packages/@uppy/form/package.json
  73. 1 1
      packages/@uppy/golden-retriever/package.json
  74. 1 1
      packages/@uppy/google-drive/package.json
  75. 1 14
      packages/@uppy/google-drive/src/index.js
  76. 1 1
      packages/@uppy/informer/package.json
  77. 1 1
      packages/@uppy/instagram/package.json
  78. 1 11
      packages/@uppy/instagram/src/index.js
  79. 1 1
      packages/@uppy/locales/package.json
  80. 156 0
      packages/@uppy/locales/src/bg_BG.js
  81. 2 0
      packages/@uppy/locales/src/en_US.js
  82. 16 16
      packages/@uppy/locales/src/gl_ES.js
  83. 165 0
      packages/@uppy/locales/src/sk_SK.js
  84. 73 63
      packages/@uppy/locales/src/zh_CN.js
  85. 1 1
      packages/@uppy/onedrive/package.json
  86. 1 11
      packages/@uppy/onedrive/src/index.js
  87. 1 1
      packages/@uppy/progress-bar/package.json
  88. 1 1
      packages/@uppy/provider-views/package.json
  89. 2 2
      packages/@uppy/provider-views/src/Filter.js
  90. 2 2
      packages/@uppy/provider-views/src/Item/components/ItemIcon.js
  91. 11 0
      packages/@uppy/provider-views/src/index.js
  92. 1 1
      packages/@uppy/react/package.json
  93. 2 2
      packages/@uppy/robodog/README.md
  94. 1 1
      packages/@uppy/robodog/package.json
  95. 1 1
      packages/@uppy/robodog/src/addDashboardPlugin.js
  96. 1 1
      packages/@uppy/screen-capture/package.json
  97. 2 2
      packages/@uppy/screen-capture/src/RecordButton.js
  98. 1 1
      packages/@uppy/screen-capture/src/ScreenRecIcon.js
  99. 2 2
      packages/@uppy/screen-capture/src/StreamStatus.js
  100. 1 1
      packages/@uppy/screen-capture/src/SubmitButton.js

+ 11 - 3
.travis.yml

@@ -77,13 +77,21 @@ jobs:
         script: bin/travis-deploy-companion
         on:
           branch: master
-    - name: 'Run Companion tests (Node.js 6)'
+    - name: 'Run Companion tests (Node.js 10)'
       node_js: 12
       services:
         - docker
       script:
-        - nvm install 6.0.0
-        - nvm use 6.0.0
+        - nvm install 10.0.0
+        - nvm use 10.0.0
+        - npm run test:companion
+    - name: 'Run Companion tests (Node.js 8)'
+      node_js: 12
+      services:
+        - docker
+      script:
+        - nvm install 8.16.0
+        - nvm use 8.16.0
         - npm run test:companion
     # Build the website when things are merged to master
     # https://docs.travis-ci.com/user/deployment/#Conditional-Releases-with-on

+ 155 - 9
CHANGELOG.md

@@ -44,6 +44,7 @@ PRs are welcome! Please do open an issue to discuss first if it's a big feature,
 - [ ] website: add an example of a mini UI that features drop & progress (may involve a `mini: true` options for dashboard, may involve drop+progress) (@arturi)
 - [ ] xhr: allow sending custom headers per file (as proposed in #785)
 - [ ] dashboard: focus jumps weirdly if you remove a file https://github.com/transloadit/uppy/pull/2161#issuecomment-613565486
+- [ ] plugins: a WakeLock based plugin that keeps your phone from going to sleep while an upload is ongoing https://github.com/transloadit/uppy/issues/1725
 
 ## 2.0
 
@@ -70,13 +71,14 @@ PRs are welcome! Please do open an issue to discuss first if it's a big feature,
 - [ ] xhr: set the `limit` option to a sensible default, like 10
 - [ ] companion: add more reliable tests to catch edge cases in companion. For example testing that oauth works for multiple companion instances that use a master Oauth domain.
 - [ ] transloadit: remove `UPPY_SERVER` constant
+- [ ] providers: allow changing provider name title through locale? https://github.com/transloadit/uppy/issues/2279
 
-## 1.17
+## 1.19
 
 - [ ] plugins: WordPress Back-end plugin. Should be another Transloadit Integration based on Robodog Dashboard(?) we should add a provider, and possibly offer already-uploaded content
 - [ ] webcam: Specify the resolution of the webcam images/video. We should add a way to specify any custom 'constraints' (aspect ratio, resolution, mimetype (`/video/mp4;codec=h264`), bits per second, etc) to the Webcam plugin #876
 
-## 1.16
+## 1.18
 
 - [ ] dashboard: add option to use `body` or `window` or CSS selector as drop zone / paste zone as well, `DropPasteTarget` #1593 (@arturi)
 - [ ] dashboard/dragndrop/fileinput: Add a `disabled` (`true`||`false`) option (https://github.com/transloadit/uppy/issues/1530)
@@ -85,12 +87,11 @@ PRs are welcome! Please do open an issue to discuss first if it's a big feature,
 - [ ] provider: Image search (via Google or Bing or DuckDuckGo) (@arturi)
 - [ ] core: add AngularJS wrapper component for the Dashboard (@arturi)
 - [ ] dashboard: allow selecting folders (add separate hidden input button for folders) #447 #1027 (@arturi)
-- [ ] dashboard: Customizable meta editor for the Dashboard. Some people want maps, some to disable autocomplete, some validation. Perhaps via jsx rendering. (See https://github.com/transloadit/uppy/issues/2007#issuecomment-573592859, https://github.com/transloadit/uppy/issues/809#issuecomment-417282743) (#617, #809, #454, @arturi)
 - [ ] provider: MediaLibrary provider which shows you files that have already been uploaded #450, #1121, #1112 #362
 
 # next
 
-## 1.15
+## 1.17
 
 - [ ] test: add deepFreeze to test that state in not mutated anywhere by accident, use default's store #320
 - [ ] provider: add Box (@ife)
@@ -99,13 +100,160 @@ PRs are welcome! Please do open an issue to discuss first if it's a big feature,
 - [ ] core: add Vue.js wrapper component for the Dashboard (@arturi)
 - [ ] goldenretriever: confirmation before restore, add “ghost” files #443 #257 (@arturi) (@arturi)
 - [ ] dashboard: fix Dashboard issues with Angular — it’s incredibly slow presumably because of ResizeObserver. (See #1613) (@arturi)
-- [ ] dashboard: add VirtualList, so it can render 5000 files without lag (@goto-bus-stop, @lakesare)
 - [ ] dashboard: support for right-to-left languages (Arabic, Hebrew) (@arturi)
 - [ ] plugins: Transformations, cropping, filters for images, study https://github.com/MattKetmo/darkroomjs/, https://github.com/fengyuanchen/cropperjs #151 #53 (@arturi)
 - [ ] core: add maxTotalFileSize restriction #514 (@arturi)
 - [ ] companion: what happens if access token expires during/between an download & upload (@ife)
 - [ ] providers: Provider Browser don't handle uppy restrictions, can we hide things that don't match the restrictions in Google Drive and Instagram? #1827 (@arturi)
 
+## 1.16.1
+
+Released: 2020-06-19
+
+| Package | Version | Package | Version |
+|-|-|-|-|
+| @uppy/companion | 2.0.0-alpha.6 | - | - |
+
+- @uppy/companion: Import url (#2328 / @ifedapoolarewaju)
+
+Released: 2020-06-18
+
+⚠️ This release patches a Server Side Request Forgery (SSRF) Security vulnerability on `@uppy/companion`
+
+| Package | Version | Package | Version |
+|-|-|-|-|
+| @uppy/companion | 1.13.2, 2.0.0-alpha.5 | @uppy/onedrive | 1.1.8 |
+| @uppy/dashboard | 1.10.1 | @uppy/provider-views | 1.6.8 |
+| @uppy/drag-drop | 1.4.15 | @uppy/react | 1.8.1 |
+| @uppy/dropbox | 1.4.8 | @uppy/robodog | 1.7.1 |
+| @uppy/facebook | 1.1.8 | @uppy/thumbnail-generator | 1.6.2 |
+| @uppy/google-drive | 1.5.8 | @uppy/transloadit | 1.6.1 |
+| @uppy/instagram | 1.4.8 | uppy | 1.16.1 |
+
+- @uppy/thumbnail-generator: upgrade exifr (@goto-bus-stop)
+- @uppy/companion: set grant related options for custom providers (#2317 / @ifedapoolarewaju)
+- @uppy/provider-views: handle all plugin state in provider-views (#2318 / @ifedapoolarewaju)
+- @uppy/drag-drop: Add uppy-DragDrop-input class name back (ab88612dff3ce24b001acb3b626516f0e2f7fd0c / @arturi)
+- @uppy/companion: block redirects to urls with different protocol (#2322 / @ifedapoolarewaju)
+
+## 1.16.0
+
+Released: 2020-06-13
+
+This release fixes Drag Drop plugin bug introduced in the previous release (@uppy/drag-drop@1.4.13) and adds NetworkError reporting and `error.isNetworkError` to the Transloadit plugin.
+
+| Package | Version | Package | Version |
+|-|-|-|-|
+| @uppy/aws-s3-multipart | 1.7.1 | @uppy/onedrive | 1.1.7 |
+| @uppy/aws-s3 | 1.6.6 | @uppy/progress-bar | 1.3.15 |
+| @uppy/companion-client | 1.5.0 | @uppy/provider-views | 1.6.7 |
+| @uppy/companion | 2.0.0-alpha.4 | @uppy/react | 1.8.0 |
+| @uppy/core | 1.11.0 | @uppy/robodog | 1.7.0 |
+| @uppy/dashboard | 1.10.0 | @uppy/screen-capture | 1.0.3 |
+| @uppy/drag-drop | 1.4.14 | @uppy/status-bar | 1.7.0 |
+| @uppy/dropbox | 1.4.7 | @uppy/thumbnail-generator | 1.6.1 |
+| @uppy/facebook | 1.1.7 | @uppy/transloadit | 1.6.0 |
+| @uppy/file-input | 1.4.13 | @uppy/tus | 1.6.0 |
+| @uppy/form | 1.3.16 | @uppy/url | 1.5.7 |
+| @uppy/golden-retriever | 1.3.15 | @uppy/utils | 3.1.0 |
+| @uppy/google-drive | 1.5.7 | @uppy/webcam | 1.6.7 |
+| @uppy/informer | 1.5.7 | @uppy/xhr-upload | 1.6.0 |
+| @uppy/instagram | 1.4.7 | uppy | 1.16.0 |
+| @uppy/locales | 1.15.0 | - | - |
+
+- @uppy/dashboard: Refactor FileProgress component (#2303, #2292 / @arturi, @atsawin)
+- @uppy/dashboard:  Move the FileItem’s new ErrorButton, it was overlapping the edit button (0e78e32e4cf50b276ee4a48f1bf57e6be279b539 / @arturi)
+- @uppy/drag-drop: Fix the issue with click event occuring twice, try hiding the input altogether (#2307 / @arturi)
+- @uppy/transloadit: Add NetworkError handling to Transloadit plugin, refactor things, update docs about `error.isNetworkError` (#2291 / @arturi)
+- @uppy/companion: Companion 2.0 (pre-released as alpha for now) (#2273 / @ifedapoolarewaju)
+- @uppy/locales: Update of Galician i18n strings. (#2308 / @jarey)
+- build: chores: catch custom version suffices (alpha, beta etc.) (#2311 / ifedapoolarewaju)
+
+## 1.15.0
+
+Released: 2020-05-25
+
+This release features Bug Fixes And Performance Improvements™ (actually significant ones), two new languages, and a handful of nifty new Dashboard features.
+
+| Package | Version | Package | Version |
+|-|-|-|-|
+| @uppy/aws-s3-multipart | 1.7.0 | @uppy/onedrive | 1.1.6 |
+| @uppy/aws-s3 | 1.6.5 | @uppy/progress-bar | 1.3.14 |
+| @uppy/companion-client | 1.4.5 | @uppy/provider-views | 1.6.6 |
+| @uppy/companion | 2.0.0-alpha.3 | @uppy/react | 1.7.0 |
+| @uppy/core | 1.10.5 | @uppy/robodog | 1.6.7 |
+| @uppy/dashboard | 1.9.0 | @uppy/screen-capture | 1.0.2 |
+| @uppy/drag-drop | 1.4.13 | @uppy/status-bar | 1.6.6 |
+| @uppy/dropbox | 1.4.6 | @uppy/thumbnail-generator | 1.6.0 |
+| @uppy/facebook | 1.1.6 | @uppy/transloadit | 1.5.11 |
+| @uppy/file-input | 1.4.12 | @uppy/tus | 1.5.13 |
+| @uppy/form | 1.3.15 | @uppy/url | 1.5.6 |
+| @uppy/golden-retriever | 1.3.14 | @uppy/utils | 3.0.0 |
+| @uppy/google-drive | 1.5.6 | @uppy/webcam | 1.6.6 |
+| @uppy/informer | 1.5.6 | @uppy/xhr-upload | 1.5.11 |
+| @uppy/instagram | 1.4.6 | uppy | 1.15.0 |
+| @uppy/locales | 1.14.0 | - | - |
+
+- @uppy/aws-s3-multipart: make chunk size configurable (#2253 / @goto-bus-stop)
+- @uppy/aws-s3: add missing `cuid` dependency (#2236 / @tmaier)
+- @uppy/aws-s3: fix accidental overwrite of file metadata (#2276 / @goto-bus-stop)
+- @uppy/companion-client: add missing `@uppy/utils` dependency (#2266 / @goto-bus-stop)
+- @uppy/companion: fix crash if provider returns an empty error response (#2264 / @ifedapoolarewaju)
+- @uppy/companion: ignore environment variables that contain the empty string (#2283 / @ifedapoolarewaju)
+- @uppy/companion: validate options when using the Node.js API (#2275 / @ifedapoolarewaju)
+- @uppy/core: add more suggestions to console warning when incorrect `target` option is provided (#2242 / @goto-bus-stop)
+- @uppy/dashboard: add option to let users remove already uploaded files, UI only (#2284 / @arturi)
+- @uppy/dashboard: display error message for individual files (#2224 / @lafe)
+- @uppy/dashboard: render only visible files to the DOM (VirtualList) to drastically improve performance (#2161 / @goto-bus-stop)
+- @uppy/drag-drop: add a more accessible `<label>` element for the hidden input (#2257 / @arturi)
+- @uppy/locales: add Bulgarian `bg_BG` (#2280 / @intenzive)
+- @uppy/locales: add Slovakian `sk_SK` (#2261 / @suchoproduction)
+- @uppy/progress-bar: hide the progress bar if no upload is in progress (#2252 / @nicojones)
+- @uppy/thumbnail-generator: generate 80% quality JPEGs instead of high-quality PNGs for a 30% perf win (#2246 / @goto-bus-stop)
+- @uppy/thumbnail-generator: support optional lazy thumbnail generation (#2161 / @goto-bus-stop)
+- @uppy/transloadit: add typings for Companion URL constants (#2244 / @goto-bus-stop)
+- @uppy/transloadit: fix typo that caused outdated Assembly data in `'complete'` event (#2287 / @goto-bus-stop)
+- @uppy/transloadit: when cancelling all uploads, only cancel assemblies that belong to an ongoing upload (#2277 / @goto-bus-stop)
+- @uppy/tus: fix tus uploads getting terminated if the file is removed from Uppy after the upload completed (#2262 / @zachconner)
+- @uppy/utils: fix typescript typings for the `Translator` constructor (#2263 / @goto-bus-stop)
+- @uppy/utils: remove `@uppy/utils/lib/prettyBytes`, use `@transloadit/prettier-bytes` instead (#2231 / @kvz)
+- @uppy/webcam: show an "Enable Camera" screen if no camera device is available (#2282 / @arturi)
+- website: list Robodog size and sort size stats by plugin name (#2259 / @goto-bus-stop)
+
+## 1.14.1
+
+Released: 2020-05-01
+
+| Package | Version | Package | Version |
+|-|-|-|-|
+| @uppy/companion | 2.0.0-alpha.2 | - | - |
+
+- @uppy/companion: make it node 8 compatible (temporarily) (#2234 / @ifedapoolarewaju)
+
+Released: 2020-04-30
+
+| Package | Version | Package | Version |
+|-|-|-|-|
+| @uppy/aws-s3-multipart | 1.6.4 | @uppy/locales | 1.13.3 |
+| @uppy/aws-s3 | 1.6.4 | @uppy/onedrive | 1.1.5 |
+| @uppy/companion-client | 1.4.4 | @uppy/progress-bar | 1.3.13 |
+| @uppy/companion | 2.0.0-alpha.1 | @uppy/react | 1.6.5 |
+| @uppy/core | 1.10.4 | @uppy/robodog | 1.6.6 |
+| @uppy/dashboard | 1.8.5 | @uppy/screen-capture | 1.0.1 |
+| @uppy/drag-drop | 1.4.12 | @uppy/status-bar | 1.6.5 |
+| @uppy/dropbox | 1.4.5 | @uppy/thumbnail-generator | 1.5.12 |
+| @uppy/facebook | 1.1.5 | @uppy/transloadit | 1.5.10 |
+| @uppy/file-input | 1.4.11 | @uppy/tus | 1.5.12 |
+| @uppy/form | 1.3.14 | @uppy/url | 1.5.5 |
+| @uppy/golden-retriever | 1.3.13 | @uppy/utils | 2.4.4 |
+| @uppy/google-drive | 1.5.5 | @uppy/webcam | 1.6.5 |
+| @uppy/informer | 1.5.5 | @uppy/xhr-upload | 1.5.10 |
+| @uppy/instagram | 1.4.5 | uppy | 1.14.1 |
+| @uppy/provider-views | 1.6.5 | - | - |
+
+- @uppy/companion: catch download failures via response status codes (#2223 / @ifedapoolarewaju)
+- @uppy/companion: mask secrets present in log messages (#2214 / @ifedapoolarewaju)
+
 ## 1.14
 
 Released: 2020-04-29
@@ -115,7 +263,7 @@ Released: 2020-04-29
 | @uppy/aws-s3-multipart | 1.6.3 | @uppy/onedrive | 1.1.4 |
 | @uppy/aws-s3 | 1.6.3 | @uppy/progress-bar | 1.3.12 |
 | @uppy/companion-client | 1.4.3 | @uppy/provider-views | 1.6.4 |
-| @uppy/companion | 2.0.0 | @uppy/react | 1.6.4 |
+| @uppy/companion | 2.0.0-alpha.0 | @uppy/react | 1.6.4 |
 | @uppy/core | 1.10.3 | @uppy/robodog | 1.6.5 |
 | @uppy/dashboard | 1.8.4 | @uppy/screen-capture | 1.0.0 |
 | @uppy/drag-drop | 1.4.11 | @uppy/status-bar | 1.6.4 |
@@ -319,7 +467,7 @@ This release offers a bunch of Companion improvements and bug fixes.
 - @uppy/companion:: don’t log redundant errors in production (#2112 / @ifedapoolarewaju)
 - docs: Add S3 ACL option to companion docs (#2109 / @jasonbosco)
 
-## 1.9.5
+## 1.9.4
 
 Released 2020-02-28
 
@@ -331,8 +479,6 @@ This release rolls out a fix for companion an issue introduced after [this PR](h
 
 - @uppy/companion: read state from session in oauth-redirect controller (#2096 / @ifedapoolarewaju)
 
-## 1.9.4
-
 Released: 2020-02-27
 
 Previous `1.9.3` release has been deprecated due to broken URL Provider (see [#2094](https://github.com/transloadit/uppy/pull/2094)).

+ 62 - 58
README.md

@@ -65,7 +65,7 @@ $ npm install @uppy/core @uppy/dashboard @uppy/tus
 
 We recommend installing from npm and then using a module bundler such as [Webpack](https://webpack.js.org/), [Browserify](http://browserify.org/) or [Rollup.js](http://rollupjs.org/).
 
-Add CSS [uppy.min.css](https://transloadit.edgly.net/releases/uppy/v1.14.1/uppy.min.css), either to your HTML page's `<head>` or include in JS, if your bundler of choice supports it — transforms and plugins are available for Browserify and Webpack.
+Add CSS [uppy.min.css](https://transloadit.edgly.net/releases/uppy/v1.16.1/uppy.min.css), either to your HTML page's `<head>` or include in JS, if your bundler of choice supports it — transforms and plugins are available for Browserify and Webpack.
 
 Alternatively, you can also use a pre-built bundle from Transloadit's CDN: Edgly. In that case `Uppy` will attach itself to the global `window.Uppy` object.
 
@@ -73,10 +73,10 @@ Alternatively, you can also use a pre-built bundle from Transloadit's CDN: Edgly
 
 ```html
 <!-- 1. Add CSS to `<head>` -->
-<link href="https://transloadit.edgly.net/releases/uppy/v1.14.1/uppy.min.css" rel="stylesheet">
+<link href="https://transloadit.edgly.net/releases/uppy/v1.16.1/uppy.min.css" rel="stylesheet">
 
 <!-- 2. Add JS before the closing `</body>` -->
-<script src="https://transloadit.edgly.net/releases/uppy/v1.14.1/uppy.min.js"></script>
+<script src="https://transloadit.edgly.net/releases/uppy/v1.16.1/uppy.min.js"></script>
 
 <!-- 3. Initialize -->
 <div class="UppyDragDrop"></div>
@@ -111,12 +111,12 @@ Alternatively, you can also use a pre-built bundle from Transloadit's CDN: Edgly
 - [`Drag & Drop`](https://uppy.io/docs/drag-drop/) — plain and simple drag and drop area
 - [`File Input`](https://uppy.io/docs/file-input/) — even plainer “select files” button
 - [`Webcam`](https://uppy.io/docs/webcam/) — snap and record those selfies 📷
-- ⓒ [`Google Drive`](https://uppy.io/docs/google-drive/)
-- ⓒ [`Dropbox`](https://uppy.io/docs/dropbox/)
-- ⓒ [`Instagram`](https://uppy.io/docs/instagram/)
-- ⓒ [`Facebook`](https://uppy.io/docs/facebook/)
-- ⓒ [`OneDrive`](https://uppy.io/docs/onedrive/)
-- ⓒ [`Import From URL`](https://uppy.io/docs/url/) — support picking files from remote providers or direct URLs from anywhere on the web
+- ⓒ [`Google Drive`](https://uppy.io/docs/google-drive/) — import files from Google Drive
+- ⓒ [`Dropbox`](https://uppy.io/docs/dropbox/) — import files from Dropbox
+- ⓒ [`Instagram`](https://uppy.io/docs/instagram/) — import images and videos from Instagram
+- ⓒ [`Facebook`](https://uppy.io/docs/facebook/) — import images and videos from Facebook
+- ⓒ [`OneDrive`](https://uppy.io/docs/onedrive/) — import files from Microsoft OneDrive
+- ⓒ [`Import From URL`](https://uppy.io/docs/url/) — import direct URLs from anywhere on the web
 
 The ⓒ mark means that [`@uppy/companion`](https://uppy.io/docs/companion), a server-side component, is needed for a plugin to work.
 
@@ -124,8 +124,8 @@ The ⓒ mark means that [`@uppy/companion`](https://uppy.io/docs/companion), a s
 
 - [`Tus`](https://uppy.io/docs/tus/) — resumable uploads via the open [tus](http://tus.io) standard
 - [`XHR Upload`](https://uppy.io/docs/xhr-upload/) — regular uploads for any backend out there (like Apache, Nginx)
-- [`AWS S3`](https://uppy.io/docs/aws-s3/) — uploader for AWS S3
-- [`AWS S3 Multipart`](https://uppy.io/docs/aws-s3-multipart/) — upload to AWS S3
+- [`AWS S3`](https://uppy.io/docs/aws-s3/) — plain upload to AWS S3 or compatible services
+- [`AWS S3 Multipart`](https://uppy.io/docs/aws-s3-multipart/) — S3-style "Multipart" upload to AWS or compatible services
 
 ### File Processing
 
@@ -174,7 +174,7 @@ const Uppy = require('@uppy/core')
 If you're using Uppy from CDN, `es6-promise` and `whatwg-fetch` are already included in the bundle, so no need to include anything additionally:
 
 ```html
-<script src="https://transloadit.edgly.net/releases/uppy/v1.14.1/uppy.min.js"></script>
+<script src="https://transloadit.edgly.net/releases/uppy/v1.16.1/uppy.min.js"></script>
 ```
 
 ## FAQ
@@ -263,105 +263,109 @@ Use Uppy in your project? [Let us know](https://github.com/transloadit/uppy/issu
 :---: |:---: |:---: |:---: |:---: |:---: |
 [craigjennings11](https://github.com/craigjennings11) |[davekiss](https://github.com/davekiss) |[frobinsonj](https://github.com/frobinsonj) |[geertclerx](https://github.com/geertclerx) |[jasonbosco](https://github.com/jasonbosco) |[jedwood](https://github.com/jedwood) |
 
-[<img alt="Mactaivsh" src="https://avatars0.githubusercontent.com/u/12948083?v=4&s=117" width="117">](https://github.com/Mactaivsh) |[<img alt="maferland" src="https://avatars3.githubusercontent.com/u/5889721?v=4&s=117" width="117">](https://github.com/maferland) |[<img alt="Martin005" src="https://avatars0.githubusercontent.com/u/10096404?v=4&s=117" width="117">](https://github.com/Martin005) |[<img alt="martiuslim" src="https://avatars2.githubusercontent.com/u/17944339?v=4&s=117" width="117">](https://github.com/martiuslim) |[<img alt="behnammodi" src="https://avatars0.githubusercontent.com/u/1549069?v=4&s=117" width="117">](https://github.com/behnammodi) |[<img alt="msand" src="https://avatars2.githubusercontent.com/u/1131362?v=4&s=117" width="117">](https://github.com/msand) |
+[<img alt="Mactaivsh" src="https://avatars0.githubusercontent.com/u/12948083?v=4&s=117" width="117">](https://github.com/Mactaivsh) |[<img alt="maferland" src="https://avatars3.githubusercontent.com/u/5889721?v=4&s=117" width="117">](https://github.com/maferland) |[<img alt="Martin005" src="https://avatars0.githubusercontent.com/u/10096404?v=4&s=117" width="117">](https://github.com/Martin005) |[<img alt="martiuslim" src="https://avatars2.githubusercontent.com/u/17944339?v=4&s=117" width="117">](https://github.com/martiuslim) |[<img alt="MatthiasKunnen" src="https://avatars3.githubusercontent.com/u/16807587?v=4&s=117" width="117">](https://github.com/MatthiasKunnen) |[<img alt="behnammodi" src="https://avatars0.githubusercontent.com/u/1549069?v=4&s=117" width="117">](https://github.com/behnammodi) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[Mactaivsh](https://github.com/Mactaivsh) |[maferland](https://github.com/maferland) |[Martin005](https://github.com/Martin005) |[martiuslim](https://github.com/martiuslim) |[behnammodi](https://github.com/behnammodi) |[msand](https://github.com/msand) |
+[Mactaivsh](https://github.com/Mactaivsh) |[maferland](https://github.com/maferland) |[Martin005](https://github.com/Martin005) |[martiuslim](https://github.com/martiuslim) |[MatthiasKunnen](https://github.com/MatthiasKunnen) |[behnammodi](https://github.com/behnammodi) |
 
 [<img alt="richartkeil" src="https://avatars0.githubusercontent.com/u/8680858?v=4&s=117" width="117">](https://github.com/richartkeil) |[<img alt="richmeij" src="https://avatars0.githubusercontent.com/u/9741858?v=4&s=117" width="117">](https://github.com/richmeij) |[<img alt="rosenfeld" src="https://avatars1.githubusercontent.com/u/32246?v=4&s=117" width="117">](https://github.com/rosenfeld) |[<img alt="ThomasG77" src="https://avatars2.githubusercontent.com/u/642120?v=4&s=117" width="117">](https://github.com/ThomasG77) |[<img alt="zhuangya" src="https://avatars2.githubusercontent.com/u/499038?v=4&s=117" width="117">](https://github.com/zhuangya) |[<img alt="allenfantasy" src="https://avatars1.githubusercontent.com/u/1009294?v=4&s=117" width="117">](https://github.com/allenfantasy) |
 :---: |:---: |:---: |:---: |:---: |:---: |
 [richartkeil](https://github.com/richartkeil) |[richmeij](https://github.com/richmeij) |[rosenfeld](https://github.com/rosenfeld) |[ThomasG77](https://github.com/ThomasG77) |[zhuangya](https://github.com/zhuangya) |[allenfantasy](https://github.com/allenfantasy) |
 
-[<img alt="Zyclotrop-j" src="https://avatars0.githubusercontent.com/u/4939546?v=4&s=117" width="117">](https://github.com/Zyclotrop-j) |[<img alt="fortrieb" src="https://avatars0.githubusercontent.com/u/4126707?v=4&s=117" width="117">](https://github.com/fortrieb) |[<img alt="muhammadInam" src="https://avatars1.githubusercontent.com/u/7801708?v=4&s=117" width="117">](https://github.com/muhammadInam) |[<img alt="rettgerst" src="https://avatars2.githubusercontent.com/u/11684948?v=4&s=117" width="117">](https://github.com/rettgerst) |[<img alt="jukakoski" src="https://avatars0.githubusercontent.com/u/52720967?v=4&s=117" width="117">](https://github.com/jukakoski) |[<img alt="MatthiasKunnen" src="https://avatars3.githubusercontent.com/u/16807587?v=4&s=117" width="117">](https://github.com/MatthiasKunnen) |
+[<img alt="Zyclotrop-j" src="https://avatars0.githubusercontent.com/u/4939546?v=4&s=117" width="117">](https://github.com/Zyclotrop-j) |[<img alt="fortrieb" src="https://avatars0.githubusercontent.com/u/4126707?v=4&s=117" width="117">](https://github.com/fortrieb) |[<img alt="jarey" src="https://avatars1.githubusercontent.com/u/5025224?v=4&s=117" width="117">](https://github.com/jarey) |[<img alt="muhammadInam" src="https://avatars1.githubusercontent.com/u/7801708?v=4&s=117" width="117">](https://github.com/muhammadInam) |[<img alt="rettgerst" src="https://avatars2.githubusercontent.com/u/11684948?v=4&s=117" width="117">](https://github.com/rettgerst) |[<img alt="jukakoski" src="https://avatars0.githubusercontent.com/u/52720967?v=4&s=117" width="117">](https://github.com/jukakoski) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[Zyclotrop-j](https://github.com/Zyclotrop-j) |[fortrieb](https://github.com/fortrieb) |[muhammadInam](https://github.com/muhammadInam) |[rettgerst](https://github.com/rettgerst) |[jukakoski](https://github.com/jukakoski) |[MatthiasKunnen](https://github.com/MatthiasKunnen) |
+[Zyclotrop-j](https://github.com/Zyclotrop-j) |[fortrieb](https://github.com/fortrieb) |[jarey](https://github.com/jarey) |[muhammadInam](https://github.com/muhammadInam) |[rettgerst](https://github.com/rettgerst) |[jukakoski](https://github.com/jukakoski) |
 
-[<img alt="ajschmidt8" src="https://avatars0.githubusercontent.com/u/7400326?v=4&s=117" width="117">](https://github.com/ajschmidt8) |[<img alt="superhawk610" src="https://avatars1.githubusercontent.com/u/18172185?v=4&s=117" width="117">](https://github.com/superhawk610) |[<img alt="adamelmore" src="https://avatars2.githubusercontent.com/u/2363879?v=4&s=117" width="117">](https://github.com/adamelmore) |[<img alt="adamvigneault" src="https://avatars2.githubusercontent.com/u/18236120?v=4&s=117" width="117">](https://github.com/adamvigneault) |[<img alt="asmt3" src="https://avatars1.githubusercontent.com/u/1777709?v=4&s=117" width="117">](https://github.com/asmt3) |[<img alt="alexnj" src="https://avatars0.githubusercontent.com/u/683500?v=4&s=117" width="117">](https://github.com/alexnj) |
+[<img alt="msand" src="https://avatars2.githubusercontent.com/u/1131362?v=4&s=117" width="117">](https://github.com/msand) |[<img alt="ajschmidt8" src="https://avatars0.githubusercontent.com/u/7400326?v=4&s=117" width="117">](https://github.com/ajschmidt8) |[<img alt="superhawk610" src="https://avatars1.githubusercontent.com/u/18172185?v=4&s=117" width="117">](https://github.com/superhawk610) |[<img alt="adamelmore" src="https://avatars2.githubusercontent.com/u/2363879?v=4&s=117" width="117">](https://github.com/adamelmore) |[<img alt="adamvigneault" src="https://avatars2.githubusercontent.com/u/18236120?v=4&s=117" width="117">](https://github.com/adamvigneault) |[<img alt="asmt3" src="https://avatars1.githubusercontent.com/u/1777709?v=4&s=117" width="117">](https://github.com/asmt3) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[ajschmidt8](https://github.com/ajschmidt8) |[superhawk610](https://github.com/superhawk610) |[adamelmore](https://github.com/adamelmore) |[adamvigneault](https://github.com/adamvigneault) |[asmt3](https://github.com/asmt3) |[alexnj](https://github.com/alexnj) |
+[msand](https://github.com/msand) |[ajschmidt8](https://github.com/ajschmidt8) |[superhawk610](https://github.com/superhawk610) |[adamelmore](https://github.com/adamelmore) |[adamvigneault](https://github.com/adamvigneault) |[asmt3](https://github.com/asmt3) |
 
-[<img alt="amitport" src="https://avatars1.githubusercontent.com/u/1131991?v=4&s=117" width="117">](https://github.com/amitport) |[<img alt="functino" src="https://avatars0.githubusercontent.com/u/415498?v=4&s=117" width="117">](https://github.com/functino) |[<img alt="Botz" src="https://avatars3.githubusercontent.com/u/2706678?v=4&s=117" width="117">](https://github.com/Botz) |[<img alt="radarhere" src="https://avatars2.githubusercontent.com/u/3112309?v=4&s=117" width="117">](https://github.com/radarhere) |[<img alt="superandrew213" src="https://avatars3.githubusercontent.com/u/13059204?v=4&s=117" width="117">](https://github.com/superandrew213) |[<img alt="andychongyz" src="https://avatars0.githubusercontent.com/u/12697240?v=4&s=117" width="117">](https://github.com/andychongyz) |
+[<img alt="alexnj" src="https://avatars0.githubusercontent.com/u/683500?v=4&s=117" width="117">](https://github.com/alexnj) |[<img alt="amitport" src="https://avatars1.githubusercontent.com/u/1131991?v=4&s=117" width="117">](https://github.com/amitport) |[<img alt="functino" src="https://avatars0.githubusercontent.com/u/415498?v=4&s=117" width="117">](https://github.com/functino) |[<img alt="Botz" src="https://avatars3.githubusercontent.com/u/2706678?v=4&s=117" width="117">](https://github.com/Botz) |[<img alt="radarhere" src="https://avatars2.githubusercontent.com/u/3112309?v=4&s=117" width="117">](https://github.com/radarhere) |[<img alt="superandrew213" src="https://avatars3.githubusercontent.com/u/13059204?v=4&s=117" width="117">](https://github.com/superandrew213) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[amitport](https://github.com/amitport) |[functino](https://github.com/functino) |[Botz](https://github.com/Botz) |[radarhere](https://github.com/radarhere) |[superandrew213](https://github.com/superandrew213) |[andychongyz](https://github.com/andychongyz) |
+[alexnj](https://github.com/alexnj) |[amitport](https://github.com/amitport) |[functino](https://github.com/functino) |[Botz](https://github.com/Botz) |[radarhere](https://github.com/radarhere) |[superandrew213](https://github.com/superandrew213) |
 
-[<img alt="arthurdenner" src="https://avatars0.githubusercontent.com/u/13774309?v=4&s=117" width="117">](https://github.com/arthurdenner) |[<img alt="apuyou" src="https://avatars2.githubusercontent.com/u/520053?v=4&s=117" width="117">](https://github.com/apuyou) |[<img alt="bochkarev-artem" src="https://avatars2.githubusercontent.com/u/11025874?v=4&s=117" width="117">](https://github.com/bochkarev-artem) |[<img alt="ayhankesicioglu" src="https://avatars2.githubusercontent.com/u/36304312?v=4&s=117" width="117">](https://github.com/ayhankesicioglu) |[<img alt="azeemba" src="https://avatars0.githubusercontent.com/u/2160795?v=4&s=117" width="117">](https://github.com/azeemba) |[<img alt="bducharme" src="https://avatars2.githubusercontent.com/u/4173569?v=4&s=117" width="117">](https://github.com/bducharme) |
+[<img alt="andychongyz" src="https://avatars0.githubusercontent.com/u/12697240?v=4&s=117" width="117">](https://github.com/andychongyz) |[<img alt="arthurdenner" src="https://avatars0.githubusercontent.com/u/13774309?v=4&s=117" width="117">](https://github.com/arthurdenner) |[<img alt="apuyou" src="https://avatars2.githubusercontent.com/u/520053?v=4&s=117" width="117">](https://github.com/apuyou) |[<img alt="bochkarev-artem" src="https://avatars2.githubusercontent.com/u/11025874?v=4&s=117" width="117">](https://github.com/bochkarev-artem) |[<img alt="atsawin" src="https://avatars2.githubusercontent.com/u/666663?v=4&s=117" width="117">](https://github.com/atsawin) |[<img alt="ayhankesicioglu" src="https://avatars2.githubusercontent.com/u/36304312?v=4&s=117" width="117">](https://github.com/ayhankesicioglu) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[arthurdenner](https://github.com/arthurdenner) |[apuyou](https://github.com/apuyou) |[bochkarev-artem](https://github.com/bochkarev-artem) |[ayhankesicioglu](https://github.com/ayhankesicioglu) |[azeemba](https://github.com/azeemba) |[bducharme](https://github.com/bducharme) |
+[andychongyz](https://github.com/andychongyz) |[arthurdenner](https://github.com/arthurdenner) |[apuyou](https://github.com/apuyou) |[bochkarev-artem](https://github.com/bochkarev-artem) |[atsawin](https://github.com/atsawin) |[ayhankesicioglu](https://github.com/ayhankesicioglu) |
 
-[<img alt="Quorafind" src="https://avatars1.githubusercontent.com/u/13215013?v=4&s=117" width="117">](https://github.com/Quorafind) |[<img alt="cellvinchung" src="https://avatars2.githubusercontent.com/u/5347394?v=4&s=117" width="117">](https://github.com/cellvinchung) |[<img alt="chao" src="https://avatars2.githubusercontent.com/u/55872?v=4&s=117" width="117">](https://github.com/chao) |[<img alt="csprance" src="https://avatars0.githubusercontent.com/u/7902617?v=4&s=117" width="117">](https://github.com/csprance) |[<img alt="Aarbel" src="https://avatars2.githubusercontent.com/u/25119847?v=4&s=117" width="117">](https://github.com/Aarbel) |[<img alt="cbush06" src="https://avatars0.githubusercontent.com/u/15720146?v=4&s=117" width="117">](https://github.com/cbush06) |
+[<img alt="azeemba" src="https://avatars0.githubusercontent.com/u/2160795?v=4&s=117" width="117">](https://github.com/azeemba) |[<img alt="bducharme" src="https://avatars2.githubusercontent.com/u/4173569?v=4&s=117" width="117">](https://github.com/bducharme) |[<img alt="Quorafind" src="https://avatars1.githubusercontent.com/u/13215013?v=4&s=117" width="117">](https://github.com/Quorafind) |[<img alt="cellvinchung" src="https://avatars2.githubusercontent.com/u/5347394?v=4&s=117" width="117">](https://github.com/cellvinchung) |[<img alt="chao" src="https://avatars2.githubusercontent.com/u/55872?v=4&s=117" width="117">](https://github.com/chao) |[<img alt="csprance" src="https://avatars0.githubusercontent.com/u/7902617?v=4&s=117" width="117">](https://github.com/csprance) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[Quorafind](https://github.com/Quorafind) |[cellvinchung](https://github.com/cellvinchung) |[chao](https://github.com/chao) |[csprance](https://github.com/csprance) |[Aarbel](https://github.com/Aarbel) |[cbush06](https://github.com/cbush06) |
+[azeemba](https://github.com/azeemba) |[bducharme](https://github.com/bducharme) |[Quorafind](https://github.com/Quorafind) |[cellvinchung](https://github.com/cellvinchung) |[chao](https://github.com/chao) |[csprance](https://github.com/csprance) |
 
-[<img alt="czj" src="https://avatars2.githubusercontent.com/u/14306?v=4&s=117" width="117">](https://github.com/czj) |[<img alt="ardeois" src="https://avatars0.githubusercontent.com/u/1867939?v=4&s=117" width="117">](https://github.com/ardeois) |[<img alt="sercraig" src="https://avatars3.githubusercontent.com/u/24261518?v=4&s=117" width="117">](https://github.com/sercraig) |[<img alt="danmichaelo" src="https://avatars1.githubusercontent.com/u/434495?v=4&s=117" width="117">](https://github.com/danmichaelo) |[<img alt="mrboomer" src="https://avatars0.githubusercontent.com/u/5942912?v=4&s=117" width="117">](https://github.com/mrboomer) |[<img alt="akizor" src="https://avatars1.githubusercontent.com/u/1052439?v=4&s=117" width="117">](https://github.com/akizor) |
+[<img alt="Aarbel" src="https://avatars2.githubusercontent.com/u/25119847?v=4&s=117" width="117">](https://github.com/Aarbel) |[<img alt="cbush06" src="https://avatars0.githubusercontent.com/u/15720146?v=4&s=117" width="117">](https://github.com/cbush06) |[<img alt="czj" src="https://avatars2.githubusercontent.com/u/14306?v=4&s=117" width="117">](https://github.com/czj) |[<img alt="ardeois" src="https://avatars0.githubusercontent.com/u/1867939?v=4&s=117" width="117">](https://github.com/ardeois) |[<img alt="sercraig" src="https://avatars3.githubusercontent.com/u/24261518?v=4&s=117" width="117">](https://github.com/sercraig) |[<img alt="danmichaelo" src="https://avatars1.githubusercontent.com/u/434495?v=4&s=117" width="117">](https://github.com/danmichaelo) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[czj](https://github.com/czj) |[ardeois](https://github.com/ardeois) |[sercraig](https://github.com/sercraig) |[danmichaelo](https://github.com/danmichaelo) |[mrboomer](https://github.com/mrboomer) |[akizor](https://github.com/akizor) |
+[Aarbel](https://github.com/Aarbel) |[cbush06](https://github.com/cbush06) |[czj](https://github.com/czj) |[ardeois](https://github.com/ardeois) |[sercraig](https://github.com/sercraig) |[danmichaelo](https://github.com/danmichaelo) |
 
-[<img alt="davilima6" src="https://avatars0.githubusercontent.com/u/422130?v=4&s=117" width="117">](https://github.com/davilima6) |[<img alt="olitomas" src="https://avatars0.githubusercontent.com/u/6918659?v=4&s=117" width="117">](https://github.com/olitomas) |[<img alt="dominiceden" src="https://avatars2.githubusercontent.com/u/6367692?v=4&s=117" width="117">](https://github.com/dominiceden) |[<img alt="efbautista" src="https://avatars3.githubusercontent.com/u/35430671?v=4&s=117" width="117">](https://github.com/efbautista) |[<img alt="yoldar" src="https://avatars3.githubusercontent.com/u/1597578?v=4&s=117" width="117">](https://github.com/yoldar) |[<img alt="eliOcs" src="https://avatars1.githubusercontent.com/u/1283954?v=4&s=117" width="117">](https://github.com/eliOcs) |
+[<img alt="mrboomer" src="https://avatars0.githubusercontent.com/u/5942912?v=4&s=117" width="117">](https://github.com/mrboomer) |[<img alt="akizor" src="https://avatars1.githubusercontent.com/u/1052439?v=4&s=117" width="117">](https://github.com/akizor) |[<img alt="davilima6" src="https://avatars0.githubusercontent.com/u/422130?v=4&s=117" width="117">](https://github.com/davilima6) |[<img alt="DennisKofflard" src="https://avatars2.githubusercontent.com/u/8669129?v=4&s=117" width="117">](https://github.com/DennisKofflard) |[<img alt="olitomas" src="https://avatars0.githubusercontent.com/u/6918659?v=4&s=117" width="117">](https://github.com/olitomas) |[<img alt="efbautista" src="https://avatars3.githubusercontent.com/u/35430671?v=4&s=117" width="117">](https://github.com/efbautista) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[davilima6](https://github.com/davilima6) |[olitomas](https://github.com/olitomas) |[dominiceden](https://github.com/dominiceden) |[efbautista](https://github.com/efbautista) |[yoldar](https://github.com/yoldar) |[eliOcs](https://github.com/eliOcs) |
+[mrboomer](https://github.com/mrboomer) |[akizor](https://github.com/akizor) |[davilima6](https://github.com/davilima6) |[DennisKofflard](https://github.com/DennisKofflard) |[olitomas](https://github.com/olitomas) |[efbautista](https://github.com/efbautista) |
 
-[<img alt="lowsprofile" src="https://avatars1.githubusercontent.com/u/11029687?v=4&s=117" width="117">](https://github.com/lowsprofile) |[<img alt="fgallinari" src="https://avatars1.githubusercontent.com/u/6473638?v=4&s=117" width="117">](https://github.com/fgallinari) |[<img alt="dtrucs" src="https://avatars2.githubusercontent.com/u/1926041?v=4&s=117" width="117">](https://github.com/dtrucs) |[<img alt="FWirtz" src="https://avatars1.githubusercontent.com/u/6052785?v=4&s=117" width="117">](https://github.com/FWirtz) |[<img alt="geoffappleford" src="https://avatars2.githubusercontent.com/u/731678?v=4&s=117" width="117">](https://github.com/geoffappleford) |[<img alt="gjungb" src="https://avatars0.githubusercontent.com/u/3391068?v=4&s=117" width="117">](https://github.com/gjungb) |
+[<img alt="yoldar" src="https://avatars3.githubusercontent.com/u/1597578?v=4&s=117" width="117">](https://github.com/yoldar) |[<img alt="eliOcs" src="https://avatars1.githubusercontent.com/u/1283954?v=4&s=117" width="117">](https://github.com/eliOcs) |[<img alt="lowsprofile" src="https://avatars1.githubusercontent.com/u/11029687?v=4&s=117" width="117">](https://github.com/lowsprofile) |[<img alt="fgallinari" src="https://avatars1.githubusercontent.com/u/6473638?v=4&s=117" width="117">](https://github.com/fgallinari) |[<img alt="dtrucs" src="https://avatars2.githubusercontent.com/u/1926041?v=4&s=117" width="117">](https://github.com/dtrucs) |[<img alt="geoffappleford" src="https://avatars2.githubusercontent.com/u/731678?v=4&s=117" width="117">](https://github.com/geoffappleford) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[lowsprofile](https://github.com/lowsprofile) |[fgallinari](https://github.com/fgallinari) |[dtrucs](https://github.com/dtrucs) |[FWirtz](https://github.com/FWirtz) |[geoffappleford](https://github.com/geoffappleford) |[gjungb](https://github.com/gjungb) |
+[yoldar](https://github.com/yoldar) |[eliOcs](https://github.com/eliOcs) |[lowsprofile](https://github.com/lowsprofile) |[fgallinari](https://github.com/fgallinari) |[dtrucs](https://github.com/dtrucs) |[geoffappleford](https://github.com/geoffappleford) |
 
-[<img alt="roenschg" src="https://avatars2.githubusercontent.com/u/9590236?v=4&s=117" width="117">](https://github.com/roenschg) |[<img alt="HughbertD" src="https://avatars0.githubusercontent.com/u/1580021?v=4&s=117" width="117">](https://github.com/HughbertD) |[<img alt="HussainAlkhalifah" src="https://avatars1.githubusercontent.com/u/43642162?v=4&s=117" width="117">](https://github.com/HussainAlkhalifah) |[<img alt="huydod" src="https://avatars2.githubusercontent.com/u/37580530?v=4&s=117" width="117">](https://github.com/huydod) |[<img alt="ishendyweb" src="https://avatars1.githubusercontent.com/u/10582418?v=4&s=117" width="117">](https://github.com/ishendyweb) |[<img alt="NaxYo" src="https://avatars3.githubusercontent.com/u/1963876?v=4&s=117" width="117">](https://github.com/NaxYo) |
+[<img alt="gjungb" src="https://avatars0.githubusercontent.com/u/3391068?v=4&s=117" width="117">](https://github.com/gjungb) |[<img alt="roenschg" src="https://avatars2.githubusercontent.com/u/9590236?v=4&s=117" width="117">](https://github.com/roenschg) |[<img alt="HughbertD" src="https://avatars0.githubusercontent.com/u/1580021?v=4&s=117" width="117">](https://github.com/HughbertD) |[<img alt="HussainAlkhalifah" src="https://avatars1.githubusercontent.com/u/43642162?v=4&s=117" width="117">](https://github.com/HussainAlkhalifah) |[<img alt="huydod" src="https://avatars2.githubusercontent.com/u/37580530?v=4&s=117" width="117">](https://github.com/huydod) |[<img alt="ishendyweb" src="https://avatars1.githubusercontent.com/u/10582418?v=4&s=117" width="117">](https://github.com/ishendyweb) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[roenschg](https://github.com/roenschg) |[HughbertD](https://github.com/HughbertD) |[HussainAlkhalifah](https://github.com/HussainAlkhalifah) |[huydod](https://github.com/huydod) |[ishendyweb](https://github.com/ishendyweb) |[NaxYo](https://github.com/NaxYo) |
+[gjungb](https://github.com/gjungb) |[roenschg](https://github.com/roenschg) |[HughbertD](https://github.com/HughbertD) |[HussainAlkhalifah](https://github.com/HussainAlkhalifah) |[huydod](https://github.com/huydod) |[ishendyweb](https://github.com/ishendyweb) |
 
-[<img alt="JacobMGEvans" src="https://avatars1.githubusercontent.com/u/27247160?v=4&s=117" width="117">](https://github.com/JacobMGEvans) |[<img alt="jdssem" src="https://avatars0.githubusercontent.com/u/978944?v=4&s=117" width="117">](https://github.com/jdssem) |[<img alt="jcjmcclean" src="https://avatars3.githubusercontent.com/u/1822574?v=4&s=117" width="117">](https://github.com/jcjmcclean) |[<img alt="janklimo" src="https://avatars1.githubusercontent.com/u/7811733?v=4&s=117" width="117">](https://github.com/janklimo) |[<img alt="vith" src="https://avatars1.githubusercontent.com/u/3265539?v=4&s=117" width="117">](https://github.com/vith) |[<img alt="jessica-coursera" src="https://avatars1.githubusercontent.com/u/35155465?v=4&s=117" width="117">](https://github.com/jessica-coursera) |
+[<img alt="NaxYo" src="https://avatars3.githubusercontent.com/u/1963876?v=4&s=117" width="117">](https://github.com/NaxYo) |[<img alt="intenzive" src="https://avatars1.githubusercontent.com/u/11055931?v=4&s=117" width="117">](https://github.com/intenzive) |[<img alt="JacobMGEvans" src="https://avatars1.githubusercontent.com/u/27247160?v=4&s=117" width="117">](https://github.com/JacobMGEvans) |[<img alt="jdssem" src="https://avatars0.githubusercontent.com/u/978944?v=4&s=117" width="117">](https://github.com/jdssem) |[<img alt="jcjmcclean" src="https://avatars3.githubusercontent.com/u/1822574?v=4&s=117" width="117">](https://github.com/jcjmcclean) |[<img alt="janklimo" src="https://avatars1.githubusercontent.com/u/7811733?v=4&s=117" width="117">](https://github.com/janklimo) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[JacobMGEvans](https://github.com/JacobMGEvans) |[jdssem](https://github.com/jdssem) |[jcjmcclean](https://github.com/jcjmcclean) |[janklimo](https://github.com/janklimo) |[vith](https://github.com/vith) |[jessica-coursera](https://github.com/jessica-coursera) |
+[NaxYo](https://github.com/NaxYo) |[intenzive](https://github.com/intenzive) |[JacobMGEvans](https://github.com/JacobMGEvans) |[jdssem](https://github.com/jdssem) |[jcjmcclean](https://github.com/jcjmcclean) |[janklimo](https://github.com/janklimo) |
 
-[<img alt="theJoeBiz" src="https://avatars1.githubusercontent.com/u/189589?v=4&s=117" width="117">](https://github.com/theJoeBiz) |[<img alt="jderrough" src="https://avatars3.githubusercontent.com/u/1108358?v=4&s=117" width="117">](https://github.com/jderrough) |[<img alt="jonathanly" src="https://avatars3.githubusercontent.com/u/13286473?v=4&s=117" width="117">](https://github.com/jonathanly) |[<img alt="jorgeepc" src="https://avatars3.githubusercontent.com/u/3879892?v=4&s=117" width="117">](https://github.com/jorgeepc) |[<img alt="julianocomg" src="https://avatars1.githubusercontent.com/u/7483557?v=4&s=117" width="117">](https://github.com/julianocomg) |[<img alt="dogrocker" src="https://avatars0.githubusercontent.com/u/8379027?v=4&s=117" width="117">](https://github.com/dogrocker) |
+[<img alt="vith" src="https://avatars1.githubusercontent.com/u/3265539?v=4&s=117" width="117">](https://github.com/vith) |[<img alt="jessica-coursera" src="https://avatars1.githubusercontent.com/u/35155465?v=4&s=117" width="117">](https://github.com/jessica-coursera) |[<img alt="theJoeBiz" src="https://avatars1.githubusercontent.com/u/189589?v=4&s=117" width="117">](https://github.com/theJoeBiz) |[<img alt="jderrough" src="https://avatars3.githubusercontent.com/u/1108358?v=4&s=117" width="117">](https://github.com/jderrough) |[<img alt="jonathanly" src="https://avatars3.githubusercontent.com/u/13286473?v=4&s=117" width="117">](https://github.com/jonathanly) |[<img alt="jorgeepc" src="https://avatars3.githubusercontent.com/u/3879892?v=4&s=117" width="117">](https://github.com/jorgeepc) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[theJoeBiz](https://github.com/theJoeBiz) |[jderrough](https://github.com/jderrough) |[jonathanly](https://github.com/jonathanly) |[jorgeepc](https://github.com/jorgeepc) |[julianocomg](https://github.com/julianocomg) |[dogrocker](https://github.com/dogrocker) |
+[vith](https://github.com/vith) |[jessica-coursera](https://github.com/jessica-coursera) |[theJoeBiz](https://github.com/theJoeBiz) |[jderrough](https://github.com/jderrough) |[jonathanly](https://github.com/jonathanly) |[jorgeepc](https://github.com/jorgeepc) |
 
-[<img alt="firesharkstudios" src="https://avatars1.githubusercontent.com/u/17069637?v=4&s=117" width="117">](https://github.com/firesharkstudios) |[<img alt="kyleparisi" src="https://avatars0.githubusercontent.com/u/1286753?v=4&s=117" width="117">](https://github.com/kyleparisi) |[<img alt="larowlan" src="https://avatars2.githubusercontent.com/u/555254?v=4&s=117" width="117">](https://github.com/larowlan) |[<img alt="dviry" src="https://avatars3.githubusercontent.com/u/1230260?v=4&s=117" width="117">](https://github.com/dviry) |[<img alt="galli-leo" src="https://avatars3.githubusercontent.com/u/5339762?v=4&s=117" width="117">](https://github.com/galli-leo) |[<img alt="leods92" src="https://avatars0.githubusercontent.com/u/879395?v=4&s=117" width="117">](https://github.com/leods92) |
+[<img alt="julianocomg" src="https://avatars1.githubusercontent.com/u/7483557?v=4&s=117" width="117">](https://github.com/julianocomg) |[<img alt="dogrocker" src="https://avatars0.githubusercontent.com/u/8379027?v=4&s=117" width="117">](https://github.com/dogrocker) |[<img alt="firesharkstudios" src="https://avatars1.githubusercontent.com/u/17069637?v=4&s=117" width="117">](https://github.com/firesharkstudios) |[<img alt="kyleparisi" src="https://avatars0.githubusercontent.com/u/1286753?v=4&s=117" width="117">](https://github.com/kyleparisi) |[<img alt="lafe" src="https://avatars1.githubusercontent.com/u/4070008?v=4&s=117" width="117">](https://github.com/lafe) |[<img alt="larowlan" src="https://avatars2.githubusercontent.com/u/555254?v=4&s=117" width="117">](https://github.com/larowlan) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[firesharkstudios](https://github.com/firesharkstudios) |[kyleparisi](https://github.com/kyleparisi) |[larowlan](https://github.com/larowlan) |[dviry](https://github.com/dviry) |[galli-leo](https://github.com/galli-leo) |[leods92](https://github.com/leods92) |
+[julianocomg](https://github.com/julianocomg) |[dogrocker](https://github.com/dogrocker) |[firesharkstudios](https://github.com/firesharkstudios) |[kyleparisi](https://github.com/kyleparisi) |[lafe](https://github.com/lafe) |[larowlan](https://github.com/larowlan) |
 
-[<img alt="louim" src="https://avatars2.githubusercontent.com/u/923718?v=4&s=117" width="117">](https://github.com/louim) |[<img alt="lucaperret" src="https://avatars1.githubusercontent.com/u/1887122?v=4&s=117" width="117">](https://github.com/lucaperret) |[<img alt="mperrando" src="https://avatars2.githubusercontent.com/u/525572?v=4&s=117" width="117">](https://github.com/mperrando) |[<img alt="marcusforsberg" src="https://avatars0.githubusercontent.com/u/1009069?v=4&s=117" width="117">](https://github.com/marcusforsberg) |[<img alt="mattfik" src="https://avatars2.githubusercontent.com/u/1638028?v=4&s=117" width="117">](https://github.com/mattfik) |[<img alt="hrsh" src="https://avatars3.githubusercontent.com/u/1929359?v=4&s=117" width="117">](https://github.com/hrsh) |
+[<img alt="dviry" src="https://avatars3.githubusercontent.com/u/1230260?v=4&s=117" width="117">](https://github.com/dviry) |[<img alt="galli-leo" src="https://avatars3.githubusercontent.com/u/5339762?v=4&s=117" width="117">](https://github.com/galli-leo) |[<img alt="leods92" src="https://avatars0.githubusercontent.com/u/879395?v=4&s=117" width="117">](https://github.com/leods92) |[<img alt="louim" src="https://avatars2.githubusercontent.com/u/923718?v=4&s=117" width="117">](https://github.com/louim) |[<img alt="lucaperret" src="https://avatars1.githubusercontent.com/u/1887122?v=4&s=117" width="117">](https://github.com/lucaperret) |[<img alt="mperrando" src="https://avatars2.githubusercontent.com/u/525572?v=4&s=117" width="117">](https://github.com/mperrando) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[louim](https://github.com/louim) |[lucaperret](https://github.com/lucaperret) |[mperrando](https://github.com/mperrando) |[marcusforsberg](https://github.com/marcusforsberg) |[mattfik](https://github.com/mattfik) |[hrsh](https://github.com/hrsh) |
+[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) |[mperrando](https://github.com/mperrando) |
 
-[<img alt="mhulet" src="https://avatars0.githubusercontent.com/u/293355?v=4&s=117" width="117">](https://github.com/mhulet) |[<img alt="mkabatek" src="https://avatars0.githubusercontent.com/u/1764486?v=4&s=117" width="117">](https://github.com/mkabatek) |[<img alt="achmiral" src="https://avatars0.githubusercontent.com/u/10906059?v=4&s=117" width="117">](https://github.com/achmiral) |[<img alt="mnafees" src="https://avatars1.githubusercontent.com/u/1763885?v=4&s=117" width="117">](https://github.com/mnafees) |[<img alt="shahimclt" src="https://avatars3.githubusercontent.com/u/8318002?v=4&s=117" width="117">](https://github.com/shahimclt) |[<img alt="naveed-ahmad" src="https://avatars2.githubusercontent.com/u/701567?v=4&s=117" width="117">](https://github.com/naveed-ahmad) |
+[<img alt="marcusforsberg" src="https://avatars0.githubusercontent.com/u/1009069?v=4&s=117" width="117">](https://github.com/marcusforsberg) |[<img alt="mattfik" src="https://avatars2.githubusercontent.com/u/1638028?v=4&s=117" width="117">](https://github.com/mattfik) |[<img alt="hrsh" src="https://avatars3.githubusercontent.com/u/1929359?v=4&s=117" width="117">](https://github.com/hrsh) |[<img alt="mhulet" src="https://avatars0.githubusercontent.com/u/293355?v=4&s=117" width="117">](https://github.com/mhulet) |[<img alt="mkabatek" src="https://avatars0.githubusercontent.com/u/1764486?v=4&s=117" width="117">](https://github.com/mkabatek) |[<img alt="achmiral" src="https://avatars0.githubusercontent.com/u/10906059?v=4&s=117" width="117">](https://github.com/achmiral) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[mhulet](https://github.com/mhulet) |[mkabatek](https://github.com/mkabatek) |[achmiral](https://github.com/achmiral) |[mnafees](https://github.com/mnafees) |[shahimclt](https://github.com/shahimclt) |[naveed-ahmad](https://github.com/naveed-ahmad) |
+[marcusforsberg](https://github.com/marcusforsberg) |[mattfik](https://github.com/mattfik) |[hrsh](https://github.com/hrsh) |[mhulet](https://github.com/mhulet) |[mkabatek](https://github.com/mkabatek) |[achmiral](https://github.com/achmiral) |
 
-[<img alt="olemoign" src="https://avatars3.githubusercontent.com/u/11632871?v=4&s=117" width="117">](https://github.com/olemoign) |[<img alt="leftdevel" src="https://avatars3.githubusercontent.com/u/843337?v=4&s=117" width="117">](https://github.com/leftdevel) |[<img alt="cryptic022" src="https://avatars2.githubusercontent.com/u/18145703?v=4&s=117" width="117">](https://github.com/cryptic022) |[<img alt="phillipalexander" src="https://avatars0.githubusercontent.com/u/1577682?v=4&s=117" width="117">](https://github.com/phillipalexander) |[<img alt="Pzoco" src="https://avatars0.githubusercontent.com/u/3101348?v=4&s=117" width="117">](https://github.com/Pzoco) |[<img alt="eman8519" src="https://avatars2.githubusercontent.com/u/2380804?v=4&s=117" width="117">](https://github.com/eman8519) |
+[<img alt="mnafees" src="https://avatars1.githubusercontent.com/u/1763885?v=4&s=117" width="117">](https://github.com/mnafees) |[<img alt="shahimclt" src="https://avatars3.githubusercontent.com/u/8318002?v=4&s=117" width="117">](https://github.com/shahimclt) |[<img alt="naveed-ahmad" src="https://avatars2.githubusercontent.com/u/701567?v=4&s=117" width="117">](https://github.com/naveed-ahmad) |[<img alt="nicojones" src="https://avatars2.githubusercontent.com/u/6078915?v=4&s=117" width="117">](https://github.com/nicojones) |[<img alt="olemoign" src="https://avatars3.githubusercontent.com/u/11632871?v=4&s=117" width="117">](https://github.com/olemoign) |[<img alt="leftdevel" src="https://avatars3.githubusercontent.com/u/843337?v=4&s=117" width="117">](https://github.com/leftdevel) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[olemoign](https://github.com/olemoign) |[leftdevel](https://github.com/leftdevel) |[cryptic022](https://github.com/cryptic022) |[phillipalexander](https://github.com/phillipalexander) |[Pzoco](https://github.com/Pzoco) |[eman8519](https://github.com/eman8519) |
+[mnafees](https://github.com/mnafees) |[shahimclt](https://github.com/shahimclt) |[naveed-ahmad](https://github.com/naveed-ahmad) |[nicojones](https://github.com/nicojones) |[olemoign](https://github.com/olemoign) |[leftdevel](https://github.com/leftdevel) |
 
-[<img alt="luarmr" src="https://avatars3.githubusercontent.com/u/817416?v=4&s=117" width="117">](https://github.com/luarmr) |[<img alt="phobos101" src="https://avatars2.githubusercontent.com/u/7114944?v=4&s=117" width="117">](https://github.com/phobos101) |[<img alt="romain-preston" src="https://avatars3.githubusercontent.com/u/1517040?v=4&s=117" width="117">](https://github.com/romain-preston) |[<img alt="scherroman" src="https://avatars3.githubusercontent.com/u/7923938?v=4&s=117" width="117">](https://github.com/scherroman) |[<img alt="fortunto2" src="https://avatars1.githubusercontent.com/u/1236751?v=4&s=117" width="117">](https://github.com/fortunto2) |[<img alt="jrschumacher" src="https://avatars1.githubusercontent.com/u/46549?v=4&s=117" width="117">](https://github.com/jrschumacher) |
+[<img alt="cryptic022" src="https://avatars2.githubusercontent.com/u/18145703?v=4&s=117" width="117">](https://github.com/cryptic022) |[<img alt="phillipalexander" src="https://avatars0.githubusercontent.com/u/1577682?v=4&s=117" width="117">](https://github.com/phillipalexander) |[<img alt="Pzoco" src="https://avatars0.githubusercontent.com/u/3101348?v=4&s=117" width="117">](https://github.com/Pzoco) |[<img alt="eman8519" src="https://avatars2.githubusercontent.com/u/2380804?v=4&s=117" width="117">](https://github.com/eman8519) |[<img alt="luarmr" src="https://avatars3.githubusercontent.com/u/817416?v=4&s=117" width="117">](https://github.com/luarmr) |[<img alt="phobos101" src="https://avatars2.githubusercontent.com/u/7114944?v=4&s=117" width="117">](https://github.com/phobos101) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[luarmr](https://github.com/luarmr) |[phobos101](https://github.com/phobos101) |[romain-preston](https://github.com/romain-preston) |[scherroman](https://github.com/scherroman) |[fortunto2](https://github.com/fortunto2) |[jrschumacher](https://github.com/jrschumacher) |
+[cryptic022](https://github.com/cryptic022) |[phillipalexander](https://github.com/phillipalexander) |[Pzoco](https://github.com/Pzoco) |[eman8519](https://github.com/eman8519) |[luarmr](https://github.com/luarmr) |[phobos101](https://github.com/phobos101) |
 
-[<img alt="samuelcolburn" src="https://avatars2.githubusercontent.com/u/9741902?v=4&s=117" width="117">](https://github.com/samuelcolburn) |[<img alt="sergei-zelinsky" src="https://avatars2.githubusercontent.com/u/19428086?v=4&s=117" width="117">](https://github.com/sergei-zelinsky) |[<img alt="SpazzMarticus" src="https://avatars0.githubusercontent.com/u/5716457?v=4&s=117" width="117">](https://github.com/SpazzMarticus) |[<img alt="waptik" src="https://avatars1.githubusercontent.com/u/1687551?v=4&s=117" width="117">](https://github.com/waptik) |[<img alt="steverob" src="https://avatars2.githubusercontent.com/u/1220480?v=4&s=117" width="117">](https://github.com/steverob) |[<img alt="tajchumber" src="https://avatars3.githubusercontent.com/u/16062635?v=4&s=117" width="117">](https://github.com/tajchumber) |
+[<img alt="romain-preston" src="https://avatars3.githubusercontent.com/u/1517040?v=4&s=117" width="117">](https://github.com/romain-preston) |[<img alt="scherroman" src="https://avatars3.githubusercontent.com/u/7923938?v=4&s=117" width="117">](https://github.com/scherroman) |[<img alt="fortunto2" src="https://avatars1.githubusercontent.com/u/1236751?v=4&s=117" width="117">](https://github.com/fortunto2) |[<img alt="jrschumacher" src="https://avatars1.githubusercontent.com/u/46549?v=4&s=117" width="117">](https://github.com/jrschumacher) |[<img alt="samuelcolburn" src="https://avatars2.githubusercontent.com/u/9741902?v=4&s=117" width="117">](https://github.com/samuelcolburn) |[<img alt="sergei-zelinsky" src="https://avatars2.githubusercontent.com/u/19428086?v=4&s=117" width="117">](https://github.com/sergei-zelinsky) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[samuelcolburn](https://github.com/samuelcolburn) |[sergei-zelinsky](https://github.com/sergei-zelinsky) |[SpazzMarticus](https://github.com/SpazzMarticus) |[waptik](https://github.com/waptik) |[steverob](https://github.com/steverob) |[tajchumber](https://github.com/tajchumber) |
+[romain-preston](https://github.com/romain-preston) |[scherroman](https://github.com/scherroman) |[fortunto2](https://github.com/fortunto2) |[jrschumacher](https://github.com/jrschumacher) |[samuelcolburn](https://github.com/samuelcolburn) |[sergei-zelinsky](https://github.com/sergei-zelinsky) |
 
-[<img alt="Tashows" src="https://avatars2.githubusercontent.com/u/16656928?v=4&s=117" width="117">](https://github.com/Tashows) |[<img alt="twarlop" src="https://avatars3.githubusercontent.com/u/2856082?v=4&s=117" width="117">](https://github.com/twarlop) |[<img alt="tomsaleeba" src="https://avatars0.githubusercontent.com/u/1773838?v=4&s=117" width="117">](https://github.com/tomsaleeba) |[<img alt="tvaliasek" src="https://avatars2.githubusercontent.com/u/8644946?v=4&s=117" width="117">](https://github.com/tvaliasek) |[<img alt="vially" src="https://avatars1.githubusercontent.com/u/433598?v=4&s=117" width="117">](https://github.com/vially) |[<img alt="nagyv" src="https://avatars2.githubusercontent.com/u/126671?v=4&s=117" width="117">](https://github.com/nagyv) |
+[<img alt="SpazzMarticus" src="https://avatars0.githubusercontent.com/u/5716457?v=4&s=117" width="117">](https://github.com/SpazzMarticus) |[<img alt="suchoproduction" src="https://avatars3.githubusercontent.com/u/6931349?v=4&s=117" width="117">](https://github.com/suchoproduction) |[<img alt="waptik" src="https://avatars1.githubusercontent.com/u/1687551?v=4&s=117" width="117">](https://github.com/waptik) |[<img alt="steverob" src="https://avatars2.githubusercontent.com/u/1220480?v=4&s=117" width="117">](https://github.com/steverob) |[<img alt="tajchumber" src="https://avatars3.githubusercontent.com/u/16062635?v=4&s=117" width="117">](https://github.com/tajchumber) |[<img alt="Tashows" src="https://avatars2.githubusercontent.com/u/16656928?v=4&s=117" width="117">](https://github.com/Tashows) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[Tashows](https://github.com/Tashows) |[twarlop](https://github.com/twarlop) |[tomsaleeba](https://github.com/tomsaleeba) |[tvaliasek](https://github.com/tvaliasek) |[vially](https://github.com/vially) |[nagyv](https://github.com/nagyv) |
+[SpazzMarticus](https://github.com/SpazzMarticus) |[suchoproduction](https://github.com/suchoproduction) |[waptik](https://github.com/waptik) |[steverob](https://github.com/steverob) |[tajchumber](https://github.com/tajchumber) |[Tashows](https://github.com/Tashows) |
 
-[<img alt="willycamargo" src="https://avatars1.githubusercontent.com/u/5041887?v=4&s=117" width="117">](https://github.com/willycamargo) |[<img alt="xhocquet" src="https://avatars2.githubusercontent.com/u/8116516?v=4&s=117" width="117">](https://github.com/xhocquet) |[<img alt="YehudaKremer" src="https://avatars3.githubusercontent.com/u/946652?v=4&s=117" width="117">](https://github.com/YehudaKremer) |[<img alt="zacharylawson" src="https://avatars3.githubusercontent.com/u/7375444?v=4&s=117" width="117">](https://github.com/zacharylawson) |[<img alt="agreene-coursera" src="https://avatars0.githubusercontent.com/u/30501355?v=4&s=117" width="117">](https://github.com/agreene-coursera) |[<img alt="alfatv" src="https://avatars2.githubusercontent.com/u/62238673?v=4&s=117" width="117">](https://github.com/alfatv) |
+[<img alt="twarlop" src="https://avatars3.githubusercontent.com/u/2856082?v=4&s=117" width="117">](https://github.com/twarlop) |[<img alt="tmaier" src="https://avatars0.githubusercontent.com/u/350038?v=4&s=117" width="117">](https://github.com/tmaier) |[<img alt="tomsaleeba" src="https://avatars0.githubusercontent.com/u/1773838?v=4&s=117" width="117">](https://github.com/tomsaleeba) |[<img alt="tvaliasek" src="https://avatars2.githubusercontent.com/u/8644946?v=4&s=117" width="117">](https://github.com/tvaliasek) |[<img alt="vially" src="https://avatars1.githubusercontent.com/u/433598?v=4&s=117" width="117">](https://github.com/vially) |[<img alt="nagyv" src="https://avatars2.githubusercontent.com/u/126671?v=4&s=117" width="117">](https://github.com/nagyv) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[willycamargo](https://github.com/willycamargo) |[xhocquet](https://github.com/xhocquet) |[YehudaKremer](https://github.com/YehudaKremer) |[zacharylawson](https://github.com/zacharylawson) |[agreene-coursera](https://github.com/agreene-coursera) |[alfatv](https://github.com/alfatv) |
+[twarlop](https://github.com/twarlop) |[tmaier](https://github.com/tmaier) |[tomsaleeba](https://github.com/tomsaleeba) |[tvaliasek](https://github.com/tvaliasek) |[vially](https://github.com/vially) |[nagyv](https://github.com/nagyv) |
 
-[<img alt="arggh" src="https://avatars3.githubusercontent.com/u/17210302?v=4&s=117" width="117">](https://github.com/arggh) |[<img alt="avalla" src="https://avatars1.githubusercontent.com/u/986614?v=4&s=117" width="117">](https://github.com/avalla) |[<img alt="c0b41" src="https://avatars1.githubusercontent.com/u/2834954?v=4&s=117" width="117">](https://github.com/c0b41) |[<img alt="canvasbh" src="https://avatars3.githubusercontent.com/u/44477734?v=4&s=117" width="117">](https://github.com/canvasbh) |[<img alt="craigcbrunner" src="https://avatars3.githubusercontent.com/u/2780521?v=4&s=117" width="117">](https://github.com/craigcbrunner) |[<img alt="darthf1" src="https://avatars2.githubusercontent.com/u/17253332?v=4&s=117" width="117">](https://github.com/darthf1) |
+[<img alt="willycamargo" src="https://avatars1.githubusercontent.com/u/5041887?v=4&s=117" width="117">](https://github.com/willycamargo) |[<img alt="xhocquet" src="https://avatars2.githubusercontent.com/u/8116516?v=4&s=117" width="117">](https://github.com/xhocquet) |[<img alt="YehudaKremer" src="https://avatars3.githubusercontent.com/u/946652?v=4&s=117" width="117">](https://github.com/YehudaKremer) |[<img alt="zachconner" src="https://avatars0.githubusercontent.com/u/11339326?v=4&s=117" width="117">](https://github.com/zachconner) |[<img alt="zacharylawson" src="https://avatars3.githubusercontent.com/u/7375444?v=4&s=117" width="117">](https://github.com/zacharylawson) |[<img alt="agreene-coursera" src="https://avatars0.githubusercontent.com/u/30501355?v=4&s=117" width="117">](https://github.com/agreene-coursera) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[arggh](https://github.com/arggh) |[avalla](https://github.com/avalla) |[c0b41](https://github.com/c0b41) |[canvasbh](https://github.com/canvasbh) |[craigcbrunner](https://github.com/craigcbrunner) |[darthf1](https://github.com/darthf1) |
+[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) |[agreene-coursera](https://github.com/agreene-coursera) |
 
-[<img alt="dkisic" src="https://avatars2.githubusercontent.com/u/32257921?v=4&s=117" width="117">](https://github.com/dkisic) |[<img alt="franckl" src="https://avatars0.githubusercontent.com/u/3875803?v=4&s=117" width="117">](https://github.com/franckl) |[<img alt="green-mike" src="https://avatars1.githubusercontent.com/u/5584225?v=4&s=117" width="117">](https://github.com/green-mike) |[<img alt="jarey" src="https://avatars1.githubusercontent.com/u/5025224?v=4&s=117" width="117">](https://github.com/jarey) |[<img alt="johnmanjiro13" src="https://avatars1.githubusercontent.com/u/28798279?v=4&s=117" width="117">](https://github.com/johnmanjiro13) |[<img alt="magumbo" src="https://avatars3.githubusercontent.com/u/6683765?v=4&s=117" width="117">](https://github.com/magumbo) |
+[<img alt="alfatv" src="https://avatars2.githubusercontent.com/u/62238673?v=4&s=117" width="117">](https://github.com/alfatv) |[<img alt="arggh" src="https://avatars3.githubusercontent.com/u/17210302?v=4&s=117" width="117">](https://github.com/arggh) |[<img alt="avalla" src="https://avatars1.githubusercontent.com/u/986614?v=4&s=117" width="117">](https://github.com/avalla) |[<img alt="c0b41" src="https://avatars1.githubusercontent.com/u/2834954?v=4&s=117" width="117">](https://github.com/c0b41) |[<img alt="canvasbh" src="https://avatars3.githubusercontent.com/u/44477734?v=4&s=117" width="117">](https://github.com/canvasbh) |[<img alt="craigcbrunner" src="https://avatars3.githubusercontent.com/u/2780521?v=4&s=117" width="117">](https://github.com/craigcbrunner) |
 :---: |:---: |:---: |:---: |:---: |:---: |
-[dkisic](https://github.com/dkisic) |[franckl](https://github.com/franckl) |[green-mike](https://github.com/green-mike) |[jarey](https://github.com/jarey) |[johnmanjiro13](https://github.com/johnmanjiro13) |[magumbo](https://github.com/magumbo) |
+[alfatv](https://github.com/alfatv) |[arggh](https://github.com/arggh) |[avalla](https://github.com/avalla) |[c0b41](https://github.com/c0b41) |[canvasbh](https://github.com/canvasbh) |[craigcbrunner](https://github.com/craigcbrunner) |
+
+[<img alt="darthf1" src="https://avatars2.githubusercontent.com/u/17253332?v=4&s=117" width="117">](https://github.com/darthf1) |[<img alt="dkisic" src="https://avatars2.githubusercontent.com/u/32257921?v=4&s=117" width="117">](https://github.com/dkisic) |[<img alt="franckl" src="https://avatars0.githubusercontent.com/u/3875803?v=4&s=117" width="117">](https://github.com/franckl) |[<img alt="green-mike" src="https://avatars1.githubusercontent.com/u/5584225?v=4&s=117" width="117">](https://github.com/green-mike) |[<img alt="johnmanjiro13" src="https://avatars1.githubusercontent.com/u/28798279?v=4&s=117" width="117">](https://github.com/johnmanjiro13) |[<img alt="magumbo" src="https://avatars3.githubusercontent.com/u/6683765?v=4&s=117" width="117">](https://github.com/magumbo) |
+:---: |:---: |:---: |:---: |:---: |:---: |
+[darthf1](https://github.com/darthf1) |[dkisic](https://github.com/dkisic) |[franckl](https://github.com/franckl) |[green-mike](https://github.com/green-mike) |[johnmanjiro13](https://github.com/johnmanjiro13) |[magumbo](https://github.com/magumbo) |
 
 [<img alt="ninesalt" src="https://avatars2.githubusercontent.com/u/7952255?v=4&s=117" width="117">](https://github.com/ninesalt) |[<img alt="luntta" src="https://avatars0.githubusercontent.com/u/14221637?v=4&s=117" width="117">](https://github.com/luntta) |[<img alt="rhymes" src="https://avatars3.githubusercontent.com/u/146201?v=4&s=117" width="117">](https://github.com/rhymes) |[<img alt="rlebosse" src="https://avatars0.githubusercontent.com/u/2794137?v=4&s=117" width="117">](https://github.com/rlebosse) |[<img alt="rtaieb" src="https://avatars2.githubusercontent.com/u/35224301?v=4&s=117" width="117">](https://github.com/rtaieb) |[<img alt="thanhthot" src="https://avatars0.githubusercontent.com/u/50633205?v=4&s=117" width="117">](https://github.com/thanhthot) |
 :---: |:---: |:---: |:---: |:---: |:---: |
 [ninesalt](https://github.com/ninesalt) |[luntta](https://github.com/luntta) |[rhymes](https://github.com/rhymes) |[rlebosse](https://github.com/rlebosse) |[rtaieb](https://github.com/rtaieb) |[thanhthot](https://github.com/thanhthot) |
 
-[<img alt="tinny77" src="https://avatars2.githubusercontent.com/u/1872936?v=4&s=117" width="117">](https://github.com/tinny77) |[<img alt="yoann-hellopret" src="https://avatars3.githubusercontent.com/u/46525558?v=4&s=117" width="117">](https://github.com/yoann-hellopret) |[<img alt="DennisKofflard" src="https://avatars2.githubusercontent.com/u/8669129?v=4&s=117" width="117">](https://github.com/DennisKofflard) |
+[<img alt="tinny77" src="https://avatars2.githubusercontent.com/u/1872936?v=4&s=117" width="117">](https://github.com/tinny77) |[<img alt="yoann-hellopret" src="https://avatars3.githubusercontent.com/u/46525558?v=4&s=117" width="117">](https://github.com/yoann-hellopret) |[<img alt="dominiceden" src="https://avatars2.githubusercontent.com/u/6367692?v=4&s=117" width="117">](https://github.com/dominiceden) |
 :---: |:---: |:---: |
-[tinny77](https://github.com/tinny77) |[yoann-hellopret](https://github.com/yoann-hellopret) |[DennisKofflard](https://github.com/DennisKofflard) |
+[tinny77](https://github.com/tinny77) |[yoann-hellopret](https://github.com/yoann-hellopret) |[dominiceden](https://github.com/dominiceden) |
 
 
 <!--/contributors-->

+ 2 - 0
bin/disc.js

@@ -1,6 +1,7 @@
 const fs = require('fs')
 const path = require('path')
 const { PassThrough } = require('stream')
+const replace = require('replacestream')
 const browserify = require('browserify')
 const babelify = require('babelify')
 const minify = require('minify-stream')
@@ -30,6 +31,7 @@ bundler.transform(minifyify, { global: true })
 bundler.bundle()
   .pipe(disc())
   .pipe(prepend('---\nlayout: false\n---\n'))
+  .pipe(replace('http://', 'https://', { limit: 1 }))
   .pipe(fs.createWriteStream(outputPath))
   .on('error', (err) => {
     throw err

+ 3 - 2
bin/make-new-versions-table

@@ -21,12 +21,13 @@ const commit = match[1]
 
 const tagStdout = execSync(`git tag --list --contains ${commit}`)
 const tags = tagStdout.toString()
-const rx = /([@/\w-]+)@(\d+\.\d+\.\d+)/g
+const rx = /([@\/\w-]+)@(\d+\.\d+\.\d+)(-alpha\.\d+|-beta\.\d+)?/g
 
 const versions = []
 let m
 while ((m = rx.exec(tags))) {
-  const [, pkg, version] = m
+  const [, pkg, versionPrefix, versionSuffix] = m
+  const version = `${versionPrefix}${versionSuffix || ''}`
   versions.push({ pkg, version })
 }
 

+ 2 - 2
examples/cdn-example/index.html

@@ -4,11 +4,11 @@
     <title></title>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1">
-    <link href="https://transloadit.edgly.net/releases/uppy/v1.14.1/uppy.min.css" rel="stylesheet">
+    <link href="https://transloadit.edgly.net/releases/uppy/v1.16.1/uppy.min.css" rel="stylesheet">
   </head>
   <body>
     <button id="uppyModalOpener">Open Modal</button>
-    <script src="https://transloadit.edgly.net/releases/uppy/v1.14.1/uppy.min.js"></script>
+    <script src="https://transloadit.edgly.net/releases/uppy/v1.16.1/uppy.min.js"></script>
     <script>
       const uppy = Uppy.Core({debug: true, autoProceed: false})
         .use(Uppy.Dashboard, { trigger: '#uppyModalOpener' })

+ 3 - 0
examples/dev/Dashboard.js

@@ -103,6 +103,9 @@ module.exports = () => {
     }
     console.log('successful files:', result.successful)
     console.log('failed files:', result.failed)
+    if (UPLOADER === 'transloadit') {
+      console.log('Transloadit result:', result.transloadit)
+    }
   })
 
   const modalTrigger = document.querySelector('#pick-files')

+ 1 - 1
examples/react-native-expo/package.json

@@ -5,7 +5,7 @@
     "@uppy/dashboard": "file:../../packages/@uppy/dashboard",
     "@uppy/instagram": "file:../../packages/@uppy/instagram",
     "@uppy/react-native": "file:../../packages/@uppy/react-native",
-    "@uppy/tus": "file:../../packages/@uppy/tus",
+    "@uppy/tus": "1.6.0",
     "@uppy/url": "file:../../packages/@uppy/url",
     "@uppy/xhr-upload": "file:../../packages/@uppy/xhr-upload",
     "babel-preset-expo": "^5.0.0",

+ 1 - 1
examples/transloadit-textarea/index.html

@@ -2,7 +2,7 @@
 <html>
   <head>
     <meta charset="utf-8">
-    <link rel="stylesheet" href="https://transloadit.edgly.net/releases/uppy/robodog/v1.6.6/robodog.css">
+    <link rel="stylesheet" href="https://transloadit.edgly.net/releases/uppy/robodog/v1.7.1/robodog.css">
     <style>
       body {
         font-family: Roboto, Open Sans;

+ 2 - 2
examples/uppy-with-companion/client/index.html

@@ -4,11 +4,11 @@
     <title></title>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1">
-    <link href="https://transloadit.edgly.net/releases/uppy/v1.14.1/uppy.min.css" rel="stylesheet">
+    <link href="https://transloadit.edgly.net/releases/uppy/v1.16.1/uppy.min.css" rel="stylesheet">
   </head>
   <body>
     <button id="uppyModalOpener">Open Modal</button>
-    <script src="https://transloadit.edgly.net/releases/uppy/v1.14.1/uppy.min.js"></script>
+    <script src="https://transloadit.edgly.net/releases/uppy/v1.16.1/uppy.min.js"></script>
     <script>
       const uppy = Uppy.Core({debug: true, autoProceed: false})
         .use(Uppy.Dashboard, { trigger: '#uppyModalOpener' })

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 267 - 280
package-lock.json


+ 6 - 3
package.json

@@ -88,7 +88,7 @@
     "@babel/preset-env": "7.9.5",
     "@babel/register": "7.9.0",
     "@jamen/lorem": "0.2.0",
-    "@lerna/run": "3.20.0",
+    "@lerna/run": "^3.21.0",
     "@octokit/rest": "16.43.1",
     "@size-limit/preset-big-lib": "2.2.4",
     "@transloadit/prettier-bytes": "0.0.7",
@@ -106,7 +106,6 @@
     "@types/node": "12.12.27",
     "@types/react": "16.9.19",
     "@types/request": "2.48.4",
-    "@types/tus-js-client": "1.8.0",
     "@types/uuid": "3.4.7",
     "@types/ws": "6.0.4",
     "@wdio/cli": "5.18.6",
@@ -116,6 +115,7 @@
     "adm-zip": "0.4.14",
     "aliasify": "2.1.0",
     "autoprefixer": "9.7.4",
+    "aws-sdk": "^2.701.0",
     "babel-eslint": "10.0.3",
     "babel-jest": "24.9.0",
     "babel-plugin-inline-package-json": "2.0.0",
@@ -126,6 +126,7 @@
     "browserify": "16.5.1",
     "chai": "4.2.0",
     "chalk": "2.4.2",
+    "concat-stream": "^2.0.0",
     "cssnano": "4.1.10",
     "disc": "1.3.3",
     "envify": "4.1.0",
@@ -157,7 +158,7 @@
     "jest": "24.9.0",
     "json3": "3.3.3",
     "last-commit-message": "1.0.0",
-    "lerna": "3.20.2",
+    "lerna": "^3.22.0",
     "lint-staged": "9.5.0",
     "mime-types": "2.1.26",
     "minify-stream": "1.2.1",
@@ -181,6 +182,8 @@
     "redux": "4.0.5",
     "remark-cli": "^8.0.0",
     "replace-x": "1.7.2",
+    "replacestream": "^4.0.3",
+    "resolve": "^1.17.0",
     "rimraf": "2.7.1",
     "stringify-object": "3.3.0",
     "supertest": "3.4.2",

+ 1 - 1
packages/@uppy/aws-s3-multipart/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/aws-s3-multipart",
   "description": "Upload to Amazon S3 with Uppy and S3's Multipart upload strategy",
-  "version": "1.6.4",
+  "version": "1.7.1",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "types/index.d.ts",

+ 13 - 2
packages/@uppy/aws-s3-multipart/src/MultipartUploader.js

@@ -2,6 +2,9 @@ const MB = 1024 * 1024
 
 const defaultOptions = {
   limit: 1,
+  getChunkSize (file) {
+    return Math.ceil(file.size / 10000)
+  },
   onStart () {},
   onProgress () {},
   onPartComplete () {},
@@ -22,11 +25,16 @@ class MultipartUploader {
       ...defaultOptions,
       ...options
     }
+    // Use default `getChunkSize` if it was null or something
+    if (!this.options.getChunkSize) {
+      this.options.getChunkSize = defaultOptions.getChunkSize
+    }
+
     this.file = file
 
     this.key = this.options.key || null
     this.uploadId = this.options.uploadId || null
-    this.parts = this.options.parts || []
+    this.parts = []
 
     // Do `this.createdPromise.then(OP)` to execute an operation `OP` _only_ if the
     // upload was created already. That also ensures that the sequencing is right
@@ -48,7 +56,10 @@ class MultipartUploader {
 
   _initChunks () {
     const chunks = []
-    const chunkSize = Math.max(Math.ceil(this.file.size / 10000), 5 * MB)
+    const desiredChunkSize = this.options.getChunkSize(this.file)
+    // at least 5MB per request, at most 10k requests
+    const minChunkSize = Math.max(5 * MB, Math.ceil(this.file.size / 10000))
+    const chunkSize = Math.max(desiredChunkSize, minChunkSize)
 
     for (let i = 0; i < this.file.size; i += chunkSize) {
       const end = Math.min(this.file.size, i + chunkSize)

+ 2 - 12
packages/@uppy/aws-s3-multipart/src/index.js

@@ -134,8 +134,7 @@ module.exports = class AwsS3Multipart extends Plugin {
           s3Multipart: {
             ...cFile.s3Multipart,
             key: data.key,
-            uploadId: data.uploadId,
-            parts: []
+            uploadId: data.uploadId
           }
         })
       }
@@ -175,20 +174,10 @@ module.exports = class AwsS3Multipart extends Plugin {
       }
 
       const onPartComplete = (part) => {
-        // Store completed parts in state.
         const cFile = this.uppy.getFile(file.id)
         if (!cFile) {
           return
         }
-        this.uppy.setFileState(file.id, {
-          s3Multipart: {
-            ...cFile.s3Multipart,
-            parts: [
-              ...cFile.s3Multipart.parts,
-              part
-            ]
-          }
-        })
 
         this.uppy.emit('s3-multipart:part-uploaded', cFile, part)
       }
@@ -200,6 +189,7 @@ module.exports = class AwsS3Multipart extends Plugin {
         prepareUploadPart: this.opts.prepareUploadPart.bind(this, file),
         completeMultipartUpload: this.opts.completeMultipartUpload.bind(this, file),
         abortMultipartUpload: this.opts.abortMultipartUpload.bind(this, file),
+        getChunkSize: this.opts.getChunkSize ? this.opts.getChunkSize.bind(this) : null,
 
         onStart,
         onProgress,

+ 1 - 0
packages/@uppy/aws-s3-multipart/types/index.d.ts

@@ -11,6 +11,7 @@ declare module AwsS3Multipart {
 
   interface AwsS3MultipartOptions extends Uppy.PluginOptions {
     companionUrl?: string
+    getChunkSize?: (file: Uppy.UppyFile) => number
     createMultipartUpload?: (
       file: Uppy.UppyFile
     ) => MaybePromise<{ uploadId: string; key: string }>

+ 9 - 1
packages/@uppy/aws-s3-multipart/types/index.test-d.ts

@@ -1,4 +1,4 @@
-import { expectType } from 'tsd'
+import { expectError, expectType } from 'tsd'
 import Uppy = require('@uppy/core')
 import AwsS3Multipart = require('../')
 
@@ -37,3 +37,11 @@ import AwsS3Multipart = require('../')
     }
   })
 }
+
+{
+  const uppy = Uppy<Uppy.StrictTypes>()
+  expectError(uppy.use(AwsS3Multipart, { getChunkSize: 100 }))
+  expectError(uppy.use(AwsS3Multipart, { getChunkSize: () => 'not a number' }))
+  uppy.use(AwsS3Multipart, { getChunkSize: () => 100 })
+  uppy.use(AwsS3Multipart, { getChunkSize: (file) => file.size })
+}

+ 1 - 1
packages/@uppy/aws-s3/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/aws-s3",
   "description": "Upload to Amazon S3 with Uppy",
-  "version": "1.6.4",
+  "version": "1.6.6",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "types/index.d.ts",

+ 5 - 3
packages/@uppy/aws-s3/src/index.js

@@ -160,7 +160,6 @@ module.exports = class AwsS3 extends Plugin {
       this.uppy.emit('upload-started', file)
     })
 
-    // Wrapping rate-limited opts.getUploadParameters in a Promise takes some boilerplate!
     const getUploadParameters = this.requests.wrapPromiseFunction((file) => {
       return this.opts.getUploadParameters(file)
     })
@@ -168,10 +167,11 @@ module.exports = class AwsS3 extends Plugin {
     const numberOfFiles = fileIDs.length
 
     return settle(fileIDs.map((id, index) => {
-      const file = this.uppy.getFile(id)
-      paramsPromises[id] = getUploadParameters(file)
+      paramsPromises[id] = getUploadParameters(this.uppy.getFile(id))
       return paramsPromises[id].then((params) => {
         delete paramsPromises[id]
+
+        const file = this.uppy.getFile(id)
         this.validateParameters(file, params)
 
         const {
@@ -199,6 +199,8 @@ module.exports = class AwsS3 extends Plugin {
         return this._uploader.uploadFile(file.id, index, numberOfFiles)
       }).catch((error) => {
         delete paramsPromises[id]
+
+        const file = this.uppy.getFile(id)
         this.uppy.emit('upload-error', file, error)
       })
     })).then((settled) => {

+ 1 - 1
packages/@uppy/aws-s3/types/index.d.ts

@@ -15,7 +15,7 @@ declare module AwsS3 {
     getUploadParameters?: (
       file: Uppy.UppyFile
     ) => MaybePromise<AwsS3UploadParameters>
-    metaFields?: string[],
+    metaFields?: string[]
     timeout?: number
     limit?: number
   }

+ 2 - 1
packages/@uppy/companion-client/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/companion-client",
   "description": "Client library for communication with Companion. Intended for use in Uppy plugins.",
-  "version": "1.4.4",
+  "version": "1.5.0",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "types/index.d.ts",
@@ -21,6 +21,7 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
+    "@uppy/utils": "file:../utils",
     "namespace-emitter": "^2.0.1"
   }
 }

+ 4 - 25
packages/@uppy/companion-client/src/RequestClient.js

@@ -1,7 +1,7 @@
 'use strict'
 
 const AuthError = require('./AuthError')
-const NetworkError = require('@uppy/utils/lib/NetworkError')
+const fetchWithNetworkError = require('@uppy/utils/lib/fetchWithNetworkError')
 
 // Remove the trailing slash so we can always safely append /xyz.
 function stripSlash (url) {
@@ -134,18 +134,11 @@ module.exports = class RequestClient {
   get (path, skipPostResponse) {
     return new Promise((resolve, reject) => {
       this.preflightAndHeaders(path).then((headers) => {
-        fetch(this._getUrl(path), {
+        fetchWithNetworkError(this._getUrl(path), {
           method: 'get',
           headers: headers,
           credentials: 'same-origin'
         })
-          .catch((err) => {
-            if (err.name === 'AbortError') {
-              throw err
-            } else {
-              throw new NetworkError(err)
-            }
-          })
           .then(this._getPostResponseFunc(skipPostResponse))
           .then((res) => this._json(res).then(resolve))
           .catch((err) => {
@@ -159,19 +152,12 @@ module.exports = class RequestClient {
   post (path, data, skipPostResponse) {
     return new Promise((resolve, reject) => {
       this.preflightAndHeaders(path).then((headers) => {
-        fetch(this._getUrl(path), {
+        fetchWithNetworkError(this._getUrl(path), {
           method: 'post',
           headers: headers,
           credentials: 'same-origin',
           body: JSON.stringify(data)
         })
-          .catch((err) => {
-            if (err.name === 'AbortError') {
-              throw err
-            } else {
-              throw new NetworkError(err)
-            }
-          })
           .then(this._getPostResponseFunc(skipPostResponse))
           .then((res) => this._json(res).then(resolve))
           .catch((err) => {
@@ -185,19 +171,12 @@ module.exports = class RequestClient {
   delete (path, data, skipPostResponse) {
     return new Promise((resolve, reject) => {
       this.preflightAndHeaders(path).then((headers) => {
-        fetch(`${this.hostname}/${path}`, {
+        fetchWithNetworkError(`${this.hostname}/${path}`, {
           method: 'delete',
           headers: headers,
           credentials: 'same-origin',
           body: data ? JSON.stringify(data) : null
         })
-          .catch((err) => {
-            if (err.name === 'AbortError') {
-              throw err
-            } else {
-              throw new NetworkError(err)
-            }
-          })
           .then(this._getPostResponseFunc(skipPostResponse))
           .then((res) => this._json(res).then(resolve))
           .catch((err) => {

+ 1 - 1
packages/@uppy/companion/Dockerfile

@@ -1,4 +1,4 @@
-FROM node:8.11.4-alpine
+FROM node:10.21.0-alpine
 
 COPY package.json /app/package.json
 

+ 9 - 9
packages/@uppy/companion/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@uppy/companion",
-  "version": "2.0.0-alpha.2",
+  "version": "2.0.0-alpha.6",
   "description": "OAuth helper and remote fetcher for Uppy's (https://uppy.io) extensible file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Dropbox and Google Drive, S3 and more :dog:",
   "main": "lib/companion.js",
   "types": "types/index.d.ts",
@@ -31,37 +31,37 @@
   "dependencies": {
     "@purest/providers": "1.0.1",
     "atob": "2.1.2",
-    "aws-sdk": "2.587.0",
+    "aws-sdk": "2.701.0",
     "body-parser": "1.19.0",
     "chalk": "2.4.2",
     "common-tags": "1.8.0",
     "connect-redis": "4.0.3",
-    "cookie-parser": "1.4.4",
+    "cookie-parser": "1.4.5",
     "escape-string-regexp": "2.0.0",
     "express": "4.17.1",
     "express-interceptor": "1.2.0",
     "express-prom-bundle": "3.3.0",
     "express-request-id": "1.4.1",
-    "express-session": "1.15.6",
+    "express-session": "1.17.1",
     "grant": "4.6.5",
-    "helmet": "3.21.2",
+    "helmet": "3.23.1",
     "ip-address": "6.2.0",
     "isobject": "3.0.1",
     "jsonwebtoken": "8.5.1",
     "lodash.merge": "4.6.2",
     "mime-types": "2.1.25",
-    "morgan": "1.9.1",
+    "morgan": "1.10.0",
     "ms": "2.1.2",
     "node-redis-pubsub": "4.0.0",
     "node-schedule": "1.3.2",
     "prom-client": "11.5.3",
     "purest": "3.1.0",
     "redis": "3.0.2",
-    "request": "2.88.0",
+    "request": "2.88.2",
     "semver": "6.3.0",
     "serialize-error": "^2.1.0",
-    "tus-js-client": "1.8.0",
-    "uuid": "3.3.3",
+    "tus-js-client": "2.1.1",
+    "uuid": "8.1.0",
     "validator": "^12.1.0",
     "ws": "6.2.1"
   },

+ 38 - 2
packages/@uppy/companion/src/companion.js

@@ -1,3 +1,4 @@
+const fs = require('fs')
 const express = require('express')
 // @ts-ignore
 const Grant = require('grant').express()
@@ -48,6 +49,8 @@ module.exports.errors = { ProviderApiError, ProviderAuthError }
  * @param {object} options
  */
 module.exports.app = (options = {}) => {
+  validateConfig(options)
+
   options = merge({}, defaultOptions, options)
   const providers = providerManager.getDefaultProviders(options)
   providerManager.addProviderOptions(options, grantConfig)
@@ -268,8 +271,6 @@ const getOptionsMiddleware = (options) => {
     }
 
     logger.info(`uppy client version ${req.companion.clientVersion}`, 'companion.client.version')
-    // @todo remove req.uppy in next major release
-    req.uppy = req.companion
     next()
   }
 
@@ -300,3 +301,38 @@ const maskLogger = (companionOptions) => {
 
   logger.setMaskables(secrets)
 }
+
+/**
+ * validates that the mandatory companion options are set.
+ * If it is invalid, it will console an error of unset options and exits the process.
+ * If it is valid, nothing happens.
+ *
+ * @param {object} companionOptions
+ */
+const validateConfig = (companionOptions) => {
+  const mandatoryOptions = ['secret', 'filePath', 'server.host']
+  /** @type {string[]} */
+  const unspecified = []
+
+  mandatoryOptions.forEach((i) => {
+    const value = i.split('.').reduce((prev, curr) => prev ? prev[curr] : undefined, companionOptions)
+
+    if (!value) unspecified.push(`"${i}"`)
+  })
+
+  // vaidate that all required config is specified
+  if (unspecified.length) {
+    const messagePrefix = 'Please specify the following options to use companion:'
+    throw new Error(`${messagePrefix}\n${unspecified.join(',\n')}`)
+  }
+
+  // validate that specified filePath is writeable/readable.
+  try {
+    // @ts-ignore
+    fs.accessSync(`${companionOptions.filePath}`, fs.R_OK | fs.W_OK)
+  } catch (err) {
+    throw new Error(
+      `No access to "${companionOptions.filePath}". Please ensure the directory exists and with read/write permissions.`
+    )
+  }
+}

+ 2 - 33
packages/@uppy/companion/src/server/Uploader.js

@@ -56,12 +56,6 @@ class Uploader {
     this.uploadFileName = this.options.metadata.name || path.basename(this.path)
     this.streamsEnded = false
     this.uploadStopped = false
-    this.duplexStream = null
-    // @TODO disabling parallel uploads and downloads for now
-    // if (this.options.protocol === PROTOCOLS.tus) {
-    //   this.duplexStream = new stream.PassThrough()
-    //     .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.duplex.error'))
-    // }
     this.writeStream = fs.createWriteStream(this.path, { mode: 0o666 }) // no executable files
       .on('error', (err) => logger.error(`${err}`, 'uploader.write.error', this.shortToken))
     /** @type {number} */
@@ -88,7 +82,7 @@ class Uploader {
         this._paused = true
         if (this.tus) {
           const shouldTerminate = !!this.tus.url
-          this.tus.abort(shouldTerminate)
+          this.tus.abort(shouldTerminate).catch(() => {})
         }
         this.cleanUp()
       })
@@ -302,31 +296,8 @@ class Uploader {
     })
   }
 
-  /**
-   * @param {Buffer | Buffer[]} chunk
-   * @param {function} cb
-   */
-  writeToStreams (chunk, cb) {
-    const done = []
-    const doneLength = this.duplexStream ? 2 : 1
-    const onDone = () => {
-      done.push(true)
-      if (done.length >= doneLength) {
-        cb()
-      }
-    }
-
-    this.writeStream.write(chunk, onDone)
-    if (this.duplexStream) {
-      this.duplexStream.write(chunk, onDone)
-    }
-  }
-
   endStreams () {
     this.writeStream.end()
-    if (this.duplexStream) {
-      this.duplexStream.end()
-    }
   }
 
   getResponse () {
@@ -443,15 +414,13 @@ class Uploader {
     const file = fs.createReadStream(this.path)
     const uploader = this
 
-    // @ts-ignore
     this.tus = new tus.Upload(file, {
       endpoint: this.options.endpoint,
       uploadUrl: this.options.uploadUrl,
-      // @ts-ignore
       uploadLengthDeferred: false,
-      resume: true,
       retryDelays: [0, 1000, 3000, 5000],
       uploadSize: this.bytesWritten,
+      addRequestId: true,
       metadata: Object.assign(
         {
           // file name and type as required by the tusd tus server

+ 2 - 2
packages/@uppy/companion/src/server/controllers/oauth-redirect.js

@@ -1,5 +1,5 @@
 const qs = require('querystring')
-const parseUrl = require('url').parse // eslint-disable-line node/no-deprecated-api
+const { URL } = require('url')
 const { hasMatch } = require('../helpers/utils')
 const oAuthState = require('../helpers/oauth-state')
 
@@ -15,7 +15,7 @@ module.exports = function oauthRedirect (req, res) {
     return res.status(400).send('Cannot find state in session')
   }
   const handler = oAuthState.getFromState(state, 'companionInstance', req.companion.options.secret)
-  const handlerHostName = parseUrl(handler).host
+  const handlerHostName = (new URL(handler)).host
 
   if (hasMatch(handlerHostName, req.companion.options.server.validHosts)) {
     const providerName = req.companion.provider.authProvider

+ 2 - 2
packages/@uppy/companion/src/server/controllers/send-token.js

@@ -3,7 +3,7 @@
  * sends auth token to uppy client
  */
 const tokenService = require('../helpers/jwt')
-const parseUrl = require('url').parse // eslint-disable-line node/no-deprecated-api
+const { URL } = require('url')
 const { hasMatch, sanitizeHtml } = require('../helpers/utils')
 const oAuthState = require('../helpers/oauth-state')
 const versionCmp = require('../helpers/version')
@@ -32,7 +32,7 @@ module.exports = function sendToken (req, res, next) {
     )
     const allowedClients = req.companion.options.clients
     // if no preset clients then allow any client
-    if (!allowedClients || hasMatch(origin, allowedClients) || hasMatch(parseUrl(origin).host, allowedClients)) {
+    if (!allowedClients || hasMatch(origin, allowedClients) || hasMatch((new URL(origin)).host, allowedClients)) {
       const allowsStringMessage = versionCmp.gte(clientVersion, '1.0.2')
       return res.send(allowsStringMessage ? htmlContent(uppyAuthToken, origin) : oldHtmlContent(uppyAuthToken, origin))
     }

+ 21 - 9
packages/@uppy/companion/src/server/controllers/url.js

@@ -1,9 +1,10 @@
 const router = require('express').Router
 const request = require('request')
+const { URL } = require('url')
 const Uploader = require('../Uploader')
 const validator = require('validator')
 const utils = require('../helpers/utils')
-const { getProtectedHttpAgent } = require('../helpers/request')
+const { getProtectedHttpAgent, getRedirectEvaluator } = require('../helpers/request')
 const logger = require('../logger')
 
 module.exports = () => {
@@ -30,7 +31,8 @@ const meta = (req, res) => {
     .then((meta) => res.json(meta))
     .catch((err) => {
       logger.error(err, 'controller.url.meta.error', req.id)
-      return res.status(500).json({ error: err })
+      // @todo send more meaningful error message and status code to client if possible
+      return res.status(err.status || 500).json({ message: 'failed to fetch URL metadata' })
     })
 }
 
@@ -49,7 +51,7 @@ const get = (req, res) => {
     return res.status(400).json({ error: 'Invalid request body' })
   }
 
-  utils.getURLMeta(req.body.url)
+  utils.getURLMeta(req.body.url, !debug)
     .then(({ size }) => {
       // @ts-ignore
       logger.debug('Instantiating uploader.', null, req.id)
@@ -71,8 +73,8 @@ const get = (req, res) => {
       res.status(response.status).json(response.body)
     }).catch((err) => {
       logger.error(err, 'controller.url.get.error', req.id)
-      // @todo this should send back error (not err)
-      res.json({ err })
+      // @todo send more meaningful error message and status code to client if possible
+      return res.status(err.status || 500).json({ message: 'failed to fetch URL metadata' })
     })
 }
 
@@ -113,12 +115,22 @@ const downloadURL = (url, onDataChunk, blockLocalIPs, traceId) => {
   const opts = {
     uri: url,
     method: 'GET',
-    followAllRedirects: true,
-    agentClass: getProtectedHttpAgent(utils.parseURL(url).protocol, blockLocalIPs)
+    followRedirect: getRedirectEvaluator(url, blockLocalIPs),
+    agentClass: getProtectedHttpAgent((new URL(url)).protocol, blockLocalIPs)
   }
 
   request(opts)
-    .on('data', (chunk) => onDataChunk(null, chunk))
+    .on('response', (resp) => {
+      if (resp.statusCode >= 300) {
+        const err = new Error(`URL server responded with status: ${resp.statusCode}`)
+        onDataChunk(err, null)
+      } else {
+        resp.on('data', (chunk) => onDataChunk(null, chunk))
+      }
+    })
     .on('end', () => onDataChunk(null, null))
-    .on('error', (err) => logger.error(err, 'controller.url.download.error', traceId))
+    .on('error', (err) => {
+      logger.error(err, 'controller.url.download.error', traceId)
+      onDataChunk(err, null)
+    })
 }

+ 20 - 0
packages/@uppy/companion/src/server/helpers/request.js

@@ -1,7 +1,9 @@
 const http = require('http')
 const https = require('https')
+const { URL } = require('url')
 const dns = require('dns')
 const ipAddress = require('ip-address')
+const logger = require('../logger')
 const FORBIDDEN_IP_ADDRESS = 'Forbidden IP address'
 
 function isIPAddress (address) {
@@ -75,6 +77,24 @@ function isPrivateIP (ipAddress) {
 
 module.exports.FORBIDDEN_IP_ADDRESS = FORBIDDEN_IP_ADDRESS
 
+module.exports.getRedirectEvaluator = (requestURL, blockPrivateIPs) => {
+  const protocol = (new URL(requestURL)).protocol
+  return (res) => {
+    if (!blockPrivateIPs) {
+      return true
+    }
+
+    const redirectURL = res.headers.location
+    const shouldRedirect = redirectURL ? new URL(redirectURL).protocol === protocol : false
+    if (!shouldRedirect) {
+      logger.info(
+        `blocking redirect from ${requestURL} to ${redirectURL}`, 'redirect.protection')
+    }
+
+    return shouldRedirect
+  }
+}
+
 /**
  * Returns http Agent that will prevent requests to private IPs (to preven SSRF)
  * @param {string} protocol http or http: or https: or https protocol needed for the request

+ 9 - 17
packages/@uppy/companion/src/server/helpers/utils.js

@@ -1,7 +1,7 @@
 const request = require('request')
-const urlParser = require('url')
+const { URL } = require('url')
 const crypto = require('crypto')
-const { getProtectedHttpAgent } = require('./request')
+const { getProtectedHttpAgent, getRedirectEvaluator } = require('./request')
 
 /**
  *
@@ -43,17 +43,6 @@ exports.sanitizeHtml = (text) => {
   return text ? text.replace(/<\/?[^>]+(>|$)/g, '') : text
 }
 
-/**
- * Node 6(and beyond) compatible url parser
- * @todo drop the use of url.parse when support for node 6 is dropped
- *
- * @param {string} url URL to be parsed
- */
-exports.parseURL = (url) => {
-  // eslint-disable-next-line
-  return urlParser.URL ? new urlParser.URL(url) : urlParser.parse(url)
-}
-
 /**
  * Gets the size and content type of a url's content
  *
@@ -66,12 +55,15 @@ exports.getURLMeta = (url, blockLocalIPs = false) => {
     const opts = {
       uri: url,
       method: 'HEAD',
-      followAllRedirects: true,
-      agentClass: getProtectedHttpAgent(exports.parseURL(url).protocol, blockLocalIPs)
+      followRedirect: getRedirectEvaluator(url, blockLocalIPs),
+      agentClass: getProtectedHttpAgent((new URL(url)).protocol, blockLocalIPs)
     }
 
-    request(opts, (err, response, body) => {
-      if (err) {
+    request(opts, (err, response) => {
+      if (err || response.statusCode >= 300) {
+        // @todo possibly set a status code in the error object to get a more helpful
+        // hint at what the cause of error is.
+        err = err || new Error(`URL server responded with status: ${response.statusCode}`)
         reject(err)
       } else {
         resolve({

+ 1 - 1
packages/@uppy/companion/src/server/provider/dropbox/index.js

@@ -215,7 +215,7 @@ class DropBox extends Provider {
   _error (err, resp) {
     if (resp) {
       const fallbackMessage = `request to ${this.authProvider} returned ${resp.statusCode}`
-      const errMsg = resp.body.error_summary ? resp.body.error_summary : fallbackMessage
+      const errMsg = (resp.body || {}).error_summary ? resp.body.error_summary : fallbackMessage
       return resp.statusCode === 401 ? new ProviderAuthError() : new ProviderApiError(errMsg, resp.statusCode)
     }
 

+ 8 - 2
packages/@uppy/companion/src/server/provider/index.js

@@ -46,7 +46,7 @@ module.exports.getProviderMiddleware = (providers) => {
  */
 module.exports.getDefaultProviders = (companionOptions) => {
   const { providerOptions } = companionOptions || { providerOptions: null }
-  // @todo: 2.0 we should rename drive to googledrive or google-drive or google
+  // @todo: we should rename drive to googledrive or google-drive or google
   const providers = { dropbox, drive, facebook, onedrive }
   // Instagram's Graph API key is just numbers, while the old API key is hex
   const usesGraphAPI = () => /^\d+$/.test(providerOptions.instagram.key)
@@ -70,7 +70,13 @@ module.exports.getDefaultProviders = (companionOptions) => {
 module.exports.addCustomProviders = (customProviders, providers, grantConfig) => {
   Object.keys(customProviders).forEach((providerName) => {
     providers[providerName] = customProviders[providerName].module
-    grantConfig[providerName] = customProviders[providerName].config
+    const providerConfig = Object.assign({}, customProviders[providerName].config)
+    // todo: consider setting these options from a universal point also used
+    // by official providers. It'll prevent these from getting left out if the
+    // requirement changes.
+    providerConfig.callback = `/${providerName}/callback`
+    providerConfig.transport = 'session'
+    grantConfig[providerName] = providerConfig
   })
 }
 

+ 1 - 15
packages/@uppy/companion/src/server/provider/instagram/graph/adapter.js

@@ -19,22 +19,8 @@ exports.getItemIcon = (item) => {
 exports.getItemSubList = (item) => {
   const newItems = []
   item.data.forEach((subItem) => {
-    // exclude videos because of bug https://developers.facebook.com/support/bugs/801145630390846/
-    // @todo remove this clause when bug is fixed
-    if (isVideo(subItem)) {
-      return
-    }
-
     if (subItem.media_type === MEDIA_TYPES.carousel) {
-      subItem.children.data.forEach((i) => {
-        // exclude videos because of bug https://developers.facebook.com/support/bugs/801145630390846/
-        // @todo remove this clause when bug is fixed
-        if (isVideo(i)) {
-          return
-        }
-
-        newItems.push(i)
-      })
+      subItem.children.data.forEach((i) => newItems.push(i))
     } else {
       newItems.push(subItem)
     }

+ 1 - 1
packages/@uppy/companion/src/server/provider/onedrive/index.js

@@ -137,7 +137,7 @@ class OneDrive extends Provider {
   _error (err, resp) {
     if (resp) {
       const fallbackMsg = `request to ${this.authProvider} returned ${resp.statusCode}`
-      const errMsg = resp.body.error ? resp.body.error.message : fallbackMsg
+      const errMsg = (resp.body || {}).error ? resp.body.error.message : fallbackMsg
       return resp.statusCode === 401 ? new ProviderAuthError() : new ProviderApiError(errMsg, resp.statusCode)
     }
 

+ 3 - 39
packages/@uppy/companion/src/standalone/helper.js

@@ -94,8 +94,9 @@ const getConfigFromEnv = () => {
  * @returns {string}
  */
 const getSecret = (baseEnvVar) => {
-  return `${baseEnvVar}_FILE` in process.env
-    ? fs.readFileSync(process.env[`${baseEnvVar}_FILE`]).toString()
+  const secretFile = process.env[`${baseEnvVar}_FILE`]
+  return secretFile
+    ? fs.readFileSync(secretFile).toString()
     : process.env[baseEnvVar]
 }
 
@@ -143,43 +144,6 @@ const getConfigPath = () => {
   return configPath
 }
 
-/**
- * validates that the mandatory companion options are set.
- * If it is invalid, it will console an error of unset options and exits the process.
- * If it is valid, nothing happens.
- *
- * @param {object} config
- */
-exports.validateConfig = (config) => {
-  const mandatoryOptions = ['secret', 'filePath', 'server.host']
-  /** @type {string[]} */
-  const unspecified = []
-
-  mandatoryOptions.forEach((i) => {
-    const value = i.split('.').reduce((prev, curr) => prev[curr], config)
-
-    if (!value) unspecified.push(`"${i}"`)
-  })
-
-  // vaidate that all required config is specified
-  if (unspecified.length) {
-    console.error('\x1b[31m', 'Please specify the following options',
-      'to run companion as Standalone:\n', unspecified.join(',\n'), '\x1b[0m')
-    process.exit(1)
-  }
-
-  // validate that specified filePath is writeable/readable.
-  // TODO: consider moving this into the companion module itself.
-  try {
-    // @ts-ignore
-    fs.accessSync(`${config.filePath}`, fs.R_OK | fs.W_OK)
-  } catch (err) {
-    console.error('\x1b[31m', `No access to "${config.filePath}".`,
-      'Please ensure the directory exists and with read/write permissions.', '\x1b[0m')
-    process.exit(1)
-  }
-}
-
 /**
  *
  * @param {string} url

+ 14 - 6
packages/@uppy/companion/src/standalone/index.js

@@ -6,7 +6,7 @@ const morgan = require('morgan')
 const bodyParser = require('body-parser')
 const redis = require('../server/redis')
 const logger = require('../server/logger')
-const { parseURL } = require('../server/helpers/utils')
+const { URL } = require('url')
 const merge = require('lodash.merge')
 // @ts-ignore
 const promBundle = require('express-prom-bundle')
@@ -80,7 +80,7 @@ morgan.token('url', (req, res) => {
 morgan.token('referrer', (req, res) => {
   const ref = req.headers.referer || req.headers.referrer
   if (typeof ref === 'string') {
-    const parsed = parseURL(ref)
+    const parsed = new URL(ref)
     const rawQuery = qs.parse(parsed.search.replace('?', ''))
     const { query, censored } = censorQuery(rawQuery)
     return censored ? `${parsed.href.split('?')[0]}?${qs.stringify(query)}` : parsed.href
@@ -162,12 +162,20 @@ app.get('/', (req, res) => {
   res.send(helper.buildHelpfulStartupMessage(companionOptions))
 })
 
-// initialize companion
-helper.validateConfig(companionOptions)
+let companionApp
+try {
+  // initialize companion
+  companionApp = companion.app(companionOptions)
+} catch (error) {
+  console.error('\x1b[31m', error.message, '\x1b[0m')
+  process.exit(1)
+}
+
+// add companion to server middlewear
 if (process.env.COMPANION_PATH) {
-  app.use(process.env.COMPANION_PATH, companion.app(companionOptions))
+  app.use(process.env.COMPANION_PATH, companionApp)
 } else {
-  app.use(companion.app(companionOptions))
+  app.use(companionApp)
 }
 
 // WARNING: This route is added in order to validate your app with OneDrive.

+ 31 - 1
packages/@uppy/companion/test/__tests__/http-agent.js

@@ -1,10 +1,40 @@
 /* global test:false, expect:false, describe:false, */
 
-const { getProtectedHttpAgent, FORBIDDEN_IP_ADDRESS } = require('../../src/server/helpers/request')
+const { getProtectedHttpAgent, getRedirectEvaluator, FORBIDDEN_IP_ADDRESS } = require('../../src/server/helpers/request')
 const request = require('request')
 const http = require('http')
 const https = require('https')
 
+describe('test getRedirectEvaluator', () => {
+  const httpURL = 'http://uppy.io'
+  const httpsURL = 'https://uppy.io'
+  const httpRedirectResp = {
+    headers: {
+      location: 'http://transloadit.com'
+    }
+  }
+
+  const httpsRedirectResp = {
+    headers: {
+      location: 'https://transloadit.com'
+    }
+  }
+
+  test('when original URL has "https:" as protocol', (done) => {
+    const shouldRedirectHttps = getRedirectEvaluator(httpsURL, true)
+    expect(shouldRedirectHttps(httpsRedirectResp)).toEqual(true)
+    expect(shouldRedirectHttps(httpRedirectResp)).toEqual(false)
+    done()
+  })
+
+  test('when original URL has "http:" as protocol', (done) => {
+    const shouldRedirectHttp = getRedirectEvaluator(httpURL, true)
+    expect(shouldRedirectHttp(httpRedirectResp)).toEqual(true)
+    expect(shouldRedirectHttp(httpsRedirectResp)).toEqual(false)
+    done()
+  })
+})
+
 describe('test getProtectedHttpAgent', () => {
   test('setting "https:" as protocol', (done) => {
     const Agent = getProtectedHttpAgent('https:')

+ 1 - 1
packages/@uppy/core/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/core",
   "description": "Core module for the extensible JavaScript file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Instagram, Dropbox, Google Drive, S3 and more :dog:",
-  "version": "1.10.4",
+  "version": "1.11.0",
   "license": "MIT",
   "main": "lib/index.js",
   "style": "dist/style.min.css",

+ 14 - 3
packages/@uppy/core/src/Plugin.js

@@ -158,9 +158,20 @@ module.exports = class Plugin {
     }
 
     this.uppy.log(`Not installing ${callerPluginName}`)
-    throw new Error(`Invalid target option given to ${callerPluginName}. Please make sure that the element
-      exists on the page, or that the plugin you are targeting has been installed. Check that the <script> tag initializing Uppy
-      comes at the bottom of the page, before the closing </body> tag (see https://github.com/transloadit/uppy/issues/1042).`)
+
+    let message = `Invalid target option given to ${callerPluginName}.`
+    if (typeof target === 'function') {
+      message += ' The given target is not a Plugin class. ' +
+        'Please check that you\'re not specifying a React Component instead of a plugin. ' +
+        'If you are using @uppy/* packages directly, make sure you have only 1 version of @uppy/core installed: ' +
+        'run `npm ls @uppy/core` on the command line and verify that all the versions match and are deduped correctly.'
+    } else {
+      message += 'If you meant to target an HTML element, please make sure that the element exists. ' +
+        'Check that the <script> tag initializing Uppy is right before the closing </body> tag at the end of the page. ' +
+        '(see https://github.com/transloadit/uppy/issues/1042)\n\n' +
+        'If you meant to target a plugin, please confirm that your `import` statements or `require` calls are correct.'
+    }
+    throw new Error(message)
   }
 
   render (state) {

+ 12 - 11
packages/@uppy/core/src/_common.scss

@@ -1,5 +1,5 @@
 /**
-* General Uppy styles that apply to everything inside the .Uppy container
+* General Uppy styles that apply to everything inside the .uppy-Root container
 */
 
 .uppy-Root {
@@ -21,16 +21,6 @@
   display: none;
 }
 
-// https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4
-
-.UppyIcon {
-  max-width: 100%;
-  max-height: 100%;
-  fill: currentColor; /* no !important */
-  display: inline-block;
-  overflow: hidden;
-}
-
 // Utilities
 
 .uppy-u-reset {
@@ -122,6 +112,17 @@
   }
 }
 
+// Icon
+
+// https://blog.prototypr.io/align-svg-icons-to-text-and-say-goodbye-to-font-icons-d44b3d7b26b4
+.uppy-c-icon {
+  max-width: 100%;
+  max-height: 100%;
+  fill: currentColor;
+  display: inline-block;
+  overflow: hidden;
+}
+
 // Buttons
 
 .uppy-c-btn {

+ 5 - 5
packages/@uppy/core/src/index.js

@@ -711,7 +711,7 @@ class Uppy {
     }
   }
 
-  removeFiles (fileIDs) {
+  removeFiles (fileIDs, reason) {
     const { files, currentUploads } = this.getState()
     const updatedFiles = { ...files }
     const updatedUploads = { ...currentUploads }
@@ -764,7 +764,7 @@ class Uppy {
 
     const removedFileIDs = Object.keys(removedFiles)
     removedFileIDs.forEach((fileID) => {
-      this.emit('file-removed', removedFiles[fileID])
+      this.emit('file-removed', removedFiles[fileID], reason)
     })
 
     if (removedFileIDs.length > 5) {
@@ -774,8 +774,8 @@ class Uppy {
     }
   }
 
-  removeFile (fileID) {
-    this.removeFiles([fileID])
+  removeFile (fileID, reason = null) {
+    this.removeFiles([fileID], reason)
   }
 
   pauseResume (fileID) {
@@ -866,7 +866,7 @@ class Uppy {
 
     const fileIDs = Object.keys(files)
     if (fileIDs.length) {
-      this.removeFiles(fileIDs)
+      this.removeFiles(fileIDs, 'cancel-all')
     }
 
     this.setState({

+ 1 - 1
packages/@uppy/dashboard/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/dashboard",
   "description": "Universal UI plugin for Uppy.",
-  "version": "1.8.5",
+  "version": "1.10.1",
   "license": "MIT",
   "main": "lib/index.js",
   "style": "dist/style.min.css",

+ 17 - 13
packages/@uppy/dashboard/src/components/AddFiles.js

@@ -1,4 +1,3 @@
-const { iconMyDevice } = require('./icons')
 const { h, Component } = require('preact')
 
 class AddFiles extends Component {
@@ -21,7 +20,7 @@ class AddFiles extends Component {
   renderPoweredByUppy () {
     const uppyBranding = (
       <span>
-        <svg aria-hidden="true" focusable="false" class="UppyIcon uppy-Dashboard-poweredByIcon" width="11" height="11" viewBox="0 0 11 11">
+        <svg aria-hidden="true" focusable="false" class="uppy-c-icon uppy-Dashboard-poweredByIcon" width="11" height="11" viewBox="0 0 11 11">
           <path d="M7.365 10.5l-.01-4.045h2.612L5.5.806l-4.467 5.65h2.604l.01 4.044h3.718z" fill-rule="evenodd" />
         </svg>
         <span class="uppy-Dashboard-poweredByUppy">Uppy</span>
@@ -47,14 +46,6 @@ class AddFiles extends Component {
     )
   }
 
-  renderCloudIcon = () => {
-    return (
-      <svg class="uppy-Dashboard-dropFilesIcon" aria-hidden="true" width="64" height="45" viewBox="0 0 64 45" xmlns="http://www.w3.org/2000/svg">
-        <path d="M38 44.932V31h8L33 15 20 31h8v13.932H13.538C6.075 44.932 0 38.774 0 31.202c0-6.1 4.06-11.512 9.873-13.162l.005-.017c.345-5.8 5.248-10.534 10.922-10.534.502 0 1.164.017 1.868.16C25.9 2.85 31.225 0 36.923 0c9.5 0 17.23 7.838 17.23 17.473l-.011.565.012.002C60.039 19.685 64 24.975 64 31.203c0 7.57-6.075 13.729-13.538 13.729H38z" fill="#E2E2E2" fill-rule="nonzero" />
-      </svg>
-    )
-  }
-
   renderHiddenFileInput = () => {
     return (
       <input
@@ -74,7 +65,11 @@ class AddFiles extends Component {
 
   renderMyDeviceAcquirer = () => {
     return (
-      <div class="uppy-DashboardTab" role="presentation">
+      <div
+        class="uppy-DashboardTab"
+        role="presentation"
+        data-uppy-acquirer-id="MyDevice"
+      >
         <button
           type="button"
           class="uppy-DashboardTab-btn"
@@ -83,7 +78,12 @@ class AddFiles extends Component {
           data-uppy-super-focusable
           onclick={this.triggerFileInputClick}
         >
-          {iconMyDevice()}
+          <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32">
+            <g fill="none" fill-rule="evenodd">
+              <rect width="32" height="32" rx="16" fill="#2275D7" />
+              <path d="M21.973 21.152H9.863l-1.108-5.087h14.464l-1.246 5.087zM9.935 11.37h3.958l.886 1.444a.673.673 0 0 0 .585.316h6.506v1.37H9.935v-3.13zm14.898 3.44a.793.793 0 0 0-.616-.31h-.978v-2.126c0-.379-.275-.613-.653-.613H15.75l-.886-1.445a.673.673 0 0 0-.585-.316H9.232c-.378 0-.667.209-.667.587V14.5h-.782a.793.793 0 0 0-.61.303.795.795 0 0 0-.155.663l1.45 6.633c.078.36.396.618.764.618h13.354c.36 0 .674-.246.76-.595l1.631-6.636a.795.795 0 0 0-.144-.675z" fill="#FFF" />
+            </g>
+          </svg>
           <div class="uppy-DashboardTab-name">{this.props.i18n('myDevice')}</div>
         </button>
       </div>
@@ -115,7 +115,11 @@ class AddFiles extends Component {
 
   renderAcquirer = (acquirer) => {
     return (
-      <div class="uppy-DashboardTab" role="presentation">
+      <div
+        class="uppy-DashboardTab"
+        role="presentation"
+        data-uppy-acquirer-id={acquirer.id}
+      >
         <button
           type="button"
           class="uppy-DashboardTab-btn"

+ 1 - 1
packages/@uppy/dashboard/src/components/FileCard/index.scss

@@ -56,7 +56,7 @@
       }
     }
 
-      .uppy-Dashboard-FileCard-preview img.uppy-DashboardItem-previewImg {
+      .uppy-Dashboard-FileCard-preview img.uppy-Dashboard-Item-previewImg {
         max-width: 90%;
         max-height: 90%;
         object-fit: cover;

+ 30 - 13
packages/@uppy/dashboard/src/components/FileItem/Buttons/index.js

@@ -1,8 +1,6 @@
 const { h } = require('preact')
 const copyToClipboard = require('../../../utils/copyToClipboard')
 
-const { iconPencil, iconCross, iconCopyLink } = require('../../icons')
-
 function EditButton ({
   file,
   uploadInProgressOrComplete,
@@ -15,13 +13,19 @@ function EditButton ({
       metaFields.length > 0) {
     return (
       <button
-        class="uppy-u-reset uppy-DashboardItem-action uppy-DashboardItem-action--edit"
+        class="uppy-u-reset uppy-Dashboard-Item-action uppy-Dashboard-Item-action--edit"
         type="button"
         aria-label={i18n('editFile') + ' ' + file.meta.name}
         title={i18n('editFile')}
         onclick={() => onClick()}
       >
-        {iconPencil()}
+        <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="14" height="14" viewBox="0 0 14 14">
+          <g fill-rule="evenodd">
+            <path d="M1.5 10.793h2.793A1 1 0 0 0 5 10.5L11.5 4a1 1 0 0 0 0-1.414L9.707.793a1 1 0 0 0-1.414 0l-6.5 6.5A1 1 0 0 0 1.5 8v2.793zm1-1V8L9 1.5l1.793 1.793-6.5 6.5H2.5z" fill-rule="nonzero" />
+            <rect x="1" y="12.293" width="11" height="1" rx=".5" />
+            <path fill-rule="nonzero" d="M6.793 2.5L9.5 5.207l.707-.707L7.5 1.793z" />
+          </g>
+        </svg>
       </button>
     )
   }
@@ -31,18 +35,21 @@ function EditButton ({
 function RemoveButton ({ i18n, onClick }) {
   return (
     <button
-      class="uppy-u-reset uppy-DashboardItem-action uppy-DashboardItem-action--remove"
+      class="uppy-u-reset uppy-Dashboard-Item-action uppy-Dashboard-Item-action--remove"
       type="button"
       aria-label={i18n('removeFile')}
       title={i18n('removeFile')}
       onclick={() => onClick()}
     >
-      {iconCross()}
+      <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="18" height="18" viewBox="0 0 18 18">
+        <path d="M9 0C4.034 0 0 4.034 0 9s4.034 9 9 9 9-4.034 9-9-4.034-9-9-9z" />
+        <path fill="#FFF" d="M13 12.222l-.778.778L9 9.778 5.778 13 5 12.222 8.222 9 5 5.778 5.778 5 9 8.222 12.222 5l.778.778L9.778 9z" />
+      </svg>
     </button>
   )
 }
 
-const copyLinkToClipboard = (event, props) =>
+const copyLinkToClipboard = (event, props) => {
   copyToClipboard(props.file.uploadURL, props.i18n('copyLinkToClipboardFallback'))
     .then(() => {
       props.log('Link copied to clipboard.')
@@ -51,17 +58,20 @@ const copyLinkToClipboard = (event, props) =>
     .catch(props.log)
     // avoid losing focus
     .then(() => event.target.focus({ preventScroll: true }))
+}
 
 function CopyLinkButton (props) {
   return (
     <button
-      class="uppy-u-reset uppy-DashboardItem-action uppy-DashboardItem-action--copyLink"
+      class="uppy-u-reset uppy-Dashboard-Item-action uppy-Dashboard-Item-action--copyLink"
       type="button"
       aria-label={props.i18n('copyLink')}
       title={props.i18n('copyLink')}
       onclick={(event) => copyLinkToClipboard(event, props)}
     >
-      {iconCopyLink()}
+      <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="14" height="14" viewBox="0 0 14 12">
+        <path d="M7.94 7.703a2.613 2.613 0 0 1-.626 2.681l-.852.851a2.597 2.597 0 0 1-1.849.766A2.616 2.616 0 0 1 2.764 7.54l.852-.852a2.596 2.596 0 0 1 2.69-.625L5.267 7.099a1.44 1.44 0 0 0-.833.407l-.852.851a1.458 1.458 0 0 0 1.03 2.486c.39 0 .755-.152 1.03-.426l.852-.852c.231-.231.363-.522.406-.824l1.04-1.038zm4.295-5.937A2.596 2.596 0 0 0 10.387 1c-.698 0-1.355.272-1.849.766l-.852.851a2.614 2.614 0 0 0-.624 2.688l1.036-1.036c.041-.304.173-.6.407-.833l.852-.852c.275-.275.64-.426 1.03-.426a1.458 1.458 0 0 1 1.03 2.486l-.852.851a1.442 1.442 0 0 1-.824.406l-1.04 1.04a2.596 2.596 0 0 0 2.683-.628l.851-.85a2.616 2.616 0 0 0 0-3.697zm-6.88 6.883a.577.577 0 0 0 .82 0l3.474-3.474a.579.579 0 1 0-.819-.82L5.355 7.83a.579.579 0 0 0 0 .819z" />
+      </svg>
     </button>
   )
 }
@@ -75,11 +85,13 @@ module.exports = function Buttons (props) {
     showRemoveButton,
     i18n,
     removeFile,
-    toggleFileCard
+    toggleFileCard,
+    log,
+    info
   } = props
 
   return (
-    <div className="uppy-DashboardItem-actionWrapper">
+    <div className="uppy-Dashboard-Item-actionWrapper">
       <EditButton
         i18n={i18n}
         file={file}
@@ -88,14 +100,19 @@ module.exports = function Buttons (props) {
         onClick={() => toggleFileCard(file.id)}
       />
       {showLinkToFileUploadResult && file.uploadURL ? (
-        <CopyLinkButton i18n={i18n} />
+        <CopyLinkButton
+          file={file}
+          i18n={i18n}
+          info={info}
+          log={log}
+        />
       ) : null}
       {showRemoveButton ? (
         <RemoveButton
           i18n={i18n}
           info={props.info}
           log={props.log}
-          onClick={() => removeFile(file.id)}
+          onClick={() => removeFile(file.id, 'removed-by-user')}
         />
       ) : null}
     </div>

+ 7 - 7
packages/@uppy/dashboard/src/components/FileItem/Buttons/index.scss

@@ -1,5 +1,5 @@
 // On both mobile and .md+ screens
-.uppy-DashboardItem-action {
+.uppy-Dashboard-Item-action {
   @include blue-border-focus;
   cursor: pointer;
   color: $gray-500;
@@ -19,7 +19,7 @@
   }  
 }
 
-.uppy-DashboardItem-action--remove {
+.uppy-Dashboard-Item-action--remove {
   color: $gray-900;
   opacity: 0.95;
 
@@ -40,12 +40,12 @@
 // Only for mobile screens
 .uppy-Dashboard:not(.uppy-size--md) {
   // Vertically center Edit&Remove buttons on mobile
-  .uppy-DashboardItem-actionWrapper {
+  .uppy-Dashboard-Item-actionWrapper {
     display: flex;
     align-items: center;
   }
   // Same inline design for Edit, Remove, and CopyLink buttons
-  .uppy-DashboardItem-action {
+  .uppy-Dashboard-Item-action {
     width: 22px;
     height: 22px;
     padding: 3px;
@@ -59,8 +59,8 @@
 // Only for screens bigger than .md
 .uppy-size--md {
   // Edit and CopyLink buttons are inline
-  .uppy-DashboardItem-action--copyLink,
-  .uppy-DashboardItem-action--edit {
+  .uppy-Dashboard-Item-action--copyLink,
+  .uppy-Dashboard-Item-action--edit {
     width: 16px;
     height: 16px;
     padding: 0;
@@ -69,7 +69,7 @@
     }
   }
   // Remove button is in the top right corner
-  .uppy-DashboardItem-action--remove {
+  .uppy-Dashboard-Item-action--remove {
     z-index: $zIndex-3;
     position: absolute;
     top: -8px;

+ 29 - 5
packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.js

@@ -10,7 +10,7 @@ const renderAcquirerIcon = (acquirer, props) =>
 const renderFileSource = (props) => (
   props.file.source &&
   props.file.source !== props.id &&
-    <div class="uppy-DashboardItem-sourceIcon">
+    <div class="uppy-Dashboard-Item-sourceIcon">
       {props.acquirers.map(acquirer => {
         if (acquirer.id === props.file.source) {
           return renderAcquirerIcon(acquirer, props)
@@ -34,7 +34,7 @@ const renderFileName = (props) => {
   }
 
   return (
-    <div class="uppy-DashboardItem-name" title={props.file.meta.name}>
+    <div class="uppy-Dashboard-Item-name" title={props.file.meta.name}>
       {truncateString(props.file.meta.name, maxNameLength)}
     </div>
   )
@@ -42,18 +42,42 @@ const renderFileName = (props) => {
 
 const renderFileSize = (props) => (
   props.file.data.size &&
-    <div class="uppy-DashboardItem-statusSize">
+    <div class="uppy-Dashboard-Item-statusSize">
       {prettierBytes(props.file.data.size)}
     </div>
 )
 
+const ErrorButton = ({ file, onClick }) => {
+  if (file.error) {
+    return (
+      <span
+        class="uppy-Dashboard-Item-errorDetails"
+        aria-label={file.error}
+        data-microtip-position="bottom"
+        data-microtip-size="medium"
+        role="tooltip"
+        onclick={onClick}
+      >
+        ?
+      </span>
+    )
+  }
+  return null
+}
+
 module.exports = function FileInfo (props) {
   return (
-    <div class="uppy-DashboardItem-fileInfo" data-uppy-file-source={props.file.source}>
+    <div class="uppy-Dashboard-Item-fileInfo" data-uppy-file-source={props.file.source}>
       {renderFileName(props)}
-      <div class="uppy-DashboardItem-status">
+      <div class="uppy-Dashboard-Item-status">
         {renderFileSize(props)}
         {renderFileSource(props)}
+        <ErrorButton
+          file={props.file}
+          onClick={() => {
+            alert(props.file.error)
+          }}
+        />
       </div>
     </div>
   )

+ 7 - 7
packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.scss

@@ -1,7 +1,7 @@
-.uppy-DashboardItem-fileInfo {
+.uppy-Dashboard-Item-fileInfo {
   padding-right: 5px;
 }
-  .uppy-DashboardItem-name {
+  .uppy-Dashboard-Item-name {
     font-size: 12px;
     line-height: 1.3;
     font-weight: 500;
@@ -17,7 +17,7 @@
     }
   }
 
-  .uppy-DashboardItem-status {
+  .uppy-Dashboard-Item-status {
     font-size: 11px;
     line-height: 1.3;
     font-weight: normal;
@@ -27,12 +27,12 @@
       color: $gray-400;
     }
   }
-    .uppy-DashboardItem-statusSize {
+    .uppy-Dashboard-Item-statusSize {
       display: inline-block;
       vertical-align: bottom;
       text-transform: uppercase;
     }
-    .uppy-DashboardItem-sourceIcon {
+    .uppy-Dashboard-Item-sourceIcon {
       // display: inline-block;
       display: none;
       vertical-align: bottom;
@@ -53,5 +53,5 @@
         height: 12px;
       }
     }
-  // ...uppy-DashboardItem-status|
-// ...uppy-DashboardItem-fileInfo|
+  // ...uppy-Dashboard-Item-status|
+// ...uppy-Dashboard-Item-fileInfo|

+ 2 - 2
packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.js

@@ -5,14 +5,14 @@ const getFileTypeIcon = require('../../../utils/getFileTypeIcon')
 module.exports = function FilePreviewAndLink (props) {
   return (
     <div
-      class="uppy-DashboardItem-previewInnerWrap"
+      class="uppy-Dashboard-Item-previewInnerWrap"
       style={{ backgroundColor: getFileTypeIcon(props.file.type).color }}
     >
       {
         props.showLinkToFileUploadResult &&
         props.file.uploadURL &&
           <a
-            class="uppy-DashboardItem-previewLink"
+            class="uppy-Dashboard-Item-previewLink"
             href={props.file.uploadURL}
             rel="noreferrer noopener"
             target="_blank"

+ 5 - 5
packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.scss

@@ -1,4 +1,4 @@
-.uppy-DashboardItem-previewInnerWrap {
+.uppy-Dashboard-Item-previewInnerWrap {
   width: 100%;
   height: 100%;
   overflow: hidden;
@@ -17,7 +17,7 @@
     box-shadow: 0 1px 2px rgba($black, 0.15);
   }
 }
-  .uppy-DashboardItem-previewInnerWrap:after {
+  .uppy-Dashboard-Item-previewInnerWrap:after {
     content: '';
     position: absolute;
     left: 0; right: 0;
@@ -27,7 +27,7 @@
     z-index: $zIndex-2;
   }
 
-  .uppy-DashboardItem-previewLink {
+  .uppy-Dashboard-Item-previewLink {
     position: absolute;
     left: 0; right: 0;
     top: 0; bottom: 0;
@@ -42,7 +42,7 @@
     }
   }
 
-  .uppy-DashboardItem-preview img.uppy-DashboardItem-previewImg {
+  .uppy-Dashboard-Item-preview img.uppy-Dashboard-Item-previewImg {
     width: 100%;
     height: 100%;
     object-fit: cover;
@@ -52,4 +52,4 @@
     // We need a repeated border-radius because of the transform.
     border-radius: 3px;
   }
-// ...uppy-DashboardItem-previewInnerWrap|
+// ...uppy-Dashboard-Item-previewInnerWrap|

+ 0 - 37
packages/@uppy/dashboard/src/components/FileItem/FileProgress/PauseResumeCancelIcon.js

@@ -1,37 +0,0 @@
-const { h } = require('preact')
-
-// http://codepen.io/Harkko/pen/rVxvNM
-// https://css-tricks.com/svg-line-animation-works/
-// https://gist.github.com/eswak/ad4ea57bcd5ff7aa5d42
-
-// circle length equals 2 * PI * R
-const circleLength = 2 * Math.PI * 15
-
-// stroke-dashoffset is a percentage of the progress from circleLength,
-// substracted from circleLength, because its an offset
-module.exports = function PauseResumeCancelIcon (props) {
-  return (
-    <svg aria-hidden="true" focusable="false" width="70" height="70" viewBox="0 0 36 36" class="UppyIcon UppyIcon-progressCircle">
-      <g class="progress-group">
-        <circle class="bg" r="15" cx="18" cy="18" stroke-width="2" fill="none" />
-        <circle
-          class="progress" r="15" cx="18" cy="18" transform="rotate(-90, 18, 18)" stroke-width="2" fill="none"
-          stroke-dasharray={circleLength}
-          stroke-dashoffset={circleLength - (circleLength / 100 * props.progress)}
-        />
-      </g>
-      {
-        !props.hidePauseResumeCancelButtons &&
-          <g>
-            <polygon class="play" transform="translate(3, 3)" points="12 20 12 10 20 15" />
-            <g class="pause" transform="translate(14.5, 13)">
-              <rect x="0" y="0" width="2" height="10" rx="0" />
-              <rect x="5" y="0" width="2" height="10" rx="0" />
-            </g>
-            <polygon class="cancel" transform="translate(2, 2)" points="19.8856516 11.0625 16 14.9481516 12.1019737 11.0625 11.0625 12.1143484 14.9481516 16 11.0625 19.8980263 12.1019737 20.9375 16 17.0518484 19.8856516 20.9375 20.9375 19.8980263 17.0518484 16 20.9375 12" />
-          </g>
-      }
-      <polygon class="check" transform="translate(2, 3)" points="14 22.5 7 15.2457065 8.99985857 13.1732815 14 18.3547104 22.9729883 9 25 11.1005634" />
-    </svg>
-  )
-}

+ 118 - 39
packages/@uppy/dashboard/src/components/FileItem/FileProgress/index.js

@@ -1,6 +1,4 @@
 const { h } = require('preact')
-const { iconRetry } = require('../../icons')
-const PauseResumeCancelIcon = require('./PauseResumeCancelIcon')
 
 function onPauseResumeCancelRetry (props) {
   if (props.isUploaded) return
@@ -10,13 +8,9 @@ function onPauseResumeCancelRetry (props) {
     return
   }
 
-  if (props.hidePauseResumeCancelButtons) {
-    return
-  }
-
-  if (props.resumableUploads) {
+  if (props.resumableUploads && !props.hidePauseResumeButton) {
     props.pauseUpload(props.file.id)
-  } else if (props.individualCancellation) {
+  } else if (props.individualCancellation && !props.hideCancelButton) {
     props.cancelUpload(props.file.id)
   }
 }
@@ -42,43 +36,128 @@ function progressIndicatorTitle (props) {
   return ''
 }
 
+function ProgressIndicatorButton (props) {
+  return (
+    <div class="uppy-Dashboard-Item-progress">
+      <button
+        class="uppy-u-reset uppy-Dashboard-Item-progressIndicator"
+        type="button"
+        aria-label={progressIndicatorTitle(props)}
+        title={progressIndicatorTitle(props)}
+        onclick={() => onPauseResumeCancelRetry(props)}
+      >
+        {props.children}
+      </button>
+    </div>
+  )
+}
+
+function ProgressCircleContainer ({ children }) {
+  return (
+    <svg
+      aria-hidden="true"
+      focusable="false"
+      width="70"
+      height="70"
+      viewBox="0 0 36 36"
+      class="uppy-c-icon uppy-Dashboard-Item-progressIcon--circle"
+    >
+      {children}
+    </svg>
+  )
+}
+
+function ProgressCircle ({ progress }) {
+  // circle length equals 2 * PI * R
+  const circleLength = 2 * Math.PI * 15
+
+  return (
+    <g>
+      <circle class="uppy-Dashboard-Item-progressIcon--bg" r="15" cx="18" cy="18" stroke-width="2" fill="none" />
+      <circle
+        class="uppy-Dashboard-Item-progressIcon--progress" r="15" cx="18" cy="18" transform="rotate(-90, 18, 18)" stroke-width="2" fill="none"
+        stroke-dasharray={circleLength}
+        stroke-dashoffset={circleLength - (circleLength / 100 * progress)}
+      />
+    </g>
+  )
+}
+
 module.exports = function FileProgress (props) {
-  if (props.hideRetryButton && props.error) {
-    return <div class="uppy-DashboardItem-progress" />
-  } else if (
-    props.isUploaded ||
-    (props.hidePauseResumeCancelButtons && !props.error)
-  ) {
+  // Nothing if upload has not started
+  if (!props.file.progress.uploadStarted) {
+    return null
+  }
+
+  // Green checkmark when complete
+  if (props.isUploaded) {
     return (
-      <div class="uppy-DashboardItem-progress">
-        <div class="uppy-DashboardItem-progressIndicator">
-          <PauseResumeCancelIcon
-            progress={props.file.progress.percentage}
-            hidePauseResumeCancelButtons={props.hidePauseResumeCancelButtons}
-          />
+      <div class="uppy-Dashboard-Item-progress">
+        <div class="uppy-Dashboard-Item-progressIndicator">
+          <ProgressCircleContainer>
+            <circle r="15" cx="18" cy="18" fill="#1bb240" />
+            <polygon class="uppy-Dashboard-Item-progressIcon--check" transform="translate(2, 3)" points="14 22.5 7 15.2457065 8.99985857 13.1732815 14 18.3547104 22.9729883 9 25 11.1005634" />
+          </ProgressCircleContainer>
         </div>
       </div>
     )
-  } else {
+  }
+
+  // Retry button for error
+  if (props.error && !props.hideRetryButton) {
     return (
-      <div class="uppy-DashboardItem-progress">
-        <button
-          class="uppy-u-reset uppy-DashboardItem-progressIndicator"
-          type="button"
-          aria-label={progressIndicatorTitle(props)}
-          title={progressIndicatorTitle(props)}
-          onclick={() => onPauseResumeCancelRetry(props)}
-        >
-          {props.error ? (
-            props.hideRetryButton ? null : iconRetry()
-          ) : (
-            <PauseResumeCancelIcon
-              progress={props.file.progress.percentage}
-              hidePauseResumeCancelButtons={props.hidePauseResumeCancelButtons}
-            />
-          )}
-        </button>
-      </div>
+      <ProgressIndicatorButton {...props}>
+        <svg aria-hidden="true" focusable="false" class="uppy-c-icon uppy-Dashboard-Item-progressIcon--retry" width="28" height="31" viewBox="0 0 16 19">
+          <path d="M16 11a8 8 0 1 1-8-8v2a6 6 0 1 0 6 6h2z" />
+          <path d="M7.9 3H10v2H7.9z" />
+          <path d="M8.536.5l3.535 3.536-1.414 1.414L7.12 1.914z" />
+          <path d="M10.657 2.621l1.414 1.415L8.536 7.57 7.12 6.157z" />
+        </svg>
+      </ProgressIndicatorButton>
+    )
+  }
+
+  // Pause/resume button for resumable uploads
+  if (props.resumableUploads && !props.hidePauseResumeButton) {
+    return (
+      <ProgressIndicatorButton {...props}>
+        <ProgressCircleContainer>
+          <ProgressCircle progress={props.file.progress.percentage} />
+          {
+            props.file.isPaused
+              ? <polygon class="uppy-Dashboard-Item-progressIcon--play" transform="translate(3, 3)" points="12 20 12 10 20 15" />
+              : (
+                <g class="uppy-Dashboard-Item-progressIcon--pause" transform="translate(14.5, 13)">
+                  <rect x="0" y="0" width="2" height="10" rx="0" />
+                  <rect x="5" y="0" width="2" height="10" rx="0" />
+                </g>
+              )
+          }
+        </ProgressCircleContainer>
+      </ProgressIndicatorButton>
     )
   }
+
+  // Cancel button for non-resumable uploads if individualCancellation is supported (not bundled)
+  if (!props.resumableUploads && props.individualCancellation && !props.hideCancelButton) {
+    return (
+      <ProgressIndicatorButton {...props}>
+        <ProgressCircleContainer>
+          <ProgressCircle progress={props.file.progress.percentage} />
+          <polygon class="cancel" transform="translate(2, 2)" points="19.8856516 11.0625 16 14.9481516 12.1019737 11.0625 11.0625 12.1143484 14.9481516 16 11.0625 19.8980263 12.1019737 20.9375 16 17.0518484 19.8856516 20.9375 20.9375 19.8980263 17.0518484 16 20.9375 12" />
+        </ProgressCircleContainer>
+      </ProgressIndicatorButton>
+    )
+  }
+
+  // Just progress when buttons are disabled
+  return (
+    <div class="uppy-Dashboard-Item-progress">
+      <div class="uppy-Dashboard-Item-progressIndicator">
+        <ProgressCircleContainer>
+          <ProgressCircle progress={props.file.progress.percentage} />
+        </ProgressCircleContainer>
+      </div>
+    </div>
+  )
 }

+ 59 - 106
packages/@uppy/dashboard/src/components/FileItem/FileProgress/index.scss

@@ -1,5 +1,4 @@
-.uppy-DashboardItem-progress {
-  display: none;
+.uppy-Dashboard-Item-progress {
   position: absolute;
   top: 50%;
   left: 50%;
@@ -10,79 +9,84 @@
   width: 120px;
   transition: all .35 ease;
 }
-  .uppy-DashboardItem-progressIndicator {
+
+.uppy-Dashboard-Item-progressIndicator {
+  display: inline-block;
+  width: 38px;
+  height: 38px;
+  opacity: 0.9;
+
+  .uppy-size--md & {
+    width: 55px;
+    height: 55px;
+  }
+}
+
+  button.uppy-Dashboard-Item-progressIndicator {
     @include clear-focus;
-    display: inline-block;
-    width: 38px;
-    height: 38px;
-    opacity: 0.9;
     cursor: pointer;
-    &:focus{
-      svg.UppyIcon-progressCircle .bg,
-      svg.retry{
+
+    &:focus {
+      .uppy-Dashboard-Item-progressIcon--bg,
+      .uppy-Dashboard-Item-progressIcon--retry {
         fill: lighten($blue, 20%);
       }
     }
   }
-// ...uppy-DashboardItem-progress|
 
-svg.UppyIcon-progressCircle {
+// ProgressCircle svg styles
+
+.uppy-Dashboard-Item-progressIcon--circle {
   width: 100%;
   height: 100%;
-  .bg {
-    stroke: rgba($white, 0.4);
-    opacity: 0;
-  }
-  .progress {
-    stroke: $white;
-    transition: stroke-dashoffset .5s ease-out;
-    opacity: 0;
-  }
-  .play {
-    stroke: $white;
-    fill: $white;
-    opacity: 0;
-    transition: all 0.2s;
-    display: none;
-  }
-  .cancel {
-    fill: $white;
-    opacity: 0;
-    transition: all 0.2s;
-  }
-  .pause {
-    stroke: $white;
-    fill: $white;
-    opacity: 0;
-    transition: all 0.2s;
-    display: none;
-  }
-  .check {
-    opacity: 0;
-    fill: $white;
-    transition: all 0.2s;
-  }
 }
-svg.UppyIcon.retry {
+
+.uppy-Dashboard-Item-progressIcon--bg {
+  stroke: rgba($white, 0.4);
+}
+
+.uppy-Dashboard-Item-progressIcon--progress {
+  stroke: $white;
+  transition: stroke-dashoffset .5s ease-out;
+}
+
+.uppy-Dashboard-Item-progressIcon--play {
+  stroke: $white;
+  fill: $white;
+  transition: all 0.2s;
+}
+
+.uppy-Dashboard-Item-progressIcon--cancel {
   fill: $white;
+  transition: all 0.2s;
 }
 
+.uppy-Dashboard-Item-progressIcon--pause {
+  stroke: $white;
+  fill: $white;
+  transition: all 0.2s;
+}
+
+.uppy-Dashboard-Item-progressIcon--check {
+  fill: $white;
+  transition: all 0.2s;
+}
+
+.uppy-Dashboard-Item-progressIcon--retry {
+  fill: $white;
+}
 
 // Svg styles that depend on the state of the file.
-.uppy-DashboardItem.is-complete .uppy-DashboardItem-progress {
+
+.uppy-Dashboard-Item.is-complete .uppy-Dashboard-Item-progress {
   transform: initial;
   top: -9px;
   right: -8px;
   left: initial;
   width: auto;
 }
-.uppy-DashboardItem.is-inprogress .uppy-DashboardItem-progress,
-.uppy-DashboardItem.is-complete .uppy-DashboardItem-progress,
-.uppy-DashboardItem.is-error .uppy-DashboardItem-progress {
-  display: block;
-}
 
-.uppy-DashboardItem.is-error .uppy-DashboardItem-progressIndicator {
+.uppy-Dashboard-Item.is-error .uppy-Dashboard-Item-progressIndicator {
   width: 18px;
   height: 18px;
 
@@ -92,7 +96,7 @@ svg.UppyIcon.retry {
   }
 }
 
-.uppy-DashboardItem.is-complete .uppy-DashboardItem-progressIndicator {
+.uppy-Dashboard-Item.is-complete .uppy-Dashboard-Item-progressIndicator {
   width: 18px;
   height: 18px;
   opacity: 1;
@@ -103,57 +107,6 @@ svg.UppyIcon.retry {
   }
 }
 
-.uppy-DashboardItem.is-paused svg.UppyIcon-progressCircle {
-  .pause {
-    opacity: 0;
-  }
-  .play {
-    opacity: 1;
-  }
-}
-
-.uppy-DashboardItem.is-noIndividualCancellation {
-  .uppy-DashboardItem-progressIndicator {
-    cursor: default;
-  }
-
-  .cancel {
-    display: none;
-  }
-}
-
-.uppy-DashboardItem.is-processing .uppy-DashboardItem-progress {
+.uppy-Dashboard-Item.is-processing .uppy-Dashboard-Item-progress {
   opacity: 0;
 }
-
-.uppy-DashboardItem.is-complete {
-  .uppy-DashboardItem-progressIndicator {
-    cursor: default;
-  }
-
-  .progress {
-    stroke: $green;
-    fill: $green;
-    opacity: 1;
-  }
-
-  .check {
-    opacity: 1;
-  }
-}
-
-.uppy-size--md .uppy-DashboardItem-progressIndicator {
-  width: 55px;
-  height: 55px;
-}
-
-.uppy-DashboardItem.is-resumable {
-  .pause, .play { display: block; }
-  .cancel { display: none; }
-}
-
-.uppy-DashboardItem.is-inprogress {
-  .bg, .progress, .pause, .cancel {
-    opacity: 1;
-  }
-}

+ 12 - 8
packages/@uppy/dashboard/src/components/FileItem/index.js

@@ -32,20 +32,21 @@ module.exports = class FileItem extends Component {
     const isUploaded = file.progress.uploadComplete && !isProcessing && !file.error
     const uploadInProgressOrComplete = file.progress.uploadStarted || isProcessing
     const uploadInProgress = (file.progress.uploadStarted && !file.progress.uploadComplete) || isProcessing
-    const isPaused = file.isPaused || false
     const error = file.error || false
 
-    const showRemoveButton = this.props.individualCancellation
+    let showRemoveButton = this.props.individualCancellation
       ? !isUploaded
       : !uploadInProgress && !isUploaded
 
+    if (isUploaded && this.props.showRemoveButtonAfterComplete) {
+      showRemoveButton = true
+    }
+
     const dashboardItemClass = classNames({
-      'uppy-u-reset': true,
-      'uppy-DashboardItem': true,
+      'uppy-Dashboard-Item': true,
       'is-inprogress': uploadInProgress,
       'is-processing': isProcessing,
       'is-complete': isUploaded,
-      'is-paused': isPaused,
       'is-error': !!error,
       'is-resumable': this.props.resumableUploads,
       'is-noIndividualCancellation': !this.props.individualCancellation
@@ -57,7 +58,7 @@ module.exports = class FileItem extends Component {
         id={`uppy_${file.id}`}
         role={this.props.role}
       >
-        <div class="uppy-DashboardItem-preview">
+        <div class="uppy-Dashboard-Item-preview">
           <FilePreviewAndLink
             file={file}
             showLinkToFileUploadResult={this.props.showLinkToFileUploadResult}
@@ -68,7 +69,10 @@ module.exports = class FileItem extends Component {
             isUploaded={isUploaded}
 
             hideRetryButton={this.props.hideRetryButton}
-            hidePauseResumeCancelButtons={this.props.hidePauseResumeCancelButtons}
+            hideCancelButton={this.props.hideCancelButton}
+            hidePauseResumeButton={this.props.hidePauseResumeButton}
+
+            showRemoveButtonAfterComplete={this.props.showRemoveButtonAfterComplete}
 
             resumableUploads={this.props.resumableUploads}
             individualCancellation={this.props.individualCancellation}
@@ -80,7 +84,7 @@ module.exports = class FileItem extends Component {
           />
         </div>
 
-        <div class="uppy-DashboardItem-fileInfoAndButtons">
+        <div class="uppy-Dashboard-Item-fileInfoAndButtons">
           <FileInfo
             file={file}
             id={this.props.id}

+ 40 - 17
packages/@uppy/dashboard/src/components/FileItem/index.scss

@@ -3,7 +3,7 @@
 @import './FileInfo/index.scss';
 @import './Buttons/index.scss';
 
-.uppy-DashboardItem {
+.uppy-Dashboard-Item {
   display: flex;
   align-items: center;
   border-bottom: 1px solid $gray-200;
@@ -44,7 +44,7 @@
   }
 }
 
-  .uppy-DashboardItem-preview {
+  .uppy-Dashboard-Item-preview {
     // for the FileProgress.js icons
     position: relative;
 
@@ -67,7 +67,8 @@
       height: 140px;
     }
   }
-  .uppy-DashboardItem-fileInfoAndButtons {
+
+  .uppy-Dashboard-Item-fileInfoAndButtons {
     flex-grow: 1;
     padding-right: 8px;
     padding-left: 12px;
@@ -83,31 +84,53 @@
       padding-top: 9px;
     }
   }
-    .uppy-DashboardItem-fileInfo {
+
+    .uppy-Dashboard-Item-fileInfo {
       flex-grow: 1;
       flex-shrink: 1;
     }
-    .uppy-DashboardItem-actionWrapper {
+
+    .uppy-Dashboard-Item-actionWrapper {
       flex-grow: 0;
       flex-shrink: 0;
     }
-  // ...uppy-DashboardItem-fileInfoAndButtons|
-// ...uppy-DashboardItem|
+  // ...uppy-Dashboard-Item-fileInfoAndButtons|
+// ...uppy-Dashboard-Item|
 
 
-// Css that depends on status of the file (could be logic in js instead?)
-.uppy-DashboardItem.is-inprogress {
-  .uppy-DashboardItem-previewInnerWrap:after {
-    display: block;
-  }
-}
-.uppy-DashboardItem.is-error {
-  .uppy-DashboardItem-previewInnerWrap:after {
+// CSS that depends on status of the file (could be logic in js instead?)
+.uppy-Dashboard-Item.is-inprogress,
+.uppy-Dashboard-Item.is-error {
+  .uppy-Dashboard-Item-previewInnerWrap:after {
     display: block;
   }
 }
-.uppy-DashboardItem.is-inprogress:not(.is-resumable) {
-  .uppy-DashboardItem-action--remove {
+
+.uppy-Dashboard-Item.is-inprogress:not(.is-resumable) {
+  .uppy-Dashboard-Item-action--remove {
     display: none;
   }
 }
+
+.uppy-Dashboard-Item-errorDetails {
+  line-height: 12px;
+  width: 12px;
+  height: 12px;
+  display: inline-block;
+  vertical-align: middle;
+  color: $white;
+  background-color: $gray-500;
+  border-radius: 50%;
+  position: relative;
+  top: -1px;
+  left: 6px;
+  font-size: 8px;
+  font-weight: 600;
+  text-align: center;
+  cursor: help;
+}
+
+  .uppy-Dashboard-Item-errorDetails:after {
+    line-height: 1.3;
+    word-wrap: break-word;
+  }

+ 3 - 1
packages/@uppy/dashboard/src/components/FileList.js

@@ -47,8 +47,10 @@ module.exports = (props) => {
     individualCancellation: props.individualCancellation,
     // visual options
     hideRetryButton: props.hideRetryButton,
-    hidePauseResumeCancelButtons: props.hidePauseResumeCancelButtons,
+    hidePauseResumeButton: props.hidePauseResumeButton,
+    hideCancelButton: props.hideCancelButton,
     showLinkToFileUploadResult: props.showLinkToFileUploadResult,
+    showRemoveButtonAfterComplete: props.showRemoveButtonAfterComplete,
     isWide: props.isWide,
     metaFields: props.metaFields,
     // callbacks

+ 3 - 2
packages/@uppy/dashboard/src/components/PickerPanelTopBar.js

@@ -1,5 +1,4 @@
 const { h } = require('preact')
-const { iconPlus } = require('./icons')
 
 const uploadStates = {
   STATE_ERROR: 'error',
@@ -102,7 +101,9 @@ function PanelTopBar (props) {
           title={props.i18n('addMoreFiles')}
           onclick={() => props.toggleAddFilesPanel(true)}
         >
-          {iconPlus()}
+          <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="15" height="15" viewBox="0 0 15 15">
+            <path d="M8 6.5h6a.5.5 0 0 1 .5.5v.5a.5.5 0 0 1-.5.5H8v6a.5.5 0 0 1-.5.5H7a.5.5 0 0 1-.5-.5V8h-6a.5.5 0 0 1-.5-.5V7a.5.5 0 0 1 .5-.5h6v-6A.5.5 0 0 1 7 0h.5a.5.5 0 0 1 .5.5v6z" />
+          </svg>
           <span class="uppy-DashboardContent-addMoreCaption">{props.i18n('addMore')}</span>
         </button>
       ) : (

+ 0 - 201
packages/@uppy/dashboard/src/components/icons.js

@@ -1,201 +0,0 @@
-const { h } = require('preact')
-
-// https://css-tricks.com/creating-svg-icon-system-react/
-
-function defaultPickerIcon () {
-  return (
-    <svg aria-hidden="true" focusable="false" width="30" height="30" viewBox="0 0 30 30">
-      <path d="M15 30c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15C6.716 0 0 6.716 0 15c0 8.284 6.716 15 15 15zm4.258-12.676v6.846h-8.426v-6.846H5.204l9.82-12.364 9.82 12.364H19.26z" />
-    </svg>
-  )
-}
-
-function iconCopy () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="51" height="51" viewBox="0 0 51 51">
-      <path d="M17.21 45.765a5.394 5.394 0 0 1-7.62 0l-4.12-4.122a5.393 5.393 0 0 1 0-7.618l6.774-6.775-2.404-2.404-6.775 6.776c-3.424 3.427-3.424 9 0 12.426l4.12 4.123a8.766 8.766 0 0 0 6.216 2.57c2.25 0 4.5-.858 6.214-2.57l13.55-13.552a8.72 8.72 0 0 0 2.575-6.213 8.73 8.73 0 0 0-2.575-6.213l-4.123-4.12-2.404 2.404 4.123 4.12a5.352 5.352 0 0 1 1.58 3.81c0 1.438-.562 2.79-1.58 3.808l-13.55 13.55z" />
-      <path d="M44.256 2.858A8.728 8.728 0 0 0 38.043.283h-.002a8.73 8.73 0 0 0-6.212 2.574l-13.55 13.55a8.725 8.725 0 0 0-2.575 6.214 8.73 8.73 0 0 0 2.574 6.216l4.12 4.12 2.405-2.403-4.12-4.12a5.357 5.357 0 0 1-1.58-3.812c0-1.437.562-2.79 1.58-3.808l13.55-13.55a5.348 5.348 0 0 1 3.81-1.58c1.44 0 2.792.562 3.81 1.58l4.12 4.12c2.1 2.1 2.1 5.518 0 7.617L39.2 23.775l2.404 2.404 6.775-6.777c3.426-3.427 3.426-9 0-12.426l-4.12-4.12z" />
-    </svg>
-  )
-}
-
-function iconResume () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="25" height="25" viewBox="0 0 44 44">
-      <polygon class="play" transform="translate(6, 5.5)" points="13 21.6666667 13 11 21 16.3333333" />
-    </svg>
-  )
-}
-
-function iconPause () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="25px" height="25px" viewBox="0 0 44 44">
-      <g transform="translate(18, 17)" class="pause">
-        <rect x="0" y="0" width="2" height="10" rx="0" />
-        <rect x="6" y="0" width="2" height="10" rx="0" />
-      </g>
-    </svg>
-  )
-}
-
-function localIcon () {
-  return (
-    <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
-      <g fill="none" fill-rule="evenodd">
-        <rect fill="#5B5B5B" width="32" height="32" rx="16" />
-        <g fill="#FFF" fill-rule="nonzero">
-          <path d="M11.31 12.504a.185.185 0 0 0 .167.104h2.868v2.324c0 .898.741 1.328 1.655 1.328.913 0 1.653-.43 1.653-1.328v-2.324h2.868c.073 0 .137-.04.169-.104a.18.18 0 0 0-.027-.192l-4.524-5.25a.187.187 0 0 0-.28 0l-4.52 5.25a.179.179 0 0 0-.029.192z" />
-          <path d="M22.4 10.018c-.13-.45-.32-.703-.716-.703h-2.877l.603.699h2.34l1.21 6.541h-4.263v1.617h-5.296v-1.615H9.039l1.577-6.542h1.973l.603-.7h-2.877c-.396 0-.628.273-.717.703L8 16.752v4.185c0 .486.4.878.895.878h14.21a.887.887 0 0 0 .895-.878v-4.185l-1.6-6.734z" />
-        </g>
-      </g>
-    </svg>
-  )
-}
-
-function iconMyDevice () {
-  return (
-    <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
-      <g fill="none" fill-rule="evenodd">
-        <rect width="32" height="32" rx="16" fill="#2275D7" />
-        <path d="M21.973 21.152H9.863l-1.108-5.087h14.464l-1.246 5.087zM9.935 11.37h3.958l.886 1.444a.673.673 0 0 0 .585.316h6.506v1.37H9.935v-3.13zm14.898 3.44a.793.793 0 0 0-.616-.31h-.978v-2.126c0-.379-.275-.613-.653-.613H15.75l-.886-1.445a.673.673 0 0 0-.585-.316H9.232c-.378 0-.667.209-.667.587V14.5h-.782a.793.793 0 0 0-.61.303.795.795 0 0 0-.155.663l1.45 6.633c.078.36.396.618.764.618h13.354c.36 0 .674-.246.76-.595l1.631-6.636a.795.795 0 0 0-.144-.675z" fill="#FFF" />
-      </g>
-    </svg>
-  )
-}
-
-function iconRetry () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon retry" width="28" height="31" viewBox="0 0 16 19">
-      <path d="M16 11a8 8 0 1 1-8-8v2a6 6 0 1 0 6 6h2z" />
-      <path d="M7.9 3H10v2H7.9z" />
-      <path d="M8.536.5l3.535 3.536-1.414 1.414L7.12 1.914z" />
-      <path d="M10.657 2.621l1.414 1.415L8.536 7.57 7.12 6.157z" />
-    </svg>
-  )
-}
-
-function checkIcon () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon UppyIcon-check" width="13" height="9" viewBox="0 0 13 9">
-      <polygon points="5 7.293 1.354 3.647 0.646 4.354 5 8.707 12.354 1.354 11.646 0.647" />
-    </svg>
-  )
-}
-
-function iconImage () {
-  return (
-    <svg aria-hidden="true" focusable="false" width="25" height="25" viewBox="0 0 25 25" xmlns="http://www.w3.org/2000/svg">
-      <g fill="#686DE0" fill-rule="evenodd">
-        <path d="M5 7v10h15V7H5zm0-1h15a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z" fill-rule="nonzero" />
-        <path d="M6.35 17.172l4.994-5.026a.5.5 0 0 1 .707 0l2.16 2.16 3.505-3.505a.5.5 0 0 1 .707 0l2.336 2.31-.707.72-1.983-1.97-3.505 3.505a.5.5 0 0 1-.707 0l-2.16-2.159-3.938 3.939-1.409.026z" fill-rule="nonzero" />
-        <circle cx="7.5" cy="9.5" r="1.5" />
-      </g>
-    </svg>
-  )
-}
-
-function iconAudio () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="25" height="25" viewBox="0 0 25 25">
-      <path d="M9.5 18.64c0 1.14-1.145 2-2.5 2s-2.5-.86-2.5-2c0-1.14 1.145-2 2.5-2 .557 0 1.079.145 1.5.396V7.25a.5.5 0 0 1 .379-.485l9-2.25A.5.5 0 0 1 18.5 5v11.64c0 1.14-1.145 2-2.5 2s-2.5-.86-2.5-2c0-1.14 1.145-2 2.5-2 .557 0 1.079.145 1.5.396V8.67l-8 2v7.97zm8-11v-2l-8 2v2l8-2zM7 19.64c.855 0 1.5-.484 1.5-1s-.645-1-1.5-1-1.5.484-1.5 1 .645 1 1.5 1zm9-2c.855 0 1.5-.484 1.5-1s-.645-1-1.5-1-1.5.484-1.5 1 .645 1 1.5 1z" fill="#049BCF" fill-rule="nonzero" />
-    </svg>
-  )
-}
-
-function iconVideo () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="25" height="25" viewBox="0 0 25 25">
-      <path d="M16 11.834l4.486-2.691A1 1 0 0 1 22 10v6a1 1 0 0 1-1.514.857L16 14.167V17a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2.834zM15 9H5v8h10V9zm1 4l5 3v-6l-5 3z" fill="#19AF67" fill-rule="nonzero" />
-    </svg>
-  )
-}
-
-function iconPDF () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="25" height="25" viewBox="0 0 25 25">
-      <path d="M9.766 8.295c-.691-1.843-.539-3.401.747-3.726 1.643-.414 2.505.938 2.39 3.299-.039.79-.194 1.662-.537 3.148.324.49.66.967 1.055 1.51.17.231.382.488.629.757 1.866-.128 3.653.114 4.918.655 1.487.635 2.192 1.685 1.614 2.84-.566 1.133-1.839 1.084-3.416.249-1.141-.604-2.457-1.634-3.51-2.707a13.467 13.467 0 0 0-2.238.426c-1.392 4.051-4.534 6.453-5.707 4.572-.986-1.58 1.38-4.206 4.914-5.375.097-.322.185-.656.264-1.001.08-.353.306-1.31.407-1.737-.678-1.059-1.2-2.031-1.53-2.91zm2.098 4.87c-.033.144-.068.287-.104.427l.033-.01-.012.038a14.065 14.065 0 0 1 1.02-.197l-.032-.033.052-.004a7.902 7.902 0 0 1-.208-.271c-.197-.27-.38-.526-.555-.775l-.006.028-.002-.003c-.076.323-.148.632-.186.8zm5.77 2.978c1.143.605 1.832.632 2.054.187.26-.519-.087-1.034-1.113-1.473-.911-.39-2.175-.608-3.55-.608.845.766 1.787 1.459 2.609 1.894zM6.559 18.789c.14.223.693.16 1.425-.413.827-.648 1.61-1.747 2.208-3.206-2.563 1.064-4.102 2.867-3.633 3.62zm5.345-10.97c.088-1.793-.351-2.48-1.146-2.28-.473.119-.564 1.05-.056 2.405.213.566.52 1.188.908 1.859.18-.858.268-1.453.294-1.984z" fill="#E2514A" fill-rule="nonzero" />
-    </svg>
-  )
-}
-
-function iconArchive () {
-  return (
-    <svg aria-hidden="true" focusable="false" width="25" height="25" viewBox="0 0 25 25" xmlns="http://www.w3.org/2000/svg">
-      <path d="M10.45 2.05h1.05a.5.5 0 0 1 .5.5v.024a.5.5 0 0 1-.5.5h-1.05a.5.5 0 0 1-.5-.5V2.55a.5.5 0 0 1 .5-.5zm2.05 1.024h1.05a.5.5 0 0 1 .5.5V3.6a.5.5 0 0 1-.5.5H12.5a.5.5 0 0 1-.5-.5v-.025a.5.5 0 0 1 .5-.5v-.001zM10.45 0h1.05a.5.5 0 0 1 .5.5v.025a.5.5 0 0 1-.5.5h-1.05a.5.5 0 0 1-.5-.5V.5a.5.5 0 0 1 .5-.5zm2.05 1.025h1.05a.5.5 0 0 1 .5.5v.024a.5.5 0 0 1-.5.5H12.5a.5.5 0 0 1-.5-.5v-.024a.5.5 0 0 1 .5-.5zm-2.05 3.074h1.05a.5.5 0 0 1 .5.5v.025a.5.5 0 0 1-.5.5h-1.05a.5.5 0 0 1-.5-.5v-.025a.5.5 0 0 1 .5-.5zm2.05 1.025h1.05a.5.5 0 0 1 .5.5v.024a.5.5 0 0 1-.5.5H12.5a.5.5 0 0 1-.5-.5v-.024a.5.5 0 0 1 .5-.5zm-2.05 1.024h1.05a.5.5 0 0 1 .5.5v.025a.5.5 0 0 1-.5.5h-1.05a.5.5 0 0 1-.5-.5v-.025a.5.5 0 0 1 .5-.5zm2.05 1.025h1.05a.5.5 0 0 1 .5.5v.025a.5.5 0 0 1-.5.5H12.5a.5.5 0 0 1-.5-.5v-.025a.5.5 0 0 1 .5-.5zm-2.05 1.025h1.05a.5.5 0 0 1 .5.5v.025a.5.5 0 0 1-.5.5h-1.05a.5.5 0 0 1-.5-.5v-.025a.5.5 0 0 1 .5-.5zm2.05 1.025h1.05a.5.5 0 0 1 .5.5v.024a.5.5 0 0 1-.5.5H12.5a.5.5 0 0 1-.5-.5v-.024a.5.5 0 0 1 .5-.5zm-1.656 3.074l-.82 5.946c.52.302 1.174.458 1.976.458.803 0 1.455-.156 1.975-.458l-.82-5.946h-2.311zm0-1.025h2.312c.512 0 .946.378 1.015.885l.82 5.946c.056.412-.142.817-.501 1.026-.686.398-1.515.597-2.49.597-.974 0-1.804-.199-2.49-.597a1.025 1.025 0 0 1-.5-1.026l.819-5.946c.07-.507.503-.885 1.015-.885zm.545 6.6a.5.5 0 0 1-.397-.561l.143-.999a.5.5 0 0 1 .495-.429h.74a.5.5 0 0 1 .495.43l.143.998a.5.5 0 0 1-.397.561c-.404.08-.819.08-1.222 0z" fill="#00C469" fill-rule="nonzero" />
-    </svg>
-  )
-}
-
-function iconFile () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="25" height="25" viewBox="0 0 25 25">
-      <g fill="#A7AFB7" fill-rule="nonzero">
-        <path d="M5.5 22a.5.5 0 0 1-.5-.5v-18a.5.5 0 0 1 .5-.5h10.719a.5.5 0 0 1 .367.16l3.281 3.556a.5.5 0 0 1 .133.339V21.5a.5.5 0 0 1-.5.5h-14zm.5-1h13V7.25L16 4H6v17z" />
-        <path d="M15 4v3a1 1 0 0 0 1 1h3V7h-3V4h-1z" />
-      </g>
-    </svg>
-  )
-}
-
-function iconText () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="25" height="25" viewBox="0 0 25 25">
-      <path d="M4.5 7h13a.5.5 0 1 1 0 1h-13a.5.5 0 0 1 0-1zm0 3h15a.5.5 0 1 1 0 1h-15a.5.5 0 1 1 0-1zm0 3h15a.5.5 0 1 1 0 1h-15a.5.5 0 1 1 0-1zm0 3h10a.5.5 0 1 1 0 1h-10a.5.5 0 1 1 0-1z" fill="#5A5E69" fill-rule="nonzero" />
-    </svg>
-  )
-}
-
-function iconCopyLink () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="14" height="14" viewBox="0 0 14 12">
-      <path d="M7.94 7.703a2.613 2.613 0 0 1-.626 2.681l-.852.851a2.597 2.597 0 0 1-1.849.766A2.616 2.616 0 0 1 2.764 7.54l.852-.852a2.596 2.596 0 0 1 2.69-.625L5.267 7.099a1.44 1.44 0 0 0-.833.407l-.852.851a1.458 1.458 0 0 0 1.03 2.486c.39 0 .755-.152 1.03-.426l.852-.852c.231-.231.363-.522.406-.824l1.04-1.038zm4.295-5.937A2.596 2.596 0 0 0 10.387 1c-.698 0-1.355.272-1.849.766l-.852.851a2.614 2.614 0 0 0-.624 2.688l1.036-1.036c.041-.304.173-.6.407-.833l.852-.852c.275-.275.64-.426 1.03-.426a1.458 1.458 0 0 1 1.03 2.486l-.852.851a1.442 1.442 0 0 1-.824.406l-1.04 1.04a2.596 2.596 0 0 0 2.683-.628l.851-.85a2.616 2.616 0 0 0 0-3.697zm-6.88 6.883a.577.577 0 0 0 .82 0l3.474-3.474a.579.579 0 1 0-.819-.82L5.355 7.83a.579.579 0 0 0 0 .819z" />
-    </svg>
-  )
-}
-
-function iconPencil () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="14" height="14" viewBox="0 0 14 14">
-      <g fill-rule="evenodd"><path d="M1.5 10.793h2.793A1 1 0 0 0 5 10.5L11.5 4a1 1 0 0 0 0-1.414L9.707.793a1 1 0 0 0-1.414 0l-6.5 6.5A1 1 0 0 0 1.5 8v2.793zm1-1V8L9 1.5l1.793 1.793-6.5 6.5H2.5z" fill-rule="nonzero" /><rect x="1" y="12.293" width="11" height="1" rx=".5" /><path fill-rule="nonzero" d="M6.793 2.5L9.5 5.207l.707-.707L7.5 1.793z" /></g>
-    </svg>
-  )
-}
-
-function iconCross () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="18" height="18" viewBox="0 0 18 18">
-      <path d="M9 0C4.034 0 0 4.034 0 9s4.034 9 9 9 9-4.034 9-9-4.034-9-9-9z" />
-      <path fill="#FFF" d="M13 12.222l-.778.778L9 9.778 5.778 13 5 12.222 8.222 9 5 5.778 5.778 5 9 8.222 12.222 5l.778.778L9.778 9z" />
-    </svg>
-  )
-}
-
-function iconPlus () {
-  return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width="15" height="15" viewBox="0 0 15 15">
-      <path d="M8 6.5h6a.5.5 0 0 1 .5.5v.5a.5.5 0 0 1-.5.5H8v6a.5.5 0 0 1-.5.5H7a.5.5 0 0 1-.5-.5V8h-6a.5.5 0 0 1-.5-.5V7a.5.5 0 0 1 .5-.5h6v-6A.5.5 0 0 1 7 0h.5a.5.5 0 0 1 .5.5v6z" />
-    </svg>
-  )
-}
-
-module.exports = {
-  defaultPickerIcon,
-  iconCopy,
-  iconResume,
-  iconPause,
-  iconRetry,
-  localIcon,
-  iconMyDevice,
-  checkIcon,
-  iconImage,
-  iconAudio,
-  iconVideo,
-  iconPDF,
-  iconArchive,
-  iconFile,
-  iconText,
-  iconCopyLink,
-  iconPencil,
-  iconCross,
-  iconPlus
-}

+ 14 - 2
packages/@uppy/dashboard/src/index.js

@@ -10,7 +10,6 @@ const getDroppedFiles = require('@uppy/utils/lib/getDroppedFiles')
 const trapFocus = require('./utils/trapFocus')
 const cuid = require('cuid')
 const ResizeObserver = require('resize-observer-polyfill').default || require('resize-observer-polyfill')
-const { defaultPickerIcon } = require('./components/icons')
 const createSuperFocus = require('./utils/createSuperFocus')
 const memoize = require('memoize-one').default || require('memoize-one')
 
@@ -26,6 +25,14 @@ function createPromise () {
   return o
 }
 
+function defaultPickerIcon () {
+  return (
+    <svg aria-hidden="true" focusable="false" width="30" height="30" viewBox="0 0 30 30">
+      <path d="M15 30c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15C6.716 0 0 6.716 0 15c0 8.284 6.716 15 15 15zm4.258-12.676v6.846h-8.426v-6.846H5.204l9.82-12.364 9.82 12.364H19.26z" />
+    </svg>
+  )
+}
+
 /**
  * Dashboard UI with previews, metadata editing, tabs for various services and more
  */
@@ -107,8 +114,9 @@ module.exports = class Dashboard extends Plugin {
       showLinkToFileUploadResult: true,
       showProgressDetails: false,
       hideUploadButton: false,
+      hideCancelButton: false,
       hideRetryButton: false,
-      hidePauseResumeCancelButtons: false,
+      hidePauseResumeButton: false,
       hideProgressAfterFinish: false,
       note: null,
       closeModalOnClickOutside: false,
@@ -121,6 +129,7 @@ module.exports = class Dashboard extends Plugin {
       proudlyDisplayPoweredByUppy: true,
       onRequestCloseModal: () => this.closeModal(),
       showSelectedFiles: true,
+      showRemoveButtonAfterComplete: false,
       browserBackButtonClose: false,
       theme: 'light'
     }
@@ -866,6 +875,9 @@ module.exports = class Dashboard extends Plugin {
       showLinkToFileUploadResult: this.opts.showLinkToFileUploadResult,
       proudlyDisplayPoweredByUppy: this.opts.proudlyDisplayPoweredByUppy,
       hideCancelButton: this.opts.hideCancelButton,
+      hideRetryButton: this.opts.hideRetryButton,
+      hidePauseResumeButton: this.opts.hidePauseResumeButton,
+      showRemoveButtonAfterComplete: this.opts.showRemoveButtonAfterComplete,
       containerWidth: pluginState.containerWidth,
       containerHeight: pluginState.containerHeight,
       areInsidesReadyToBeVisible: pluginState.areInsidesReadyToBeVisible,

+ 4 - 4
packages/@uppy/dashboard/src/style.scss

@@ -751,7 +751,7 @@ a.uppy-Dashboard-poweredBy {
   vertical-align: text-top;
 }
 
-.uppy-DashboardItem-previewIcon {
+.uppy-Dashboard-Item-previewIcon {
   width: 25px;
   height: 25px;
   z-index: $zIndex-1;
@@ -771,13 +771,13 @@ a.uppy-Dashboard-poweredBy {
   }
 }
 
-.uppy-DashboardItem-previewIconWrap {
+.uppy-Dashboard-Item-previewIconWrap {
   height: 76px;
   max-height: 75%;
   position: relative;
 }
 
-.uppy-DashboardItem-previewIconBg {
+.uppy-Dashboard-Item-previewIconBg {
   width: 100%;
   height: 100%;
   filter: drop-shadow(rgba($black, 0.1) 0px 1px 1px);
@@ -794,7 +794,7 @@ a.uppy-Dashboard-poweredBy {
   }
 }
 
-.uppy-Dashboard-upload .UppyIcon {
+.uppy-Dashboard-upload .uppy-c-icon {
   position: relative;
   top: 1px;
   width: 50%;

+ 64 - 1
packages/@uppy/dashboard/src/utils/getFileTypeIcon.js

@@ -1,4 +1,67 @@
-const { iconFile, iconText, iconImage, iconAudio, iconVideo, iconPDF, iconArchive } = require('../components/icons')
+const { h } = require('preact')
+
+function iconImage () {
+  return (
+    <svg aria-hidden="true" focusable="false" width="25" height="25" viewBox="0 0 25 25">
+      <g fill="#686DE0" fill-rule="evenodd">
+        <path d="M5 7v10h15V7H5zm0-1h15a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1z" fill-rule="nonzero" />
+        <path d="M6.35 17.172l4.994-5.026a.5.5 0 0 1 .707 0l2.16 2.16 3.505-3.505a.5.5 0 0 1 .707 0l2.336 2.31-.707.72-1.983-1.97-3.505 3.505a.5.5 0 0 1-.707 0l-2.16-2.159-3.938 3.939-1.409.026z" fill-rule="nonzero" />
+        <circle cx="7.5" cy="9.5" r="1.5" />
+      </g>
+    </svg>
+  )
+}
+
+function iconAudio () {
+  return (
+    <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="25" height="25" viewBox="0 0 25 25">
+      <path d="M9.5 18.64c0 1.14-1.145 2-2.5 2s-2.5-.86-2.5-2c0-1.14 1.145-2 2.5-2 .557 0 1.079.145 1.5.396V7.25a.5.5 0 0 1 .379-.485l9-2.25A.5.5 0 0 1 18.5 5v11.64c0 1.14-1.145 2-2.5 2s-2.5-.86-2.5-2c0-1.14 1.145-2 2.5-2 .557 0 1.079.145 1.5.396V8.67l-8 2v7.97zm8-11v-2l-8 2v2l8-2zM7 19.64c.855 0 1.5-.484 1.5-1s-.645-1-1.5-1-1.5.484-1.5 1 .645 1 1.5 1zm9-2c.855 0 1.5-.484 1.5-1s-.645-1-1.5-1-1.5.484-1.5 1 .645 1 1.5 1z" fill="#049BCF" fill-rule="nonzero" />
+    </svg>
+  )
+}
+
+function iconVideo () {
+  return (
+    <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="25" height="25" viewBox="0 0 25 25">
+      <path d="M16 11.834l4.486-2.691A1 1 0 0 1 22 10v6a1 1 0 0 1-1.514.857L16 14.167V17a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2.834zM15 9H5v8h10V9zm1 4l5 3v-6l-5 3z" fill="#19AF67" fill-rule="nonzero" />
+    </svg>
+  )
+}
+
+function iconPDF () {
+  return (
+    <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="25" height="25" viewBox="0 0 25 25">
+      <path d="M9.766 8.295c-.691-1.843-.539-3.401.747-3.726 1.643-.414 2.505.938 2.39 3.299-.039.79-.194 1.662-.537 3.148.324.49.66.967 1.055 1.51.17.231.382.488.629.757 1.866-.128 3.653.114 4.918.655 1.487.635 2.192 1.685 1.614 2.84-.566 1.133-1.839 1.084-3.416.249-1.141-.604-2.457-1.634-3.51-2.707a13.467 13.467 0 0 0-2.238.426c-1.392 4.051-4.534 6.453-5.707 4.572-.986-1.58 1.38-4.206 4.914-5.375.097-.322.185-.656.264-1.001.08-.353.306-1.31.407-1.737-.678-1.059-1.2-2.031-1.53-2.91zm2.098 4.87c-.033.144-.068.287-.104.427l.033-.01-.012.038a14.065 14.065 0 0 1 1.02-.197l-.032-.033.052-.004a7.902 7.902 0 0 1-.208-.271c-.197-.27-.38-.526-.555-.775l-.006.028-.002-.003c-.076.323-.148.632-.186.8zm5.77 2.978c1.143.605 1.832.632 2.054.187.26-.519-.087-1.034-1.113-1.473-.911-.39-2.175-.608-3.55-.608.845.766 1.787 1.459 2.609 1.894zM6.559 18.789c.14.223.693.16 1.425-.413.827-.648 1.61-1.747 2.208-3.206-2.563 1.064-4.102 2.867-3.633 3.62zm5.345-10.97c.088-1.793-.351-2.48-1.146-2.28-.473.119-.564 1.05-.056 2.405.213.566.52 1.188.908 1.859.18-.858.268-1.453.294-1.984z" fill="#E2514A" fill-rule="nonzero" />
+    </svg>
+  )
+}
+
+function iconArchive () {
+  return (
+    <svg aria-hidden="true" focusable="false" width="25" height="25" viewBox="0 0 25 25">
+      <path d="M10.45 2.05h1.05a.5.5 0 0 1 .5.5v.024a.5.5 0 0 1-.5.5h-1.05a.5.5 0 0 1-.5-.5V2.55a.5.5 0 0 1 .5-.5zm2.05 1.024h1.05a.5.5 0 0 1 .5.5V3.6a.5.5 0 0 1-.5.5H12.5a.5.5 0 0 1-.5-.5v-.025a.5.5 0 0 1 .5-.5v-.001zM10.45 0h1.05a.5.5 0 0 1 .5.5v.025a.5.5 0 0 1-.5.5h-1.05a.5.5 0 0 1-.5-.5V.5a.5.5 0 0 1 .5-.5zm2.05 1.025h1.05a.5.5 0 0 1 .5.5v.024a.5.5 0 0 1-.5.5H12.5a.5.5 0 0 1-.5-.5v-.024a.5.5 0 0 1 .5-.5zm-2.05 3.074h1.05a.5.5 0 0 1 .5.5v.025a.5.5 0 0 1-.5.5h-1.05a.5.5 0 0 1-.5-.5v-.025a.5.5 0 0 1 .5-.5zm2.05 1.025h1.05a.5.5 0 0 1 .5.5v.024a.5.5 0 0 1-.5.5H12.5a.5.5 0 0 1-.5-.5v-.024a.5.5 0 0 1 .5-.5zm-2.05 1.024h1.05a.5.5 0 0 1 .5.5v.025a.5.5 0 0 1-.5.5h-1.05a.5.5 0 0 1-.5-.5v-.025a.5.5 0 0 1 .5-.5zm2.05 1.025h1.05a.5.5 0 0 1 .5.5v.025a.5.5 0 0 1-.5.5H12.5a.5.5 0 0 1-.5-.5v-.025a.5.5 0 0 1 .5-.5zm-2.05 1.025h1.05a.5.5 0 0 1 .5.5v.025a.5.5 0 0 1-.5.5h-1.05a.5.5 0 0 1-.5-.5v-.025a.5.5 0 0 1 .5-.5zm2.05 1.025h1.05a.5.5 0 0 1 .5.5v.024a.5.5 0 0 1-.5.5H12.5a.5.5 0 0 1-.5-.5v-.024a.5.5 0 0 1 .5-.5zm-1.656 3.074l-.82 5.946c.52.302 1.174.458 1.976.458.803 0 1.455-.156 1.975-.458l-.82-5.946h-2.311zm0-1.025h2.312c.512 0 .946.378 1.015.885l.82 5.946c.056.412-.142.817-.501 1.026-.686.398-1.515.597-2.49.597-.974 0-1.804-.199-2.49-.597a1.025 1.025 0 0 1-.5-1.026l.819-5.946c.07-.507.503-.885 1.015-.885zm.545 6.6a.5.5 0 0 1-.397-.561l.143-.999a.5.5 0 0 1 .495-.429h.74a.5.5 0 0 1 .495.43l.143.998a.5.5 0 0 1-.397.561c-.404.08-.819.08-1.222 0z" fill="#00C469" fill-rule="nonzero" />
+    </svg>
+  )
+}
+
+function iconFile () {
+  return (
+    <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="25" height="25" viewBox="0 0 25 25">
+      <g fill="#A7AFB7" fill-rule="nonzero">
+        <path d="M5.5 22a.5.5 0 0 1-.5-.5v-18a.5.5 0 0 1 .5-.5h10.719a.5.5 0 0 1 .367.16l3.281 3.556a.5.5 0 0 1 .133.339V21.5a.5.5 0 0 1-.5.5h-14zm.5-1h13V7.25L16 4H6v17z" />
+        <path d="M15 4v3a1 1 0 0 0 1 1h3V7h-3V4h-1z" />
+      </g>
+    </svg>
+  )
+}
+
+function iconText () {
+  return (
+    <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="25" height="25" viewBox="0 0 25 25">
+      <path d="M4.5 7h13a.5.5 0 1 1 0 1h-13a.5.5 0 0 1 0-1zm0 3h15a.5.5 0 1 1 0 1h-15a.5.5 0 1 1 0-1zm0 3h15a.5.5 0 1 1 0 1h-15a.5.5 0 1 1 0-1zm0 3h10a.5.5 0 1 1 0 1h-10a.5.5 0 1 1 0-1z" fill="#5A5E69" fill-rule="nonzero" />
+    </svg>
+  )
+}
 
 module.exports = function getIconByMime (fileType) {
   const defaultChoice = {

+ 1 - 0
packages/@uppy/dashboard/types/index.d.ts

@@ -42,6 +42,7 @@ declare module Dashboard {
     showLinkToFileUploadResult?: boolean
     showProgressDetails?: boolean
     showSelectedFiles?: boolean
+    showRemoveButtonAfterComplete?: boolean
     replaceTargetContent?: boolean
     target?: Uppy.PluginTarget
     theme?: 'auto' | 'dark' | 'light'

+ 1 - 1
packages/@uppy/drag-drop/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/drag-drop",
   "description": "Droppable zone UI for Uppy. Drag and drop files into it to upload.",
-  "version": "1.4.12",
+  "version": "1.4.15",
   "license": "MIT",
   "main": "lib/index.js",
   "style": "dist/style.min.css",

+ 4 - 6
packages/@uppy/drag-drop/src/index.js

@@ -144,8 +144,7 @@ module.exports = class DragDrop extends Plugin {
       <input
         class="uppy-DragDrop-input"
         type="file"
-        tabindex={-1}
-        focusable="false"
+        hidden
         ref={(ref) => { this.fileInputRef = ref }}
         name={this.opts.inputName}
         multiple={restrictions.maxNumberOfFiles !== 1}
@@ -157,7 +156,7 @@ module.exports = class DragDrop extends Plugin {
 
   renderArrowSvg () {
     return (
-      <svg aria-hidden="true" focusable="false" class="UppyIcon uppy-DragDrop-arrow" width="16" height="16" viewBox="0 0 16 16">
+      <svg aria-hidden="true" focusable="false" class="uppy-c-icon uppy-DragDrop-arrow" width="16" height="16" viewBox="0 0 16 16">
         <path d="M11 10V0H5v10H2l6 6 6-6h-3zm0 0" fill-rule="evenodd" />
       </svg>
     )
@@ -180,11 +179,10 @@ module.exports = class DragDrop extends Plugin {
   }
 
   render (state) {
-    const dragDropClass = `
-      uppy-Root
+    const dragDropClass = `uppy-Root
       uppy-u-reset
       uppy-DragDrop-container
-      ${this.isDragDropSupported ? 'uppy-DragDrop--is-dragdrop-supported' : ''}
+      ${this.isDragDropSupported ? 'uppy-DragDrop--isDragDropSupported' : ''}
       ${this.getPluginState().isDraggingOver ? 'uppy-DragDrop--isDraggingOver' : ''}
     `
 

+ 6 - 16
packages/@uppy/drag-drop/src/style.scss

@@ -29,16 +29,6 @@
   line-height: 1.4;
 }
 
-// http://tympanus.net/codrops/2015/09/15/styling-customizing-file-inputs-smart-way/
-.uppy-DragDrop-input {
-  width: 0.1px;
-  height: 0.1px;
-  opacity: 0;
-  overflow: hidden;
-  position: absolute;
-  z-index: -1;
-}
-
 .uppy-DragDrop-arrow {
   width: 60px;
   height: 60px;
@@ -46,7 +36,7 @@
   margin-bottom: 17px;
 }
 
-  .uppy-DragDrop--is-dragdrop-supported {
+  .uppy-DragDrop--isDragDropSupported {
     border: 2px dashed lighten($gray-500, 10%);
   }
 
@@ -61,16 +51,16 @@
 
 .uppy-DragDrop-label {
   display: block;
-  cursor: pointer;
   font-size: 1.15em;
   margin-bottom: 5px;
 }
 
+.uppy-DragDrop-browse {
+  color: $blue;
+  cursor: pointer;
+}
+
 .uppy-DragDrop-note {
   font-size: 1em;
   color: lighten($gray-500, 10%);
 }
-
-.uppy-DragDrop-browse {
-  color: $blue;
-}

+ 1 - 1
packages/@uppy/dropbox/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/dropbox",
   "description": "Import files from Dropbox, into Uppy.",
-  "version": "1.4.5",
+  "version": "1.4.8",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "types/index.d.ts",

+ 1 - 11
packages/@uppy/dropbox/src/index.js

@@ -12,7 +12,7 @@ module.exports = class Dropbox extends Plugin {
     Provider.initPlugin(this, opts)
     this.title = this.opts.title || 'Dropbox'
     this.icon = () => (
-      <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+      <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32">
         <g fill="none" fill-rule="evenodd">
           <rect fill="#0D2481" width="32" height="32" rx="16" />
           <path d="M11 8l5 3.185-5 3.186-5-3.186L11 8zm10 0l5 3.185-5 3.186-5-3.186L21 8zM6 17.556l5-3.185 5 3.185-5 3.186-5-3.186zm15-3.185l5 3.185-5 3.186-5-3.186 5-3.185zm-10 7.432l5-3.185 5 3.185-5 3.186-5-3.186z" fill="#FFF" fill-rule="nonzero" />
@@ -35,16 +35,6 @@ module.exports = class Dropbox extends Plugin {
     this.view = new ProviderViews(this, {
       provider: this.provider
     })
-    // Set default state for Dropbox
-    this.setPluginState({
-      authenticated: false,
-      files: [],
-      folders: [],
-      directories: [],
-      activeRow: -1,
-      filterInput: '',
-      isSearchVisible: false
-    })
 
     const target = this.opts.target
     if (target) {

+ 1 - 1
packages/@uppy/facebook/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/facebook",
   "description": "Import files from Facebook, into Uppy.",
-  "version": "1.1.5",
+  "version": "1.1.8",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "types/index.d.ts",

+ 1 - 11
packages/@uppy/facebook/src/index.js

@@ -12,7 +12,7 @@ module.exports = class Facebook extends Plugin {
     Provider.initPlugin(this, opts)
     this.title = this.opts.title || 'Facebook'
     this.icon = () => (
-      <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+      <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32">
         <g fill="none" fill-rule="evenodd">
           <rect width="32" height="32" rx="16" fill="#3C5A99" />
           <path d="M17.842 26v-8.667h2.653l.398-3.377h-3.051v-2.157c0-.978.248-1.644 1.527-1.644H21V7.132A19.914 19.914 0 0 0 18.623 7c-2.352 0-3.963 1.574-3.963 4.465v2.49H12v3.378h2.66V26h3.182z" fill="#FFF" fill-rule="nonzero" />
@@ -35,16 +35,6 @@ module.exports = class Facebook extends Plugin {
     this.view = new ProviderViews(this, {
       provider: this.provider
     })
-    // Set default state for Dropbox
-    this.setPluginState({
-      authenticated: false,
-      files: [],
-      folders: [],
-      directories: [],
-      activeRow: -1,
-      filterInput: '',
-      isSearchVisible: false
-    })
 
     const target = this.opts.target
     if (target) {

+ 1 - 1
packages/@uppy/file-input/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/file-input",
   "description": "Simple UI of a file input button that works with Uppy right out of the box",
-  "version": "1.4.11",
+  "version": "1.4.13",
   "license": "MIT",
   "main": "lib/index.js",
   "style": "dist/style.min.css",

+ 1 - 1
packages/@uppy/form/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/form",
   "description": "Connect Uppy to an existing HTML <form>.",
-  "version": "1.3.14",
+  "version": "1.3.16",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "types/index.d.ts",

+ 1 - 1
packages/@uppy/golden-retriever/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/golden-retriever",
   "description": "The GoldenRetriever Uppy plugin saves selected files in browser cache to seamlessly resume uploding after browser crash or accidentally closed tab",
-  "version": "1.3.13",
+  "version": "1.3.15",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "types/index.d.ts",

+ 1 - 1
packages/@uppy/google-drive/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/google-drive",
   "description": "The Google Drive plugin for Uppy lets users import files from their Google Drive account",
-  "version": "1.5.5",
+  "version": "1.5.8",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "types/index.d.ts",

+ 1 - 14
packages/@uppy/google-drive/src/index.js

@@ -13,7 +13,7 @@ module.exports = class GoogleDrive extends Plugin {
     Provider.initPlugin(this, opts)
     this.title = this.opts.title || 'Google Drive'
     this.icon = () => (
-      <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+      <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32">
         <g fill="none" fill-rule="evenodd">
           <rect fill="#4285F4" width="32" height="32" rx="16" />
           <path d="M10.324 23.3l3-5.1H25l-3 5.1H10.324zM13 18.2l-3 5.1-3-5.1 5.839-9.924 2.999 5.1L13 18.2zm11.838-.276h-6L13 8h6l5.84 9.924h-.002z" fill="#FFF" />
@@ -37,19 +37,6 @@ module.exports = class GoogleDrive extends Plugin {
     this.view = new DriveProviderViews(this, {
       provider: this.provider
     })
-    // Set default state for Google Drive
-    this.setPluginState({
-      authenticated: false,
-      files: [],
-      folders: [],
-      directories: [],
-      activeRow: -1,
-      filterInput: '',
-      isSearchVisible: false,
-      hasTeamDrives: false,
-      teamDrives: [],
-      teamDriveId: ''
-    })
 
     const target = this.opts.target
     if (target) {

+ 1 - 1
packages/@uppy/informer/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/informer",
   "description": "A notification and error pop-up bar for Uppy.",
-  "version": "1.5.5",
+  "version": "1.5.7",
   "license": "MIT",
   "main": "lib/index.js",
   "style": "dist/style.min.css",

+ 1 - 1
packages/@uppy/instagram/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/instagram",
   "description": "Import photos and videos from Instagram, into Uppy.",
-  "version": "1.4.5",
+  "version": "1.4.8",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "types/index.d.ts",

+ 1 - 11
packages/@uppy/instagram/src/index.js

@@ -12,7 +12,7 @@ module.exports = class Instagram extends Plugin {
     Provider.initPlugin(this, opts)
     this.title = this.opts.title || 'Instagram'
     this.icon = () => (
-      <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+      <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32">
         <g fill="none" fill-rule="evenodd">
           <rect fill="#E1306C" width="32" height="32" rx="16" />
           <path d="M16 8.622c2.403 0 2.688.009 3.637.052.877.04 1.354.187 1.67.31.392.144.745.374 1.036.673.299.29.529.644.673 1.035.123.317.27.794.31 1.671.043.95.052 1.234.052 3.637s-.009 2.688-.052 3.637c-.04.877-.187 1.354-.31 1.671a2.98 2.98 0 0 1-1.708 1.708c-.317.123-.794.27-1.671.31-.95.043-1.234.053-3.637.053s-2.688-.01-3.637-.053c-.877-.04-1.354-.187-1.671-.31a2.788 2.788 0 0 1-1.035-.673 2.788 2.788 0 0 1-.673-1.035c-.123-.317-.27-.794-.31-1.671-.043-.949-.052-1.234-.052-3.637s.009-2.688.052-3.637c.04-.877.187-1.354.31-1.67.144-.392.374-.745.673-1.036.29-.299.644-.529 1.035-.673.317-.123.794-.27 1.671-.31.95-.043 1.234-.052 3.637-.052zM16 7c-2.444 0-2.75.01-3.71.054-.959.044-1.613.196-2.185.419-.6.225-1.145.58-1.594 1.038-.458.45-.813.993-1.039 1.594-.222.572-.374 1.226-.418 2.184C7.01 13.25 7 13.556 7 16s.01 2.75.054 3.71c.044.959.196 1.613.419 2.185.226.6.58 1.145 1.038 1.594.45.458.993.813 1.594 1.038.572.223 1.227.375 2.184.419.96.044 1.267.054 3.711.054s2.75-.01 3.71-.054c.959-.044 1.613-.196 2.185-.419a4.602 4.602 0 0 0 2.632-2.632c.223-.572.375-1.226.419-2.184.044-.96.054-1.267.054-3.711s-.01-2.75-.054-3.71c-.044-.959-.196-1.613-.419-2.185A4.412 4.412 0 0 0 23.49 8.51a4.412 4.412 0 0 0-1.594-1.039c-.572-.222-1.226-.374-2.184-.418C18.75 7.01 18.444 7 16 7zm0 4.5a4.5 4.5 0 1 0 0 9 4.5 4.5 0 0 0 0-9zm0 7.421a2.921 2.921 0 1 1 0-5.842 2.921 2.921 0 0 1 0 5.842zm4.875-6.671a1.125 1.125 0 1 1 0-2.25 1.125 1.125 0 0 1 0 2.25z" fill="#FFF" />
@@ -40,16 +40,6 @@ module.exports = class Instagram extends Plugin {
       showFilter: false,
       showBreadcrumbs: false
     })
-    // Set default state for Instagram
-    this.setPluginState({
-      authenticated: false,
-      files: [],
-      folders: [],
-      directories: [],
-      activeRow: -1,
-      filterInput: '',
-      isSearchVisible: false
-    })
 
     const target = this.opts.target
     if (target) {

+ 1 - 1
packages/@uppy/locales/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/locales",
   "description": "Uppy language packs",
-  "version": "1.13.3",
+  "version": "1.15.0",
   "license": "MIT",
   "keywords": [
     "uppy",

+ 156 - 0
packages/@uppy/locales/src/bg_BG.js

@@ -0,0 +1,156 @@
+const bg_BG = {}
+
+bg_BG.strings = {
+  addBulkFilesFailed: {
+    '0': 'Файлът %{smart_count} не може да бъде добавен поради вътрешна грешка',
+    '1': 'Не могат да се добавят %{smart_count} файла поради вътрешни грешки'
+  },
+  addMore: 'Добави повече',
+  addMoreFiles: 'Добави повече файлове',
+  addingMoreFiles: 'Добавяне на повече файлове',
+  allowAccessDescription: 'За да правите изображения или запишете видео с камерата моля, разшерете достъп до камерата за този сайт',
+  allowAccessTitle: 'Моля, разрешете достъп до камерата',
+  authenticateWith: 'Свържете се с %{pluginName}',
+  authenticateWithTitle: 'Моля, впишете се с %{pluginName}, за да изберете файлове',
+  back: 'Назад',
+  browse: 'преглед',
+  cancel: 'Отказ',
+  cancelUpload: 'Отказване на качването',
+  chooseFiles: 'Изберете файлове',
+  closeModal: 'Затваряне на прозорец',
+  companionError: 'Неуспешна връзка с Companion',
+  companionUnauthorizeHint: 'За да се отпишете от акаута си в %{provider} посетете %{url}',
+  complete: 'Завършен',
+  connectedToInternet: 'Свързан с интернет',
+  copyLink: 'Копиране на линк',
+  copyLinkToClipboardFallback: 'Копиране на долния линк',
+  copyLinkToClipboardSuccess: 'Линкът е копиран',
+  creatingAssembly: 'Подготовка за качване...',
+  creatingAssemblyFailed: 'Transloadit: библиотеката не може да се създаде',
+  dashboardTitle: 'Качване на файлове',
+  dashboardWindowTitle: 'Прозорец за качване на файлове (Натиснете ESC за затваряне)',
+  dataUploadedOfTotal: '%{complete} от %{total}',
+  done: 'Готово',
+  dropHereOr: 'Пуснете файлове тук или %{browse}',
+  dropHint: 'Пуснете файловете си тук',
+  dropPaste: 'Пуснете файловете си тук, поставете или %{browse}',
+  dropPasteImport: 'Пуснете файловете си тук, поставете, %{browse} или импортирайте от:',
+  editFile: 'Редакция файл',
+  editing: 'Редактиране %{file}',
+  emptyFolderAdded: 'Не са добавени файлове от празна директория',
+  encoding: 'Кодиране...',
+  enterCorrectUrl: 'Неправилен адрес: Моля, уверете се, че въвеждате директна връзка към файл',
+  enterUrlToImport: 'Въведете адрес за да импортиране файл',
+  exceedsSize: 'Размерът на файла надвишава максимално разрешения размер от',
+  exceedsSize2: '%{backwardsCompat} %{size}',
+  failedToFetch: 'Companion не успя да достъпи този адрес, уверете се че е правилен',
+  failedToUpload: 'Грешка при качване на %{file}',
+  fileSource: 'Име на сорс файл: %{name}',
+  filesUploadedOfTotal: {
+    '0': '%{complete} от %{smart_count} файл качен',
+    '1': '%{complete} от %{smart_count} файлове качени'
+  },
+  filter: 'Филтър',
+  finishEditingFile: 'Край на редакцията на файла',
+  folderAdded: {
+    '0': 'Добавен %{smart_count} файл от %{folder}',
+    '1': 'Добавени %{smart_count} файлове от %{folder}'
+  },
+  generatingThumbnails: 'Генериране на миниатюри...',
+  import: 'Импортиране',
+  importFrom: 'Импортиране от %{name}',
+  loading: 'Зареждане...',
+  logOut: 'Изход',
+  micDisabled: 'Достъп до микрофонът е отказан от потребителя',
+  myDevice: 'Моето устройство',
+  noDuplicates: 'Файлът \'%{fileName}\' съществува. Не може да добавите дублиращи файлове',
+  noFilesFound: 'Тук нямате файлове или директории',
+  noInternetConnection: 'Няма връзка с интернет',
+  noNewAlreadyUploading: 'Не може да се добавят нови файлове: в процес на качване',
+  openFolderNamed: 'Отваряне на директория %{name}',
+  pause: 'Пауза',
+  pauseUpload: 'Паузиране на качването',
+  paused: 'Паузиран',
+  poweredBy: 'Powered by',
+  poweredBy2: '%{backwardsCompat} %{uppy}',
+  processingXFiles: {
+    '0': 'Обработване %{smart_count} файл',
+    '1': 'Обработване %{smart_count} файлове'
+  },
+  recording: 'Записване',
+  recordingLength: 'Дължина на записа %{recording_length}',
+  recordingStoppedMaxSize: 'Записът е прекъснат, защото размерът на файла наближава максимално допустимия размер',
+  removeFile: 'Премахване на файл',
+  resetFilter: 'Изчистване на филтър',
+  resume: 'Възстановяване',
+  resumeUpload: 'Възстановяване на качването',
+  retry: 'Нов опит',
+  retryUpload: 'Нов опит за качване',
+  saveChanges: 'Запис на промените',
+  selectAllFilesFromFolderNamed: 'Изберете всички файлове от директория %{name}',
+  selectFileNamed: 'Изберете файл %{name}',
+  selectX: {
+    '0': 'Избран %{smart_count}',
+    '1': 'Избрани %{smart_count}'
+  },
+  smile: 'Усмивка! ;)',
+  startCapturing: 'Започване запис на екрана',
+  startRecording: 'Започване запис на видео',
+  stopCapturing: 'Спиране на запис на екрана',
+  stopRecording: 'Спиране на видео запис',
+  streamActive: 'Активен стрийм',
+  streamPassive: 'Пасивен стрийм',
+  submitRecordedFile: 'Подаване на записаното видео',
+  takePicture: 'Направа на снимка',
+  timedOut: 'Качването е в застой за %{seconds} секунди, прекъсване.',
+  unselectAllFilesFromFolderNamed: 'Размаркиране на всички файлове от директория %{name}',
+  unselectFileNamed: 'Размаркиране файл %{name}',
+  upload: 'Качване',
+  uploadComplete: 'Качването е успешно',
+  uploadFailed: 'Качването неуспешно',
+  uploadPaused: 'Качването е паузирано',
+  uploadXFiles: {
+    '0': 'Качване %{smart_count} файл',
+    '1': 'качване %{smart_count} файлове'
+  },
+  uploadXNewFiles: {
+    '0': 'Качване +%{smart_count} файл',
+    '1': 'Качване +%{smart_count} файлове'
+  },
+  uploading: 'Качване',
+  uploadingXFiles: {
+    '0': 'Качване %{smart_count} файл',
+    '1': 'Качване %{smart_count} файлове'
+  },
+  xFilesSelected: {
+    '0': '%{smart_count} файл е избран',
+    '1': '%{smart_count} файлове са избрани'
+  },
+  xMoreFilesAdded: {
+    '0': 'Още %{smart_count} файл е добавен',
+    '1': 'Оше %{smart_count} файла са добавени'
+  },
+  xTimeLeft: 'Остават %{time}',
+  youCanOnlyUploadFileTypes: 'Можете да качване само файлове: %{types}',
+  youCanOnlyUploadX: {
+    '0': 'Може да качвате само %{smart_count} файл',
+    '1': 'Може да качвате само %{smart_count} файлове'
+  },
+  youHaveToAtLeastSelectX: {
+    '0': 'Трябва да изберете поне %{smart_count} файл',
+    '1': 'Трябва да изберете поне %{smart_count} файла'
+  }
+}
+
+bg_BG.pluralize = function (count) {
+  if (count === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.bg_BG = bg_BG
+}
+
+module.exports = bg_BG

+ 2 - 0
packages/@uppy/locales/src/en_US.js

@@ -63,6 +63,8 @@ en_US.strings = {
   logOut: 'Log out',
   micDisabled: 'Microphone access denied by user',
   myDevice: 'My Device',
+  noCameraDescription: 'In order to take pictures or record video, please connect a camera device',
+  noCameraTitle: 'Camera Not Available',
   noDuplicates: 'Cannot add the duplicate file \'%{fileName}\', it already exists',
   noFilesFound: 'You have no files or folders here',
   noInternetConnection: 'No Internet connection',

+ 16 - 16
packages/@uppy/locales/src/gl_ES.js

@@ -1,9 +1,9 @@
 const gl_ES = {}
 
 gl_ES.strings = {
-  addMore: 'añadir más',
-  addMoreFiles: 'Agregar máis arquivos',
-  addingMoreFiles: 'Agregando máis arquivos',
+  addMore: 'Engadir máis',
+  addMoreFiles: 'Engadir máis arquivos',
+  addingMoreFiles: 'Engadir máis arquivos',
   allowAccessDescription: 'Para tomar fotos ou grabar video coa túa cámara, por favor permite a este sitio o acceso á cámara.',
   allowAccessTitle: 'Por favor permite o acceso á tua cámara',
   authenticateWith: 'Conectar a %{pluginName}',
@@ -48,9 +48,9 @@ gl_ES.strings = {
   filter: 'Filtrar',
   finishEditingFile: 'Rematar edición de arquivo',
   folderAdded: {
-    '0': 'Agregado %{smart_count} arquivo dende %{folder}',
-    '1': 'Agregados %{smart_count} arquivos dende %{folder}',
-    '2': 'Agregados %{smart_count} arquivos dende %{folder}'
+    '0': 'Engadido %{smart_count} arquivo dende %{folder}',
+    '1': 'Engadidos %{smart_count} arquivos dende %{folder}',
+    '2': 'Engadidos %{smart_count} arquivos dende %{folder}'
   },
   import: 'Importar',
   importFrom: 'Importar dende %{name}',
@@ -82,7 +82,7 @@ gl_ES.strings = {
   },
   smile: 'Sorrí!',
   startRecording: 'Comezar a grabación de vídeo',
-  stopRecording: 'Detener la grabación de vídeo',
+  stopRecording: 'Deter a grabación de vídeo',
   takePicture: 'Tomar unha foto',
   timedOut: 'Subida estancada por %{seconds} segundos, anulando.',
   upload: 'Subir',
@@ -99,7 +99,7 @@ gl_ES.strings = {
     '1': 'Subir +%{smart_count} arquivos',
     '2': 'Subir +%{smart_count} arquivos'
   },
-  uploading: 'Subiendo',
+  uploading: 'Subindo',
   uploadingXFiles: {
     '0': 'Subindo %{smart_count} arquivo',
     '1': 'Subindo %{smart_count} arquivos',
@@ -111,9 +111,9 @@ gl_ES.strings = {
     '2': '%{smart_count} arquivos seleccionados'
   },
   xMoreFilesAdded: {
-    '0': '%{smart_count} arquivo máis agregado',
-    '1': '%{smart_count} arquivos máis agregados',
-    '2': '%{smart_count} arquivos máis agregados'
+    '0': '%{smart_count} arquivo máis engadido',
+    '1': '%{smart_count} arquivos máis engadidos',
+    '2': '%{smart_count} arquivos máis engadidos'
   },
   xTimeLeft: '%{time} restantes',
   youCanOnlyUploadFileTypes: 'Soamente podes subir: %{types}',
@@ -127,11 +127,11 @@ gl_ES.strings = {
     '1': 'Tes que seleccionar polo menos %{smart_count} arquivos',
     '2': 'Tes que seleccionar polo menos %{smart_count} arquivos'
   },
-  selectAllFilesFromFolderNamed: 'Seleccionar todos los archivos de la carpeta %{name}',
-  unselectAllFilesFromFolderNamed: 'Deselecciona todos los archivos de la carpeta %{name}',
-  selectFileNamed: 'Seleccione archivo %{name}',
-  unselectFileNamed: 'Deseleccionar archivo %{name}',
-  openFolderNamed: 'Carpeta abierta %{name}'
+  selectAllFilesFromFolderNamed: 'Seleccionar todos os arquivos do cartafol %{name}',
+  unselectAllFilesFromFolderNamed: 'Deselecciona todos os arquivos do cartafol %{name}',
+  selectFileNamed: 'Seleccione arquivo %{name}',
+  unselectFileNamed: 'Deseleccionar arquivo %{name}',
+  openFolderNamed: 'Cartafol aberto %{name}'
 }
 
 gl_ES.pluralize = function (n) {

+ 165 - 0
packages/@uppy/locales/src/sk_SK.js

@@ -0,0 +1,165 @@
+const sk_SK = {}
+
+sk_SK.strings = {
+  addBulkFilesFailed: {
+    '0': 'Súbor %{smart_count} sa nepodarilo pridať z dôvodu vnútornej chyby',
+    '1': 'Nepodarilo sa pridať %{smart_count} súbory z dôvodu vnútorných chýb',
+    '2': 'Nepodarilo sa pridať %{smart_count} súborov z dôvodu vnútorných chýb'
+  },
+  addMore: 'Pridať ďalšie',
+  addMoreFiles: 'Pridať ďalšie súbory',
+  addingMoreFiles: 'Pridávajú sa ďalšie súbory',
+  allowAccessDescription: 'Pokiaľ chcete urobiť fotografiu alebo nahrať video vaším zariadením, povoľte prosím prístup ku kamere.',
+  allowAccessTitle: 'Povoľte prístup ku kamere',
+  authenticateWith: 'Pripojiť k %{pluginName}',
+  authenticateWithTitle: 'Prosím príhláste sa k %{pluginName} pre výber súborov',
+  back: 'Späť',
+  browse: 'prechádzať',
+  cancel: 'Zrušiť',
+  cancelUpload: 'Zrušiť nahrávanie',
+  chooseFiles: 'Vyberte súbory',
+  closeModal: 'Zavrieť okno',
+  companionError: 'Spojenie s modulom Companion sa nepodarilo',
+  companionUnauthorizeHint: 'Pokiaľ nechcete povoliť prístup k účtu %{provider}, prosím prejdite na túto adresu %{url}',
+  complete: 'Hotovo',
+  connectedToInternet: 'Pripojené k internetu',
+  copyLink: 'Kopírovať odkaz',
+  copyLinkToClipboardFallback: 'Skopírujte odkaz nižšie',
+  copyLinkToClipboardSuccess: 'Odkaz bol skopírovaný do schránky',
+  creatingAssembly: 'Pripravuje sa nahrávanie...',
+  creatingAssemblyFailed: 'Transloadit: Nepodarilo sa vytvoriť Assembly',
+  dashboardTitle: 'Nahrať súbory',
+  dashboardWindowTitle: 'Okno na nahrávanie súborov. (Stlačením ESC ho zavriete)',
+  dataUploadedOfTotal: '%{complete} z %{total}',
+  done: 'Dokončené',
+  dropHereOr: 'Presuňte sem súbory alebo %{browse}',
+  dropHint: 'Presuňte sem súbory',
+  dropPaste: 'Presuňte sem súbory alebo %{browse}',
+  dropPasteImport: 'Presuňte sem súbory, vložte ich, %{browse} alebo ich importujte:',
+  editFile: 'Upraviť súbor',
+  editing: 'Úprava %{file}',
+  emptyFolderAdded: 'Neboli pridané žiadne súbory, pretože adresár je prázdny.',
+  encoding: 'Konvertovanie...',
+  enterCorrectUrl: 'Nesprávna adresa URL: Uistite sa, že zadávate priamy odkaz na súbor',
+  enterUrlToImport: 'Ak chcete importovať súbor, zadajte adresu URL',
+  exceedsSize: 'Tento súbor presahuje maximálnu povolenú veľkosť súboru',
+  exceedsSize2: '%{backwardsCompat} %{size}',
+  failedToFetch: 'Nepodarilo sa načítať túto webovú adresu. Skontrolujte, či je správna',
+  failedToUpload: 'Nepodarilo sa nahrať súbor %{file}',
+  fileSource: 'Zdroj súboru: %{name}',
+  filesUploadedOfTotal: {
+    '0': '%{complete} z %{smart_count} nahraných súborov',
+    '1': '%{complete} z %{smart_count} nahraných súborov'
+  },
+  filter: 'Filtrovať',
+  finishEditingFile: 'Dokončiť úpravu súborov',
+  folderAdded: {
+    '0': 'Pridaný %{smart_count} súbor zo zložky %{folder}',
+    '1': 'Pridané %{smart_count} súbory zo zložky %{folder}',
+    '2': 'Pridaných %{smart_count} súborov zo zložky %{folder}'
+  },
+  generatingThumbnails: 'Vytváram miniatury...',
+  import: 'Importovať',
+  importFrom: 'Import z %{name}',
+  loading: 'Nahrávanie...',
+  logOut: 'Odhlásiť',
+  micDisabled: 'Užívateľ odmietol prístup k mikrofónu',
+  myDevice: 'Moje zariadenie',
+  noDuplicates: 'Nemôžete pridať duplikátny súbor \'%{fileName}\', ktorý už existuje',
+  noFilesFound: 'Nemáte pridané žiadne súbory ani zložky',
+  noInternetConnection: 'Žiadne internetové pripojenie',
+  noNewAlreadyUploading: 'Počas nahrávania nemôžete pridať ďalšie súbory',
+  openFolderNamed: 'Otvoriť zložku %{name}',
+  pause: 'Pozastaviť',
+  pauseUpload: 'Pozastaviť nahrávanie',
+  paused: 'Pozastavené',
+  poweredBy: 'Vytvorení pomocou',
+  poweredBy2: '%{backwardsCompat} %{uppy}',
+  processingXFiles: {
+    '0': 'Spracovanie %{smart_count} súboru',
+    '1': 'Spracovanie %{smart_count} súborov'
+  },
+  recording: 'Nahrávanie',
+  recordingLength: 'Dĺžka záznamu %{recording_length}',
+  recordingStoppedMaxSize: 'Nahrávanie sa zastavilo, pretože veľkosť súboru sa chystá prekročiť limit',
+  removeFile: 'Odstrániť súbor',
+  resetFilter: 'Zrušiť filter',
+  resume: 'Pokračovať',
+  resumeUpload: 'Pokračovať v nahrávaní',
+  retry: 'Opakovať',
+  retryUpload: 'Opakovať nahrávanie',
+  saveChanges: 'Uložiť zmeny',
+  selectAllFilesFromFolderNamed: 'Vybrať všetky súbory zo zložky %{name}',
+  selectFileNamed: 'Vybrať súbor %{name}',
+  selectX: {
+    '0': 'Vybrať %{smart_count}',
+    '1': 'Vybrať %{smart_count}'
+  },
+  smile: 'Úsmev prosím!',
+  startCapturing: 'Spustiť snímanie obrazovky',
+  startRecording: 'Spustiť nahrávanie videa',
+  stopCapturing: 'Zastaviť snímanie obrazovky',
+  stopRecording: 'Zastaviť nahrávanie videa',
+  streamActive: 'Stream je aktívny',
+  streamPassive: 'Stream nieje aktívny',
+  submitRecordedFile: 'Odoslať nahraté video',
+  takePicture: 'Urobiť fotku',
+  timedOut: 'Nahrávanie bolo prerušené na %{seconds}, prerušuje sa.',
+  unselectAllFilesFromFolderNamed: 'Zrušiť výber všetkých súborov zo zložky %{name}',
+  unselectFileNamed: 'Zrušiť výber súboru %{name}',
+  upload: 'Nahrať',
+  uploadComplete: 'Nahrávanie dokončené',
+  uploadFailed: 'Nahrávanie sa nepodarilo',
+  uploadPaused: 'Nahrávanie pozastavené',
+  uploadXFiles: {
+    '0': 'Nahrať %{smart_count} súbor',
+    '1': 'Nahrať %{smart_count} súbory',
+    '2': 'Nahrať %{smart_count} súborov'
+  },
+  uploadXNewFiles: {
+    '0': 'Nahrať +%{smart_count} súbor',
+    '1': 'Nahrať +%{smart_count} súbory',
+    '2': 'Nahrať +%{smart_count} súborov'
+  },
+  uploading: 'Nahrávanie',
+  uploadingXFiles: {
+    '0': 'Nahrávám %{smart_count} súbor',
+    '1': 'Nahrávám %{smart_count} súbory',
+    '2': 'Nahrávám %{smart_count} súborov'
+  },
+  xFilesSelected: {
+    '0': '%{smart_count} vybratý súbor',
+    '1': '%{smart_count} vybrané súbory',
+    '2': '%{smart_count} vybraných súborov'
+  },
+  xMoreFilesAdded: {
+    '0': '%{smart_count} ďalší pridaný súbor',
+    '1': '%{smart_count} ďalšie pridané súbory',
+    '2': '%{smart_count} ďalších pridaných súborov'
+  },
+  xTimeLeft: '%{time} zostáva',
+  youCanOnlyUploadFileTypes: 'Môžete nahrať iba tieto typy súborov: %{types}',
+  youCanOnlyUploadX: {
+    '0': 'Môžete nahrať iba %{smart_count} súbor',
+    '1': 'Môžete nahrať iba %{smart_count} súbory',
+    '2': 'Môžete nahrať iba %{smart_count} súborov'
+  },
+  youHaveToAtLeastSelectX: {
+    '0': 'Musíte vybrať aspoň %{smart_count} súbor',
+    '1': 'Musíte vybrať aspoň %{smart_count} súbory',
+    '2': 'Musíte vybrať aspoň %{smart_count} súborov'
+  }
+}
+
+sk_SK.pluralize = function (count) {
+  if (count === 1) {
+    return 0
+  }
+  return 1
+}
+
+if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
+  window.Uppy.locales.sk_SK = sk_SK
+}
+
+module.exports = sk_SK

+ 73 - 63
packages/@uppy/locales/src/zh_CN.js

@@ -1,137 +1,147 @@
 const zh_CN = {}
 
 zh_CN.strings = {
+  addBulkFilesFailed: {
+    '0': '内部错误导致添加 %{smart_count} 个文件失败',
+    '1': '内部错误导致添加 %{smart_count} 个文件失败'
+  },
+  addMore: '添加更多文件',
   addMoreFiles: '添加更多文件',
-  addingMoreFiles: '正在添加更多文件',
+  addingMoreFiles: '添加更多文件',
   allowAccessDescription: '为了通过您的相机进行拍照或录像,请给网站相机的访问权限',
   allowAccessTitle: '请允许对相机的访问权限',
-  authenticateWith: '连接到%{pluginName}',
-  authenticateWithTitle: '请使用%{pluginName}进行身份验证以选择文件',
+  authenticateWith: '连接到 %{pluginName}',
+  authenticateWithTitle: '请使用 %{pluginName} 进行认证以选择文件',
   back: '返回',
-  addMore: '添加更多',
   browse: '浏览',
   cancel: '取消',
   cancelUpload: '取消上传',
   chooseFiles: '选择文件',
-  closeModal: '关闭模态',
-  companionError: '与Companion的连接失败',
-  complete: '完成',
+  closeModal: '关闭窗口',
+  companionError: '和 Companion 连接失败了',
+  companionUnauthorizeHint: '请访问 %{url} 以认证您的 %{provider} 账户',
+  complete: '上传完毕',
   connectedToInternet: '连接至网络',
   copyLink: '复制链接',
   copyLinkToClipboardFallback: '复制以下网址',
   copyLinkToClipboardSuccess: '链接已复制到剪贴板',
-  creatingAssembly: '准备上传中...',
+  creatingAssembly: '正在准备上传…',
   creatingAssemblyFailed: 'Transloadit:无法创建程序集',
   dashboardTitle: '文件上传工具',
   dashboardWindowTitle: '文件上传工具窗口(点击离开以关闭)',
   dataUploadedOfTotal: '%{total}%{complete}',
   done: '完成',
-  dropHereOr: '拖动文件到这里或%{browse}',
-  dropHint: '拖文件到这里',
-  dropPaste: '拖动文件到这里,粘贴或者%{browse}',
-  dropPasteImport: '拖动文件到这里,粘贴,%{browse}或者导入',
+  dropHereOr: '拖拽文件到这里,或%{browse}',
+  dropHint: '拖文件到这里',
+  dropPaste: '拖拽文件到这里,或者%{browse}文件',
+  dropPasteImport: '拖拽文件到这里,粘贴、%{browse}或者导入',
   editFile: '编辑文件',
-  editing: '正在编辑%{file}',
+  editing: '正在编辑 %{file}',
   emptyFolderAdded: '无法从空文件夹添加文件',
-  encoding: '编码中...',
+  encoding: '正在编码…',
   enterCorrectUrl: '错误链接: 请确认您输入的是文件的链接',
   enterUrlToImport: '输入链接或者导入文件',
-  exceedsSize: '此文件超出允许的最大大小',
-  failedToFetch: 'Companion无法抓取此链接,请确保它是正确的',
-  failedToUpload: '上传%{file}失败',
+  exceedsSize: '文件超过了最大尺寸限制',
+  exceedsSize2: '%{backwardsCompat} %{size}',
+  failedToFetch: 'Companion 无法抓取此链接,请确保它是正确的',
+  failedToUpload: '上传 %{file} 失败',
   fileSource: '文件源:%{name}',
   filesUploadedOfTotal: {
-    '0': '%{smart_count}个文件上传%{complete}',
-    '1': '%{smart_count}个文件上传%{complete}',
-    '2': '%{smart_count}个文件上传%{complete}'
+    '0': '%{smart_count} 个文件上传 %{complete}',
+    '1': '%{smart_count} 个文件上传 %{complete}'
   },
   filter: '筛选器',
   finishEditingFile: '完成文件编辑',
   folderAdded: {
-    '0': '从%{folder}添加了%{smart_count}个文件',
-    '1': '从%{folder}添加了%{smart_count}个文件',
-    '2': '从%{folder}添加了%{smart_count}个文件'
+    '0': '从 %{folder} 添加了 %{smart_count} 个文件',
+    '1': '从 %{folder} 添加了 %{smart_count} 个文件'
   },
+  generatingThumbnails: '正在生成缩略图…',
   import: '导入',
-  importFrom: '从%{name}导入',
-  loading: '载入中...',
+  importFrom: '从 %{name} 导入',
+  loading: '正在载入…',
   logOut: '登出',
+  micDisabled: '麦克风的权限访问被用户拒绝',
   myDevice: '我的设备',
+  noCameraDescription: '为了拍摄照片或录制视频,请连接一个摄像设备',
+  noCameraTitle: '摄像头不可用',
+  noDuplicates: '无法添加重复文件 %{fileName},该文件已存在',
   noFilesFound: '这里空空如也',
   noInternetConnection: '无法连接到网络',
+  noNewAlreadyUploading: '无法添加新文件:已正在上传文件',
+  openFolderNamed: '打开文件夹 %{name}',
   pause: '暂停',
   pauseUpload: '暂停上传',
   paused: '已暂停',
   poweredBy: '强力驱动于',
+  poweredBy2: '%{backwardsCompat} %{uppy}',
   processingXFiles: {
-    '0': '%{smart_count}个文件处理中',
-    '1': '%{smart_count}个文件处理中',
-    '2': '%{smart_count}个文件处理中'
+    '0': '正在处理 %{smart_count} 个文件',
+    '1': '正在处理 %{smart_count} 个文件'
   },
-  removeFile: '移除文件',
+  recording: '正在录制',
+  recordingLength: '录制长度 %{recording_length}',
+  recordingStoppedMaxSize: '录像已停止,文件大小即将超过限制',
+  removeFile: '删除文件',
   resetFilter: '重置筛选器',
   resume: '恢复',
   resumeUpload: '恢复上传',
   retry: '重试',
-  retryUpload: '重试上传',
+  retryUpload: '重试',
   saveChanges: '保存变更',
+  selectAllFilesFromFolderNamed: '从文件夹中选择所有文件 %{name}',
+  selectFileNamed: '选择文件 %{name}',
   selectX: {
-    '0': '选择%{smart_count}',
-    '1': '选择%{smart_count}',
-    '2': '选择%{smart_count}'
+    '0': '选择 %{smart_count}',
+    '1': '选择 %{smart_count}'
   },
-  smile: '笑!',
+  smile: '笑一笑!',
+  startCapturing: '开始屏幕录制',
   startRecording: '开始视频录制',
+  stopCapturing: '停止屏幕录制',
   stopRecording: '停止视频录制',
+  streamActive: '视频流已激活',
+  streamPassive: '视频流未激活',
+  submitRecordedFile: '提交已录制视频',
   takePicture: '拍照',
-  timedOut: '上传已经停滞不前%{seconds}秒,中止上传',
+  timedOut: '上传已超时 %{seconds} 秒,中止上传',
+  unselectAllFilesFromFolderNamed: '取消选择文件夹中的所有文件 %{name}',
+  unselectFileNamed: '取消选择文件 %{name}',
   upload: '上传',
   uploadComplete: '上传完成',
   uploadFailed: '上传失败',
   uploadPaused: '上传暂停',
   uploadXFiles: {
-    '0': '上传%{smart_count}个文件',
-    '1': '上传%{smart_count}个文件',
-    '2': '上传%{smart_count}个文件'
+    '0': '上传 %{smart_count} 个文件',
+    '1': '上传 %{smart_count} 个文件'
   },
   uploadXNewFiles: {
-    '0': '新上传了%{smart_count}个文件',
-    '1': '新上传了%{smart_count}个文件',
-    '2': '新上传了%{smart_count}个文件'
+    '0': '新上传了 %{smart_count} 个文件',
+    '1': '新上传了 %{smart_count} 个文件'
   },
   uploading: '正在上传',
   uploadingXFiles: {
-    '0': '正在上传%{smart_count}个文件',
-    '1': '正在上传%{smart_count}个文件',
-    '2': '正在上传%{smart_count}个文件'
+    '0': '正在上传 %{smart_count} 个文件',
+    '1': '正在上传 %{smart_count} 个文件'
   },
   xFilesSelected: {
-    '0': '%{smart_count}个文件被选中',
-    '1': '%{smart_count}个文件被选中',
-    '2': '%{smart_count}个文件被选中'
+    '0': '%{smart_count} 个文件待上传',
+    '1': '%{smart_count} 个文件待上传'
   },
   xMoreFilesAdded: {
-    '0': '又有%{smart_count}个文件被添加',
-    '1': '又有%{smart_count}个文件被添加',
-    '2': '又有%{smart_count}个文件被添加'
+    '0': '又有 %{smart_count} 个文件被添加',
+    '1': '又有 %{smart_count} 个文件被添加'
   },
-  xTimeLeft: '还剩下%{time}',
+  xTimeLeft: '还剩下 %{time}',
   youCanOnlyUploadFileTypes: '您只能上传这些文件类型:%{types}',
   youCanOnlyUploadX: {
-    '0': '您只能上传%{smart_count}个文件',
-    '1': '您只能上传%{smart_count}个文件',
-    '2': '您只能上传%{smart_count}个文件'
+    '0': '您只能上传 %{smart_count} 个文件',
+    '1': '您只能上传 %{smart_count} 个文件'
   },
   youHaveToAtLeastSelectX: {
-    '0': '您至少要选择%{smart_count}个文件',
-    '1': '您至少要选择%{smart_count}个文件',
-    '2': '您至少要选择%{smart_count}个文件'
-  },
-  selectAllFilesFromFolderNamed: '从文件夹中选择所有文件 %{name}',
-  unselectAllFilesFromFolderNamed: '取消选择文件夹中的所有文件 %{name}',
-  selectFileNamed: '选择文件 %{name}',
-  unselectFileNamed: '取消选择文件 %{name}',
-  openFolderNamed: '打开文件夹 %{name}'
+    '0': '您至少要选择 %{smart_count} 个文件',
+    '1': '您至少要选择 %{smart_count} 个文件'
+  }
 }
 
 zh_CN.pluralize = function (n) {

+ 1 - 1
packages/@uppy/onedrive/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/onedrive",
   "description": "Import files from OneDrive, into Uppy.",
-  "version": "1.1.5",
+  "version": "1.1.8",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "types/index.d.ts",

+ 1 - 11
packages/@uppy/onedrive/src/index.js

@@ -12,7 +12,7 @@ module.exports = class OneDrive extends Plugin {
     Provider.initPlugin(this, opts)
     this.title = this.opts.title || 'OneDrive'
     this.icon = () => (
-      <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+      <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32">
         <g fill="none" fill-rule="evenodd">
           <rect width="32" height="32" rx="16" fill="#0262C0" />
           <g fill="#FFF" fill-rule="nonzero">
@@ -38,16 +38,6 @@ module.exports = class OneDrive extends Plugin {
     this.view = new ProviderViews(this, {
       provider: this.provider
     })
-    // Set default state for Dropbox
-    this.setPluginState({
-      authenticated: false,
-      files: [],
-      folders: [],
-      directories: [],
-      activeRow: -1,
-      filterInput: '',
-      isSearchVisible: false
-    })
 
     const target = this.opts.target
     if (target) {

+ 1 - 1
packages/@uppy/progress-bar/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/progress-bar",
   "description": "A progress bar UI for Uppy",
-  "version": "1.3.13",
+  "version": "1.3.15",
   "license": "MIT",
   "main": "lib/index.js",
   "style": "dist/style.min.css",

+ 1 - 1
packages/@uppy/provider-views/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/provider-views",
   "description": "View library for Uppy remote provider plugins.",
-  "version": "1.6.5",
+  "version": "1.6.8",
   "license": "MIT",
   "main": "lib/index.js",
   "style": "dist/style.min.css",

+ 2 - 2
packages/@uppy/provider-views/src/Filter.js

@@ -27,7 +27,7 @@ module.exports = class Filter extends Component {
           oninput={(e) => this.props.filterQuery(e)}
           value={this.props.filterInput}
         />
-        <svg aria-hidden="true" focusable="false" class="UppyIcon uppy-ProviderBrowser-searchIcon" width="12" height="12" viewBox="0 0 12 12">
+        <svg aria-hidden="true" focusable="false" class="uppy-c-icon uppy-ProviderBrowser-searchIcon" width="12" height="12" viewBox="0 0 12 12">
           <path d="M8.638 7.99l3.172 3.172a.492.492 0 1 1-.697.697L7.91 8.656a4.977 4.977 0 0 1-2.983.983C2.206 9.639 0 7.481 0 4.819 0 2.158 2.206 0 4.927 0c2.721 0 4.927 2.158 4.927 4.82a4.74 4.74 0 0 1-1.216 3.17zm-3.71.685c2.176 0 3.94-1.726 3.94-3.856 0-2.129-1.764-3.855-3.94-3.855C2.75.964.984 2.69.984 4.819c0 2.13 1.765 3.856 3.942 3.856z" />
         </svg>
         {this.props.filterInput && (
@@ -38,7 +38,7 @@ module.exports = class Filter extends Component {
             title={this.props.i18n('resetFilter')}
             onclick={this.props.filterQuery}
           >
-            <svg aria-hidden="true" focusable="false" class="UppyIcon" viewBox="0 0 19 19">
+            <svg aria-hidden="true" focusable="false" class="uppy-c-icon" viewBox="0 0 19 19">
               <path d="M17.318 17.232L9.94 9.854 9.586 9.5l-.354.354-7.378 7.378h.707l-.62-.62v.706L9.318 9.94l.354-.354-.354-.354L1.94 1.854v.707l.62-.62h-.706l7.378 7.378.354.354.354-.354 7.378-7.378h-.707l.622.62v-.706L9.854 9.232l-.354.354.354.354 7.378 7.378.708-.707-7.38-7.378v.708l7.38-7.38.353-.353-.353-.353-.622-.622-.353-.353-.354.352-7.378 7.38h.708L2.56 1.23 2.208.88l-.353.353-.622.62-.353.355.352.353 7.38 7.38v-.708l-7.38 7.38-.353.353.352.353.622.622.353.353.354-.353 7.38-7.38h-.708l7.38 7.38z" />
             </svg>
           </button>

+ 2 - 2
packages/@uppy/provider-views/src/Item/components/ItemIcon.js

@@ -2,7 +2,7 @@ const { h } = require('preact')
 
 function FileIcon () {
   return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" width={11} height={14.5} viewBox="0 0 44 58">
+    <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width={11} height={14.5} viewBox="0 0 44 58">
       <path d="M27.437.517a1 1 0 0 0-.094.03H4.25C2.037.548.217 2.368.217 4.58v48.405c0 2.212 1.82 4.03 4.03 4.03H39.03c2.21 0 4.03-1.818 4.03-4.03V15.61a1 1 0 0 0-.03-.28 1 1 0 0 0 0-.093 1 1 0 0 0-.03-.032 1 1 0 0 0 0-.03 1 1 0 0 0-.032-.063 1 1 0 0 0-.03-.063 1 1 0 0 0-.032 0 1 1 0 0 0-.03-.063 1 1 0 0 0-.032-.03 1 1 0 0 0-.03-.063 1 1 0 0 0-.063-.062l-14.593-14a1 1 0 0 0-.062-.062A1 1 0 0 0 28 .708a1 1 0 0 0-.374-.157 1 1 0 0 0-.156 0 1 1 0 0 0-.03-.03l-.003-.003zM4.25 2.547h22.218v9.97c0 2.21 1.82 4.03 4.03 4.03h10.564v36.438a2.02 2.02 0 0 1-2.032 2.032H4.25c-1.13 0-2.032-.9-2.032-2.032V4.58c0-1.13.902-2.032 2.03-2.032zm24.218 1.345l10.375 9.937.75.718H30.5c-1.13 0-2.032-.9-2.032-2.03V3.89z" />
     </svg>
   )
@@ -10,7 +10,7 @@ function FileIcon () {
 
 function FolderIcon () {
   return (
-    <svg aria-hidden="true" focusable="false" class="UppyIcon" style={{ width: 16, marginRight: 3 }} viewBox="0 0 276.157 276.157">
+    <svg aria-hidden="true" focusable="false" class="uppy-c-icon" style={{ width: 16, marginRight: 3 }} viewBox="0 0 276.157 276.157">
       <path d="M273.08 101.378c-3.3-4.65-8.86-7.32-15.254-7.32h-24.34V67.59c0-10.2-8.3-18.5-18.5-18.5h-85.322c-3.63 0-9.295-2.875-11.436-5.805l-6.386-8.735c-4.982-6.814-15.104-11.954-23.546-11.954H58.73c-9.292 0-18.638 6.608-21.737 15.372l-2.033 5.752c-.958 2.71-4.72 5.37-7.596 5.37H18.5C8.3 49.09 0 57.39 0 67.59v167.07c0 .886.16 1.73.443 2.52.152 3.306 1.18 6.424 3.053 9.064 3.3 4.652 8.86 7.32 15.255 7.32h188.487c11.395 0 23.27-8.425 27.035-19.18l40.677-116.188c2.11-6.035 1.43-12.164-1.87-16.816zM18.5 64.088h8.864c9.295 0 18.64-6.607 21.738-15.37l2.032-5.75c.96-2.712 4.722-5.373 7.597-5.373h29.565c3.63 0 9.295 2.876 11.437 5.806l6.386 8.735c4.982 6.815 15.104 11.954 23.546 11.954h85.322c1.898 0 3.5 1.602 3.5 3.5v26.47H69.34c-11.395 0-23.27 8.423-27.035 19.178L15 191.23V67.59c0-1.898 1.603-3.5 3.5-3.5zm242.29 49.15l-40.676 116.188c-1.674 4.78-7.812 9.135-12.877 9.135H18.75c-1.447 0-2.576-.372-3.02-.997-.442-.625-.422-1.814.057-3.18l40.677-116.19c1.674-4.78 7.812-9.134 12.877-9.134h188.487c1.448 0 2.577.372 3.02.997.443.625.423 1.814-.056 3.18z" />
     </svg>
   )

+ 11 - 0
packages/@uppy/provider-views/src/index.js

@@ -85,6 +85,17 @@ module.exports = class ProviderView {
     this.render = this.render.bind(this)
 
     this.clearSelection()
+
+    // Set default state for the plugin
+    this.plugin.setPluginState({
+      authenticated: false,
+      files: [],
+      folders: [],
+      directories: [],
+      activeRow: -1,
+      filterInput: '',
+      isSearchVisible: false
+    })
   }
 
   tearDown () {

+ 1 - 1
packages/@uppy/react/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/react",
   "description": "React component wrappers around Uppy's official UI plugins.",
-  "version": "1.6.5",
+  "version": "1.8.1",
   "license": "MIT",
   "main": "index.js",
   "module": "index.mjs",

+ 2 - 2
packages/@uppy/robodog/README.md

@@ -18,8 +18,8 @@ We recommend installing from npm and then using a module bundler such as [Webpac
 Alternatively, you can also use this package in a pre-built bundle from Transloadit's CDN: Edgly.
 
 ```html
-<link rel="stylesheet" href="https://transloadit.edgly.net/releases/uppy/v1.14.1/robodog.min.css">
-<script src="https://transloadit.edgly.net/releases/uppy/v1.14.1/robodog.min.js"></script>
+<link rel="stylesheet" href="https://transloadit.edgly.net/releases/uppy/v1.16.1/robodog.min.css">
+<script src="https://transloadit.edgly.net/releases/uppy/v1.16.1/robodog.min.js"></script>
 ```
 
 Then, a global `Robodog` variable will be available. For usage instructions, please see the [main Robodog documentation](https://uppy.io/docs/robodog).

+ 1 - 1
packages/@uppy/robodog/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/robodog",
   "description": "Transloadit SDK for browsers based on Uppy",
-  "version": "1.6.6",
+  "version": "1.7.1",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",

+ 1 - 1
packages/@uppy/robodog/src/addDashboardPlugin.js

@@ -9,7 +9,7 @@ const dashboardOptionNames = [
   'showLinkToFileUploadResult',
   'showProgressDetails',
   'hideRetryButton',
-  'hidePauseResumeCancelButtons',
+  'hideCancelButton',
   'hideUploadButton',
   'hideProgressAfterFinish',
   'note',

+ 1 - 1
packages/@uppy/screen-capture/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/screen-capture",
   "description": "Uppy plugin that captures video from display or application.",
-  "version": "1.0.1",
+  "version": "1.0.3",
   "license": "MIT",
   "main": "lib/index.js",
   "style": "dist/style.min.css",

+ 2 - 2
packages/@uppy/screen-capture/src/RecordButton.js

@@ -14,7 +14,7 @@ module.exports = function RecordButton ({ recording, onStartRecording, onStopRec
         onclick={onStopRecording}
         data-uppy-super-focusable
       >
-        <svg aria-hidden="true" focusable="false" class="UppyIcon" width="100" height="100" viewBox="0 0 100 100">
+        <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="100" height="100" viewBox="0 0 100 100">
           <rect x="15" y="15" width="70" height="70" />
         </svg>
       </button>
@@ -30,7 +30,7 @@ module.exports = function RecordButton ({ recording, onStartRecording, onStopRec
       onclick={onStartRecording}
       data-uppy-super-focusable
     >
-      <svg aria-hidden="true" focusable="false" class="UppyIcon" width="100" height="100" viewBox="0 0 100 100">
+      <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="100" height="100" viewBox="0 0 100 100">
         <circle cx="50" cy="50" r="40" />
       </svg>
     </button>

+ 1 - 1
packages/@uppy/screen-capture/src/ScreenRecIcon.js

@@ -2,7 +2,7 @@ const { h } = require('preact')
 
 module.exports = () => {
   return (
-    <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
+    <svg aria-hidden="true" focusable="false" width="32" height="32" viewBox="0 0 32 32">
       <g fill="none" fill-rule="evenodd">
         <rect fill="#2C3E50" width="32" height="32" rx="16" />
         <path d="M24.182 9H7.818C6.81 9 6 9.742 6 10.667v10c0 .916.81 1.666 1.818 1.666h4.546V24h7.272v-1.667h4.546c1 0 1.809-.75 1.809-1.666l.009-10C26 9.742 25.182 9 24.182 9zM24 21H8V11h16v10z" fill="#FFF" fill-rule="nonzero" />

+ 2 - 2
packages/@uppy/screen-capture/src/StreamStatus.js

@@ -3,11 +3,11 @@ const { h } = require('preact')
 module.exports = ({ streamActive, i18n }) => {
   if (streamActive) {
     return (
-      <div title={i18n('streamActive')} aria-label={i18n('streamActive')} class="uppy-ScreenCapture-icon--stream uppy-ScreenCapture-icon--streamActive"><svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" opacity=".1" fill="none" /><path d="M0 0h24v24H0z" fill="none" /><path d="M1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm18-7H5v1.63c3.96 1.28 7.09 4.41 8.37 8.37H19V7zM1 10v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11zm20-7H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" /></svg></div>
+      <div title={i18n('streamActive')} aria-label={i18n('streamActive')} class="uppy-ScreenCapture-icon--stream uppy-ScreenCapture-icon--streamActive"><svg aria-hidden="true" focusable="false" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" opacity=".1" fill="none" /><path d="M0 0h24v24H0z" fill="none" /><path d="M1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm18-7H5v1.63c3.96 1.28 7.09 4.41 8.37 8.37H19V7zM1 10v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11zm20-7H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" /></svg></div>
     )
   } else {
     return (
-      <div title={i18n('streamPassive')} aria-label={i18n('streamPassive')} class="uppy-ScreenCapture-icon--stream"><svg aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" opacity=".1" fill="none" /><path d="M0 0h24v24H0z" fill="none" /><path d="M21 3H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm0-4v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11z" /></svg></div>
+      <div title={i18n('streamPassive')} aria-label={i18n('streamPassive')} class="uppy-ScreenCapture-icon--stream"><svg aria-hidden="true" focusable="false" width="24" height="24" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" opacity=".1" fill="none" /><path d="M0 0h24v24H0z" fill="none" /><path d="M21 3H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm0-4v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11z" /></svg></div>
     )
   }
 }

+ 1 - 1
packages/@uppy/screen-capture/src/SubmitButton.js

@@ -14,7 +14,7 @@ module.exports = function SubmitButton ({ recording, recordedVideo, onSubmit, i1
         onclick={onSubmit}
         data-uppy-super-focusable
       >
-        <svg aria-hidden="true" focusable="false" class="UppyIcon" width="48" height="48" viewBox="0 0 48 48">
+        <svg aria-hidden="true" focusable="false" class="uppy-c-icon" width="48" height="48" viewBox="0 0 48 48">
           <path d="M0 0h48v48h-48z" fill="none" />
           <path d="M38.71 20.07c-1.36-6.88-7.43-12.07-14.71-12.07-5.78 0-10.79 3.28-13.3 8.07-6.01.65-10.7 5.74-10.7 11.93 0 6.63 5.37 12 12 12h26c5.52 0 10-4.48 10-10 0-5.28-4.11-9.56-9.29-9.93zm-10.71 5.93v8h-8v-8h-6l10-10 10 10h-6z" />
         </svg>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است