Bladeren bron

Merge master

Abdelhadi Khiati 6 jaren geleden
bovenliggende
commit
12219660c3
100 gewijzigde bestanden met toevoegingen van 3338 en 1758 verwijderingen
  1. 3 2
      .travis.yml
  2. 32 10
      CHANGELOG.md
  3. 3 3
      README.md
  4. 1 1
      bin/companion
  5. 3 3
      bin/upload-to-cdn.sh
  6. 2 0
      examples/bundled-example/main.js
  7. 2 2
      examples/cdn-example/index.html
  8. 1 1
      examples/react-example/package.json
  9. 2 2
      examples/uppy-with-server/client/index.html
  10. 311 318
      package-lock.json
  11. 18 8
      package.json
  12. 4 4
      packages/@uppy/aws-s3-multipart/package.json
  13. 3 0
      packages/@uppy/aws-s3-multipart/src/index.js
  14. 4 4
      packages/@uppy/aws-s3/package.json
  15. 1 1
      packages/@uppy/companion-client/package.json
  16. 8 8
      packages/@uppy/companion/ARCHITECTURE.md
  17. 18 18
      packages/@uppy/companion/KUBERNETES.md
  18. 10 10
      packages/@uppy/companion/README.md
  19. 6 6
      packages/@uppy/companion/infra/kube/gcloud-deploy.sh
  20. 1620 373
      packages/@uppy/companion/package-lock.json
  21. 1 2
      packages/@uppy/companion/package.json
  22. 1 0
      packages/@uppy/companion/src/server/Uploader.js
  23. 14 4
      packages/@uppy/companion/src/server/provider/drive.js
  24. 4 4
      packages/@uppy/companion/src/standalone/helper.js
  25. 1 1
      packages/@uppy/companion/src/standalone/start-server.js
  26. 3 3
      packages/@uppy/core/package.json
  27. 8 52
      packages/@uppy/core/src/_common.scss
  28. 1 1
      packages/@uppy/core/src/_variables.scss
  29. 1 1
      packages/@uppy/core/src/index.js
  30. 11 9
      packages/@uppy/dashboard/package.json
  31. 0 100
      packages/@uppy/dashboard/src/FileCard.js
  32. 0 60
      packages/@uppy/dashboard/src/FileList.js
  33. 0 78
      packages/@uppy/dashboard/src/Tabs.js
  34. 2 2
      packages/@uppy/dashboard/src/components/ActionBrowseTagline.js
  35. 101 0
      packages/@uppy/dashboard/src/components/AddFiles.js
  36. 21 0
      packages/@uppy/dashboard/src/components/AddFilesPanel.js
  37. 35 27
      packages/@uppy/dashboard/src/components/Dashboard.js
  38. 111 0
      packages/@uppy/dashboard/src/components/FileCard.js
  39. 31 31
      packages/@uppy/dashboard/src/components/FileItem.js
  40. 0 0
      packages/@uppy/dashboard/src/components/FileItemProgress.js
  41. 23 0
      packages/@uppy/dashboard/src/components/FileList.js
  42. 1 1
      packages/@uppy/dashboard/src/components/FilePreview.js
  43. 28 0
      packages/@uppy/dashboard/src/components/PanelContent.js
  44. 31 0
      packages/@uppy/dashboard/src/components/PanelTopBar.js
  45. 4 18
      packages/@uppy/dashboard/src/components/icons.js
  46. 47 9
      packages/@uppy/dashboard/src/index.js
  47. 274 207
      packages/@uppy/dashboard/src/style.scss
  48. 0 0
      packages/@uppy/dashboard/src/utils/copyToClipboard.js
  49. 0 0
      packages/@uppy/dashboard/src/utils/copyToClipboard.test.js
  50. 1 1
      packages/@uppy/dashboard/src/utils/getFileTypeIcon.js
  51. 17 0
      packages/@uppy/dashboard/src/utils/ignoreEvent.js
  52. 0 0
      packages/@uppy/dashboard/src/utils/truncateString.js
  53. 0 0
      packages/@uppy/dashboard/src/utils/truncateString.test.js
  54. 4 4
      packages/@uppy/drag-drop/package.json
  55. 5 5
      packages/@uppy/dropbox/package.json
  56. 0 14
      packages/@uppy/dropbox/src/icons.js
  57. 3 4
      packages/@uppy/dropbox/src/index.js
  58. 3 3
      packages/@uppy/file-input/package.json
  59. 3 3
      packages/@uppy/form/package.json
  60. 3 3
      packages/@uppy/golden-retriever/package.json
  61. 5 5
      packages/@uppy/google-drive/package.json
  62. 20 14
      packages/@uppy/google-drive/src/index.js
  63. 3 3
      packages/@uppy/informer/package.json
  64. 6 7
      packages/@uppy/informer/src/index.js
  65. 36 21
      packages/@uppy/informer/src/style.scss
  66. 5 5
      packages/@uppy/instagram/package.json
  67. 5 7
      packages/@uppy/instagram/src/index.js
  68. 3 3
      packages/@uppy/progress-bar/package.json
  69. 3 3
      packages/@uppy/provider-views/package.json
  70. 4 8
      packages/@uppy/provider-views/src/AuthView.js
  71. 3 3
      packages/@uppy/provider-views/src/Breadcrumbs.js
  72. 8 6
      packages/@uppy/provider-views/src/Browser.js
  73. 26 2
      packages/@uppy/provider-views/src/Item.js
  74. 144 83
      packages/@uppy/provider-views/src/style.scss
  75. 7 7
      packages/@uppy/react/package.json
  76. 2 2
      packages/@uppy/redux-dev-tools/package.json
  77. 3 3
      packages/@uppy/status-bar/package.json
  78. 4 5
      packages/@uppy/status-bar/src/StatusBar.js
  79. 77 39
      packages/@uppy/status-bar/src/style.scss
  80. 1 1
      packages/@uppy/store-default/package.json
  81. 1 1
      packages/@uppy/store-redux/package.json
  82. 3 3
      packages/@uppy/thumbnail-generator/package.json
  83. 6 6
      packages/@uppy/transloadit/package.json
  84. 4 4
      packages/@uppy/tus/package.json
  85. 4 4
      packages/@uppy/url/package.json
  86. 21 24
      packages/@uppy/url/src/index.js
  87. 3 2
      packages/@uppy/url/src/style.scss
  88. 1 1
      packages/@uppy/utils/package.json
  89. 3 3
      packages/@uppy/webcam/package.json
  90. 1 1
      packages/@uppy/webcam/src/CameraIcon.js
  91. 2 3
      packages/@uppy/webcam/src/PermissionsScreen.js
  92. 7 3
      packages/@uppy/webcam/src/index.js
  93. 24 15
      packages/@uppy/webcam/src/style.scss
  94. 4 4
      packages/@uppy/xhr-upload/package.json
  95. 26 26
      packages/uppy/package.json
  96. 5 0
      test/endtoend/create-react-app/package-lock.json
  97. 1 0
      test/endtoend/create-react-app/package.json
  98. 1 0
      test/endtoend/create-react-app/src/index.js
  99. 10 9
      test/endtoend/create-react-app/test.js
  100. 1 1
      website/src/_posts/2018-07-0.26.md

+ 3 - 2
.travis.yml

@@ -15,8 +15,9 @@ addons:
 cache:
   apt: true
   directories:
-  - node_modules
-  - website/node_modules
+  - ~/.npm
+before_install:
+- nvm install-latest-npm
 script:
 - npm run web:install
 - npm run build

+ 32 - 10
CHANGELOG.md

@@ -66,11 +66,13 @@ PRs are welcome! Please do open an issue to discuss first if it's a big feature,
 - [ ] webcam: Stop recording when file size is exceeded, should be possible given how the MediaRecorder API works
 - [ ] dashboard: add option to disable uploading from local disk #657
 - [ ] dashboard: display data like image resolution on file cards #783
+- [ ] core: look into utilizing https://github.com/que-etc/resize-observer-polyfill for responsive components. See also https://github.com/transloadit/uppy/issues/750
 - [ ] server: pass metadata to S3 `getKey` option, see https://github.com/transloadit/uppy/issues/689
 - [ ] core: I think there is a use case for having a single-use mode or something for Uppy, where pressing "Upload" locks it down (no new files can be added) and once the upload is finished it's just done. especially with the Form plugin
 - [ ] dashboard: hiding pause/resume from the UI by default (with option) would be good too probably (we could auto pause and show a resume button when detecting a network change to a metered network using https://devdocs.io/dom/networkinformation/type)
 - [ ] test: Add a prepublish test that checks if `npm pack` is not massive
 - [ ] Add release documentation. eg: test on transloadit website, check examples on the uppy.io website
+- [ ] dashboard: add image cropping, study https://github.com/MattKetmo/darkroomjs/, https://github.com/fengyuanchen/cropperjs #151
 
 ## 1.0 Goals
 
@@ -87,7 +89,7 @@ What we need to do to release Uppy 1.0
 - [ ] QA: add one integration test that uses a Provider (investigate if possible with a dedicated Google Drive API key for uppy server, so _with_ oauth dance) (@ife)
 - [ ] QA: add one integration test that uses more exotic (tus) options such as `useFastRemoteRetry` (@arturi)
 - [ ] feature: preset for Transloadit that mimics jQuery SDK, check https://github.com/transloadit/jquery-sdk docs (@goto-bus-stop)
-- [ ] dashboard: implement Alex' redesign (@arturi)
+- [x] dashboard: implement Alex and Artur’s Dashboard redesign (@arturi)
 - [ ] feature: basic React Native support (@arturi owner+ios, @ife android)
 - [ ] refactoring: Make `uppy-server` module live in main Uppy repo in `./server` as a second stage todo (after Lerna is done and we're happy) (@ife)
 - [x] QA: add one integration test that uses a Webpack and React/Redux environment (e.g. via `create-react-app`) (@goto-bus-stop)
@@ -110,29 +112,49 @@ What we need to do to release Uppy 1.0
 
 # next
 
-## 0.27.0
+## 0.28.0
 
 - [ ] dashboard: allow minimizing the Dashboard during upload (Uppy then becomes just a tiny progress indicator) (@arturi)
 - [ ] core: customizing metadata fields, boolean metadata; see #809, #454 and related (@arturi)
 - [ ] core: figure out per-plugin locales and i18n strings packs #491
-- [ ] goldenretriever: confirmation before restore, add “ghost” files #443 (@arturi)
+- [ ] goldenretriever: confirmation before restore, add “ghost” files #443 #257 (@arturi)
 - [ ] test: add typescript with JSDoc (@arturi)
 - [ ] dragdrop: allow customizing arrow icon https://github.com/transloadit/uppy/pull/374#issuecomment-334116208 (@arturi)
 - [ ] dashboard: cancel button for transloadit assemblies (@arturi, @goto-bus-stop)
 - [ ] dashboard: optional alert `onbeforeunload` while upload is in progress, safeguarding from accidentaly navigating away from a page with an ongoing upload
-- [ ] dashboard: add image cropping, study https://github.com/MattKetmo/darkroomjs/, https://github.com/fengyuanchen/cropperjs #151
 - [ ] docs: quick start guide: https://community.transloadit.com/t/quick-start-guide-would-be-really-helpful/14605 (@arturi)
 - [ ] transloadit: add error reporting (@goto-bus-stop)
-- [ ] core: look into utilizing https://github.com/que-etc/resize-observer-polyfill for responsive components. See also https://github.com/transloadit/uppy/issues/750
 - [ ] core: use Browserslist config to share between PostCSS, Autoprefixer and Babel https://github.com/browserslist/browserslist, https://github.com/amilajack/eslint-plugin-compat (@arturi)
 - [ ] core: utilize https://github.com/jonathantneal/postcss-preset-env, maybe https://github.com/jonathantneal/postcss-normalize (@arturi)
-- [ ] core: default `autoProceed` to `false` (@arturi)
-- [x] website: list bundle sizes for each package on stats page (#962 / @goto-bus-stop)
-- [x] s3: Abort all chunk requests when aborting the multipart upload (#967 / @pekala)
-- [x] s3: Catch and handle errors in prepareUploadPart (#966 / @pekala)
-- [x] Split integration tests and add one using create-react-app (#952)
 - [ ] server: bump minor and deprecate that on npm in favour of @uppy/companion
 
+## 0.27.0
+
+Released: 2018-08-11.
+
+- [x] @uppy/aws-s3-multipart: Check for file existance (#981 / @bartvde)
+- [x] @uppy/aws-s3: Abort all chunk requests when aborting the multipart upload (#967 / @pekala)
+- [x] @uppy/aws-s3: Catch and handle errors in prepareUploadPart (#966 / @pekala)
+- [x] @uppy/core: allow editing plugin titles (names) so that e.g. “Camera” can be translated into different languages, fixes #920 (#942 / @arturi)
+- [x] @uppy/core: fix `setPluginState` (#968 / @goto-bus-stop)
+- [x] @uppy/core: make Uppy run in React Native (by adding `window !== undefined` check) (@arturi / #960)
+- [x] @uppy/core: remove all: initial — was causing issues when multiple uppy stylesheets are used (#942 / @arturi)
+- [x] @uppy/core: ⚠️ **breaking**  default `autoProceed` to `false` (#961 / @arturi)
+- [x] @uppy/dashboard: downgrade `drag-drop` module to support folders again (#942 / @arturi)
+- [x] @uppy/dashboard: fix animation — wait for closing animation to finish before opening modal (#942 / @arturi)
+- [x] @uppy/dashboard: ⚠️ **breaking** Introduce `.uppy-size--md` and `.uppy-size--lg` breakpoint classes; throttle the function that checks for width (#942 / @arturi)
+- [x] @uppy/dashboard: ⚠️ **breaking** UI overhaul: AddFiles panel, significantly improved mobile styles,  (#942 / @arturi, @nqst)
+- [x] @uppy/informer: ⚠️ **breaking** make it monochrome and round. always gray, no status colors (#942 / @arturi)
+- [x] @uppy/provider-views: fix wrong 'no files available' msg flash (#938 / @ifedapoolarewaju)
+- [x] @uppy/url: fix Url plugin reacting to wrong drop/paste events, add ignoreEvent (#942 / @arturi)
+- [x] @uppy/webcam: add webcam permission screen i18 strings, fixes #931 (#942 / @arturi)
+- [x] build: Add object rest spread transform (#965 / @goto-bus-stop)
+- [x] build: Split integration tests and add one using create-react-app (#952 / @goto-bus-stop)
+- [x] build: Upload to CDN when commit starts with “Release” (#989 / @arturi)
+- [x] server: google Drive — move to v3 API (#977 / @pauln)
+- [x] website: docs fixes and improvements @@AJvanLoon)
+- [x] website: list bundle sizes for each package on stats page (#962 / @goto-bus-stop)
+
 ## 0.26.0
 
 Released: 2018-06-28.

+ 3 - 3
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](http://webpack.github.io/), [Browserify](http://browserify.org/) or [Rollup.js](http://rollupjs.org/).
 
-Add CSS [uppy.min.css](https://transloadit.edgly.net/releases/uppy/v0.26.0/dist/uppy.min.css), either to `<head>` of your HTML page 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/v0.27.0/dist/uppy.min.css), either to `<head>` of your HTML page 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/v0.26.0/dist/uppy.min.css" rel="stylesheet">
+<link href="https://transloadit.edgly.net/releases/uppy/v0.27.0/dist/uppy.min.css" rel="stylesheet">
 
 <!-- 2. Add JS before the closing `</body>` -->
-<script src="https://transloadit.edgly.net/releases/uppy/v0.26.0/dist/uppy.min.js"></script>
+<script src="https://transloadit.edgly.net/releases/uppy/v0.27.0/dist/uppy.min.js"></script>
 
 <!-- 3. Initialize -->
 <div class="UppyDragDrop"></div>

+ 1 - 1
bin/companion

@@ -1,5 +1,5 @@
 #!/usr/bin/env bash
-sh ../env.sh
+. ./env.sh
 
 env \
   UPPYSERVER_DATADIR="./output" \

+ 3 - 3
bin/upload-to-cdn.sh

@@ -8,7 +8,7 @@
 #  - Checks if a tag is being built (on Travis - otherwise opts to continue execution regardless)
 #  - Installs AWS CLI if needed
 #  - Assumed a fully built uppy is in root dir (unless a specific tag was specified, then it's fetched from npm)
-#  - Runs npm pack, and stores files to e.g. https://transloadit.edgly.net/releases/uppy/v0.26.0/dist/uppy.css
+#  - Runs npm pack, and stores files to e.g. https://transloadit.edgly.net/releases/uppy/v0.27.0/dist/uppy.css
 #  - Uses local package by default, if [version] argument was specified, takes package from npm
 #
 # Run as:
@@ -44,8 +44,8 @@ pushd "${__root}" > /dev/null 2>&1
       echo "On Travis (TRAVIS is '${TRAVIS}'), I'm not pushing releases to the CDN for pull requests (TRAVIS_PULL_REQUEST is '${TRAVIS_PULL_REQUEST}')"
       exit 0
     fi
-    if [ -z "${TRAVIS_TAG:-}" ]; then
-      echo "On Travis (TRAVIS is '${TRAVIS}'), I'm only pushing releases to the CDN when a tag is being built (TRAVIS_TAG is '${TRAVIS_TAG}')"
+    if [[ ! "$TRAVIS_COMMIT_MESSAGE" =~ ^Release* ]]; then
+      echo "On Travis (TRAVIS is '${TRAVIS}'), I'm not pushing releases to the CDN unless commit message starts with 'Release' (TRAVIS_COMMIT is '${TRAVIS_COMMIT_MESSAGE}')"
       exit 0
     fi
   fi

+ 2 - 0
examples/bundled-example/main.js

@@ -1,6 +1,7 @@
 const Uppy = require('./../../packages/@uppy/core/src')
 const Dashboard = require('./../../packages/@uppy/dashboard/src')
 const Instagram = require('./../../packages/@uppy/instagram/src')
+const Dropbox = require('./../../packages/@uppy/dropbox/src')
 const GoogleDrive = require('./../../packages/@uppy/google-drive/src')
 const Url = require('./../../packages/@uppy/url/src')
 const Webcam = require('./../../packages/@uppy/webcam/src')
@@ -30,6 +31,7 @@ const uppy = Uppy({
   })
   .use(GoogleDrive, { target: Dashboard, serverUrl: 'http://localhost:3020' })
   .use(Instagram, { target: Dashboard, serverUrl: 'http://localhost:3020' })
+  .use(Dropbox, { target: Dashboard, serverUrl: 'http://localhost:3020' })
   .use(Url, { target: Dashboard, serverUrl: 'http://localhost:3020' })
   .use(Webcam, { target: Dashboard })
   .use(Tus, { endpoint: TUS_ENDPOINT })

+ 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/v0.26.0/dist/uppy.min.css" rel="stylesheet">
+    <link href="https://transloadit.edgly.net/releases/uppy/v0.27.0/dist/uppy.min.css" rel="stylesheet">
   </head>
   <body>
     <button id="uppyModalOpener">Open Modal</button>
-    <script src="https://transloadit.edgly.net/releases/uppy/v0.26.0/dist/uppy.min.js"></script>
+    <script src="https://transloadit.edgly.net/releases/uppy/v0.27.0/dist/uppy.min.js"></script>
     <script>
       const uppy = Uppy.Core({debug: true, autoProceed: false})
         .use(Uppy.Dashboard, { trigger: '#uppyModalOpener' })

+ 1 - 1
examples/react-example/package.json

@@ -2,7 +2,7 @@
   "private": true,
   "name": "uppy-react-example",
   "scripts": {
-    "css": "cp ../../dist/uppy.min.css .",
+    "css": "cp ../../packages/uppy/dist/uppy.min.css .",
     "start": "npm run css && budo main.js:bundle.js -- -r react:react -r react-dom:react-dom -t babelify -g aliasify"
   },
   "aliasify": {

+ 2 - 2
examples/uppy-with-server/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/v0.26.0/dist/uppy.min.css" rel="stylesheet">
+    <link href="https://transloadit.edgly.net/releases/uppy/v0.27.0/dist/uppy.min.css" rel="stylesheet">
   </head>
   <body>
     <button id="uppyModalOpener">Open Modal</button>
-    <script src="https://transloadit.edgly.net/releases/uppy/v0.26.0/dist/uppy.min.js"></script>
+    <script src="https://transloadit.edgly.net/releases/uppy/v0.27.0/dist/uppy.min.js"></script>
     <script>
       const uppy = Uppy.Core({debug: true, autoProceed: false})
         .use(Uppy.Dashboard, { trigger: '#uppyModalOpener' })

File diff suppressed because it is too large
+ 311 - 318
package-lock.json


+ 18 - 8
package.json

@@ -12,7 +12,7 @@
     "autoprefixer": "^7.2.6",
     "babel-cli": "^6.26.0",
     "babel-core": "^6.26.3",
-    "babel-jest": "^22.4.4",
+    "babel-jest": "^23.4.0",
     "babel-plugin-transform-object-assign": "^6.22.0",
     "babel-plugin-transform-object-rest-spread": "^6.26.0",
     "babel-plugin-transform-proto-to-assign": "^6.26.0",
@@ -22,7 +22,7 @@
     "babel-register": "^6.26.0",
     "babelify": "^8.0.0",
     "browser-resolve": "^1.11.3",
-    "browser-sync": "^2.24.5",
+    "browser-sync": "^2.24.6",
     "browserify": "^16.2.2",
     "chai": "^4.1.2",
     "chalk": "^2.4.1",
@@ -36,7 +36,7 @@
     "eslint-config-standard-preact": "^1.1.6",
     "eslint-plugin-compat": "^2.4.0",
     "eslint-plugin-import": "^2.13.0",
-    "eslint-plugin-jest": "^21.17.0",
+    "eslint-plugin-jest": "^21.18.0",
     "eslint-plugin-node": "^4.2.3",
     "eslint-plugin-promise": "^3.8.0",
     "eslint-plugin-standard": "^3.1.0",
@@ -46,7 +46,7 @@
     "glob": "^7.1.2",
     "gzip-size": "^5.0.0",
     "isomorphic-fetch": "2.2.1",
-    "jest": "^22.4.4",
+    "jest": "^23.4.1",
     "json3": "^3.3.2",
     "lerna": "^2.11.0",
     "lint-staged": "^6.1.1",
@@ -54,7 +54,7 @@
     "mkdirp": "0.5.1",
     "multi-glob": "1.0.1",
     "nock": "^9.3.3",
-    "node-sass": "^4.9.0",
+    "node-sass": "^4.9.3",
     "npm-auth-to-token": "^1.0.0",
     "npm-run-all": "^4.1.3",
     "onchange": "^4.0.0",
@@ -99,11 +99,11 @@
     "test:prepare-ci": "npm-run-all --parallel --race test:registry test:build-ci",
     "test:acceptance": "npm run test:prepare-ci && wdio test/endtoend/wdio.remote.conf.js",
     "test:acceptance:local": "npm run test:build && wdio test/endtoend/wdio.local.conf.js",
-    "test:unit": "jest --testPathPattern=./src --coverage",
+    "test:unit": "jest",
     "test:companion": "cd ./packages/@uppy/companion && npm run test",
     "test:type": "tsc -p .",
-    "test": "npm run lint && npm run test:unit && npm run test:type && npm run test:companion",
-    "test:watch": "jest --watch --testPathPattern=src",
+    "test": "npm run lint && npm run test:unit && npm run test:type",
+    "test:watch": "jest --watch",
     "travis:deletecache": "travis cache --delete",
     "watch:css": "onchange 'packages/**/*.scss' --initial --verbose -- npm run build:css",
     "watch:js": "onchange 'packages/{@uppy/,}*/src/**/*.js' --initial --verbose -- npm run build:bundle",
@@ -129,5 +129,15 @@
     "prepare": "lerna bootstrap --hoist",
     "contributors": "githubcontrib --owner transloadit --repo uppy --cols 6 $([ \"${GITHUB_TOKEN:-}\" == \"\" ] && echo \"\" || echo \"--authToken ${GITHUB_TOKEN}\") --showlogin true --sortOrder desc",
     "contributors:save": "replace-x -m '<!--contributors-->[\\s\\S]+<!--/contributors-->' \"<!--contributors-->\n## Contributors\n\n$(npm run --silent contributors)\n<!--/contributors-->\" README.md"
+  },
+  "jest": {
+    "automock": false,
+    "collectCoverage": true,
+    "collectCoverageFrom": [
+      "packages/**/src/**"
+    ],
+    "testMatch": [
+      "**/packages/**/*.test.js"
+    ]
   }
 }

+ 4 - 4
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": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -24,12 +24,12 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/companion-client": "0.26.0",
-    "@uppy/utils": "0.26.0",
+    "@uppy/companion-client": "0.27.0",
+    "@uppy/utils": "0.27.0",
     "resolve-url": "^0.2.1"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

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

@@ -183,6 +183,9 @@ module.exports = class AwsS3Multipart extends Plugin {
         onPartComplete: (part) => {
           // Store completed parts in state.
           const cFile = this.uppy.getFile(file.id)
+          if (!cFile) {
+            return
+          }
           this.uppy.setFileState(file.id, {
             s3Multipart: Object.assign({}, cFile.s3Multipart, {
               parts: [

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

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/aws-s3",
   "description": "Upload to Amazon S3 with Uppy",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -23,12 +23,12 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "0.26.0",
-    "@uppy/xhr-upload": "0.26.0",
+    "@uppy/utils": "0.27.0",
+    "@uppy/xhr-upload": "0.27.0",
     "resolve-url": "^0.2.1"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

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

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/companion-client",
   "description": "Client library for communication with Uppy Server. Intended for use in Uppy plugins.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",

+ 8 - 8
packages/@uppy/companion/ARCHITECTURE.md

@@ -1,10 +1,10 @@
-Uppy Server is the server side component for Uppy.  It is currently built with the Express.js.
-The purpose of Uppy Server is to interface with third party APIs and handle remote file uploading from them.
+Companion is the server side component for Uppy.  It is currently built with the Express.js.
+The purpose of Companion is to interface with third party APIs and handle remote file uploading from them.
 
 # How it works
 
 ## oAuth with Grant, Sessions
-Uppy Server uses an oAuth middleware library called `Grant` to simplify oAuth authentication.
+Companion uses an oAuth middleware library called `Grant` to simplify oAuth authentication.
 Inside of `config/grant.js`, you configure the oAuth providers you wish to use, providing things like client key, 
 client secret, scopes, and the callback URL you wish to use.  For example:
 
@@ -53,7 +53,7 @@ There are 5 controllers:
 These controllers are generalized to work for any provider.  The provider specific implementation code for each provider can be found under `server/providers`.
 
 ## Adding new providers
-To add a new provider to Uppy Server, you need to do two things: add the provider config to `config/grant.js`, and then create a new file in `server/providers` that describes how to interface with the provider's API.
+To add a new provider to Companion, you need to do two things: add the provider config to `config/grant.js`, and then create a new file in `server/providers` that describes how to interface with the provider's API.
 
 We are using a library called [purest](https://github.com/simov/purest) to make it easier to interface with third party APIs.  Instead of dealing with each and every single provider's client library/SDK, we use Purest, a "generic REST API client library" that gives us a consistent, "generic" API to interface with any provider.  This makes life a lot easier.
 
@@ -64,19 +64,19 @@ Since each API works differently, we need to describe how to `download` and `lis
 This whole approach was inspired by an example from `purest 2.x`.  Keep in mind that we're using `3.x`, so the API is different, but here is the example for reference: https://github.com/simov/purest/tree/2.x/examples/storage
 
 ## WebSockets
-Uppy Server uses WebSockets to transfer `progress` events to the client during file transfers.  It's currently only set up to transfer progress during Tus uploads to the target server.  
+Companion uses WebSockets to transfer `progress` events to the client during file transfers.  It's currently only set up to transfer progress during Tus uploads to the target server.  
 
 When a request is made to `/:provider/get` to start a transfer, a token is generated and sent back to the client in response.  The client then connects to `wss://your-server/whatever-their-token-is`.  Any events that are emitted using the token as the name (i.e. `emitter.emit('whatever-their-token-is', progressData)`) are sent back to the client.
 
 WebSockets aren't particularly secure, but we feel this is safe because the token is only usable during the corresponding file transfer, and no sensitive information is being sent, only a file id and the progress.
 
 # Design Goals
-These are the goals I had in mind while designing and building Uppy Server.
+These are the goals I had in mind while designing and building Companion.
 
 ## Standalone Server / Pluggable Module
-Uppy Server currently works as a standalone server.  It should also work as a module that can easily be incorporated into an already existing server, so people don't have to manage another server just to use Uppy.
+Companion currently works as a standalone server.  It should also work as a module that can easily be incorporated into an already existing server, so people don't have to manage another server just to use Uppy.
 
-One issue here is that `Grant` has different versions for Koa, Express, and Hapi.  We're using `grant-express` right now, and also use all express modules.  This becomes a problem if someone is using Koa, or Hapi, or something else.  I don't think we can make Uppy Server completely framework agnostic, so best case scenario would be to follow Grant and make versions for Koa, Hapi, and Express.
+One issue here is that `Grant` has different versions for Koa, Express, and Hapi.  We're using `grant-express` right now, and also use all express modules.  This becomes a problem if someone is using Koa, or Hapi, or something else.  I don't think we can make Companion completely framework agnostic, so best case scenario would be to follow Grant and make versions for Koa, Hapi, and Express.
 
 All of this may be more trouble than it's worth if no one needs it, so I'd get some community feedback beforehand.
 

+ 18 - 18
packages/@uppy/companion/KUBERNETES.md

@@ -1,6 +1,6 @@
-### Run uppy-server on kuberenetes
+### Run companion on kuberenetes
 
-You can use our docker container to run uppy-server on kubernetes with the following configuration.
+You can use our docker container to run companion on kubernetes with the following configuration.
 ```bash
 kubectl create ns uppy
 ```
@@ -13,7 +13,7 @@ We will need a Redis container that we can get through [helm](https://github.com
     stable/redis
 ```
 
-> uppy-server-env.yml
+> companion-env.yml
 ```yaml
 apiVersion: v1
 data:
@@ -38,17 +38,17 @@ data:
   UPPYSERVER_UPLOAD_URLS: "http://master.tus.io/files/,https://master.tus.io/files/"
 kind: Secret
 metadata:
-  name: uppy-server-env
+  name: companion-env
   namespace: uppy
 type: Opaque
 ```
 
-> uppy-server-deployment.yml
+> companion-deployment.yml
 ```yaml
 apiVersion: extensions/v1beta1
 kind: Deployment
 metadata:
-  name: uppy-server
+  name: companion
   namespace: uppy
 spec:
   replicas: 2
@@ -61,12 +61,12 @@ spec:
   template:
     metadata:
       labels:
-        app: uppy-server
+        app: companion
     spec:
       containers:
-      - image: docker.io/transloadit/uppy-server:latest
+      - image: docker.io/transloadit/companion:latest
         imagePullPolicy: ifNotPresent
-        name: uppy-server        
+        name: companion        
         resources:
           limits:
             memory: 150Mi
@@ -74,26 +74,26 @@ spec:
             memory: 100Mi
         envFrom:
         - secretRef:
-            name: uppy-server-env
+            name: companion-env
         ports:
         - containerPort: 3020
         volumeMounts:
-        - name: uppy-server-data
-          mountPath: /mnt/uppy-server-data
+        - name: companion-data
+          mountPath: /mnt/companion-data
       volumes:
-      - name: uppy-server-data
+      - name: companion-data
         emptyDir: {}
 ```
 
-`kubectl apply -f uppy-server-deployment.yml`
+`kubectl apply -f companion-deployment.yml`
 
-> uppy-server-service.yml
+> companion-service.yml
 
 ```yaml
 apiVersion: v1
 kind: Service
 metadata:
-  name: uppy-server
+  name: companion
   namespace: uppy
 spec:
   ports:
@@ -101,10 +101,10 @@ spec:
     targetPort: 3020
     protocol: TCP
   selector:
-    app: uppy-server
+    app: companion
 ```
 
-`kubectl apply -f uppy-server-service.yml`
+`kubectl apply -f companion-service.yml`
 
 ## Logging
 

+ 10 - 10
packages/@uppy/companion/README.md

@@ -1,23 +1,23 @@
-# uppy-server
+# Companion
 
 <img src="http://uppy.io/images/logos/uppy-dog-full.svg" width="120" alt="Uppy logo — a superman puppy in a pink suit" align="right">
 
-[![Build Status](https://travis-ci.org/transloadit/uppy-server.svg?branch=master)](https://travis-ci.org/transloadit/uppy-server)
+[![Build Status](https://travis-ci.org/transloadit/uppy.svg?branch=master)](https://travis-ci.org/transloadit/uppy)
 
-Uppy-server is a server integration for [Uppy](https://github.com/transloadit/uppy) file uploader.
+Companion is a server integration for [Uppy](https://github.com/transloadit/uppy) file uploader.
 
 It handles the server-to-server communication between your server and file storage providers such as Google Drive, Dropbox,
-Instagram, etc. [See here for full documentation](https://uppy.io/docs/server/)
+Instagram, etc. **Companion is not a target to upload files to**. For this, use a <https://tus.io> server (if you want resumable) or your existing Apache/Nginx server (if you don't). [See here for full documentation](https://uppy.io/docs/server/)
 
 ## Install
 
 ```bash
-npm install uppy-server
+npm install @uppy/companion
 ```
 
 ## Usage
 
-Uppy-server may either be used as pluggable express app, which you plug to your already existing server, or it may simply be run as a standalone server:
+companion may either be used as pluggable express app, which you plug to your already existing server, or it may simply be run as a standalone server:
 
 ### Plug to already existing server
 
@@ -26,7 +26,7 @@ Uppy-server may either be used as pluggable express app, which you plug to your
 var express = require('express')
 var bodyParser = require('body-parser')
 var session = require('express-session')
-var uppy = require('uppy-server')
+var uppy = require('@uppy/companion')
 
 var app = express()
 app.use(bodyParser.json())
@@ -62,10 +62,10 @@ uppy.socket(server, options)
 ```
 
 ### Run as standalone server
-Please ensure that the required env variables are set before runnning/using uppy-server as a standalone server. [See](https://uppy.io/docs/server/#Configure-Standalone).
+Please ensure that the required env variables are set before runnning/using companion as a standalone server. [See](https://uppy.io/docs/server/#Configure-Standalone).
 
 ```bash
-$ uppy-server
+$ companion
 ```
 
 If you cloned the repo from gtihub and want to run it as a standalone server, you may also run the following command from within its
@@ -77,7 +77,7 @@ npm start
 
 ### Run as a serverless function
 
-Uppy-server can be deployed as a serverless function to AWS Lambda or other cloud providers through `serverless`. Check [this guide](https://serverless.com/framework/docs/getting-started/) to get started.
+Companion can be deployed as a serverless function to AWS Lambda or other cloud providers through `serverless`. Check [this guide](https://serverless.com/framework/docs/getting-started/) to get started.
 
 After you have cloned the repo go inside `examples/serverless`:
 ```

+ 6 - 6
packages/@uppy/companion/infra/kube/gcloud-deploy.sh

@@ -15,10 +15,10 @@ sudo mv ./kubectl /usr/local/bin/kubectl
 
 
 # Store the new image in docker hub
-docker build --quiet -t transloadit/uppy-server:latest -t transloadit/uppy-companion:$TRAVIS_COMMIT -f "${__companion}/Dockerfile" "${__companion}";
+docker build --quiet -t transloadit/companion:latest -t transloadit/companion:$TRAVIS_COMMIT .;
 docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD";
-docker push transloadit/uppy-companion:$TRAVIS_COMMIT;
-docker push transloadit/uppy-companion:latest;
+docker push transloadit/companion:$TRAVIS_COMMIT;
+docker push transloadit/companion:latest;
 
 echo $KUBECONFIG | base64 --decode -i > ${HOME}/.kube/config
 
@@ -30,9 +30,9 @@ kubectl config current-context
 
 kubectl apply -f "${__kube}/companion/uppy-env.yaml"
 sleep 10s # This cost me some precious debugging time.
-kubectl apply -f "${__kube}/companion/uppy-server-kube.yaml"
-kubectl apply -f "${__kube}/companion/uppy-server-redis.yaml"
-kubectl set image statefulset uppy-server --namespace=uppy companion=docker.io/transloadit/uppy-companion:$TRAVIS_COMMIT
+kubectl apply -f "${__kube}/companion/companion-kube.yaml"
+kubectl apply -f "${__kube}/companion/companion-redis.yaml"
+kubectl set image statefulset companion --namespace=uppy companion=docker.io/transloadit/companion:$TRAVIS_COMMIT
 sleep 10s
 
 kubectl get pods --namespace=uppy

File diff suppressed because it is too large
+ 1620 - 373
packages/@uppy/companion/package-lock.json


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

@@ -1,6 +1,6 @@
 {
   "name": "@uppy/companion",
-  "version": "0.13.4",
+  "version": "0.14.0",
   "description": "Server component 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/uppy.js",
   "types": "types/index.d.ts",
@@ -45,7 +45,6 @@
     "@types/tus-js-client": "^1.4.1",
     "@types/uuid": "^3.4.3",
     "@types/ws": "^3.2.1",
-    "jest-cli": "^23.1.0",
     "nodemon": "^1.17.5",
     "supertest": "3.0.0"
   },

+ 1 - 0
packages/@uppy/companion/src/server/Uploader.js

@@ -400,6 +400,7 @@ class Uploader {
 }
 
 Uploader.FILE_NAME_PREFIX = 'uppy-file'
+// @todo do a proper migration to change this name
 Uploader.STORAGE_PREFIX = 'uppy-server'
 
 module.exports = Uploader

+ 14 - 4
packages/@uppy/companion/src/server/provider/drive.js

@@ -2,7 +2,8 @@ const request = require('request')
 // @ts-ignore
 const purest = require('purest')({ request })
 const logger = require('../logger')
-
+const DRIVE_FILE_FIELDS = 'kind,id,name,mimeType,ownedByMe,permissions(role,emailAddress),size,modifiedTime,iconLink,thumbnailLink'
+const DRIVE_FILES_FIELDS = `kind,nextPageToken,incompleteSearch,files(${DRIVE_FILE_FIELDS})`
 /**
  * @class
  * @implements {Provider}
@@ -11,6 +12,7 @@ class Drive {
   constructor (options) {
     this.authProvider = options.provider = Drive.authProvider
     options.alias = 'drive'
+    options.version = 'v3'
 
     this.client = purest(options)
   }
@@ -26,13 +28,21 @@ class Drive {
     return this.client
       .query()
       .get('files')
-      .where({ q: `'${directory}' in parents and trashed=${trashed}` })
+      .where({
+        fields: DRIVE_FILES_FIELDS,
+        q: `'${directory}' in parents and trashed=${trashed}`
+      })
       .auth(options.token)
       .request(done)
   }
 
   stats ({ id, token }, done) {
-    return this.client.query().get(`files/${id}`).auth(token).request(done)
+    return this.client
+      .query()
+      .get(`files/${id}`)
+      .where({fields: DRIVE_FILE_FIELDS})
+      .auth(token)
+      .request(done)
   }
 
   download ({ id, token }, onData) {
@@ -65,7 +75,7 @@ class Drive {
         logger.error(err, 'provider.drive.size.error')
         return done(null)
       }
-      done(parseInt(body.fileSize))
+      done(parseInt(body.size))
     })
   }
 }

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

@@ -178,18 +178,18 @@ exports.buildHelpfulStartupMessage = (uppyOptions) => {
   })
 
   return stripIndent`
-    Welcome to Uppy Server v${version}
+    Welcome to Companion v${version}
     ===================================
 
-    Congratulations on setting up Uppy Server! Thanks for joining our cause, you have taken
+    Congratulations on setting up Companion! Thanks for joining our cause, you have taken
     the first step towards the future of file uploading! We
     hope you are as excited about this as we are!
 
-    While you did an awesome job on getting Uppy Server running, this is just the welcome
+    While you did an awesome job on getting Companion running, this is just the welcome
     message, so let's talk about the places that really matter:
 
     - Be sure to add ${callbackURLs.join(', ')} as your Oauth redirect uris on their corresponding developer interfaces.
-    - The URL ${buildURL('/metrics', true)} is available for  statistics to keep Uppy Server running smoothly
+    - The URL ${buildURL('/metrics', true)} is available for  statistics to keep Companion running smoothly
     - https://github.com/transloadit/uppy/issues - report your bugs here
 
     So quit lollygagging, start uploading and experience the future!

+ 1 - 1
packages/@uppy/companion/src/standalone/start-server.js

@@ -7,5 +7,5 @@ const PORT = process.env.UPPYSERVER_PORT || 3020
 
 uppy.socket(app.listen(PORT))
 
-console.log(`Welcome to Uppy Server! v${version}`)
+console.log(`Welcome to Companion! v${version}`)
 console.log(`Listening on http://0.0.0.0:${PORT}`)

+ 3 - 3
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": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -21,8 +21,8 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/store-default": "0.26.0",
-    "@uppy/utils": "0.26.0",
+    "@uppy/store-default": "0.27.0",
+    "@uppy/utils": "0.27.0",
     "cuid": "^2.1.1",
     "lodash.throttle": "^4.1.1",
     "mime-match": "^1.0.2",

+ 8 - 52
packages/@uppy/core/src/_common.scss

@@ -3,11 +3,11 @@
 */
 
 .uppy-Root {
-  all: initial;
   box-sizing: border-box;
   font-family: $font-family-base;
   line-height: 1;
   -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
 }
 
 .uppy-Root *, .uppy-Root *:before, .uppy-Root *:after {
@@ -28,12 +28,9 @@
 .UppyIcon {
   max-width: 100%;
   max-height: 100%;
-  fill: currentColor;
+  fill: currentColor; /* no !important */
   display: inline-block;
-  vertical-align: text-top;
   overflow: hidden;
-  // width: 1em;
-  // height: 1em;
 }
 
 .UppyIcon--svg-baseline {
@@ -41,46 +38,6 @@
   position: relative;
 }
 
-// Buttons
-
-.UppyButton--circular {
-  @include reset-button;
-  box-shadow: 1px 2px 4px 0px rgba($color-black, 0.2);
-  border-radius: 50%;
-  cursor: pointer;
-  transition: all 0.3s;
-}
-
-.UppyButton--blue {
-  color: $color-white;
-  background-color: $color-cornflower-blue;
-
-  &:hover,
-  &:focus {
-    background-color: darken($color-cornflower-blue, 10%);
-  }
-}
-
-.UppyButton--red {
-  color: $color-white;
-  background-color: $color-red;
-
-  &:hover,
-  &:focus {
-    background-color: darken($color-red, 10%);
-  }
-}
-
-.UppyButton--sizeM {
-  width: 60px;
-  height: 60px;
-}
-
-.UppyButton--sizeS {
-  width: 45px;
-  height: 45px;
-}
-
 // Utilities
 
 .uppy-u-reset {
@@ -180,7 +137,7 @@
   padding: 6px 8px;
 }
 
-  .uppy-Dashboard--wide .uppy-c-textInput {
+  .uppy-size--md .uppy-c-textInput {
     font-size: 15px;
     line-height: 1.8;
     padding: 8px 12px;
@@ -203,7 +160,7 @@
   font-size: 16px;
   line-height: 1;
   font-weight: 500;
-  transition: all 0.3s;
+  transition: background-color 0.3s;
   user-select: none;
 }
 
@@ -219,10 +176,9 @@
   color: $color-white;
 }
 
-  .uppy-Dashboard--wide .uppy-c-btn-primary {
+  .uppy-size--md .uppy-c-btn-primary {
     font-size: 15px;
-    padding: 13px 28px;
-    // border-radius: 4px;
+    padding: 13px 22px;
   }
 
   .uppy-c-btn-primary:hover {
@@ -243,7 +199,7 @@
   color: $color-black;
 }
 
-  .uppy-Dashboard--wide .uppy-c-btn-link {
+  .uppy-size--md .uppy-c-btn-link {
     font-size: 15px;
     padding: 13px 28px;
     // border-radius: 4px;
@@ -264,7 +220,7 @@
   border-radius: 2px;
 }
 
-  .uppy-Dashboard--wide .uppy-c-btn--small {
+  .uppy-size--md .uppy-c-btn--small {
     padding: 8px 10px;
     border-radius: 2px;
   }

+ 1 - 1
packages/@uppy/core/src/_variables.scss

@@ -28,4 +28,4 @@ $zIndex-4: 1004 !default;
 $zIndex-5: 1005 !default;
 
 // Media Queries
-$screen-medium: 'only screen and (min-width: 768px)' !default;
+$screen-medium: 'only screen and (min-width: 820px)' !default;

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

@@ -785,7 +785,7 @@ class Uppy {
     })
 
     // show informer if offline
-    if (typeof window !== 'undefined') {
+    if (typeof window !== 'undefined' && window.addEventListener) {
       window.addEventListener('online', () => this.updateOnlineStatus())
       window.addEventListener('offline', () => this.updateOnlineStatus())
       setTimeout(() => this.updateOnlineStatus(), 3000)

+ 11 - 9
packages/@uppy/dashboard/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/dashboard",
   "description": "Universal UI plugin for Uppy.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -23,19 +23,21 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/informer": "0.26.0",
-    "@uppy/provider-views": "0.26.0",
-    "@uppy/status-bar": "0.26.0",
-    "@uppy/thumbnail-generator": "0.26.0",
-    "@uppy/utils": "0.26.0",
+    "@uppy/informer": "0.27.0",
+    "@uppy/provider-views": "0.27.0",
+    "@uppy/status-bar": "0.27.0",
+    "@uppy/thumbnail-generator": "0.27.0",
+    "@uppy/utils": "0.27.0",
     "classnames": "^2.2.6",
-    "drag-drop": "^2.14.0",
+    "drag-drop": "2.13.3",
+    "lodash.throttle": "^4.1.1",
     "preact": "^8.2.9",
+    "preact-css-transition-group": "^1.3.0",
     "prettier-bytes": "^1.0.4"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0",
-    "@uppy/google-drive": "0.26.0"
+    "@uppy/core": "0.27.0",
+    "@uppy/google-drive": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 0 - 100
packages/@uppy/dashboard/src/FileCard.js

@@ -1,100 +0,0 @@
-const getFileTypeIcon = require('./getFileTypeIcon')
-const FilePreview = require('./FilePreview')
-const { h, Component } = require('preact')
-
-module.exports = class FileCard extends Component {
-  constructor (props) {
-    super(props)
-
-    this.meta = {}
-
-    this.tempStoreMetaOrSubmit = this.tempStoreMetaOrSubmit.bind(this)
-    this.renderMetaFields = this.renderMetaFields.bind(this)
-    this.handleSave = this.handleSave.bind(this)
-    this.handleCancel = this.handleCancel.bind(this)
-  }
-
-  tempStoreMetaOrSubmit (ev) {
-    const file = this.props.files[this.props.fileCardFor]
-
-    if (ev.keyCode === 13) {
-      ev.stopPropagation()
-      ev.preventDefault()
-      this.props.saveFileCard(this.meta, file.id)
-      return
-    }
-
-    const value = ev.target.value
-    const name = ev.target.dataset.name
-    this.meta[name] = value
-  }
-
-  renderMetaFields (file) {
-    const metaFields = this.props.metaFields || []
-    return metaFields.map((field) => {
-      return <fieldset class="uppy-DashboardFileCard-fieldset">
-        <label class="uppy-DashboardFileCard-label">{field.name}</label>
-        <input class="uppy-c-textInput uppy-DashboardFileCard-input"
-          type="text"
-          data-name={field.id}
-          value={file.meta[field.id]}
-          placeholder={field.placeholder}
-          onkeyup={this.tempStoreMetaOrSubmit}
-          onkeydown={this.tempStoreMetaOrSubmit}
-          onkeypress={this.tempStoreMetaOrSubmit} /></fieldset>
-    })
-  }
-
-  handleSave (ev) {
-    const fileID = this.props.fileCardFor
-    this.props.saveFileCard(this.meta, fileID)
-  }
-
-  handleCancel (ev) {
-    this.meta = {}
-    this.props.toggleFileCard()
-  }
-
-  render () {
-    if (!this.props.fileCardFor) {
-      return <div class="uppy-DashboardFileCard" aria-hidden />
-    }
-
-    const file = this.props.files[this.props.fileCardFor]
-
-    return (
-      <div class="uppy-DashboardFileCard" aria-hidden={!this.props.fileCardFor}>
-        <div style={{ width: '100%', height: '100%' }}>
-          <div class="uppy-DashboardContent-bar">
-            <div class="uppy-DashboardContent-title" role="heading" aria-level="h1">
-              {this.props.i18nArray('editing', {
-                file: <span class="uppy-DashboardContent-titleFile">{file.meta ? file.meta.name : file.name}</span>
-              })}
-            </div>
-            <button class="uppy-DashboardContent-back" type="button" title={this.props.i18n('finishEditingFile')}
-              onclick={this.handleSave}>{this.props.i18n('done')}</button>
-          </div>
-
-          <div class="uppy-DashboardFileCard-inner">
-            <div class="uppy-DashboardFileCard-preview" style={{ backgroundColor: getFileTypeIcon(file.type).color }}>
-              <FilePreview file={file} />
-            </div>
-
-            <div class="uppy-DashboardFileCard-info">
-              {this.renderMetaFields(file)}
-            </div>
-
-            <div class="uppy-Dashboard-actions">
-              <button class="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Dashboard-actionsBtn"
-                type="button"
-                onclick={this.handleSave}>{this.props.i18n('saveChanges')}</button>
-              <button class="uppy-u-reset uppy-c-btn uppy-c-btn-link uppy-Dashboard-actionsBtn"
-                type="button"
-                onclick={this.handleCancel}>{this.props.i18n('cancel')}</button>
-            </div>
-          </div>
-        </div>
-      </div>
-    )
-  }
-}

+ 0 - 60
packages/@uppy/dashboard/src/FileList.js

@@ -1,60 +0,0 @@
-const FileItem = require('./FileItem')
-const ActionBrowseTagline = require('./ActionBrowseTagline')
-// const { dashboardBgIcon } = require('./icons')
-const classNames = require('classnames')
-const { h } = require('preact')
-
-const poweredByUppy = (props) => {
-  return <a tabindex="-1" href="https://uppy.io" rel="noreferrer noopener" target="_blank" class="uppy-Dashboard-poweredBy">Powered by <svg aria-hidden="true" class="UppyIcon uppy-Dashboard-poweredByIcon" width="11" height="11" viewBox="0 0 11 11" xmlns="http://www.w3.org/2000/svg">
-    <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></a>
-}
-
-module.exports = (props) => {
-  const noFiles = props.totalFileCount === 0
-  const dashboardFilesClass = classNames(
-    'uppy-Dashboard-files',
-    { 'uppy-Dashboard-files--noFiles': noFiles }
-  )
-
-  return <ul class={dashboardFilesClass}>
-    {noFiles &&
-      <div class="uppy-Dashboard-bgIcon">
-        <div class="uppy-Dashboard-dropFilesTitle">
-          <ActionBrowseTagline
-            acquirers={props.acquirers}
-            handleInputChange={props.handleInputChange}
-            i18n={props.i18n}
-            i18nArray={props.i18nArray}
-            allowedFileTypes={props.allowedFileTypes}
-            maxNumberOfFiles={props.maxNumberOfFiles}
-          />
-        </div>
-        { props.note && <div class="uppy-Dashboard-note">{props.note}</div> }
-        { props.proudlyDisplayPoweredByUppy && poweredByUppy(props) }
-      </div>
-    }
-    {Object.keys(props.files).map((fileID) => (
-      <FileItem
-        acquirers={props.acquirers}
-        file={props.files[fileID]}
-        toggleFileCard={props.toggleFileCard}
-        showProgressDetails={props.showProgressDetails}
-        info={props.info}
-        log={props.log}
-        i18n={props.i18n}
-        removeFile={props.removeFile}
-        pauseUpload={props.pauseUpload}
-        cancelUpload={props.cancelUpload}
-        retryUpload={props.retryUpload}
-        hidePauseResumeCancelButtons={props.hidePauseResumeCancelButtons}
-        hideRetryButton={props.hideRetryButton}
-        resumableUploads={props.resumableUploads}
-        bundled={props.bundled}
-        isWide={props.isWide}
-        showLinkToFileUploadResult={props.showLinkToFileUploadResult}
-        metaFields={props.metaFields}
-      />
-    ))}
-  </ul>
-}

+ 0 - 78
packages/@uppy/dashboard/src/Tabs.js

@@ -1,78 +0,0 @@
-const ActionBrowseTagline = require('./ActionBrowseTagline')
-const { localIcon } = require('./icons')
-const { h, Component } = require('preact')
-
-class Tabs extends Component {
-  constructor (props) {
-    super(props)
-    this.handleClick = this.handleClick.bind(this)
-  }
-
-  handleClick (ev) {
-    this.input.click()
-  }
-
-  render () {
-    const isHidden = Object.keys(this.props.files).length === 0
-    const hasAcquirers = this.props.acquirers.length !== 0
-
-    if (!hasAcquirers) {
-      return (
-        <div class="uppy-DashboardTabs" aria-hidden={isHidden}>
-          <div class="uppy-DashboardTabs-title">
-            <ActionBrowseTagline
-              acquirers={this.props.acquirers}
-              handleInputChange={this.props.handleInputChange}
-              i18n={this.props.i18n}
-              i18nArray={this.props.i18nArray} />
-          </div>
-        </div>
-      )
-    }
-
-    // empty value="" on file input, so that the input is cleared after a file is selected,
-    // because Uppy will be handling the upload and so we can select same file
-    // after removing — otherwise browser thinks it’s already selected
-    return <div class="uppy-DashboardTabs">
-      <ul class="uppy-DashboardTabs-list" role="tablist">
-        <li class="uppy-DashboardTab" role="presentation">
-          <button type="button"
-            class="uppy-DashboardTab-btn"
-            role="tab"
-            tabindex={0}
-            onclick={this.handleClick}>
-            {localIcon()}
-            <div class="uppy-DashboardTab-name">{this.props.i18n('myDevice')}</div>
-          </button>
-          <input class="uppy-Dashboard-input"
-            hidden
-            aria-hidden="true"
-            tabindex={-1}
-            type="file"
-            name="files[]"
-            multiple={this.props.maxNumberOfFiles !== 1}
-            accept={this.props.allowedFileTypes}
-            onchange={this.props.handleInputChange}
-            value=""
-            ref={(input) => { this.input = input }} />
-        </li>
-        {this.props.acquirers.map((target) => {
-          return <li class="uppy-DashboardTab" role="presentation">
-            <button class="uppy-DashboardTab-btn"
-              type="button"
-              role="tab"
-              tabindex={0}
-              aria-controls={`uppy-DashboardContent-panel--${target.id}`}
-              aria-selected={this.props.activePanel.id === target.id}
-              onclick={() => this.props.showPanel(target.id)}>
-              {target.icon()}
-              <div class="uppy-DashboardTab-name">{target.name}</div>
-            </button>
-          </li>
-        })}
-      </ul>
-    </div>
-  }
-}
-
-module.exports = Tabs

+ 2 - 2
packages/@uppy/dashboard/src/ActionBrowseTagline.js → packages/@uppy/dashboard/src/components/ActionBrowseTagline.js

@@ -21,7 +21,7 @@ class ActionBrowseTagline extends Component {
     // because Uppy will be handling the upload and so we can select same file
     // after removing — otherwise browser thinks it’s already selected
     return (
-      <span>
+      <div class="uppy-Dashboard-dropFilesTitle">
         {this.props.acquirers.length === 0
           ? this.props.i18nArray('dropPaste', { browse })
           : this.props.i18nArray('dropPasteImport', { browse })
@@ -39,7 +39,7 @@ class ActionBrowseTagline extends Component {
           ref={(input) => {
             this.input = input
           }} />
-      </span>
+      </div>
     )
   }
 }

+ 101 - 0
packages/@uppy/dashboard/src/components/AddFiles.js

@@ -0,0 +1,101 @@
+const ActionBrowseTagline = require('./ActionBrowseTagline')
+const { localIcon } = require('./icons')
+const { h, Component } = require('preact')
+
+const poweredByUppy = (props) => {
+  return <a tabindex="-1" href="https://uppy.io" rel="noreferrer noopener" target="_blank" class="uppy-Dashboard-poweredBy">Powered by <svg aria-hidden="true" class="UppyIcon uppy-Dashboard-poweredByIcon" width="11" height="11" viewBox="0 0 11 11" xmlns="http://www.w3.org/2000/svg">
+    <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></a>
+}
+
+class AddFiles extends Component {
+  constructor (props) {
+    super(props)
+    this.handleClick = this.handleClick.bind(this)
+  }
+
+  handleClick (ev) {
+    this.input.click()
+  }
+
+  render () {
+    // const isHidden = Object.keys(this.props.files).length === 0
+    const hasAcquirers = this.props.acquirers.length !== 0
+
+    if (!hasAcquirers) {
+      return (
+        <div class="uppy-DashboarAddFiles">
+          <div class="uppy-DashboardTabs">
+            <ActionBrowseTagline
+              acquirers={this.props.acquirers}
+              handleInputChange={this.props.handleInputChange}
+              i18n={this.props.i18n}
+              i18nArray={this.props.i18nArray}
+              allowedFileTypes={this.props.allowedFileTypes}
+              maxNumberOfFiles={this.props.maxNumberOfFiles}
+            />
+          </div>
+        </div>
+      )
+    }
+
+    // empty value="" on file input, so that the input is cleared after a file is selected,
+    // because Uppy will be handling the upload and so we can select same file
+    // after removing — otherwise browser thinks it’s already selected
+    return (
+      <div class="uppy-DashboarAddFiles">
+        <div class="uppy-DashboardTabs">
+          <ActionBrowseTagline
+            acquirers={this.props.acquirers}
+            handleInputChange={this.props.handleInputChange}
+            i18n={this.props.i18n}
+            i18nArray={this.props.i18nArray}
+            allowedFileTypes={this.props.allowedFileTypes}
+            maxNumberOfFiles={this.props.maxNumberOfFiles}
+          />
+          <div class="uppy-DashboardTabs-list" role="tablist">
+            <div class="uppy-DashboardTab" role="presentation">
+              <button type="button"
+                class="uppy-DashboardTab-btn"
+                role="tab"
+                tabindex={0}
+                onclick={this.handleClick}>
+                {localIcon()}
+                <div class="uppy-DashboardTab-name">{this.props.i18n('myDevice')}</div>
+              </button>
+              <input class="uppy-Dashboard-input"
+                hidden
+                aria-hidden="true"
+                tabindex={-1}
+                type="file"
+                name="files[]"
+                multiple={this.props.maxNumberOfFiles !== 1}
+                accept={this.props.allowedFileTypes}
+                onchange={this.props.handleInputChange}
+                value=""
+                ref={(input) => { this.input = input }} />
+            </div>
+            {this.props.acquirers.map((target) => {
+              return <div class="uppy-DashboardTab" role="presentation">
+                <button class="uppy-DashboardTab-btn"
+                  type="button"
+                  role="tab"
+                  tabindex={0}
+                  aria-controls={`uppy-DashboardContent-panel--${target.id}`}
+                  aria-selected={this.props.activePanel.id === target.id}
+                  onclick={() => this.props.showPanel(target.id)}>
+                  {target.icon()}
+                  <div class="uppy-DashboardTab-name">{target.name}</div>
+                </button>
+              </div>
+            })}
+          </div>
+        </div>
+        { this.props.note && <div class="uppy-Dashboard-note">{this.props.note}</div> }
+        { this.props.proudlyDisplayPoweredByUppy && poweredByUppy(this.props) }
+      </div>
+    )
+  }
+}
+
+module.exports = AddFiles

+ 21 - 0
packages/@uppy/dashboard/src/components/AddFilesPanel.js

@@ -0,0 +1,21 @@
+const { h } = require('preact')
+const AddFiles = require('./AddFiles')
+
+const AddFilesPanel = (props) => {
+  return (
+    <div class="uppy-Dashboard-AddFilesPanel"
+      aria-hidden={props.showAddFilesPanel}>
+      <div class="uppy-DashboardContent-bar">
+        <div class="uppy-DashboardContent-title" role="heading" aria-level="h1">
+          {props.i18n('addingMoreFiles')}
+        </div>
+        <button class="uppy-DashboardContent-back"
+          type="button"
+          onclick={(ev) => props.toggleAddFilesPanel(false)}>{props.i18n('back')}</button>
+      </div>
+      <AddFiles {...props} />
+    </div>
+  )
+}
+
+module.exports = AddFilesPanel

+ 35 - 27
packages/@uppy/dashboard/src/Dashboard.js → packages/@uppy/dashboard/src/components/Dashboard.js

@@ -1,28 +1,23 @@
 const FileList = require('./FileList')
-const Tabs = require('./Tabs')
+const AddFiles = require('./AddFiles')
+const AddFilesPanel = require('./AddFilesPanel')
+const PanelContent = require('./PanelContent')
+const PanelTopBar = require('./PanelTopBar')
 const FileCard = require('./FileCard')
 const classNames = require('classnames')
 const isTouchDevice = require('@uppy/utils/lib/isTouchDevice')
 const { h } = require('preact')
+const PreactCSSTransitionGroup = require('preact-css-transition-group')
 
 // http://dev.edenspiekermann.com/2016/02/11/introducing-accessible-modal-dialog
 // https://github.com/ghosh/micromodal
 
-const PanelContent = (props) => {
-  return <div style={{ width: '100%', height: '100%' }}>
-    <div class="uppy-DashboardContent-bar">
-      <div class="uppy-DashboardContent-title" role="heading" aria-level="h1">
-        {props.i18n('importFrom', { name: props.activePanel.name })}
-      </div>
-      <button class="uppy-DashboardContent-back"
-        type="button"
-        onclick={props.hideAllPanels}>{props.i18n('done')}</button>
-    </div>
-    {props.getPlugin(props.activePanel.id).render(props.state)}
-  </div>
-}
-
 module.exports = function Dashboard (props) {
+  // if (!props.inline && props.modal.isHidden) {
+  //   return <span />
+  // }
+
+  const noFiles = props.totalFileCount === 0
   const dashboardClassName = classNames(
     { 'uppy-Root': props.isTargetDOMEl },
     'uppy-Dashboard',
@@ -30,7 +25,10 @@ module.exports = function Dashboard (props) {
     { 'uppy-Dashboard--animateOpenClose': props.animateOpenClose },
     { 'uppy-Dashboard--isClosing': props.isClosing },
     { 'uppy-Dashboard--modal': !props.inline },
-    { 'uppy-Dashboard--wide': props.isWide }
+    // { 'uppy-Dashboard--wide': props.isWide },
+    { 'uppy-size--md': props.containerWidth > 576 },
+    { 'uppy-size--lg': props.containerWidth > 700 },
+    { 'uppy-Dashboard--isAddFilesPanelVisible': props.showAddFilesPanel }
   )
 
   return (
@@ -57,20 +55,30 @@ module.exports = function Dashboard (props) {
         </button>
 
         <div class="uppy-Dashboard-innerWrap">
-          <Tabs {...props} />
+          { !noFiles && <PanelTopBar {...props} /> }
 
-          <FileCard {...props} />
+          { noFiles ? <AddFiles {...props} /> : <FileList {...props} /> }
 
-          <div class="uppy-Dashboard-filesContainer">
-            <FileList {...props} />
-          </div>
+          <PreactCSSTransitionGroup
+            transitionName="uppy-transition-slideDownUp"
+            transitionEnterTimeout={250}
+            transitionLeaveTimeout={250}>
+            { props.showAddFilesPanel ? <AddFilesPanel key="AddFilesPanel" {...props} /> : null }
+          </PreactCSSTransitionGroup>
 
-          <div class="uppy-DashboardContent-panel"
-            role="tabpanel"
-            id={props.activePanel && `uppy-DashboardContent-panel--${props.activePanel.id}`}
-            aria-hidden={props.activePanel ? 'false' : 'true'}>
-            {props.activePanel && <PanelContent {...props} />}
-          </div>
+          <PreactCSSTransitionGroup
+            transitionName="uppy-transition-slideDownUp"
+            transitionEnterTimeout={250}
+            transitionLeaveTimeout={250}>
+            { props.fileCardFor ? <FileCard key="FileCard" {...props} /> : null }
+          </PreactCSSTransitionGroup>
+
+          <PreactCSSTransitionGroup
+            transitionName="uppy-transition-slideDownUp"
+            transitionEnterTimeout={250}
+            transitionLeaveTimeout={250}>
+            { props.activePanel ? <PanelContent key="PanelContent" {...props} /> : null }
+          </PreactCSSTransitionGroup>
 
           <div class="uppy-Dashboard-progressindicators">
             {props.progressindicators.map((target) => {

+ 111 - 0
packages/@uppy/dashboard/src/components/FileCard.js

@@ -0,0 +1,111 @@
+const getFileTypeIcon = require('../utils/getFileTypeIcon')
+const FilePreview = require('./FilePreview')
+const ignoreEvent = require('../utils/ignoreEvent.js')
+const { h, Component } = require('preact')
+
+class FileCard extends Component {
+  constructor (props) {
+    super(props)
+
+    this.meta = {}
+
+    this.tempStoreMetaOrSubmit = this.tempStoreMetaOrSubmit.bind(this)
+    this.renderMetaFields = this.renderMetaFields.bind(this)
+    this.handleSave = this.handleSave.bind(this)
+    this.handleCancel = this.handleCancel.bind(this)
+  }
+
+  componentDidMount () {
+    setTimeout(() => {
+      if (!this.firstInput) return
+      this.firstInput.focus({ preventScroll: true })
+    }, 150)
+  }
+
+  tempStoreMetaOrSubmit (ev) {
+    const file = this.props.files[this.props.fileCardFor]
+
+    if (ev.keyCode === 13) {
+      ev.stopPropagation()
+      ev.preventDefault()
+      this.props.saveFileCard(this.meta, file.id)
+      return
+    }
+
+    const value = ev.target.value
+    const name = ev.target.dataset.name
+    this.meta[name] = value
+  }
+
+  renderMetaFields (file) {
+    const metaFields = this.props.metaFields || []
+    return metaFields.map((field, i) => {
+      return <fieldset class="uppy-DashboardFileCard-fieldset">
+        <label class="uppy-DashboardFileCard-label">{field.name}</label>
+        <input class="uppy-c-textInput uppy-DashboardFileCard-input"
+          type="text"
+          data-name={field.id}
+          value={file.meta[field.id]}
+          placeholder={field.placeholder}
+          onkeyup={this.tempStoreMetaOrSubmit}
+          onkeydown={this.tempStoreMetaOrSubmit}
+          onkeypress={this.tempStoreMetaOrSubmit}
+          ref={(el) => {
+            if (i === 0) this.firstInput = el
+          }} /></fieldset>
+    })
+  }
+
+  handleSave (ev) {
+    const fileID = this.props.fileCardFor
+    this.props.saveFileCard(this.meta, fileID)
+  }
+
+  handleCancel (ev) {
+    this.meta = {}
+    this.props.toggleFileCard()
+  }
+
+  render () {
+    const file = this.props.files[this.props.fileCardFor]
+
+    return (
+      <div class="uppy-DashboardFileCard"
+        onDragOver={ignoreEvent}
+        onDragLeave={ignoreEvent}
+        onDrop={ignoreEvent}
+        onPaste={ignoreEvent}>
+        <div class="uppy-DashboardContent-bar">
+          <div class="uppy-DashboardContent-title" role="heading" aria-level="h1">
+            {this.props.i18nArray('editing', {
+              file: <span class="uppy-DashboardContent-titleFile">{file.meta ? file.meta.name : file.name}</span>
+            })}
+          </div>
+          <button class="uppy-DashboardContent-back" type="button" title={this.props.i18n('finishEditingFile')}
+            onclick={this.handleSave}>{this.props.i18n('done')}</button>
+        </div>
+
+        <div class="uppy-DashboardFileCard-inner">
+          <div class="uppy-DashboardFileCard-preview" style={{ backgroundColor: getFileTypeIcon(file.type).color }}>
+            <FilePreview file={file} />
+          </div>
+
+          <div class="uppy-DashboardFileCard-info">
+            {this.renderMetaFields(file)}
+          </div>
+
+          <div class="uppy-Dashboard-actions">
+            <button class="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Dashboard-actionsBtn"
+              type="button"
+              onclick={this.handleSave}>{this.props.i18n('saveChanges')}</button>
+            <button class="uppy-u-reset uppy-c-btn uppy-c-btn-link uppy-Dashboard-actionsBtn"
+              type="button"
+              onclick={this.handleCancel}>{this.props.i18n('cancel')}</button>
+          </div>
+        </div>
+      </div>
+    )
+  }
+}
+
+module.exports = FileCard

+ 31 - 31
packages/@uppy/dashboard/src/FileItem.js → packages/@uppy/dashboard/src/components/FileItem.js

@@ -1,11 +1,11 @@
 const getFileNameAndExtension = require('@uppy/utils/lib/getFileNameAndExtension')
-const truncateString = require('./truncateString')
-const copyToClipboard = require('./copyToClipboard')
+const truncateString = require('../utils/truncateString')
+const copyToClipboard = require('../utils/copyToClipboard')
 const prettyBytes = require('prettier-bytes')
 const FileItemProgress = require('./FileItemProgress')
-const getFileTypeIcon = require('./getFileTypeIcon')
+const getFileTypeIcon = require('../utils/getFileTypeIcon')
 const FilePreview = require('./FilePreview')
-const { iconEdit, iconCopy, iconRetry } = require('./icons')
+const { iconCopy, iconRetry } = require('./icons')
 const classNames = require('classnames')
 const { h } = require('preact')
 
@@ -56,7 +56,7 @@ module.exports = function fileItem (props) {
   const error = file.error || false
 
   const fileName = getFileNameAndExtension(file.meta.name).name
-  const truncatedFileName = props.isWide ? truncateString(fileName, 14) : fileName
+  const truncatedFileName = props.isWide ? truncateString(fileName, 30) : fileName
 
   const onPauseResumeCancelRetry = (ev) => {
     if (isUploaded) return
@@ -127,7 +127,7 @@ module.exports = function fileItem (props) {
       </div>
       <div class="uppy-DashboardItem-status">
         {file.data.size ? <div class="uppy-DashboardItem-statusSize">{prettyBytes(file.data.size)}</div> : null}
-        {file.source && <div class="uppy-DashboardItem-sourceIcon">
+        {(file.source && file.source !== props.id) && <div class="uppy-DashboardItem-sourceIcon">
             {acquirers.map(acquirer => {
               if (acquirer.id === file.source) {
                 return <span title={props.i18n('fileSource', { name: acquirer.name })}>
@@ -137,32 +137,32 @@ module.exports = function fileItem (props) {
             })}
           </div>
         }
+        {(!uploadInProgressOrComplete && props.metaFields && props.metaFields.length)
+          ? <button class="uppy-DashboardItem-edit"
+            type="button"
+            aria-label={props.i18n('editFile')}
+            title={props.i18n('editFile')}
+            onclick={(e) => props.toggleFileCard(file.id)}>
+            {props.i18n('edit')}
+          </button>
+          : null
+        }
+        {props.showLinkToFileUploadResult && file.uploadURL
+          ? <button class="uppy-DashboardItem-copyLink"
+            type="button"
+            aria-label={props.i18n('copyLink')}
+            title={props.i18n('copyLink')}
+            onclick={() => {
+              copyToClipboard(file.uploadURL, props.i18n('copyLinkToClipboardFallback'))
+                .then(() => {
+                  props.log('Link copied to clipboard.')
+                  props.info(props.i18n('copyLinkToClipboardSuccess'), 'info', 3000)
+                })
+                .catch(props.log)
+            }}>{iconCopy()}</button>
+          : ''
+        }
       </div>
-      {(!uploadInProgressOrComplete && props.metaFields && props.metaFields.length)
-        ? <button class="uppy-DashboardItem-edit"
-          type="button"
-          aria-label={props.i18n('editFile')}
-          title={props.i18n('editFile')}
-          onclick={(e) => props.toggleFileCard(file.id)}>
-          {iconEdit()}
-        </button>
-        : null
-      }
-      {props.showLinkToFileUploadResult && file.uploadURL
-        ? <button class="uppy-DashboardItem-copyLink"
-          type="button"
-          aria-label={props.i18n('copyLink')}
-          title={props.i18n('copyLink')}
-          onclick={() => {
-            copyToClipboard(file.uploadURL, props.i18n('copyLinkToClipboardFallback'))
-              .then(() => {
-                props.log('Link copied to clipboard.')
-                props.info(props.i18n('copyLinkToClipboardSuccess'), 'info', 3000)
-              })
-              .catch(props.log)
-          }}>{iconCopy()}</button>
-        : ''
-      }
     </div>
     <div class="uppy-DashboardItem-action">
       {!isUploaded &&

+ 0 - 0
packages/@uppy/dashboard/src/FileItemProgress.js → packages/@uppy/dashboard/src/components/FileItemProgress.js


+ 23 - 0
packages/@uppy/dashboard/src/components/FileList.js

@@ -0,0 +1,23 @@
+const FileItem = require('./FileItem')
+const classNames = require('classnames')
+const { h } = require('preact')
+
+module.exports = (props) => {
+  const noFiles = props.totalFileCount === 0
+  const dashboardFilesClass = classNames(
+    'uppy-Dashboard-files',
+    { 'uppy-Dashboard-files--noFiles': noFiles }
+  )
+
+  return (
+    <ul class={dashboardFilesClass}>
+      {Object.keys(props.files).map((fileID) => (
+        <FileItem
+          {...props}
+          acquirers={props.acquirers}
+          file={props.files[fileID]}
+        />
+      ))}
+    </ul>
+  )
+}

+ 1 - 1
packages/@uppy/dashboard/src/FilePreview.js → packages/@uppy/dashboard/src/components/FilePreview.js

@@ -1,4 +1,4 @@
-const getFileTypeIcon = require('./getFileTypeIcon')
+const getFileTypeIcon = require('../utils/getFileTypeIcon')
 const { h } = require('preact')
 
 module.exports = function FilePreview (props) {

+ 28 - 0
packages/@uppy/dashboard/src/components/PanelContent.js

@@ -0,0 +1,28 @@
+const { h } = require('preact')
+const ignoreEvent = require('../utils/ignoreEvent.js')
+
+function PanelContent (props) {
+  return (
+    <div class="uppy-DashboardContent-panel"
+      role="tabpanel"
+      id={props.activePanel && `uppy-DashboardContent-panel--${props.activePanel.id}`}
+      onDragOver={ignoreEvent}
+      onDragLeave={ignoreEvent}
+      onDrop={ignoreEvent}
+      onPaste={ignoreEvent}>
+      <div class="uppy-DashboardContent-bar">
+        <div class="uppy-DashboardContent-title" role="heading" aria-level="h1">
+          {props.i18n('importFrom', { name: props.activePanel.name })}
+        </div>
+        <button class="uppy-DashboardContent-back"
+          type="button"
+          onclick={props.hideAllPanels}>{props.i18n('done')}</button>
+      </div>
+      <div class="uppy-DashboardContent-panelBody">
+        {props.getPlugin(props.activePanel.id).render(props.state)}
+      </div>
+    </div>
+  )
+}
+
+module.exports = PanelContent

+ 31 - 0
packages/@uppy/dashboard/src/components/PanelTopBar.js

@@ -0,0 +1,31 @@
+const { h } = require('preact')
+
+function DashboardContentTitle (props) {
+  if (props.newFiles.length) {
+    return props.i18n('xFilesSelected', { smart_count: props.newFiles.length })
+  }
+}
+
+function PanelTopBar (props) {
+  return (
+    <div class="uppy-DashboardContent-bar">
+      <button class="uppy-DashboardContent-back"
+        type="button"
+        onclick={props.cancelAll}>{props.i18n('cancel')}</button>
+      <div class="uppy-DashboardContent-title" role="heading" aria-level="h1">
+        <DashboardContentTitle {...props} />
+      </div>
+      <button class="uppy-DashboardContent-addMore"
+        type="button"
+        aria-label={props.i18n('addMoreFiles')}
+        title={props.i18n('addMoreFiles')}
+        onclick={() => props.toggleAddFilesPanel(true)}>
+        <svg class="UppyIcon" width="15" height="15" viewBox="0 0 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg">
+          <path d="M7,6 L13,6 L13,7 L7,7 L7,13 L6,13 L6,7 L0,7 L0,6 L6,6 L6,0 L7,0 L7,6 Z" />
+        </svg>
+      </button>
+    </div>
+  )
+}
+
+module.exports = PanelTopBar

+ 4 - 18
packages/@uppy/dashboard/src/icons.js → packages/@uppy/dashboard/src/components/icons.js

@@ -3,7 +3,7 @@ const { h } = require('preact')
 // https://css-tricks.com/creating-svg-icon-system-react/
 
 function defaultTabIcon () {
-  return <svg aria-hidden="true" class="UppyIcon" width="30" height="30" viewBox="0 0 30 30">
+  return <svg aria-hidden="true" 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>
 }
@@ -30,21 +30,15 @@ function iconPause () {
   </svg>
 }
 
-function iconEdit () {
-  return <svg aria-hidden="true" class="UppyIcon" width="28" height="28" viewBox="0 0 28 28">
-    <path d="M25.436 2.566a7.98 7.98 0 0 0-2.078-1.51C22.638.703 21.906.5 21.198.5a3 3 0 0 0-1.023.17 2.436 2.436 0 0 0-.893.562L2.292 18.217.5 27.5l9.28-1.796 16.99-16.99c.255-.254.444-.56.562-.888a3 3 0 0 0 .17-1.023c0-.708-.205-1.44-.555-2.16a8 8 0 0 0-1.51-2.077zM9.01 24.252l-4.313.834c0-.03.008-.06.012-.09.007-.944-.74-1.715-1.67-1.723-.04 0-.078.007-.118.01l.83-4.29L17.72 5.024l5.264 5.264L9.01 24.252zm16.84-16.96a.818.818 0 0 1-.194.31l-1.57 1.57-5.26-5.26 1.57-1.57a.82.82 0 0 1 .31-.194 1.45 1.45 0 0 1 .492-.074c.397 0 .917.126 1.468.397.55.27 1.13.678 1.656 1.21.53.53.94 1.11 1.208 1.655.272.55.397 1.07.393 1.468.004.193-.027.358-.074.488z" />
-  </svg>
-}
-
 function localIcon () {
-  return <svg aria-hidden="true" class="UppyIcon" width="27" height="25" viewBox="0 0 27 25">
+  return <svg aria-hidden="true" fill="#607d8b" width="27" height="25" viewBox="0 0 27 25">
     <path d="M5.586 9.288a.313.313 0 0 0 .282.176h4.84v3.922c0 1.514 1.25 2.24 2.792 2.24 1.54 0 2.79-.726 2.79-2.24V9.464h4.84c.122 0 .23-.068.284-.176a.304.304 0 0 0-.046-.324L13.735.106a.316.316 0 0 0-.472 0l-7.63 8.857a.302.302 0 0 0-.047.325z" />
     <path d="M24.3 5.093c-.218-.76-.54-1.187-1.208-1.187h-4.856l1.018 1.18h3.948l2.043 11.038h-7.193v2.728H9.114v-2.725h-7.36l2.66-11.04h3.33l1.018-1.18H3.907c-.668 0-1.06.46-1.21 1.186L0 16.456v7.062C0 24.338.676 25 1.51 25h23.98c.833 0 1.51-.663 1.51-1.482v-7.062L24.3 5.093z" />
   </svg>
 }
 
 function iconRetry () {
-  return <svg class="UppyIcon retry" width="28" height="31" viewBox="0 0 16 19" xmlns="http://www.w3.org/2000/svg">
+  return <svg aria-hidden="true" class="UppyIcon retry" width="28" height="31" viewBox="0 0 16 19" xmlns="http://www.w3.org/2000/svg">
     <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" />
@@ -88,25 +82,17 @@ function iconText () {
   </svg>
 }
 
-function dashboardBgIcon () {
-  return <svg aria-hidden="true" class="UppyIcon" width="48" height="69" viewBox="0 0 48 69">
-    <path d="M.5 1.5h5zM10.5 1.5h5zM20.5 1.5h5zM30.504 1.5h5zM45.5 11.5v5zM45.5 21.5v5zM45.5 31.5v5zM45.5 41.502v5zM45.5 51.502v5zM45.5 61.5v5zM45.5 66.502h-4.998zM35.503 66.502h-5zM25.5 66.502h-5zM15.5 66.502h-5zM5.5 66.502h-5zM.5 66.502v-5zM.5 56.502v-5zM.5 46.503V41.5zM.5 36.5v-5zM.5 26.5v-5zM.5 16.5v-5zM.5 6.5V1.498zM44.807 11H36V2.195z" />
-  </svg>
-}
-
 module.exports = {
   defaultTabIcon,
   iconCopy,
   iconResume,
   iconPause,
   iconRetry,
-  iconEdit,
   localIcon,
   checkIcon,
   iconAudio,
   iconVideo,
   iconPDF,
   iconFile,
-  iconText,
-  dashboardBgIcon
+  iconText
 }

+ 47 - 9
packages/@uppy/dashboard/src/index.js

@@ -1,14 +1,15 @@
 const { Plugin } = require('@uppy/core')
 const Translator = require('@uppy/utils/lib/Translator')
 const dragDrop = require('drag-drop')
-const DashboardUI = require('./Dashboard')
+const DashboardUI = require('./components/Dashboard')
 const StatusBar = require('@uppy/status-bar')
 const Informer = require('@uppy/informer')
 const ThumbnailGenerator = require('@uppy/thumbnail-generator')
 const findAllDOMElements = require('@uppy/utils/lib/findAllDOMElements')
 const toArray = require('@uppy/utils/lib/toArray')
 const prettyBytes = require('prettier-bytes')
-const { defaultTabIcon } = require('./icons')
+const throttle = require('lodash.throttle')
+const { defaultTabIcon } = require('./components/icons')
 
 // Some code for managing focus was adopted from https://github.com/ghosh/micromodal
 // MIT licence, https://github.com/ghosh/micromodal/blob/master/LICENSE.md
@@ -47,6 +48,8 @@ module.exports = class Dashboard extends Plugin {
         closeModal: 'Close Modal',
         upload: 'Upload',
         importFrom: 'Import from %{name}',
+        addingMoreFiles: 'Adding more files',
+        addMoreFiles: 'Add more files',
         dashboardWindowTitle: 'Uppy Dashboard Window (Press escape to close)',
         dashboardTitle: 'Uppy Dashboard',
         copyLinkToClipboardSuccess: 'Link copied to clipboard',
@@ -54,16 +57,18 @@ module.exports = class Dashboard extends Plugin {
         copyLink: 'Copy link',
         fileSource: 'File source: %{name}',
         done: 'Done',
+        back: 'Back',
         name: 'Name',
         removeFile: 'Remove file',
         editFile: 'Edit file',
         editing: 'Editing %{file}',
+        edit: 'Edit',
         finishEditingFile: 'Finish editing file',
         saveChanges: 'Save changes',
         cancel: 'Cancel',
         localDisk: 'Local Disk',
         myDevice: 'My Device',
-        dropPasteImport: 'Drop files here, paste, import from one of the locations above or %{browse}',
+        dropPasteImport: 'Drop files here, paste, %{browse} or import from',
         dropPaste: 'Drop files here, paste or %{browse}',
         browse: 'browse',
         fileProgress: 'File progress: upload speed and ETA',
@@ -74,6 +79,10 @@ module.exports = class Dashboard extends Plugin {
         resumeUpload: 'Resume upload',
         pauseUpload: 'Pause upload',
         retryUpload: 'Retry upload',
+        xFilesSelected: {
+          0: '%{smart_count} file selected',
+          1: '%{smart_count} files selected'
+        },
         uploadXFiles: {
           0: 'Upload %{smart_count} file',
           1: 'Upload %{smart_count} files'
@@ -146,10 +155,12 @@ module.exports = class Dashboard extends Plugin {
     this.onKeydown = this.onKeydown.bind(this)
     this.handleClickOutside = this.handleClickOutside.bind(this)
     this.toggleFileCard = this.toggleFileCard.bind(this)
+    this.toggleAddFilesPanel = this.toggleAddFilesPanel.bind(this)
     this.handleDrop = this.handleDrop.bind(this)
     this.handlePaste = this.handlePaste.bind(this)
     this.handleInputChange = this.handleInputChange.bind(this)
     this.updateDashboardElWidth = this.updateDashboardElWidth.bind(this)
+    this.throttledUpdateDashboardElWidth = throttle(this.updateDashboardElWidth, 500, { leading: true, trailing: true })
     this.render = this.render.bind(this)
     this.install = this.install.bind(this)
   }
@@ -196,7 +207,8 @@ module.exports = class Dashboard extends Plugin {
 
   hideAllPanels () {
     this.setPluginState({
-      activePanel: false
+      activePanel: false,
+      showAddFilesPanel: false
     })
   }
 
@@ -222,6 +234,7 @@ module.exports = class Dashboard extends Plugin {
 
   getFocusableNodes () {
     const nodes = this.el.querySelectorAll(FOCUSABLE_ELEMENTS)
+    console.log(Object.keys(nodes).map((key) => nodes[key]))
     return Object.keys(nodes).map((key) => nodes[key])
   }
 
@@ -276,10 +289,6 @@ module.exports = class Dashboard extends Plugin {
   }
 
   openModal () {
-    this.setPluginState({
-      isHidden: false
-    })
-
     // save scroll position
     this.savedScrollPosition = window.scrollY
     // save active element, so we can restore focus when modal is closed
@@ -289,6 +298,20 @@ module.exports = class Dashboard extends Plugin {
       document.body.classList.add('uppy-Dashboard-isFixed')
     }
 
+    if (this.opts.animateOpenClose && this.getPluginState().isClosing) {
+      const handler = () => {
+        this.setPluginState({
+          isHidden: false
+        })
+        this.el.removeEventListener('animationend', handler, false)
+      }
+      this.el.addEventListener('animationend', handler, false)
+    } else {
+      this.setPluginState({
+        isHidden: false
+      })
+    }
+
     if (this.opts.browserBackButtonClose) {
       this.updateBrowserHistory()
     }
@@ -419,9 +442,10 @@ module.exports = class Dashboard extends Plugin {
     })
 
     this.updateDashboardElWidth()
-    window.addEventListener('resize', this.updateDashboardElWidth)
+    window.addEventListener('resize', this.throttledUpdateDashboardElWidth)
 
     this.uppy.on('plugin-remove', this.removeTarget)
+    this.uppy.on('file-added', (ev) => this.toggleAddFilesPanel(false))
   }
 
   removeEvents () {
@@ -434,10 +458,13 @@ module.exports = class Dashboard extends Plugin {
     window.removeEventListener('resize', this.updateDashboardElWidth)
     window.removeEventListener('popstate', this.handlePopState, false)
     this.uppy.off('plugin-remove', this.removeTarget)
+    this.uppy.off('file-added', (ev) => this.toggleAddFilesPanel(false))
   }
 
   updateDashboardElWidth () {
     const dashboardEl = this.el.querySelector('.uppy-Dashboard-inner')
+    if (!dashboardEl) return
+
     this.uppy.log(`Dashboard width: ${dashboardEl.offsetWidth}`)
 
     this.setPluginState({
@@ -451,6 +478,12 @@ module.exports = class Dashboard extends Plugin {
     })
   }
 
+  toggleAddFilesPanel (show) {
+    this.setPluginState({
+      showAddFilesPanel: show
+    })
+  }
+
   handleDrop (files) {
     this.uppy.log('[Dashboard] Files were dropped')
 
@@ -576,8 +609,11 @@ module.exports = class Dashboard extends Plugin {
       pauseUpload: this.uppy.pauseResume,
       retryUpload: this.uppy.retryUpload,
       cancelUpload: cancelUpload,
+      cancelAll: this.uppy.cancelAll,
       fileCardFor: pluginState.fileCardFor,
       toggleFileCard: this.toggleFileCard,
+      toggleAddFilesPanel: this.toggleAddFilesPanel,
+      showAddFilesPanel: pluginState.showAddFilesPanel,
       saveFileCard: saveFileCard,
       updateDashboardElWidth: this.updateDashboardElWidth,
       width: this.opts.width,
@@ -586,6 +622,7 @@ module.exports = class Dashboard extends Plugin {
       proudlyDisplayPoweredByUppy: this.opts.proudlyDisplayPoweredByUppy,
       currentWidth: pluginState.containerWidth,
       isWide: pluginState.containerWidth > 400,
+      containerWidth: pluginState.containerWidth,
       isTargetDOMEl: this.isTargetDOMEl,
       allowedFileTypes: this.uppy.opts.restrictions.allowedFileTypes,
       maxNumberOfFiles: this.uppy.opts.restrictions.maxNumberOfFiles
@@ -605,6 +642,7 @@ module.exports = class Dashboard extends Plugin {
     this.setPluginState({
       isHidden: true,
       showFileCard: false,
+      showAddFilesPanel: false,
       activePanel: false,
       metaFields: this.opts.metaFields,
       targets: []

+ 274 - 207
packages/@uppy/dashboard/src/style.scss

@@ -3,6 +3,32 @@
 @import '@uppy/status-bar/src/style.scss';
 @import '@uppy/provider-views/src/style.scss';
 
+// transitions //
+
+.uppy-transition-slideDownUp-enter {
+  opacity: 0.01;
+  transform: translate3d(0, -105%, 0);
+  transition: transform 0.25s ease-in-out, opacity 0.25s ease-in-out;
+}
+
+.uppy-transition-slideDownUp-enter.uppy-transition-slideDownUp-enter-active {
+  opacity: 1;
+  transform: translate3d(0, 0, 0);
+}
+
+.uppy-transition-slideDownUp-leave {
+  opacity: 1;
+  transform: translate3d(0, 0, 0);
+  transition: transform 0.25s ease-in-out, opacity 0.25s ease-in-out;
+}
+
+.uppy-transition-slideDownUp-leave.uppy-transition-slideDownUp-leave-active {
+  opacity: 0.01;
+  transform: translate3d(0, -105%, 0);
+}
+
+// end transitions //
+
 .uppy-Dashboard--modal {
   z-index: $zIndex-2;
 }
@@ -85,15 +111,14 @@
 
 .uppy-Dashboard-inner {
   position: relative;
-  background-color: darken($color-white, 2%);
+  background-color: $color-almost-white;
   max-width: 100%; /* no !important */
   max-height: 100%; /* no !important */
-  width: 100%; /* no !important */
-  height: 100%; /* no !important */
-  min-width: 300px;
+  min-width: 290px;
   min-height: 400px;
   outline: none;
   border: 1px solid rgba($color-gray, 0.2);
+  border-radius: 5px;
 
   .uppy-Dashboard--modal & {
     z-index: $zIndex-3;
@@ -102,7 +127,6 @@
   @media #{$screen-medium} {
     width: 750px; /* no !important */
     height: 550px; /* no !important */
-    border-radius: 5px;
   }
 }
 
@@ -111,18 +135,16 @@
   flex-direction: column;
   height: 100%;
   overflow: hidden;
-  min-height: 300px;
   position: relative;
-
-  @media #{$screen-medium} {
-    border-radius: 5px;
-  }
+  border-radius: 5px;
 }
 
 .uppy-Dashboard--modal .uppy-Dashboard-inner {
   position: fixed;
-  top: 0;
-  left: 0;
+  top: 35px;
+  left: 15px;
+  right: 15px;
+  bottom: 15px;
   border: none;
 
   @media #{$screen-medium} {
@@ -137,17 +159,16 @@
   @include reset-button;
   display: none;
   position: absolute;
-  top: 2px;
-  right: 8px;
+  top: -33px;
+  right: -2px;
   cursor: pointer;
-  color: rgba($color-asphalt-gray, 0.5);
-  transition: all 0.3s;
-  font-size: 23px;
+  color: rgba($color-white, 0.9);
+  font-size: 27px;
 
-  .uppy-Dashboard--wide & {
-    font-size: 30px;
-    top: 2px;
-    right: 8px;
+  @media #{$screen-medium} {
+    font-size: 35px;
+    top: -10px;
+    right: -35px;
   }
 
   .uppy-Dashboard--modal & {
@@ -156,23 +177,26 @@
   }
 }
 
-.uppy-Dashboard-close:hover {
-  color: $color-cornflower-blue;
+.uppy-DashboarAddFiles {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+  height: 100%;
+  position: relative;
+  text-align: center;
+  flex: 1;
 }
 
-
 .uppy-DashboardTabs {
-  padding: 7px;
-  // padding-right: 28px;
-  border-bottom: 1px solid rgba($color-gray, 0.3);
-  overflow-x: auto;
-  -webkit-overflow-scrolling: touch;
-  // overflow-x: auto;
-  // -webkit-overflow-scrolling: touch;
-}
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  width: 100%;
 
-.uppy-DashboardTabs[aria-hidden=true] {
-  display: none;
+  .uppy-size--md & {
+    align-items: center;
+  }
 }
 
 .uppy-DashboardTabs-title {
@@ -184,7 +208,7 @@
   text-align: center;
   color: $color-asphalt-gray;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     font-size: 17px;
     line-height: 40px;
   }
@@ -202,70 +226,111 @@
   }
 
 .uppy-DashboardTabs-list {
-  list-style-type: none;
-  margin: 0;
-  padding: 0;
-  // display: flex;
-  // justify-content: center;
-  // align-items: center;
-  white-space: nowrap;
-  text-align: center;
+  display: flex;
+  flex-direction: column;
+  max-height: 300px;
+  overflow-x: auto;
+  -webkit-overflow-scrolling: touch;
+  margin-top: 10px;
+
+  .uppy-size--md & {
+    flex-direction: row;
+    flex-wrap: wrap;
+    justify-content: center;
+    max-width: 600px;
+    overflow-x: initial;
+    margin-top: 30px;
+  }
 }
 
 .uppy-DashboardTab {
-  width: 70px;
-  margin: 0;
+  width: 100%;
   display: inline-block;
   text-align: center;
+  border-bottom: 1px solid rgba($color-gray, 0.2);
 
-  .uppy-Dashboard--wide & {
-    width: 75px;
-    margin: 0 5px;
+  .uppy-size--md & {
+    width: initial;
+    margin-bottom: 20px;
+    border-bottom: initial;
   }
 }
 
 .uppy-DashboardTab-btn {
   width: 100%;
+  height: 100%;
   cursor: pointer;
   border: 0;
   background-color: transparent;
   -webkit-appearance: none;
   appearance: none;
-  // outline: none;
-  transition: all 0.3s;
   color: darken($color-gray, 25%);
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 14px 20px;
+  line-height: 1;
+
+  .uppy-size--md & {
+    width: 90px;
+    margin: 0 5px;
+    flex-direction: column;
+    padding: 0;
+  }
 }
 
-  // .uppy-DashboardTab-btn:focus,
-  // .uppy-DashboardTab-btn:active,
   .uppy-DashboardTab-btn:hover {
     color: $color-cornflower-blue;
   }
 
+  .uppy-DashboardTab-btn svg {
+    margin-right: 10px;
+
+    .uppy-size--md & {
+      margin-right: 0;
+    }
+  }
+
+  .uppy-DashboardTab-btn svg,
+  .uppy-DashboardTab-btn svg * {
+    max-width: 100%;
+    max-height: 100%;
+    display: inline-block;
+    vertical-align: text-top;
+    overflow: hidden;
+    transition: transform 0.2s;
+    will-change: transform;
+  }
+
+  .uppy-DashboardTab-btn:hover svg {
+    transform: scale(1.1, 1.1);
+  }
+
 .uppy-DashboardTab-name {
-  font-size: 8px;
-  line-height: 11px;
-  margin-top: 5px;
-  margin-bottom: 0;
+  font-size: 14px;
   font-weight: 500;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
+  // line-height: 14px;
+  // overflow: hidden;
+  // text-overflow: ellipsis;
+  // white-space: nowrap;
 
-  .uppy-Dashboard--wide & {
-    font-size: 9px;
+  .uppy-size--md & {
+    font-size: 11px;
+    line-height: 14px;
+    margin-top: 8px;
+    margin-bottom: 0;
   }
 }
 
 // On SVG sizing: https://sarasoueidan.com/blog/svg-style-inheritance-and-FOUSVG/
-.uppy-DashboardTab .UppyIcon {
+.uppy-DashboardTab svg {
   width: 18px;
   height: 18px;
   vertical-align: middle;
 
-  .uppy-Dashboard--wide & {
-    width: 23px;
-    height: 23px;
+  .uppy-size--md & {
+    width: 27px;
+    height: 27px;
   }
 }
 
@@ -279,20 +344,20 @@
 }
 
 .uppy-DashboardContent-bar {
-  position: absolute;
-  top: 0;
-  left: 0;
   display: flex;
   align-items: center;
+  justify-content: space-between;
   height: 40px;
   width: 100%;
   border-bottom: 1px solid rgba($color-gray, 0.3);
+  
   z-index: $zIndex-4;
-  background-color: darken($color-white, 4%);
-  padding: 0 15px;
+  background-color: $color-almost-white;
+  padding: 0 10px;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     height: 50px;
+    padding: 0 15px;
   }
 }
 
@@ -302,7 +367,7 @@
   left: 0;
   right: 0;
   text-align: center;
-  font-size: 14px;
+  font-size: 12px;
   line-height: 40px;
   font-weight: normal;
   max-width: 170px;
@@ -311,8 +376,8 @@
   overflow-x: hidden;
   margin: auto;
 
-  .uppy-Dashboard--wide & {
-    font-size: 16px;
+  .uppy-size--md & {
+    font-size: 14px;
     line-height: 50px;
     max-width: 300px;
   }
@@ -320,38 +385,75 @@
 
 .uppy-DashboardContent-back {
   @include reset-button;
-  font-size: 14px;
+  font-size: 13px;
   font-weight: 500;
   cursor: pointer;
   color: $color-cornflower-blue;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     font-size: 15px;
   }
 }
 
+.uppy-DashboardContent-addMore {
+  @include reset-button;
+  font-weight: 500;
+  cursor: pointer;
+  color: $color-cornflower-blue;
+  stroke: $color-cornflower-blue;
+  stroke-width: 0.7px;
+  width: 13px;
+  height: 13px;
+
+  .uppy-size--md & {
+    width: 15px;
+    height: 15px;
+  }
+}
+
+  .uppy-DashboardContent-addMore svg {
+    vertical-align: text-top;
+  }
+
 .uppy-DashboardContent-panel {
   position: absolute;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
-  transform: translate3d(0, -105%, 0);
-  transition: transform 0.2s ease-in-out;
   background-color: darken($color-white, 4%);
+  overflow: hidden;
+  z-index: $zIndex-5;
+  border-radius: 5px;
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+}
+
+.uppy-Dashboard-AddFilesPanel {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: $color-almost-white;
+  background: linear-gradient(0deg, $color-almost-white 35%, rgba($color-almost-white, 0.85) 100%);
   box-shadow: 0 0 10px 5px rgba($color-black, 0.15);
-  padding-top: 40px;
   overflow: hidden;
-  z-index: $zIndex-4;
+  z-index: $zIndex-5;
+  border-radius: 5px;
+  display: flex;
+  flex-direction: column;
+}
 
-  .uppy-Dashboard--wide & {
-    padding-top: 50px;
+  .uppy-Dashboard--isAddFilesPanelVisible .uppy-Dashboard-files {
+    filter: blur(2px);
   }
-}
 
-.uppy-DashboardContent-panel[aria-hidden=false] {
-  transform: translate3d(0, 0, 0);
-}
+// .uppy-Dashboard-AddFilesPanel[aria-hidden=true],
+// .uppy-DashboardContent-panel[aria-hidden=true] {
+//   transform: translate3d(0, 0, 0);
+// }
 
 // Progress bar placeholder
 
@@ -417,49 +519,40 @@
   padding: 0 0 10px 0;
   overflow-y: auto;
   -webkit-overflow-scrolling: touch;
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
+  flex: 1;
 }
 
-  .uppy-Dashboard--wide .uppy-Dashboard-files {
-    padding: 15px 10px 10px 10px;
+  .uppy-size--md .uppy-Dashboard-files {
+    padding-top: 10px;
   }
 
-.uppy-Dashboard.drag .uppy-Dashboard-innerWrap  {
-  background-color: darken($color-white, 20%)
+.uppy-Dashboard.drag .uppy-Dashboard-innerWrap {
+  background-color: darken($color-almost-white, 25%)
 }
 
-.uppy-Dashboard.drag .uppy-Dashboard-files--noFiles {
-  border-color: darken($color-white, 20%);
+.uppy-Dashboard.drag .uppy-Dashboard-AddFilesPanel {
+  background: darken($color-almost-white, 20%)
 }
 
-.uppy-Dashboard-bgIcon {
-  height: 100%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.uppy-Dashboard.drag .uppy-Dashboard-bgIcon {
-  opacity: 1;
+.uppy-Dashboard.drag .uppy-Dashboard-files--noFiles {
+  border-color: darken($color-almost-white, 20%);
 }
 
 .uppy-Dashboard-dropFilesTitle {
-  max-width: 460px;
+  max-width: 300px;
   text-align: center;
-  font-size: 18px;
+  font-size: 16px;
   line-height: 1.45;
   font-weight: 400;
-  color: rgba($color-asphalt-gray, 0.8);
+  color: $color-asphalt-gray;
+  margin: auto;
+  // margin-bottom: 10px;
   padding: 0 15px;
-  // margin: 0;
-  // margin-top: 25px;
 
-  .uppy-Dashboard--wide & {
-    font-size: 24px;
+  .uppy-size--md & {
+    max-width: 400px;
+    font-size: 27px;
+    // margin-bottom: 30px;
   }
 }
 
@@ -473,35 +566,27 @@
   left: 0;
   width: 100%;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     font-size: 16px;
   }
 }
 
 .uppy-Dashboard-poweredBy {
-  width: 100%;
+  // width: 100%;
   text-align: center;
   position: absolute;
   bottom: 23px;
   font-size: 11px;
   color: $color-gray;
   text-decoration: none;
-  padding-top: 8px;
+  margin-top: 8px;
   padding-right: 2px;
 }
 
-  // .uppy-Dashboard--modal .uppy-Dashboard-poweredBy {
-  //   color: rgba($color-white, 0.7);
-  // }
-
 .uppy-Dashboard-poweredByUppy {
   color: $color-gray;
 }
 
-  // .uppy-Dashboard--modal .uppy-Dashboard-poweredByUppy {
-  //   color: $color-white;
-  // }
-
 .uppy-Dashboard-poweredByIcon {
   stroke: $color-gray;
   fill: none;
@@ -512,28 +597,22 @@
   opacity: 0.9;
 }
 
-  // .uppy-Dashboard--modal .uppy-Dashboard-poweredByIcon {
-  //   stroke: transparent;
-  //   fill: $color-uppy-pink;
-  // }
-
 .uppy-DashboardItem {
   list-style: none;
   margin: 10px 0;
   position: relative;
-  // background-color: $color-white;
   display: flex;
   align-items: center;
   border-bottom: 1px solid lighten($color-gray, 35%);
   padding-bottom: 10px;
   padding-left: 10px;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     flex-direction: column;
     float: left;
     width: 140px;
     height: 170px;
-    margin: 5px 15px;
+    margin: 5px 20px;
     border: 0;
     background-color: initial;
     border-bottom: none;
@@ -551,7 +630,7 @@
   justify-content: center;
   align-items: center;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     width: 100%;
     height: 100px;
     border: 0;
@@ -570,14 +649,19 @@
 .uppy-DashboardItem-sourceIcon {
   display: inline-block;
   vertical-align: middle;
-  width: 10px;
-  height: 10px;
-  color: rgba($color-gray, 0.6);
+  width: 11px;
+  height: 11px;
+  color: rgba($color-gray, 0.85);
+}
 
-  .uppy-Dashboard--wide & {
-    width: 10px;
-    height: 10px;
-  }
+.uppy-DashboardItem-sourceIcon svg,
+.uppy-DashboardItem-sourceIcon svg * {
+  max-width: 100%;
+  max-height: 100%;
+  display: inline-block;
+  vertical-align: text-top;
+  overflow: hidden;
+  fill: currentColor;
 }
 
 .uppy-DashboardItem-previewInnerWrap {
@@ -592,12 +676,8 @@
   box-shadow: 0 0 2px 0 rgba($color-gray, 0.7);
   border-radius: 3px;
 
-  .uppy-Dashboard--wide & {
-    // box-shadow: 0 0 2px 0 rgba(175, 175, 175, 0.7);
-    box-shadow: 0 1px 3px rgba(0,0,0,.2);
-    border-radius: 3px;
-    // border-top-left-radius: 6px;
-    // border-top-right-radius: 6px;
+  .uppy-size--md & {
+    box-shadow: 0 1px 3px rgba($color-black,.2);
   }
 }
 
@@ -642,7 +722,7 @@
   left: 50%;
   transform: translate(-50%, -50%);
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     width: 25px;
     height: 25px;
   }
@@ -662,19 +742,15 @@
 }
 
 .uppy-DashboardItem-info {
-  // padding: 10px 19px 0 25px;
   padding-left: 15px;
   position: relative;
   max-width: 65%;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     width: 100%;
     max-width: 100%;
     flex: 1;
-    padding: 10px 19px 0 3px;
-    // border-bottom-left-radius: 6px;
-    // border-bottom-right-radius: 6px;
-    // border: 1px solid rgba($color-gray, 0.2);
+    padding: 8px 3px 0 3px;
     border-top: 0;
   }
 }
@@ -686,14 +762,15 @@
   margin: 0;
   padding: 0;
   max-height: 28px;
-  margin-bottom: 3px;
+  margin-bottom: 5px;
   text-overflow: ellipsis;
   white-space: nowrap;
   overflow: hidden;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     word-break: break-all;
     white-space: normal;
+    overflow: initial;
   }
 }
 
@@ -704,8 +781,9 @@
 
 .uppy-DashboardItem-status {
   font-size: 11px;
+  line-height: 11px;
   font-weight: normal;
-  color: $color-gray;
+  color: darken($color-gray, 15%);
   margin-bottom: 4px;
 }
 
@@ -713,54 +791,45 @@
   display: inline-block;
   vertical-align: bottom;
   text-transform: uppercase;
-  margin-right: 3px;
 }
 
 .uppy-DashboardItem-edit,
 .uppy-DashboardItem-copyLink {
   @include reset-button;
-  font-size: 12px;
-  text-align: left;
+  display: inline-block;
+  vertical-align: bottom;
   cursor: pointer;
-  position: absolute;
-  top: 0;
-  right: -20px;
-
-  .uppy-Dashboard--wide & {
-    top: 9px;
-    right: 3px;
-  }
 }
 
-.uppy-DashboardItem-edit .UppyIcon {
+.uppy-DashboardItem-copyLink {
   width: 11px;
   height: 11px;
-  color: $color-asphalt-gray;
-
-  .uppy-Dashboard--wide & {
-    width: 12px;
-    height: 12px;
-  }
 }
 
-.uppy-DashboardItem-copyLink .UppyIcon {
-  width: 11px;
-  height: 11px;
-  color: $color-asphalt-gray;
+.uppy-DashboardItem-edit:not(:first-child),
+.uppy-DashboardItem-copyLink:not(:first-child),
+.uppy-DashboardItem-sourceIcon:not(:first-child) {
+  position: relative;
+  margin-left: 14px;
+  // margin-right: 7px;
 
-  .uppy-Dashboard--wide & {
-    width: 13px;
-    height: 13px;
+  &:before {
+    content: '\00B7';
+    position: absolute;
+    top: 0;
+    left: -9px;
+    color: $color-gray;
+    font-weight: 700;
   }
 }
 
 .uppy-DashboardItem-action {
   position: absolute;
   top: 23px;
-  right: 5px;
+  right: 10px;
   z-index: $zIndex-3;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     top: -8px;
     right: -8px;
   }
@@ -769,14 +838,14 @@
 .uppy-DashboardItem-remove {
   @include reset-button;
   cursor: pointer;
-  color: lighten($color-asphalt-gray, 20%);
+  color: $color-black;
   width: 16px;
   height: 16px;
+  opacity: 0.75;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     width: 20px;
     height: 20px;
-    color: lighten($color-asphalt-gray, 8%);
   }
 }
 
@@ -820,7 +889,7 @@
   opacity: 0.9;
   transition: all .35s ease;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     width: 55px;
     height: 55px;
   }
@@ -834,7 +903,7 @@
     width: 18px;
     height: 18px;
 
-    .uppy-Dashboard--wide & {
+    .uppy-size--md & {
       width: 28px;
       height: 28px;
     }
@@ -845,7 +914,7 @@
     height: 18px;
     opacity: 1;
 
-    .uppy-Dashboard--wide & {
+    .uppy-size--md & {
       width: 25px;
       height: 25px;
     }
@@ -863,7 +932,7 @@
   width: 100%;
   text-shadow: 0 1px 0 rgba($color-black, 0.3);
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     display: block;
   }
 }
@@ -977,13 +1046,14 @@
 
 .uppy-Dashboard-actions {
   height: 55px;
-  border-top: 1px solid rgba($color-gray, 0.2);
+  border-top: 1px solid rgba($color-gray, 0.3);
   display: flex;
   align-items: center;
   padding: 0 15px;
+  background-color: $color-almost-white;
 }
 
-  .uppy-Dashboard--wide .uppy-Dashboard-actions {
+  .uppy-size--md .uppy-Dashboard-actions {
     height: 65px;
   }
 
@@ -1001,7 +1071,7 @@
   width: 50px;
   height: 50px;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     width: 60px;
     height: 60px;
   }
@@ -1025,7 +1095,7 @@
   line-height: 16px;
   font-size: 8px;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     width: 18px;
     height: 18px;
     line-height: 18px;
@@ -1038,9 +1108,8 @@
 //
 
 .uppy-DashboardFileCard {
-  transform: translate3d(0, 0, 0);
-  transition: transform 0.2s ease-in-out;
-
+  // transform: translate3d(0, 0, 0);
+  // transition: transform 0.2s ease-in-out;
   width: 100%;
   height: 100%;
   position: absolute;
@@ -1048,24 +1117,22 @@
   left: 0;
   right: 0;
   bottom: 0;
-  z-index: $zIndex-4;
+  z-index: $zIndex-5;
   box-shadow: 0px 0px 10px 4px rgba($color-black, 0.1);
   background-color: $color-white;
+  display: flex;
+  flex-direction: column;
 }
 
-  .uppy-DashboardFileCard[aria-hidden=true] {
-    transform: translate3d(0, -105%, 0);
-  }
+  // .uppy-DashboardFileCard[aria-hidden=true] {
+  //   transform: translate3d(0, -105%, 0);
+  // }
 
 .uppy-DashboardFileCard-inner {
   display: flex;
   flex-direction: column;
   height: 100%;
-  padding-top: 40px;
-
-  .uppy-Dashboard--wide & {
-    padding-top: 50px;
-  }
+  flex: 1;
 }
 
 .uppy-DashboardFileCard-preview {
@@ -1111,7 +1178,7 @@
   font-size: 12px;
   color: $color-asphalt-gray;
 
-  .uppy-Dashboard--wide & {
+  .uppy-size--md & {
     font-size: 13px;
   }
 }

+ 0 - 0
packages/@uppy/dashboard/src/copyToClipboard.js → packages/@uppy/dashboard/src/utils/copyToClipboard.js


+ 0 - 0
packages/@uppy/dashboard/src/copyToClipboard.test.js → packages/@uppy/dashboard/src/utils/copyToClipboard.test.js


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

@@ -1,4 +1,4 @@
-const { iconText, iconAudio, iconVideo, iconPDF } = require('./icons')
+const { iconText, iconAudio, iconVideo, iconPDF } = require('../components/icons')
 
 module.exports = function getIconByMime (fileType) {
   const defaultChoice = {

+ 17 - 0
packages/@uppy/dashboard/src/utils/ignoreEvent.js

@@ -0,0 +1,17 @@
+// ignore drop/paste events if they are not in input or textarea —
+// otherwise when Url plugin adds drop/paste listeners to this.el,
+// draging UI elements or pasting anything into any field triggers those events —
+// Url treats them as URLs that need to be imported
+
+function ignoreEvent (ev) {
+  const tagName = ev.target.tagName
+  if (tagName === 'INPUT' ||
+      tagName === 'TEXTAREA') {
+    ev.stopPropagation()
+    return
+  }
+  ev.preventDefault()
+  ev.stopPropagation()
+}
+
+module.exports = ignoreEvent

+ 0 - 0
packages/@uppy/dashboard/src/truncateString.js → packages/@uppy/dashboard/src/utils/truncateString.js


+ 0 - 0
packages/@uppy/dashboard/src/truncateString.test.js → packages/@uppy/dashboard/src/utils/truncateString.test.js


+ 4 - 4
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": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -26,12 +26,12 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "0.26.0",
-    "drag-drop": "^2.14.0",
+    "@uppy/utils": "0.27.0",
+    "drag-drop": "2.13.3",
     "preact": "^8.2.9"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

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

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/dropbox",
   "description": "Import files from Dropbox, into Uppy.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -21,13 +21,13 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/provider-views": "0.26.0",
-    "@uppy/companion-client": "0.26.0",
-    "@uppy/utils": "0.26.0",
+    "@uppy/companion-client": "0.27.0",
+    "@uppy/provider-views": "0.27.0",
+    "@uppy/utils": "0.27.0",
     "preact": "^8.2.9"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 0 - 14
packages/@uppy/dropbox/src/icons.js

@@ -1,14 +0,0 @@
-const { h } = require('preact')
-
-module.exports = {
-  folder: () => (
-    <svg aria-hidden="true" class="UppyIcon" 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>
-  ),
-  file: () => (
-    <svg aria-hidden="true" class="UppyIcon" 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>
-  )
-}

+ 3 - 4
packages/@uppy/dropbox/src/index.js

@@ -1,7 +1,6 @@
 const { Plugin } = require('@uppy/core')
 const { Provider } = require('@uppy/companion-client')
 const ProviderViews = require('@uppy/provider-views')
-const icons = require('./icons')
 const { h } = require('preact')
 
 module.exports = class Dropbox extends Plugin {
@@ -9,9 +8,9 @@ module.exports = class Dropbox extends Plugin {
     super(uppy, opts)
     this.id = this.opts.id || 'Dropbox'
     Provider.initPlugin(this, opts)
-    this.title = 'Dropbox'
+    this.title = this.opts.title || 'Dropbox'
     this.icon = () => (
-      <svg class="UppyIcon" width="128" height="118" viewBox="0 0 128 118">
+      <svg aria-hidden="true" fill="#0060ff" width="128" height="118" viewBox="0 0 128 118">
         <path d="M38.145.777L1.108 24.96l25.608 20.507 37.344-23.06z" />
         <path d="M1.108 65.975l37.037 24.183L64.06 68.525l-37.343-23.06zM64.06 68.525l25.917 21.633 37.036-24.183-25.61-20.51z" />
         <path d="M127.014 24.96L89.977.776 64.06 22.407l37.345 23.06zM64.136 73.18l-25.99 21.567-11.122-7.262v8.142l37.112 22.256 37.114-22.256v-8.142l-11.12 7.262z" />
@@ -72,7 +71,7 @@ module.exports = class Dropbox extends Plugin {
   }
 
   getItemIcon (item) {
-    return icons[item['.tag']]()
+    return item['.tag']
   }
 
   getItemSubList (item) {

+ 3 - 3
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": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -23,11 +23,11 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "0.26.0",
+    "@uppy/utils": "0.27.0",
     "preact": "^8.2.9"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

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

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/form",
   "description": "Connect Uppy to an existing HTML <form>.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -21,11 +21,11 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "0.26.0",
+    "@uppy/utils": "0.27.0",
     "get-form-data": "^2.0.0"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 3 - 3
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": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -24,11 +24,11 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "0.26.0",
+    "@uppy/utils": "0.27.0",
     "prettier-bytes": "^1.0.4"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 5 - 5
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": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -22,13 +22,13 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/provider-views": "0.26.0",
-    "@uppy/companion-client": "0.26.0",
-    "@uppy/utils": "0.26.0",
+    "@uppy/companion-client": "0.27.0",
+    "@uppy/provider-views": "0.27.0",
+    "@uppy/utils": "0.27.0",
     "preact": "^8.2.9"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

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

@@ -7,12 +7,18 @@ module.exports = class GoogleDrive extends Plugin {
   constructor (uppy, opts) {
     super(uppy, opts)
     this.id = this.opts.id || 'GoogleDrive'
+    this.title = this.opts.title || 'Google Drive'
     Provider.initPlugin(this, opts)
-    this.title = 'Google Drive'
-    this.icon = () =>
-      <svg aria-hidden="true" class="UppyIcon UppyModalTab-icon" width="28" height="28" viewBox="0 0 16 16">
-        <path d="M2.955 14.93l2.667-4.62H16l-2.667 4.62H2.955zm2.378-4.62l-2.666 4.62L0 10.31l5.19-8.99 2.666 4.62-2.523 4.37zm10.523-.25h-5.333l-5.19-8.99h5.334l5.19 8.99z" />
+    this.title = this.opts.title || 'Google Drive'
+    this.icon = () => (
+      <svg aria-hidden="true" width="18px" height="16px" viewBox="0 0 18 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
+        <g fill-rule="evenodd">
+          <polygon fill="#3089FC" points="6.32475 10.2 18 10.2 14.999625 15.3 3.324375 15.3" />
+          <polygon fill="#00A85D" points="3.000375 15.3 0 10.2 5.83875 0.275974026 8.838 5.37597403 5.999625 10.2" />
+          <polygon fill="#FFD024" points="11.838375 9.92402597 5.999625 0 12.000375 0 17.839125 9.92402597" />
+        </g>
       </svg>
+    )
 
     this[this.id] = new Provider(uppy, {
       serverUrl: this.opts.serverUrl,
@@ -57,11 +63,11 @@ module.exports = class GoogleDrive extends Plugin {
   }
 
   getUsername (data) {
-    for (const item of data.items) {
-      if (item.userPermission.role === 'owner') {
-        for (const owner of item.owners) {
-          if (owner.isAuthenticatedUser) {
-            return owner.emailAddress
+    for (const item of data.files) {
+      if (item.ownedByMe) {
+        for (const permission of item.permissions) {
+          if (permission.role === 'owner') {
+            return permission.emailAddress
           }
         }
       }
@@ -73,21 +79,21 @@ module.exports = class GoogleDrive extends Plugin {
   }
 
   getItemData (item) {
-    return Object.assign({}, item, {size: parseFloat(item.fileSize)})
+    return Object.assign({}, item, {size: parseFloat(item.size)})
   }
 
   getItemIcon (item) {
-    return <img src={item.iconLink} />
+    return item.iconLink
   }
 
   getItemSubList (item) {
-    return item.items.filter((i) => {
+    return item.files.filter((i) => {
       return this.isFolder(i) || !i.mimeType.startsWith('application/vnd.google')
     })
   }
 
   getItemName (item) {
-    return item.title ? item.title : '/'
+    return item.name ? item.name : '/'
   }
 
   getMimeType (item) {
@@ -103,7 +109,7 @@ module.exports = class GoogleDrive extends Plugin {
   }
 
   getItemModifiedDate (item) {
-    return item.modifiedByMeDate
+    return item.modifiedTime
   }
 
   getItemThumbnailUrl (item) {

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

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/informer",
   "description": "A notification and error pop-up bar for Uppy.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -24,11 +24,11 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "0.26.0",
+    "@uppy/utils": "0.27.0",
     "preact": "^8.2.9"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 6 - 7
packages/@uppy/informer/src/index.js

@@ -44,20 +44,19 @@ module.exports = class Informer extends Plugin {
   }
 
   render (state) {
-    const { isHidden, type, message, details } = state.info
-    const style = {
-      backgroundColor: this.opts.typeColors[type].bg,
-      color: this.opts.typeColors[type].text
-    }
+    const { isHidden, message, details } = state.info
+    // const style = {
+    //   backgroundColor: this.opts.typeColors[type].bg,
+    //   color: this.opts.typeColors[type].text
+    // }
 
     return (
       <div class="uppy uppy-Informer"
-        style={style}
         aria-hidden={isHidden}>
         <p role="alert">
           {message}
           {' '}
-          {details && <span style={{ color: this.opts.typeColors[type].bg }}
+          {details && <span
             aria-label={details}
             data-microtip-position="top"
             data-microtip-size="large"

+ 36 - 21
packages/@uppy/informer/src/style.scss

@@ -3,44 +3,58 @@
 
 .uppy-Informer {
   position: absolute;
-  bottom: 0;
+  bottom: 60px;
   left: 0;
   right: 0;
   text-align: center;
-  font-size: 12px;
-  font-weight: 500;
-  padding: 0 15px;
-  height: 35px;
-  line-height: 35px;
-  background-color: $color-black; /* no !important */
-  color: $color-white;
+  // padding: 0 15px;
+  // height: 25px;
+  // line-height: 25px;
+
   opacity: 1;
   transform: none;
-  transition: all 300ms ease-in;
-  z-index: $zIndex-4;
+  transition: all 250ms ease-in;
+  z-index: $zIndex-5;
+  // border-radius: 18px;
+  // max-width: 100%;
+  // margin: auto;
+  
 
-  .uppy-Dashboard--wide & {
-    height: 45px;
-    line-height: 45px;
-    font-size: 13px;
-  }
+  // .uppy-size--md & {
+  //   height: 35px;
+  //   line-height: 35px;
+  //   font-size: 12px;
+  //   // max-width: 500px;
+  //   padding: 0 15px;
+  // }
 }
 
   .uppy-Informer[aria-hidden=true] {
     opacity: 0;
-    transform: translateY(200%);
+    transform: translateY(350%);
     transition: all 300ms ease-in;
   }
 
 .uppy-Informer p {
+  display: inline-block;
   margin: 0;
   padding: 0;
-  height: 35px;
-  line-height: 35px;
+  // height: 25px;
+  // line-height: 25px;
+  font-size: 12px;
+  line-height: 1.4;
+  font-weight: 400;
+  padding: 6px 15px;
+  background-color: rgba($color-asphalt-gray, 0.8); /* no !important */
+  color: $color-white;
+  border-radius: 18px;
+  max-width: 90%;
 
-  .uppy-Dashboard--wide & {
-    height: 45px;
-    line-height: 45px;
+  .uppy-size--md & {
+    font-size: 14px;
+    line-height: 1.3;
+    max-width: 500px;
+    padding: 10px 20px;
   }
 }
 
@@ -50,6 +64,7 @@
   height: 13px;
   display: inline-block;
   vertical-align: middle;
+  color: $color-asphalt-gray;
   background-color: $color-white;
   border-radius: 50%;
   position: relative;

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

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/instagram",
   "description": "Import photos and videos from Instagram, into Uppy.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -24,13 +24,13 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/provider-views": "0.26.0",
-    "@uppy/companion-client": "0.26.0",
-    "@uppy/utils": "0.26.0",
+    "@uppy/companion-client": "0.27.0",
+    "@uppy/provider-views": "0.27.0",
+    "@uppy/utils": "0.27.0",
     "preact": "^8.2.9"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 5 - 7
packages/@uppy/instagram/src/index.js

@@ -8,9 +8,9 @@ module.exports = class Instagram extends Plugin {
     super(uppy, opts)
     this.id = this.opts.id || 'Instagram'
     Provider.initPlugin(this, opts)
-    this.title = 'Instagram'
+    this.title = this.opts.title || 'Instagram'
     this.icon = () => (
-      <svg aria-hidden="true" class="UppyIcon" width="28" height="28" viewBox="0 0 512 512">
+      <svg aria-hidden="true" fill="#DE3573" width="28" height="28" viewBox="0 0 512 512">
         <path d="M256,49.471c67.266,0,75.233.257,101.8,1.469,24.562,1.121,37.9,5.224,46.778,8.674a78.052,78.052,0,0,1,28.966,18.845,78.052,78.052,0,0,1,18.845,28.966c3.45,8.877,7.554,22.216,8.674,46.778,1.212,26.565,1.469,34.532,1.469,101.8s-0.257,75.233-1.469,101.8c-1.121,24.562-5.225,37.9-8.674,46.778a83.427,83.427,0,0,1-47.811,47.811c-8.877,3.45-22.216,7.554-46.778,8.674-26.56,1.212-34.527,1.469-101.8,1.469s-75.237-.257-101.8-1.469c-24.562-1.121-37.9-5.225-46.778-8.674a78.051,78.051,0,0,1-28.966-18.845,78.053,78.053,0,0,1-18.845-28.966c-3.45-8.877-7.554-22.216-8.674-46.778-1.212-26.564-1.469-34.532-1.469-101.8s0.257-75.233,1.469-101.8c1.121-24.562,5.224-37.9,8.674-46.778A78.052,78.052,0,0,1,78.458,78.458a78.053,78.053,0,0,1,28.966-18.845c8.877-3.45,22.216-7.554,46.778-8.674,26.565-1.212,34.532-1.469,101.8-1.469m0-45.391c-68.418,0-77,.29-103.866,1.516-26.815,1.224-45.127,5.482-61.151,11.71a123.488,123.488,0,0,0-44.62,29.057A123.488,123.488,0,0,0,17.3,90.982C11.077,107.007,6.819,125.319,5.6,152.134,4.369,179,4.079,187.582,4.079,256S4.369,333,5.6,359.866c1.224,26.815,5.482,45.127,11.71,61.151a123.489,123.489,0,0,0,29.057,44.62,123.486,123.486,0,0,0,44.62,29.057c16.025,6.228,34.337,10.486,61.151,11.71,26.87,1.226,35.449,1.516,103.866,1.516s77-.29,103.866-1.516c26.815-1.224,45.127-5.482,61.151-11.71a128.817,128.817,0,0,0,73.677-73.677c6.228-16.025,10.486-34.337,11.71-61.151,1.226-26.87,1.516-35.449,1.516-103.866s-0.29-77-1.516-103.866c-1.224-26.815-5.482-45.127-11.71-61.151a123.486,123.486,0,0,0-29.057-44.62A123.487,123.487,0,0,0,421.018,17.3C404.993,11.077,386.681,6.819,359.866,5.6,333,4.369,324.418,4.079,256,4.079h0Z" />
         <path d="M256,126.635A129.365,129.365,0,1,0,385.365,256,129.365,129.365,0,0,0,256,126.635Zm0,213.338A83.973,83.973,0,1,1,339.974,256,83.974,83.974,0,0,1,256,339.973Z" />
         <circle cx="390.476" cy="121.524" r="30.23" />
@@ -78,11 +78,9 @@ module.exports = class Instagram extends Plugin {
 
   getItemIcon (item) {
     if (!item.images) {
-      return <svg viewBox="0 0 58 58" opacity="0.6">
-        <path d="M36.537 28.156l-11-7a1.005 1.005 0 0 0-1.02-.033C24.2 21.3 24 21.635 24 22v14a1 1 0 0 0 1.537.844l11-7a1.002 1.002 0 0 0 0-1.688zM26 34.18V23.82L34.137 29 26 34.18z" /><path d="M57 6H1a1 1 0 0 0-1 1v44a1 1 0 0 0 1 1h56a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1zM10 28H2v-9h8v9zm-8 2h8v9H2v-9zm10 10V8h34v42H12V40zm44-12h-8v-9h8v9zm-8 2h8v9h-8v-9zm8-22v9h-8V8h8zM2 8h8v9H2V8zm0 42v-9h8v9H2zm54 0h-8v-9h8v9z" />
-      </svg>
+      return 'video'
     }
-    return <img src={item.images.low_resolution.url} />
+    return item.images.low_resolution.url
   }
 
   getItemSubList (item) {
@@ -114,7 +112,7 @@ module.exports = class Instagram extends Plugin {
         minute: 'numeric'
       })
       // adding both date and carousel_id, so the name is unique
-      return `Instagram ${date} ${item.carousel_id || ''}.${ext}`
+      return `Instagram ${date}${item.carousel_id ? ' ' + item.carousel_id : ''}.${ext}`
     }
     return ''
   }

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

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/progress-bar",
   "description": "A progress bar UI for Uppy",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -24,11 +24,11 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "0.26.0",
+    "@uppy/utils": "0.27.0",
     "preact": "^8.2.9"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

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

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/provider-views",
   "description": "View library for Uppy remote provider plugins.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -20,12 +20,12 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "0.26.0",
+    "@uppy/utils": "0.27.0",
     "classnames": "^2.2.6",
     "preact": "^8.2.9"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 4 - 8
packages/@uppy/provider-views/src/AuthView.js

@@ -34,14 +34,10 @@ class AuthView extends Component {
   }
 
   render () {
-    return (
-      <div style={{ height: '100%' }}>
-        {this.props.checkAuthInProgress
-          ? <LoaderView />
-          : <AuthBlock {...this.props} />
-        }
-      </div>
-    )
+    if (this.props.checkAuthInProgress) {
+      return <LoaderView />
+    }
+    return <AuthBlock {...this.props} />
   }
 }
 

+ 3 - 3
packages/@uppy/provider-views/src/Breadcrumbs.js

@@ -2,13 +2,13 @@ const { h } = require('preact')
 
 const Breadcrumb = (props) => {
   return (
-    <li><button type="button" onclick={props.getFolder}>{props.title}</button></li>
+    <button type="button" onclick={props.getFolder}>{props.title}</button>
   )
 }
 
 module.exports = (props) => {
   return (
-    <ul class="uppy-Provider-breadcrumbs">
+    <div class="uppy-Provider-breadcrumbs">
       {
         props.directories.map((directory, i) => {
           return Breadcrumb({
@@ -17,6 +17,6 @@ module.exports = (props) => {
           })
         })
       }
-    </ul>
+    </div>
   )
 }

+ 8 - 6
packages/@uppy/provider-views/src/Browser.js

@@ -20,12 +20,14 @@ const Browser = (props) => {
     <div class={classNames('uppy-ProviderBrowser', `uppy-ProviderBrowser-viewType--${props.viewType}`)}>
       <div class="uppy-ProviderBrowser-header">
         <div class={classNames('uppy-ProviderBrowser-headerBar', !props.showBreadcrumbs && 'uppy-ProviderBrowser-headerBar--simple')}>
-          <div class="uppy-Provider-breadcrumbsIcon">{props.pluginIcon && props.pluginIcon()}</div>
-          {props.showBreadcrumbs && Breadcrumbs({
-            getFolder: props.getFolder,
-            directories: props.directories,
-            title: props.title
-          })}
+          <div class="uppy-Provider-breadcrumbsWrap">
+            <div class="uppy-Provider-breadcrumbsIcon">{props.pluginIcon && props.pluginIcon()}</div>
+            {props.showBreadcrumbs && Breadcrumbs({
+              getFolder: props.getFolder,
+              directories: props.directories,
+              title: props.title
+            })}
+          </div>
           <span class="uppy-ProviderBrowser-user">{props.username}</span>
           <button type="button" onclick={props.logout} class="uppy-ProviderBrowser-userLogout">
             {props.i18n('logOut')}

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

@@ -1,5 +1,26 @@
 const { h } = require('preact')
 
+function mapStringToIcon (string) {
+  if (string === null) return
+
+  switch (string) {
+    case 'file':
+      return <svg aria-hidden="true" class="UppyIcon" 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>
+    case 'folder':
+      return <svg aria-hidden="true" class="UppyIcon" 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>
+    case 'video':
+      return <svg aria-hidden="true" viewBox="0 0 58 58">
+        <path d="M36.537 28.156l-11-7a1.005 1.005 0 0 0-1.02-.033C24.2 21.3 24 21.635 24 22v14a1 1 0 0 0 1.537.844l11-7a1.002 1.002 0 0 0 0-1.688zM26 34.18V23.82L34.137 29 26 34.18z" /><path d="M57 6H1a1 1 0 0 0-1 1v44a1 1 0 0 0 1 1h56a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1zM10 28H2v-9h8v9zm-8 2h8v9H2v-9zm10 10V8h34v42H12V40zm44-12h-8v-9h8v9zm-8 2h8v9h-8v-9zm8-22v9h-8V8h8zM2 8h8v9H2V8zm0 42v-9h8v9H2zm54 0h-8v-9h8v9z" />
+      </svg>
+    default:
+      return <img src={string} />
+  }
+}
+
 module.exports = (props) => {
   const stop = (ev) => {
     if (ev.keyCode === 13) {
@@ -17,8 +38,10 @@ module.exports = (props) => {
     props.handleClick(ev)
   }
 
+  const itemIcon = props.getItemIcon()
+
   return (
-    <li class={'uppy-ProviderBrowserItem' + (props.isChecked ? ' uppy-ProviderBrowserItem--selected' : '')}>
+    <li class={'uppy-ProviderBrowserItem' + (props.isChecked ? ' uppy-ProviderBrowserItem--selected' : '') + (itemIcon === 'video' ? ' uppy-ProviderBrowserItem--noPreview' : '')}>
       <div class="uppy-ProviderBrowserItem-checkbox">
         <input type="checkbox"
           role="option"
@@ -41,7 +64,8 @@ module.exports = (props) => {
         aria-label={`Select ${props.title}`}
         tabindex={0}
         onclick={handleItemClick}>
-        {props.getItemIcon()} {props.showTitles && props.title}
+        {mapStringToIcon(props.getItemIcon())}
+        {props.showTitles && props.title}
       </button>
     </li>
   )

+ 144 - 83
packages/@uppy/provider-views/src/style.scss

@@ -1,5 +1,12 @@
 @import '@uppy/core/src/style.scss';
 
+.uppy-DashboardContent-panelBody {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex: 1;
+}
+
 .uppy-Provider-auth,
 .uppy-Provider-error,
 .uppy-Provider-loading,
@@ -8,51 +15,62 @@
   align-items: center;
   justify-content: center;
   flex-flow: column wrap;
-  height: 100%;
+  flex: 1;
 }
 
-.uppy-Provider-authIcon .UppyIcon {
+.uppy-Provider-authIcon svg {
   width: 100px;
   height: 75px;
-  color: rgba($color-asphalt-gray, 0.3);
   margin-bottom: 15px;
 }
 
 .uppy-Provider-authTitle {
-  font-size: 20px;
+  font-size: 17px;
   line-height: 1.4;
   font-weight: 400;
   margin-bottom: 30px;
   padding: 0 15px;
   max-width: 500px;
   text-align: center;
+
+  .uppy-size--md & {
+    font-size: 20px;
+  }
 }
 
-.uppy-Provider-breadcrumbs {
+.uppy-Provider-breadcrumbsWrap {
   flex: 1;
+}
+
+.uppy-Provider-breadcrumbs {
+  display: inline-block;
   color: darken($color-gray, 25%);
   font-size: 12px;
-  list-style-type: none;
-  padding: 0;
-  margin: 0;
+  line-height: 1;
+  margin-bottom: 10px;
+
+  .uppy-size--md & {
+    margin-bottom: 0;
+  }
 }
 
 .uppy-Provider-breadcrumbsIcon {
   display: inline;
   color: darken($color-gray, 25%);
   vertical-align: middle;
-  // position: relative;
-  // top: 1px;
   margin-right: 8px;
+  line-height: 1;
 }
 
-  .uppy-Provider-breadcrumbsIcon .UppyIcon {
+  .uppy-Provider-breadcrumbsIcon svg {
     width: 13px;
     height: 13px;
+    fill: darken($color-gray, 25%);
   }
 
 .uppy-Provider-breadcrumbs button {
   @include reset-button;
+  display: inline-block;
   cursor: pointer;
   font-size: 14px;
 }
@@ -61,16 +79,11 @@
   text-decoration: underline;
 }
 
-.uppy-Provider-breadcrumbs button:focus {
-  outline: 1px dotted rgb(145, 145, 145);
-}
-
-.uppy-Provider-breadcrumbs li {
-  display: inline-block;
-  margin: 0;
-}
+// .uppy-Provider-breadcrumbs button:focus {
+//   outline: 1px dotted rgb(145, 145, 145);
+// }
 
-.uppy-Provider-breadcrumbs li ~ li:before {
+.uppy-Provider-breadcrumbs button ~ button:before {
   content: '/';
   padding: 0 7px;
 }
@@ -78,6 +91,7 @@
 .uppy-ProviderBrowser {
   display: flex;
   flex-direction: column;
+  flex: 1;
   font-size: 13px;
   font-weight: 400;
   height: 100%;
@@ -85,6 +99,7 @@
 
 .uppy-ProviderBrowser-user {
   margin: 0 8px 0 0;
+  line-height: 1;
 }
 
   .uppy-ProviderBrowser-user:after {
@@ -95,32 +110,44 @@
 
 .uppy-ProviderBrowser-header {
   z-index: $zIndex-2;
-  border-bottom: 1px solid lighten($color-asphalt-gray, 60%);
+  border-bottom: 1px solid rgba($color-gray, 0.3);
   position: relative;
 }
 
 .uppy-ProviderBrowser-headerBar {
-  height: 40px;
-  line-height: 40px;
-  display: flex;
-  align-items: center;
-  padding: 0 16px;
+  padding: 12px 15px;
   background-color: lighten($color-gray, 40%);
   z-index: $zIndex-2;
   color: darken($color-gray, 20%);
-}
+  line-height: 1;
 
-.uppy-ProviderBrowser-headerBar--simple {
-  text-align: center;
-  display: block;
+  .uppy-size--md & {
+    display: flex;
+    align-items: center;
+    height: 40px;
+    line-height: 40px;
+    padding: 0 15px;
+  }
 }
 
+  .uppy-ProviderBrowser-headerBar--simple {
+    text-align: center;
+    display: block;
+    justify-content: center;
+  }
+
+  .uppy-ProviderBrowser-headerBar--simple .uppy-Provider-breadcrumbsWrap {
+    flex: none;
+    display: inline-block;
+    vertical-align: middle;
+  }
+
 .uppy-ProviderBrowser-search {
   width: 100%;
   background-color: $color-white;
   position: relative;
   height: 30px;
-  margin-top: 15px;
+  margin-top: 5px;
   margin-bottom: 5px;
 }
 
@@ -169,7 +196,9 @@
   @include reset-button();
   cursor: pointer;
 
-  &:hover { text-decoration: underline; }
+  &:hover { 
+    text-decoration: underline; 
+  }
 }
 
 .uppy-ProviderBrowser-body {
@@ -187,6 +216,7 @@
   border-spacing: 0;
   overflow-x: hidden;
   overflow-y: auto;
+  -webkit-overflow-scrolling: touch;
   position: absolute;
   top: 0;
   bottom: 0;
@@ -208,37 +238,38 @@
 // ***
 
 .uppy-ProviderBrowser-viewType--list {
-
   background-color: $color-white;
 
-  .uppy-ProviderBrowser-list {
-    // padding-top: 6px;
-  }
-
   .uppy-ProviderBrowserItem {
+    display: flex;
     padding: 10px 15px;
   }
 
-  .uppy-ProviderBrowserItem-inner {
-    max-width: 80%;
-    word-wrap: break-word;
-    text-align: left;
-    line-height: 1.4;
+  .uppy-ProviderBrowserItem-checkbox {
+    vertical-align: middle;
   }
 
-  .uppy-ProviderBrowserItem-inner img {
-    vertical-align: text-top;
-    margin-right: 3px;
-  }
+    .uppy-ProviderBrowserItem-checkbox label:before {
+      border-color: rgba($color-asphalt-gray, 0.4);
+    }
 
-  .uppy-ProviderBrowserItem-checkbox label:before {
-    border-color: rgba($color-asphalt-gray, 0.4);
-  }
+    .uppy-ProviderBrowserItem-checkbox input:checked + label:before {
+      border-color: $color-cornflower-blue;
+    }
 
-  .uppy-ProviderBrowserItem-checkbox input:checked + label:before {
-    border-color: $color-cornflower-blue;
+  .uppy-ProviderBrowserItem-inner {
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    overflow: hidden;
+    text-align: left;
+    line-height: 1.4;
   }
 
+  .uppy-ProviderBrowserItem-inner img,
+  .uppy-ProviderBrowserItem-inner svg {
+    vertical-align: top;
+    margin-right: 8px;
+  }
 }
 
 // ***
@@ -265,12 +296,12 @@
     display: inline-block;
     width: 50%;
     position: relative;
-    padding: 8px;
   }
 
-    .uppy-Dashboard--wide .uppy-ProviderBrowserItem {
-      width: 33.3333%;
-      padding: 12px;
+    .uppy-ProviderBrowserItem:before {
+      content: '';
+      padding-top: 100%;
+      display: block;
     }
 
     // .uppy-ProviderBrowserItem--selected {
@@ -278,52 +309,83 @@
     //   outline: none;
     // }
 
-    .uppy-ProviderBrowserItem--selected .uppy-ProviderBrowserItem-inner {
-      box-shadow: 0 0 0 3px rgba(darken($color-cornflower-blue, 10%), 0.9);
-    }
+    // .uppy-ProviderBrowserItem--selected .uppy-ProviderBrowserItem-inner {
+    //   box-shadow: 0 0 0 3px rgba(darken($color-cornflower-blue, 10%), 0.9);
+    // }
 
   .uppy-ProviderBrowserItem-inner {
-    width: 100%;
-    height: 100%;
     border-radius: 4px;
     overflow: hidden;
-    border: 2px solid transparent;
+    position: absolute;
+    top: 7px;
+    left: 7px;
+    right: 7px;
+    bottom: 7px;
+  }
+
+  .uppy-ProviderBrowserItem-inner:focus {
+    outline: none;
+    box-shadow: 0 0 0 3px rgba($color-cornflower-blue, 0.9);
   }
 
-  .uppy-ProviderBrowserItem img {
+  .uppy-ProviderBrowserItem img,
+  .uppy-ProviderBrowserItem svg {
     width: 100%;
     height: 100%;
     vertical-align: middle;
+    object-fit: cover;
+  }
+
+  .uppy-ProviderBrowserItem--noPreview .uppy-ProviderBrowserItem-inner {
+    background-color: rgba($color-gray, 0.3);
+  }
+
+  .uppy-ProviderBrowserItem--noPreview svg {
+    fill: rgba($color-black, 0.7);
+    width: 30%;
+    height: 30%;
   }
 
   .uppy-ProviderBrowserItem-checkbox {
-    display: none;
+    position: absolute;
+    top: 13px;
+    right: 22px;
+    margin-right: 0;
+    opacity: 0.95;
+    z-index: $zIndex-3;
   }
 
-  // .uppy-ProviderBrowserItem-checkbox {
-  //   position: absolute;
-  //   top: 8px;
-  //   right: 10px;
-  //   margin-right: 0;
-  // }
+  .uppy-ProviderBrowserItem-checkbox label:before {
+    background-color: $color-cornflower-blue;
+    border-radius: 50%;
+    width: 26px;
+    height: 26px;
+  }
 
-  // .uppy-ProviderBrowserItem-checkbox label:before {
-  //   background-color: $color-cornflower-blue;
-  // }
+  .uppy-ProviderBrowserItem-checkbox label:after {
+    width: 12px;
+    height: 7px;
+    left: 7px;
+    top: 10px;
+  }
 
-  // // Hide checkbox when unchecked in grid view
-  // .uppy-ProviderBrowserItem-checkbox input + label {
-  //   opacity: 0;
-  // }
+  // Hide checkbox when unchecked in grid view
+  .uppy-ProviderBrowserItem-checkbox input + label {
+    opacity: 0;
+  }
+
+  // Unhide the checkbox on the checked state
+  .uppy-ProviderBrowserItem-checkbox input:checked + label {
+    opacity: 1;
+  }
 
-  // // Unhide the checkbox on the checked state
-  // .uppy-ProviderBrowserItem-checkbox input:checked + label {
-  //   opacity: 1;
-  // }
+}
 
+.uppy-size--md .uppy-ProviderBrowser-viewType--grid .uppy-ProviderBrowserItem {
+  width: 33.3333%;
 }
 
-.uppy-Dashboard--wide .uppy-ProviderBrowser-viewType--grid .uppy-ProviderBrowserItem {
+.uppy-size--lg .uppy-ProviderBrowser-viewType--grid .uppy-ProviderBrowserItem {
   width: 25%;
 }
 
@@ -336,7 +398,7 @@
   position: relative;
   display: inline-block;
   top: -3px;
-  margin-right: 20px;
+  margin-right: 15px;
 }
 
 .uppy-ProviderBrowserItem-checkbox label {
@@ -359,7 +421,6 @@
   border: 1px solid $color-cornflower-blue;
   background-color: $color-white;
   border-radius: 2px;
-  // border-radius: 50%;
 }
 
 // Inner checkbox

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

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/react",
   "description": "React component wrappers around Uppy's official UI plugins.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "index.js",
   "module": "index.mjs",
@@ -22,15 +22,15 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/dashboard": "0.26.0",
-    "@uppy/drag-drop": "0.26.0",
-    "@uppy/progress-bar": "0.26.0",
-    "@uppy/status-bar": "0.26.0",
-    "@uppy/utils": "0.26.0",
+    "@uppy/dashboard": "0.27.0",
+    "@uppy/drag-drop": "0.27.0",
+    "@uppy/progress-bar": "0.27.0",
+    "@uppy/status-bar": "0.27.0",
+    "@uppy/utils": "0.27.0",
     "prop-types": "^15.6.1"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 2 - 2
packages/@uppy/redux-dev-tools/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/redux-dev-tools",
   "description": "Redux developer tools plugin for Uppy that simply syncs Uppy’s state with redux-devtools browser or JS extensions, and allows for basic time travel",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -22,7 +22,7 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 3 - 3
packages/@uppy/status-bar/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/status-bar",
   "description": "A progress bar for Uppy, with many bells and whistles.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -27,14 +27,14 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "0.26.0",
+    "@uppy/utils": "0.27.0",
     "classnames": "^2.2.6",
     "lodash.throttle": "^4.1.1",
     "preact": "^8.2.9",
     "prettier-bytes": "^1.0.4"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 4 - 5
packages/@uppy/status-bar/src/StatusBar.js

@@ -177,11 +177,11 @@ const ProgressBarProcessing = (props) => {
 }
 
 const progressDetails = (props) => {
-  return <span class="uppy-StatusBar-statusSecondary">
+  return <div class="uppy-StatusBar-statusSecondary">
     { props.inProgress > 1 && props.i18n('filesUploadedOfTotal', { complete: props.complete, smart_count: props.inProgress }) + ' \u00B7 ' }
     { props.i18n('dataUploadedOfTotal', { complete: props.totalUploadedSize, total: props.totalSize }) + ' \u00B7 ' }
     { props.i18n('xTimeLeft', { time: props.totalETA }) }
-  </span>
+  </div>
 }
 
 const ThrottledProgressDetails = throttle(progressDetails, 500, { leading: true, trailing: true })
@@ -197,8 +197,7 @@ const ProgressBarUploading = (props) => {
     <div class="uppy-StatusBar-content" aria-label={title} title={title}>
       { !props.hidePauseResumeCancelButtons && <PauseResumeButtons {...props} /> }
       <div class="uppy-StatusBar-status">
-        <span class="uppy-StatusBar-statusPrimary">{title}: {props.totalProgress}%</span>
-        <br />
+        <div class="uppy-StatusBar-statusPrimary">{title}: {props.totalProgress}%</div>
         { !props.isAllPaused && <ThrottledProgressDetails {...props} /> }
       </div>
     </div>
@@ -219,7 +218,7 @@ const ProgressBarComplete = ({ totalProgress, i18n }) => {
 const ProgressBarError = ({ error, retryAll, hideRetryButton, i18n }) => {
   return (
     <div class="uppy-StatusBar-content" role="alert">
-      <strong class="uppy-StatusBar-contentPadding">{i18n('uploadFailed')}.</strong>
+      <span class="uppy-StatusBar-contentPadding">{i18n('uploadFailed')}.</span>
       { !hideRetryButton && <span class="uppy-StatusBar-contentPadding">{i18n('pleasePressRetry')}</span> }
       <span class="uppy-StatusBar-details"
         aria-label={error}

+ 77 - 39
packages/@uppy/status-bar/src/style.scss

@@ -9,18 +9,28 @@
   font-size: 12px;
   font-weight: 400;
   color: $color-white;
-  background-color: lighten($color-black, 10%);
-  // box-shadow: 1px 1px 4px 0 rgba($color-asphalt-gray, 0.3);
-  // border-top: 1px solid rgba($color-gray, 0.2);
+  background-color: $color-white;
   z-index: $zIndex-2;
   transition: height .2s;
 }
 
-  .uppy-Dashboard--wide .uppy-StatusBar {
+  .uppy-size--md .uppy-StatusBar {
     height: 45px;
     font-size: 14px;
   }
 
+  .uppy-StatusBar:before {
+    content: '';
+    position: absolute;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    width: 100%;
+    height: 2px;
+    background-color: rgba($color-gray, 0.25);
+  }
+
 .uppy-StatusBar[aria-hidden=true] {
   overflow-y: hidden;
   height: 0;
@@ -35,14 +45,18 @@
 }
 
 .uppy-StatusBar.is-complete .uppy-StatusBar-content {
-  width: 100%;
-  text-align: center;
-  padding-left: 0;
-  justify-content: center;
+  // width: 100%;
+  // text-align: center;
+  // padding-left: 0;
+  // justify-content: center;
+}
+
+.uppy-StatusBar.is-complete .uppy-StatusBar-statusIndicator {
+  cursor: default;
+  color: $color-green;
 }
 
 .uppy-StatusBar:not([aria-hidden=true]).is-waiting {
-  // background-color: darken($color-white, 2%);
   background-color: $color-white;
   height: 65px;
   border-top: 1px solid rgba($color-gray, 0.3);
@@ -50,7 +64,7 @@
 
 .uppy-StatusBar-progress {
   background-color: $color-cornflower-blue;
-  height: 100%;
+  height: 2px;
   position: absolute;
   z-index: $zIndex-2;
   transition: background-color, width .3s ease-out;
@@ -80,7 +94,8 @@
   padding-left: 15px;
   white-space: nowrap;
   text-overflow: ellipsis;
-  color: $color-white;
+  // color: $color-white;
+  color: $color-black;
   height: 100%;
 }
 
@@ -89,9 +104,11 @@
 }
 
 .uppy-StatusBar-status {
-  line-height: 1.35;
+  line-height: 1.5;
   font-weight: normal;
-  letter-spacing: 0.5px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
 }
 
 .uppy-StatusBar-statusPrimary {
@@ -101,18 +118,31 @@
 .uppy-StatusBar-statusSecondary {
   font-size: 11px;
   display: none;
+  color: rgba($color-asphalt-gray, 0.8);
+  max-width: 170px;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  overflow: hidden;
+
+  .uppy-size--md & {
+    max-width: 500px;
+  }
 }
 
   .uppy-StatusBar--detailedProgress .uppy-StatusBar-statusSecondary {
-    display: inline;
+    display: inline-block;
   }
 
 .uppy-StatusBar-statusIndicator {
-  color: $color-white;
+  color: $color-asphalt-gray;
   margin-right: 15px;
   cursor: pointer;
 }
 
+  .uppy-StatusBar-statusIndicator svg {
+    vertical-align: text-bottom;
+  }
+
   .uppy-StatusBar.is-complete .uppy-StatusBar-statusIndicator  {
     width: 15px;
     margin-right: 7px;
@@ -124,7 +154,7 @@
   position: absolute;
   top: 0;
   bottom: 0;
-  right: 15px;
+  right: 10px;
   z-index: $zIndex-4;
 }
 
@@ -132,26 +162,34 @@
   width: 100%;
   position: static;
   padding: 0 15px;
+  background-color: $color-almost-white;
 }
 
 .uppy-StatusBar-actionBtn {
   font-size: 12px;
   padding: 6px;
-  border-radius: 4px;
+  // border-radius: 4px;
+  color: $color-cornflower-blue;
 }
 
-  .uppy-Dashboard--wide .uppy-StatusBar-actionBtn {
-    padding: 7px 10px;
+  .uppy-size--md .uppy-StatusBar-actionBtn {
+    padding: 3px 5px;
   }
 
   .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload {
     font-size: 14px;
     width: 100%;
     padding: 15px 10px;
+    color: $color-white;
+    background-color: $color-green;
   }
 
-    .uppy-Dashboard--wide .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload {
-      padding: 13px 28px;
+    .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload:hover {
+      background-color: darken($color-green, 10%);
+    }
+
+    .uppy-size--md .uppy-StatusBar.is-waiting .uppy-StatusBar-actionBtn--upload {
+      padding: 16px 22px;
       width: auto;
     }
 
@@ -161,23 +199,23 @@
 
   .uppy-StatusBar:not(.is-waiting) .uppy-StatusBar-actionBtn--upload {
     background-color: transparent;
-    border: 1px solid $color-white;
-    color: $color-white;
+    // border: 1px solid $color-white;
+    color: $color-cornflower-blue;
   }
 
-  .uppy-StatusBar-actionBtn--retry {
-    background-color: $color-white;
-    color: $color-red;
-    border: 1px solid transparent;
-  }
+  // .uppy-StatusBar-actionBtn--retry {
+  //   background-color: $color-white;
+  //   color: $color-red;
+  //   border: 1px solid transparent;
+  // }
 
-  .uppy-StatusBar-actionBtn--cancel {
-    // background-color: lighten($color-asphalt-gray, 8%);
-    // border: 1px solid lighten($color-black, 10%);
-    background-color: transparent;
-    border: 1px solid $color-white;
-    color: $color-white;
-  }
+  // .uppy-StatusBar-actionBtn--cancel {
+  //   // background-color: lighten($color-asphalt-gray, 8%);
+  //   // border: 1px solid lighten($color-black, 10%);
+  //   background-color: transparent;
+  //   border: 1px solid $color-white;
+  //   color: $color-white;
+  // }
 
 .uppy-StatusBar-details {
   line-height: 12px;
@@ -185,12 +223,12 @@
   height: 13px;
   display: inline-block;
   vertical-align: middle;
-  color: $color-red;
-  background-color: $color-white;
+  color: $color-white;
+  background-color: rgba($color-black, 0.2);
   border-radius: 50%;
   position: relative;
-  top: -1px;
-  left: 6px;
+  top: 0;
+  left: 2px;
   font-size: 10px;
   text-align: center;
   cursor: help;

+ 1 - 1
packages/@uppy/store-default/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/store-default",
   "description": "The default simple object-based store for Uppy.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",

+ 1 - 1
packages/@uppy/store-redux/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/store-redux",
   "description": "Make Uppy use your existing Redux store.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",

+ 3 - 3
packages/@uppy/thumbnail-generator/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/thumbnail-generator",
   "description": "Uppy plugin that generates small previews of images to show on your upload UI.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -23,10 +23,10 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "0.26.0"
+    "@uppy/utils": "0.27.0"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0",
+    "@uppy/core": "0.27.0",
     "namespace-emitter": "^2.0.1"
   },
   "peerDependencies": {

+ 6 - 6
packages/@uppy/transloadit/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/transloadit",
   "description": "The Transloadit plugin can be used to upload files to Transloadit for all kinds of processing, such as transcoding video, resizing images, zipping/unzipping, and more",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -28,16 +28,16 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/provider-views": "0.26.0",
-    "@uppy/companion-client": "0.26.0",
-    "@uppy/tus": "0.26.0",
-    "@uppy/utils": "0.26.0",
+    "@uppy/companion-client": "0.27.0",
+    "@uppy/provider-views": "0.27.0",
+    "@uppy/tus": "0.27.0",
+    "@uppy/utils": "0.27.0",
     "namespace-emitter": "^2.0.1",
     "socket.io-client": "^2.1.1",
     "url-parse": "^1.4.1"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 4 - 4
packages/@uppy/tus/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/tus",
   "description": "Resumable uploads for Uppy using Tus.io",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -23,12 +23,12 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/companion-client": "0.26.0",
-    "@uppy/utils": "0.26.0",
+    "@uppy/companion-client": "0.27.0",
+    "@uppy/utils": "0.27.0",
     "tus-js-client": "^1.5.1"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 4 - 4
packages/@uppy/url/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/url",
   "description": "The Url plugin lets users import files from the Internet. Paste any URL and it’ll be added!",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -23,12 +23,12 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/companion-client": "0.26.0",
-    "@uppy/utils": "0.26.0",
+    "@uppy/companion-client": "0.27.0",
+    "@uppy/utils": "0.27.0",
     "preact": "^8.2.9"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 21 - 24
packages/@uppy/url/src/index.js

@@ -13,14 +13,10 @@ module.exports = class Url extends Plugin {
   constructor (uppy, opts) {
     super(uppy, opts)
     this.id = this.opts.id || 'Url'
-    this.title = 'Link'
+    this.title = this.opts.title || 'Link'
     this.type = 'acquirer'
-    this.icon = () => <svg aria-hidden="true" class="UppyIcon UppyModalTab-icon" width="64" height="64" viewBox="0 0 64 64">
-      <circle cx="32" cy="32" r="31" />
-      <g fill-rule="nonzero" fill="#FFF">
-        <path d="M25.774 47.357a4.077 4.077 0 0 1-5.76 0L16.9 44.24a4.076 4.076 0 0 1 0-5.758l5.12-5.12-1.817-1.818-5.12 5.122a6.651 6.651 0 0 0 0 9.392l3.113 3.116a6.626 6.626 0 0 0 4.699 1.943c1.7 0 3.401-.649 4.697-1.943l10.241-10.243a6.591 6.591 0 0 0 1.947-4.696 6.599 6.599 0 0 0-1.947-4.696l-3.116-3.114-1.817 1.817 3.116 3.114a4.045 4.045 0 0 1 1.194 2.88 4.045 4.045 0 0 1-1.194 2.878L25.774 47.357z" />
-        <path d="M46.216 14.926a6.597 6.597 0 0 0-4.696-1.946h-.001a6.599 6.599 0 0 0-4.696 1.945L26.582 25.167a6.595 6.595 0 0 0-1.947 4.697 6.599 6.599 0 0 0 1.946 4.698l3.114 3.114 1.818-1.816-3.114-3.114a4.05 4.05 0 0 1-1.194-2.882c0-1.086.424-2.108 1.194-2.878L38.64 16.744a4.042 4.042 0 0 1 2.88-1.194c1.089 0 2.11.425 2.88 1.194l3.114 3.114a4.076 4.076 0 0 1 0 5.758l-5.12 5.12 1.818 1.817 5.12-5.122a6.649 6.649 0 0 0 0-9.393l-3.113-3.114-.003.002z" />
-      </g>
+    this.icon = () => <svg aria-hidden="true" width="23" height="23" viewBox="0 0 23 23" xmlns="http://www.w3.org/2000/svg">
+      <path d="M20.485 11.236l-2.748 2.737c-.184.182-.367.365-.642.547-1.007.73-2.107 1.095-3.298 1.095-1.65 0-3.298-.73-4.398-2.19-.275-.365-.183-1.003.183-1.277.367-.273 1.008-.182 1.283.183 1.191 1.642 3.482 1.915 5.13.73a.714.714 0 0 0 .367-.365l2.75-2.737c1.373-1.46 1.373-3.74-.093-5.108a3.72 3.72 0 0 0-5.13 0L12.33 6.4a.888.888 0 0 1-1.283 0 .88.88 0 0 1 0-1.277l1.558-1.55a5.38 5.38 0 0 1 7.605 0c2.29 2.006 2.382 5.564.274 7.662zm-8.979 6.294L9.95 19.081a3.72 3.72 0 0 1-5.13 0c-1.467-1.368-1.467-3.74-.093-5.108l2.75-2.737.366-.365c.824-.547 1.74-.82 2.748-.73 1.008.183 1.833.639 2.382 1.46.275.365.917.456 1.283.182.367-.273.458-.912.183-1.277-.916-1.186-2.199-1.915-3.573-2.098-1.374-.273-2.84.091-4.031 1.004l-.55.547-2.749 2.737c-2.107 2.189-2.015 5.655.092 7.753C4.727 21.453 6.101 22 7.475 22c1.374 0 2.749-.547 3.848-1.55l1.558-1.551a.88.88 0 0 0 0-1.278c-.367-.364-1.008-.456-1.375-.09z" fill="#FF814F" fill-rule="nonzero" />
     </svg>
 
     // Set default options and locale
@@ -183,24 +179,25 @@ module.exports = class Url extends Plugin {
   }
 
   handlePaste (e) {
-    if (e.clipboardData.items) {
-      const items = toArray(e.clipboardData.items)
-
-      // When a file is pasted, it appears as two items: file name string, then
-      // the file itself; Url then treats file name string as URL, which is wrong.
-      // This makes sure Url ignores paste event if it contains an actual file
-      const hasFiles = items.filter(item => item.kind === 'file').length > 0
-      if (hasFiles) return
-
-      items.forEach((item) => {
-        if (item.kind === 'string' && item.type === 'text/plain') {
-          item.getAsString((url) => {
-            this.uppy.log(`[URL] Adding file from pasted url: ${url}`)
-            this.addFile(url)
-          })
-        }
-      })
+    if (!e.clipboardData.items) {
+      return
     }
+    const items = toArray(e.clipboardData.items)
+
+    // When a file is pasted, it appears as two items: file name string, then
+    // the file itself; Url then treats file name string as URL, which is wrong.
+    // This makes sure Url ignores paste event if it contains an actual file
+    const hasFiles = items.filter(item => item.kind === 'file').length > 0
+    if (hasFiles) return
+
+    items.forEach((item) => {
+      if (item.kind === 'string' && item.type === 'text/plain') {
+        item.getAsString((url) => {
+          this.uppy.log(`[URL] Adding file from pasted url: ${url}`)
+          this.addFile(url)
+        })
+      }
+    })
   }
 
   render (state) {

+ 3 - 2
packages/@uppy/url/src/style.scss

@@ -7,6 +7,7 @@
   flex-direction: column;
   justify-content: center;
   align-items: center;
+  flex: 1;
 }
 
 .uppy-Url-input {
@@ -15,7 +16,7 @@
   margin-bottom: 15px;
 }
 
-  .uppy-Dashboard--wide .uppy-Url-input  {
+  .uppy-size--md .uppy-Url-input  {
     margin-bottom: 30px;
   }
 
@@ -23,6 +24,6 @@
   padding: 13px 25px;
 }
 
-  .uppy-Dashboard--wide .uppy-Url-importButton {
+  .uppy-size--md .uppy-Url-importButton {
     padding: 13px 35px;
   }

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

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/utils",
   "description": "Shared utility functions for Uppy Core and plugins maintained by the Uppy team.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",

+ 3 - 3
packages/@uppy/webcam/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/webcam",
   "description": "Uppy plugin that takes photos or records videos using the device's camera.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -27,11 +27,11 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/utils": "0.26.0",
+    "@uppy/utils": "0.27.0",
     "preact": "^8.2.9"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 1 - 1
packages/@uppy/webcam/src/CameraIcon.js

@@ -1,7 +1,7 @@
 const { h } = require('preact')
 
 module.exports = (props) => {
-  return <svg aria-hidden="true" class="UppyIcon" width="66" height="55" viewBox="0 0 66 55" xmlns="http://www.w3.org/2000/svg">
+  return <svg aria-hidden="true" fill="#0097DC" width="66" height="55" viewBox="0 0 66 55" xmlns="http://www.w3.org/2000/svg">
     <path d="M57.3 8.433c4.59 0 8.1 3.51 8.1 8.1v29.7c0 4.59-3.51 8.1-8.1 8.1H8.7c-4.59 0-8.1-3.51-8.1-8.1v-29.7c0-4.59 3.51-8.1 8.1-8.1h9.45l4.59-7.02c.54-.54 1.35-1.08 2.16-1.08h16.2c.81 0 1.62.54 2.16 1.08l4.59 7.02h9.45zM33 14.64c-8.62 0-15.393 6.773-15.393 15.393 0 8.62 6.773 15.393 15.393 15.393 8.62 0 15.393-6.773 15.393-15.393 0-8.62-6.773-15.393-15.393-15.393zM33 40c-5.648 0-9.966-4.319-9.966-9.967 0-5.647 4.318-9.966 9.966-9.966s9.966 4.319 9.966 9.966C42.966 35.681 38.648 40 33 40z" fill-rule="evenodd" />
   </svg>
 }

+ 2 - 3
packages/@uppy/webcam/src/PermissionsScreen.js

@@ -4,9 +4,8 @@ module.exports = (props) => {
   return (
     <div class="uppy-Webcam-permissons">
       <div class="uppy-Webcam-permissonsIcon">{props.icon()}</div>
-      <h1 class="uppy-Webcam-Title">Please allow access to your camera</h1>
-      <p>You have been prompted to allow camera access from this site.<br />
-      In order to take pictures with your camera you must approve this request.</p>
+      <h1 class="uppy-Webcam-title">{props.i18n('allowAccessTitle')}</h1>
+      <p>{props.i18n('allowAccessDescription')}</p>
     </div>
   )
 }

+ 7 - 3
packages/@uppy/webcam/src/index.js

@@ -39,7 +39,7 @@ module.exports = class Webcam extends Plugin {
     this.supportsUserMedia = !!this.mediaDevices
     this.protocol = location.protocol.match(/https/i) ? 'https' : 'http'
     this.id = this.opts.id || 'Webcam'
-    this.title = 'Camera'
+    this.title = this.opts.title || 'Camera'
     this.type = 'acquirer'
     this.icon = CameraIcon
 
@@ -48,7 +48,9 @@ module.exports = class Webcam extends Plugin {
         smile: 'Smile!',
         takePicture: 'Take a picture',
         startRecording: 'Begin video recording',
-        stopRecording: 'Stop video recording'
+        stopRecording: 'Stop video recording',
+        allowAccessTitle: 'Please allow access to your camera',
+        allowAccessDescription: 'In order to take pictures or record video with your camera, please allow camera access for this site.'
       }
     }
 
@@ -325,7 +327,9 @@ module.exports = class Webcam extends Plugin {
     const webcamState = this.getPluginState()
 
     if (!webcamState.cameraReady) {
-      return <PermissionsScreen icon={CameraIcon} />
+      return <PermissionsScreen
+        icon={CameraIcon}
+        i18n={this.i18n} />
     }
 
     return <CameraScreen

+ 24 - 15
packages/@uppy/webcam/src/style.scss

@@ -15,19 +15,16 @@
   flex-grow: 1;
   overflow: hidden;
   background-color: $color-black;
-  // height: 100%;
-  // display: flex;
-  // justify-content: center;
-  // align-items: center;
+  text-align: center;
 }
 
-  .uppy-Dashboard--wide .uppy-Webcam-videoContainer {
+  .uppy-size--md .uppy-Webcam-videoContainer {
     // height: initial;
   }
 
 .uppy-Webcam-video {
-  width: 100%;
-  height: 100%;
+  // width: 100%;
+  // height: 100%;
   max-width: 100%;
   max-height: 100%;
 }
@@ -56,7 +53,18 @@
   transition: all 0.3s;
 }
 
-  .uppy-Dashboard--wide .uppy-Webcam-button {
+  .uppy-Webcam-button svg {
+    width: 30px;
+    height: 30px;
+    max-width: 100%;
+    max-height: 100%;
+    display: inline-block;
+    vertical-align: text-top;
+    overflow: hidden;
+    fill: currentColor;
+  }
+
+  .uppy-size--md .uppy-Webcam-button {
     width: 60px;
     height: 60px;
   }
@@ -70,11 +78,6 @@
     box-shadow: 0 0 0 0.2rem rgba($color-cornflower-blue, 0.5);
   }
 
-  .uppy-Webcam-button .UppyIcon {
-    width: 30px;
-    height: 30px;
-  }
-
 .uppy-Webcam-button--picture {
   margin-right: 12px;
 }
@@ -86,9 +89,15 @@
   justify-content: center;
   flex-flow: column wrap;
   height: 100%;
+  flex: 1;
+}
+
+.uppy-Webcam-permissons p {
+  max-width: 450px;
+  line-height: 1.3;
 }
 
-.uppy-Webcam-Title {
+.uppy-Webcam-title {
   font-size: 22px;
   line-height: 1.35;
   font-weight: 400;
@@ -107,7 +116,7 @@
   margin: 0;
 }
 
-.uppy-Webcam-permissonsIcon .UppyIcon {
+.uppy-Webcam-permissonsIcon svg {
   width: 100px;
   height: 75px;
   color: lighten($color-gray, 15%);

+ 4 - 4
packages/@uppy/xhr-upload/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@uppy/xhr-upload",
   "description": "Plain and simple classic HTML multipart form uploads with Uppy, as well as uploads using the HTTP PUT method.",
-  "version": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "lib/index.js",
   "jsnext:main": "src/index.js",
@@ -25,12 +25,12 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/companion-client": "0.26.0",
-    "@uppy/utils": "0.26.0",
+    "@uppy/companion-client": "0.27.0",
+    "@uppy/utils": "0.27.0",
     "cuid": "^2.1.1"
   },
   "devDependencies": {
-    "@uppy/core": "0.26.0"
+    "@uppy/core": "0.27.0"
   },
   "peerDependencies": {
     "@uppy/core": "^0.26.0"

+ 26 - 26
packages/uppy/package.json

@@ -1,7 +1,7 @@
 {
   "name": "uppy",
   "description": "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": "0.26.0",
+  "version": "0.27.0",
   "license": "MIT",
   "main": "index.js",
   "module": "index.mjs",
@@ -30,30 +30,30 @@
     "url": "git+https://github.com/transloadit/uppy.git"
   },
   "dependencies": {
-    "@uppy/aws-s3": "0.26.0",
-    "@uppy/aws-s3-multipart": "0.26.0",
-    "@uppy/core": "0.26.0",
-    "@uppy/dashboard": "0.26.0",
-    "@uppy/drag-drop": "0.26.0",
-    "@uppy/dropbox": "0.26.0",
-    "@uppy/file-input": "0.26.0",
-    "@uppy/form": "0.26.0",
-    "@uppy/golden-retriever": "0.26.0",
-    "@uppy/google-drive": "0.26.0",
-    "@uppy/informer": "0.26.0",
-    "@uppy/instagram": "0.26.0",
-    "@uppy/progress-bar": "0.26.0",
-    "@uppy/provider-views": "0.26.0",
-    "@uppy/redux-dev-tools": "0.26.0",
-    "@uppy/companion-client": "0.26.0",
-    "@uppy/status-bar": "0.26.0",
-    "@uppy/store-default": "0.26.0",
-    "@uppy/store-redux": "0.26.0",
-    "@uppy/thumbnail-generator": "0.26.0",
-    "@uppy/transloadit": "0.26.0",
-    "@uppy/tus": "0.26.0",
-    "@uppy/url": "0.26.0",
-    "@uppy/webcam": "0.26.0",
-    "@uppy/xhr-upload": "0.26.0"
+    "@uppy/aws-s3": "0.27.0",
+    "@uppy/aws-s3-multipart": "0.27.0",
+    "@uppy/companion-client": "0.27.0",
+    "@uppy/core": "0.27.0",
+    "@uppy/dashboard": "0.27.0",
+    "@uppy/drag-drop": "0.27.0",
+    "@uppy/dropbox": "0.27.0",
+    "@uppy/file-input": "0.27.0",
+    "@uppy/form": "0.27.0",
+    "@uppy/golden-retriever": "0.27.0",
+    "@uppy/google-drive": "0.27.0",
+    "@uppy/informer": "0.27.0",
+    "@uppy/instagram": "0.27.0",
+    "@uppy/progress-bar": "0.27.0",
+    "@uppy/provider-views": "0.27.0",
+    "@uppy/redux-dev-tools": "0.27.0",
+    "@uppy/status-bar": "0.27.0",
+    "@uppy/store-default": "0.27.0",
+    "@uppy/store-redux": "0.27.0",
+    "@uppy/thumbnail-generator": "0.27.0",
+    "@uppy/transloadit": "0.27.0",
+    "@uppy/tus": "0.27.0",
+    "@uppy/url": "0.27.0",
+    "@uppy/webcam": "0.27.0",
+    "@uppy/xhr-upload": "0.27.0"
   }
 }

+ 5 - 0
test/endtoend/create-react-app/package-lock.json

@@ -2954,6 +2954,11 @@
         "event-emitter": "~0.3.5"
       }
     },
+    "es6-shim": {
+      "version": "0.35.3",
+      "resolved": "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.3.tgz",
+      "integrity": "sha1-m/tzY/7//4emzbbNk+QF7DxLbyY="
+    },
     "es6-symbol": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",

+ 1 - 0
test/endtoend/create-react-app/package.json

@@ -4,6 +4,7 @@
   "private": true,
   "homepage": "/create-react-app/",
   "dependencies": {
+    "es6-shim": "^0.35.3",
     "react": "^16.4.1",
     "react-dom": "^16.4.1",
     "react-scripts": "1.1.4"

+ 1 - 0
test/endtoend/create-react-app/src/index.js

@@ -1,3 +1,4 @@
+import 'es6-shim' // Polyfill Map and Set for React
 import React from 'react'
 import ReactDOM from 'react-dom'
 import App from './App'

+ 10 - 9
test/endtoend/create-react-app/_test.js → test/endtoend/create-react-app/test.js

@@ -10,8 +10,8 @@ describe('webpack build', () => {
     const el = $('#inline-dashboard .uppy-Dashboard-inner')
     el.waitForExist()
     const bgColor = el.getCssProperty('background-color').value
-    // computed value is rgb(), not hex (but using a regex here to show the expected value too)
-    expect(/^rgb\(250, ?250, ?250\)$|^#fafafa$/.test(bgColor)).to.equal(true)
+    // computed value is rgb() or rgba(), not hex (but listing it here to show the expected value too)
+    expect(/^rgba?\(250, ?250, ?250(?:, ?1)?\)$|^#fafafa$/.test(bgColor)).to.equal(true)
   })
 })
 
@@ -34,20 +34,20 @@ describe('React: Dashboard', () => {
 
     // close
     browser.click('#inline-dashboard-toggle')
-    browser.pause(50)
+    browser.pause(250)
     // open
     browser.click('#inline-dashboard-toggle')
-    browser.pause(50)
+    browser.pause(250)
     // close
     browser.click('#inline-dashboard-toggle')
-    browser.pause(50)
+    browser.pause(250)
     // open
     browser.click('#inline-dashboard-toggle')
-    browser.pause(50)
+    browser.pause(250)
 
     // open GDrive panel
-    browser.click('.uppy-DashboardTab:nth-child(2)')
-    browser.pause(50)
+    browser.click('.uppy-DashboardTab:nth-child(2) button')
+    browser.pause(500)
 
     // side effecting property access, not a function!
     // eslint-disable-next-line no-unused-expressions
@@ -70,7 +70,8 @@ describe('React: DashboardModal', () => {
     modalToggle.click()
     browser.pause(50) // wait for the animation to start
 
-    expect(modalWrapper.getAttribute('aria-hidden')).to.equal(null)
+    // Edge appears to report empty string while others report null
+    expect(modalWrapper.getAttribute('aria-hidden')).to.be.oneOf([null, ''])
 
     browser.pause(500) // wait for the animation to complete
 

+ 1 - 1
website/src/_posts/2018-07-0.26.md

@@ -14,7 +14,7 @@ Uppy 0.26 replaces the monolithic Uppy package with a separate npm package for e
 
 ## Lerna
 
-**breaking**
+⚠️ **breaking**
 
 All Uppy plugins have moved into their own npm packages. This means you need to install the plugins you use separately in the future, and you have to update all your Uppy `require()` calls or `import` paths.
 

Some files were not shown because too many files changed in this diff