mirror of
https://github.com/transloadit/uppy.git
synced 2026-07-18 00:55:35 +00:00
Merge stable branch
This commit is contained in:
commit
6f761fffd5
73 changed files with 13404 additions and 118 deletions
|
|
@ -413,8 +413,15 @@ module.exports = {
|
|||
files: ['**/*.md', '*.md'],
|
||||
processor: 'markdown/markdown',
|
||||
},
|
||||
{
|
||||
files: ['docs/**/*.md/*.js'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.md/*.js', '**/*.md/*.javascript'],
|
||||
excludedFiles: ["docs/**/*"],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
|
|
|
|||
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Basic set up for three package managers
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
4
.github/workflows/bundlers.yml
vendored
4
.github/workflows/bundlers.yml
vendored
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
id: yarn-cache-dir-path
|
||||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
|
|
|
|||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
|
@ -43,7 +43,7 @@ jobs:
|
|||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -51,7 +51,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{matrix.node-version}}
|
||||
- name: Install dependencies
|
||||
|
|
@ -76,7 +76,7 @@ jobs:
|
|||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -84,7 +84,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
|
|
|
|||
4
.github/workflows/companion-deploy.yml
vendored
4
.github/workflows/companion-deploy.yml
vendored
|
|
@ -49,13 +49,13 @@ jobs:
|
|||
uses: actions/checkout@v3
|
||||
- name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
|
||||
with:
|
||||
images: transloadit/companion
|
||||
tags: |
|
||||
type=edge
|
||||
type=raw,value=latest,enable=false
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
- name: Log in to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
|
|
|
|||
4
.github/workflows/companion.yml
vendored
4
.github/workflows/companion.yml
vendored
|
|
@ -32,7 +32,7 @@ jobs:
|
|||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -40,7 +40,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{matrix.node-version}}
|
||||
- name: Install dependencies
|
||||
|
|
|
|||
12
.github/workflows/e2e.yml
vendored
12
.github/workflows/e2e.yml
vendored
|
|
@ -70,7 +70,7 @@ jobs:
|
|||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -78,7 +78,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
|
|
@ -230,7 +230,7 @@ jobs:
|
|||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -241,17 +241,17 @@ jobs:
|
|||
- name: Create cache folder for Cypress
|
||||
id: cypress-cache-dir-path
|
||||
run: echo "dir=$(mktemp -d)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.cypress-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-cypress
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Start Redis
|
||||
uses: supercharge/redis-github-action@1.4.0
|
||||
uses: supercharge/redis-github-action@ea9b21c6ecece47bd99595c532e481390ea0f044 # 1.8.0
|
||||
with:
|
||||
redis-version: 7
|
||||
|
||||
|
|
|
|||
29
.github/workflows/linters.yml
vendored
29
.github/workflows/linters.yml
vendored
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -38,7 +38,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
|
|
@ -62,7 +62,7 @@ jobs:
|
|||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -70,10 +70,31 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run: corepack yarn workspaces focus @uppy-dev/build
|
||||
- name: Run linter
|
||||
run: corepack yarn run lint:markdown
|
||||
|
||||
lint_docs:
|
||||
name: Lint Docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Uppy.io sources
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: transloadit/uppy.io
|
||||
- run: rm -rf docs # the other PR has not landed
|
||||
- name: Checkout docs
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: uppy
|
||||
- run: mv uppy /tmp/uppy && ln -s /tmp/uppy/docs docs
|
||||
- name: Install dependencies
|
||||
run: corepack yarn --immutable
|
||||
- name: Lint files
|
||||
run: corepack yarn lint
|
||||
- name: Test build website
|
||||
run: corepack yarn build
|
||||
|
|
|
|||
4
.github/workflows/lockfile_check.yml
vendored
4
.github/workflows/lockfile_check.yml
vendored
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
|
|
|
|||
4
.github/workflows/manual-cdn.yml
vendored
4
.github/workflows/manual-cdn.yml
vendored
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
|
|
|
|||
4
.github/workflows/release-candidate.yml
vendored
4
.github/workflows/release-candidate.yml
vendored
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -30,7 +30,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
|
|
|
|||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
|
@ -35,7 +35,7 @@ jobs:
|
|||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
|
|
@ -145,7 +145,7 @@ jobs:
|
|||
uses: actions/checkout@v3
|
||||
- name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
|
||||
with:
|
||||
images: transloadit/companion
|
||||
tags: |
|
||||
|
|
@ -153,7 +153,7 @@ jobs:
|
|||
type=semver,pattern={{version}},value=${{ needs.release.outputs.companionWasReleased }}
|
||||
# set latest tag for default branch
|
||||
type=raw,value=latest,enable=true
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
- name: Log in to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
|
|
|
|||
|
|
@ -12,5 +12,12 @@ module.exports = {
|
|||
semi: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: "docs/**",
|
||||
options: {
|
||||
semi: true,
|
||||
useTabs: true,
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
|
|
|
|||
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -165,6 +165,28 @@ Released: 2024-03-28
|
|||
- meta: prepare release workflow for beta versions (Antoine du Hamel)
|
||||
|
||||
|
||||
## 3.24.3
|
||||
|
||||
Released: 2024-04-16
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| --------------- | ------- | --------------- | ------- |
|
||||
| @uppy/dashboard | 3.8.1 | uppy | 3.24.3 |
|
||||
| @uppy/utils | 5.8.0 | | |
|
||||
|
||||
- docs: add back markdown files (Antoine du Hamel / #5064)
|
||||
- meta: fix custom provider example (Merlijn Vos / #5079)
|
||||
- @uppy/utils: add fetcher (Merlijn Vos / #5073)
|
||||
- meta: Fix prettier (Murderlon)
|
||||
- @uppy/dashboard: add missing `x-zip-compress` archive type (Younes / #5081)
|
||||
- meta: Bump docker/metadata-action from 4 to 5 (dependabot[bot] / #5086)
|
||||
- meta: Bump actions/setup-node from 3 to 4 (dependabot[bot] / #5087)
|
||||
- meta: Bump docker/setup-qemu-action from 2 to 3 (dependabot[bot] / #5089)
|
||||
- meta: bump supercharge/redis-github-action from 1.4.0 to 1.8.0 (dependabot[bot] / #5090)
|
||||
- meta: bump actions/cache from 3 to 4 (dependabot[bot] / #5088)
|
||||
- meta: add `dependabot.yml` to keep GHA up-to-date (Antoine du Hamel / #5083)
|
||||
|
||||
|
||||
## 3.24.2
|
||||
|
||||
Released: 2024-04-15
|
||||
|
|
|
|||
159
README.md
159
README.md
|
|
@ -207,9 +207,9 @@ Use Uppy in your project? [Let us know](https://github.com/transloadit/uppy/issu
|
|||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[Murderlon](https://github.com/Murderlon) |[AJvanLoon](https://github.com/AJvanLoon) |[nqst](https://github.com/nqst) |[mifi](https://github.com/mifi) |[github-actions\[bot\]](https://github.com/apps/github-actions) |[lakesare](https://github.com/lakesare) |
|
||||
|
||||
[<img alt="kiloreux" src="https://avatars.githubusercontent.com/u/6282557?v=4&s=117" width="117">](https://github.com/kiloreux) |[<img alt="dependabot[bot]" src="https://avatars.githubusercontent.com/in/29110?v=4&s=117" width="117">](https://github.com/apps/dependabot) |[<img alt="samuelayo" src="https://avatars.githubusercontent.com/u/14964486?v=4&s=117" width="117">](https://github.com/samuelayo) |[<img alt="sadovnychyi" src="https://avatars.githubusercontent.com/u/193864?v=4&s=117" width="117">](https://github.com/sadovnychyi) |[<img alt="richardwillars" src="https://avatars.githubusercontent.com/u/291004?v=4&s=117" width="117">](https://github.com/richardwillars) |[<img alt="ajkachnic" src="https://avatars.githubusercontent.com/u/44317699?v=4&s=117" width="117">](https://github.com/ajkachnic) |
|
||||
[<img alt="dependabot[bot]" src="https://avatars.githubusercontent.com/in/29110?v=4&s=117" width="117">](https://github.com/apps/dependabot) |[<img alt="kiloreux" src="https://avatars.githubusercontent.com/u/6282557?v=4&s=117" width="117">](https://github.com/kiloreux) |[<img alt="samuelayo" src="https://avatars.githubusercontent.com/u/14964486?v=4&s=117" width="117">](https://github.com/samuelayo) |[<img alt="sadovnychyi" src="https://avatars.githubusercontent.com/u/193864?v=4&s=117" width="117">](https://github.com/sadovnychyi) |[<img alt="richardwillars" src="https://avatars.githubusercontent.com/u/291004?v=4&s=117" width="117">](https://github.com/richardwillars) |[<img alt="ajkachnic" src="https://avatars.githubusercontent.com/u/44317699?v=4&s=117" width="117">](https://github.com/ajkachnic) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[kiloreux](https://github.com/kiloreux) |[dependabot\[bot\]](https://github.com/apps/dependabot) |[samuelayo](https://github.com/samuelayo) |[sadovnychyi](https://github.com/sadovnychyi) |[richardwillars](https://github.com/richardwillars) |[ajkachnic](https://github.com/ajkachnic) |
|
||||
[dependabot\[bot\]](https://github.com/apps/dependabot) |[kiloreux](https://github.com/kiloreux) |[samuelayo](https://github.com/samuelayo) |[sadovnychyi](https://github.com/sadovnychyi) |[richardwillars](https://github.com/richardwillars) |[ajkachnic](https://github.com/ajkachnic) |
|
||||
|
||||
[<img alt="zcallan" src="https://avatars.githubusercontent.com/u/13760738?v=4&s=117" width="117">](https://github.com/zcallan) |[<img alt="YukeshShr" src="https://avatars.githubusercontent.com/u/71844521?v=4&s=117" width="117">](https://github.com/YukeshShr) |[<img alt="janko" src="https://avatars.githubusercontent.com/u/795488?v=4&s=117" width="117">](https://github.com/janko) |[<img alt="oliverpool" src="https://avatars.githubusercontent.com/u/3864879?v=4&s=117" width="117">](https://github.com/oliverpool) |[<img alt="Botz" src="https://avatars.githubusercontent.com/u/2706678?v=4&s=117" width="117">](https://github.com/Botz) |[<img alt="mcallistertyler" src="https://avatars.githubusercontent.com/u/14939210?v=4&s=117" width="117">](https://github.com/mcallistertyler) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
|
|
@ -275,53 +275,53 @@ Use Uppy in your project? [Let us know](https://github.com/transloadit/uppy/issu
|
|||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[dogrocker](https://github.com/dogrocker) |[jedwood](https://github.com/jedwood) |[jasonbosco](https://github.com/jasonbosco) |[ghasrfakhri](https://github.com/ghasrfakhri) |[geertclerx](https://github.com/geertclerx) |[frobinsonj](https://github.com/frobinsonj) |
|
||||
|
||||
[<img alt="eman8519" src="https://avatars.githubusercontent.com/u/2380804?v=4&s=117" width="117">](https://github.com/eman8519) |[<img alt="luarmr" src="https://avatars.githubusercontent.com/u/817416?v=4&s=117" width="117">](https://github.com/luarmr) |[<img alt="raulibanez" src="https://avatars.githubusercontent.com/u/1070825?v=4&s=117" width="117">](https://github.com/raulibanez) |[<img alt="refo" src="https://avatars.githubusercontent.com/u/1114116?v=4&s=117" width="117">](https://github.com/refo) |[<img alt="SxDx" src="https://avatars.githubusercontent.com/u/2004247?v=4&s=117" width="117">](https://github.com/SxDx) |[<img alt="robwilson1" src="https://avatars.githubusercontent.com/u/7114944?v=4&s=117" width="117">](https://github.com/robwilson1) |
|
||||
[<img alt="neuronet77" src="https://avatars.githubusercontent.com/u/4220037?v=4&s=117" width="117">](https://github.com/neuronet77) |[<img alt="rossng" src="https://avatars.githubusercontent.com/u/565371?v=4&s=117" width="117">](https://github.com/rossng) |[<img alt="scherroman" src="https://avatars.githubusercontent.com/u/7923938?v=4&s=117" width="117">](https://github.com/scherroman) |[<img alt="robwilson1" src="https://avatars.githubusercontent.com/u/7114944?v=4&s=117" width="117">](https://github.com/robwilson1) |[<img alt="SxDx" src="https://avatars.githubusercontent.com/u/2004247?v=4&s=117" width="117">](https://github.com/SxDx) |[<img alt="refo" src="https://avatars.githubusercontent.com/u/1114116?v=4&s=117" width="117">](https://github.com/refo) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[eman8519](https://github.com/eman8519) |[luarmr](https://github.com/luarmr) |[raulibanez](https://github.com/raulibanez) |[refo](https://github.com/refo) |[SxDx](https://github.com/SxDx) |[robwilson1](https://github.com/robwilson1) |
|
||||
[neuronet77](https://github.com/neuronet77) |[rossng](https://github.com/rossng) |[scherroman](https://github.com/scherroman) |[robwilson1](https://github.com/robwilson1) |[SxDx](https://github.com/SxDx) |[refo](https://github.com/refo) |
|
||||
|
||||
[<img alt="scherroman" src="https://avatars.githubusercontent.com/u/7923938?v=4&s=117" width="117">](https://github.com/scherroman) |[<img alt="neuronet77" src="https://avatars.githubusercontent.com/u/4220037?v=4&s=117" width="117">](https://github.com/neuronet77) |[<img alt="Pzoco" src="https://avatars.githubusercontent.com/u/3101348?v=4&s=117" width="117">](https://github.com/Pzoco) |[<img alt="ppadmavilasom" src="https://avatars.githubusercontent.com/u/11167452?v=4&s=117" width="117">](https://github.com/ppadmavilasom) |[<img alt="phillipalexander" src="https://avatars.githubusercontent.com/u/1577682?v=4&s=117" width="117">](https://github.com/phillipalexander) |[<img alt="pmusaraj" src="https://avatars.githubusercontent.com/u/368961?v=4&s=117" width="117">](https://github.com/pmusaraj) |
|
||||
[<img alt="raulibanez" src="https://avatars.githubusercontent.com/u/1070825?v=4&s=117" width="117">](https://github.com/raulibanez) |[<img alt="luarmr" src="https://avatars.githubusercontent.com/u/817416?v=4&s=117" width="117">](https://github.com/luarmr) |[<img alt="eman8519" src="https://avatars.githubusercontent.com/u/2380804?v=4&s=117" width="117">](https://github.com/eman8519) |[<img alt="Pzoco" src="https://avatars.githubusercontent.com/u/3101348?v=4&s=117" width="117">](https://github.com/Pzoco) |[<img alt="ppadmavilasom" src="https://avatars.githubusercontent.com/u/11167452?v=4&s=117" width="117">](https://github.com/ppadmavilasom) |[<img alt="phillipalexander" src="https://avatars.githubusercontent.com/u/1577682?v=4&s=117" width="117">](https://github.com/phillipalexander) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[scherroman](https://github.com/scherroman) |[neuronet77](https://github.com/neuronet77) |[Pzoco](https://github.com/Pzoco) |[ppadmavilasom](https://github.com/ppadmavilasom) |[phillipalexander](https://github.com/phillipalexander) |[pmusaraj](https://github.com/pmusaraj) |
|
||||
[raulibanez](https://github.com/raulibanez) |[luarmr](https://github.com/luarmr) |[eman8519](https://github.com/eman8519) |[Pzoco](https://github.com/Pzoco) |[ppadmavilasom](https://github.com/ppadmavilasom) |[phillipalexander](https://github.com/phillipalexander) |
|
||||
|
||||
[<img alt="pedrofs" src="https://avatars.githubusercontent.com/u/56484?v=4&s=117" width="117">](https://github.com/pedrofs) |[<img alt="plneto" src="https://avatars.githubusercontent.com/u/5697434?v=4&s=117" width="117">](https://github.com/plneto) |[<img alt="patricklindsay" src="https://avatars.githubusercontent.com/u/7923681?v=4&s=117" width="117">](https://github.com/patricklindsay) |[<img alt="pascalwengerter" src="https://avatars.githubusercontent.com/u/16822008?v=4&s=117" width="117">](https://github.com/pascalwengerter) |[<img alt="ParsaArvanehPA" src="https://avatars.githubusercontent.com/u/62149413?v=4&s=117" width="117">](https://github.com/ParsaArvanehPA) |[<img alt="taj" src="https://avatars.githubusercontent.com/u/16062635?v=4&s=117" width="117">](https://github.com/taj) |
|
||||
[<img alt="pmusaraj" src="https://avatars.githubusercontent.com/u/368961?v=4&s=117" width="117">](https://github.com/pmusaraj) |[<img alt="pedrofs" src="https://avatars.githubusercontent.com/u/56484?v=4&s=117" width="117">](https://github.com/pedrofs) |[<img alt="plneto" src="https://avatars.githubusercontent.com/u/5697434?v=4&s=117" width="117">](https://github.com/plneto) |[<img alt="patricklindsay" src="https://avatars.githubusercontent.com/u/7923681?v=4&s=117" width="117">](https://github.com/patricklindsay) |[<img alt="pascalwengerter" src="https://avatars.githubusercontent.com/u/16822008?v=4&s=117" width="117">](https://github.com/pascalwengerter) |[<img alt="Tashows" src="https://avatars.githubusercontent.com/u/16656928?v=4&s=117" width="117">](https://github.com/Tashows) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[pedrofs](https://github.com/pedrofs) |[plneto](https://github.com/plneto) |[patricklindsay](https://github.com/patricklindsay) |[pascalwengerter](https://github.com/pascalwengerter) |[ParsaArvanehPA](https://github.com/ParsaArvanehPA) |[taj](https://github.com/taj) |
|
||||
[pmusaraj](https://github.com/pmusaraj) |[pedrofs](https://github.com/pedrofs) |[plneto](https://github.com/plneto) |[patricklindsay](https://github.com/patricklindsay) |[pascalwengerter](https://github.com/pascalwengerter) |[Tashows](https://github.com/Tashows) |
|
||||
|
||||
[<img alt="strayer" src="https://avatars.githubusercontent.com/u/310624?v=4&s=117" width="117">](https://github.com/strayer) |[<img alt="sjauld" src="https://avatars.githubusercontent.com/u/8232503?v=4&s=117" width="117">](https://github.com/sjauld) |[<img alt="steverob" src="https://avatars.githubusercontent.com/u/1220480?v=4&s=117" width="117">](https://github.com/steverob) |[<img alt="amaitu" src="https://avatars.githubusercontent.com/u/15688439?v=4&s=117" width="117">](https://github.com/amaitu) |[<img alt="quigebo" src="https://avatars.githubusercontent.com/u/741?v=4&s=117" width="117">](https://github.com/quigebo) |[<img alt="waptik" src="https://avatars.githubusercontent.com/u/1687551?v=4&s=117" width="117">](https://github.com/waptik) |
|
||||
[<img alt="taj" src="https://avatars.githubusercontent.com/u/16062635?v=4&s=117" width="117">](https://github.com/taj) |[<img alt="strayer" src="https://avatars.githubusercontent.com/u/310624?v=4&s=117" width="117">](https://github.com/strayer) |[<img alt="sjauld" src="https://avatars.githubusercontent.com/u/8232503?v=4&s=117" width="117">](https://github.com/sjauld) |[<img alt="steverob" src="https://avatars.githubusercontent.com/u/1220480?v=4&s=117" width="117">](https://github.com/steverob) |[<img alt="amaitu" src="https://avatars.githubusercontent.com/u/15688439?v=4&s=117" width="117">](https://github.com/amaitu) |[<img alt="quigebo" src="https://avatars.githubusercontent.com/u/741?v=4&s=117" width="117">](https://github.com/quigebo) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[strayer](https://github.com/strayer) |[sjauld](https://github.com/sjauld) |[steverob](https://github.com/steverob) |[amaitu](https://github.com/amaitu) |[quigebo](https://github.com/quigebo) |[waptik](https://github.com/waptik) |
|
||||
[taj](https://github.com/taj) |[strayer](https://github.com/strayer) |[sjauld](https://github.com/sjauld) |[steverob](https://github.com/steverob) |[amaitu](https://github.com/amaitu) |[quigebo](https://github.com/quigebo) |
|
||||
|
||||
[<img alt="SpazzMarticus" src="https://avatars.githubusercontent.com/u/5716457?v=4&s=117" width="117">](https://github.com/SpazzMarticus) |[<img alt="szh" src="https://avatars.githubusercontent.com/u/546965?v=4&s=117" width="117">](https://github.com/szh) |[<img alt="sergei-zelinsky" src="https://avatars.githubusercontent.com/u/19428086?v=4&s=117" width="117">](https://github.com/sergei-zelinsky) |[<img alt="sebasegovia01" src="https://avatars.githubusercontent.com/u/35777287?v=4&s=117" width="117">](https://github.com/sebasegovia01) |[<img alt="sdebacker" src="https://avatars.githubusercontent.com/u/134503?v=4&s=117" width="117">](https://github.com/sdebacker) |[<img alt="samuelcolburn" src="https://avatars.githubusercontent.com/u/9741902?v=4&s=117" width="117">](https://github.com/samuelcolburn) |
|
||||
[<img alt="waptik" src="https://avatars.githubusercontent.com/u/1687551?v=4&s=117" width="117">](https://github.com/waptik) |[<img alt="SpazzMarticus" src="https://avatars.githubusercontent.com/u/5716457?v=4&s=117" width="117">](https://github.com/SpazzMarticus) |[<img alt="szh" src="https://avatars.githubusercontent.com/u/546965?v=4&s=117" width="117">](https://github.com/szh) |[<img alt="sergei-zelinsky" src="https://avatars.githubusercontent.com/u/19428086?v=4&s=117" width="117">](https://github.com/sergei-zelinsky) |[<img alt="sebasegovia01" src="https://avatars.githubusercontent.com/u/35777287?v=4&s=117" width="117">](https://github.com/sebasegovia01) |[<img alt="sdebacker" src="https://avatars.githubusercontent.com/u/134503?v=4&s=117" width="117">](https://github.com/sdebacker) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[SpazzMarticus](https://github.com/SpazzMarticus) |[szh](https://github.com/szh) |[sergei-zelinsky](https://github.com/sergei-zelinsky) |[sebasegovia01](https://github.com/sebasegovia01) |[sdebacker](https://github.com/sdebacker) |[samuelcolburn](https://github.com/samuelcolburn) |
|
||||
[waptik](https://github.com/waptik) |[SpazzMarticus](https://github.com/SpazzMarticus) |[szh](https://github.com/szh) |[sergei-zelinsky](https://github.com/sergei-zelinsky) |[sebasegovia01](https://github.com/sebasegovia01) |[sdebacker](https://github.com/sdebacker) |
|
||||
|
||||
[<img alt="fortunto2" src="https://avatars.githubusercontent.com/u/1236751?v=4&s=117" width="117">](https://github.com/fortunto2) |[<img alt="GNURub" src="https://avatars.githubusercontent.com/u/1318648?v=4&s=117" width="117">](https://github.com/GNURub) |[<img alt="rart" src="https://avatars.githubusercontent.com/u/3928341?v=4&s=117" width="117">](https://github.com/rart) |[<img alt="rossng" src="https://avatars.githubusercontent.com/u/565371?v=4&s=117" width="117">](https://github.com/rossng) |[<img alt="mkopinsky" src="https://avatars.githubusercontent.com/u/591435?v=4&s=117" width="117">](https://github.com/mkopinsky) |[<img alt="mhulet" src="https://avatars.githubusercontent.com/u/293355?v=4&s=117" width="117">](https://github.com/mhulet) |
|
||||
[<img alt="samuelcolburn" src="https://avatars.githubusercontent.com/u/9741902?v=4&s=117" width="117">](https://github.com/samuelcolburn) |[<img alt="fortunto2" src="https://avatars.githubusercontent.com/u/1236751?v=4&s=117" width="117">](https://github.com/fortunto2) |[<img alt="GNURub" src="https://avatars.githubusercontent.com/u/1318648?v=4&s=117" width="117">](https://github.com/GNURub) |[<img alt="rart" src="https://avatars.githubusercontent.com/u/3928341?v=4&s=117" width="117">](https://github.com/rart) |[<img alt="ken-kuro" src="https://avatars.githubusercontent.com/u/47441476?v=4&s=117" width="117">](https://github.com/ken-kuro) |[<img alt="mkopinsky" src="https://avatars.githubusercontent.com/u/591435?v=4&s=117" width="117">](https://github.com/mkopinsky) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[fortunto2](https://github.com/fortunto2) |[GNURub](https://github.com/GNURub) |[rart](https://github.com/rart) |[rossng](https://github.com/rossng) |[mkopinsky](https://github.com/mkopinsky) |[mhulet](https://github.com/mhulet) |
|
||||
[samuelcolburn](https://github.com/samuelcolburn) |[fortunto2](https://github.com/fortunto2) |[GNURub](https://github.com/GNURub) |[rart](https://github.com/rart) |[ken-kuro](https://github.com/ken-kuro) |[mkopinsky](https://github.com/mkopinsky) |
|
||||
|
||||
[<img alt="hrsh" src="https://avatars.githubusercontent.com/u/1929359?v=4&s=117" width="117">](https://github.com/hrsh) |[<img alt="mauricioribeiro" src="https://avatars.githubusercontent.com/u/2589856?v=4&s=117" width="117">](https://github.com/mauricioribeiro) |[<img alt="matthewhartstonge" src="https://avatars.githubusercontent.com/u/6119549?v=4&s=117" width="117">](https://github.com/matthewhartstonge) |[<img alt="mjesuele" src="https://avatars.githubusercontent.com/u/871117?v=4&s=117" width="117">](https://github.com/mjesuele) |[<img alt="mattfik" src="https://avatars.githubusercontent.com/u/1638028?v=4&s=117" width="117">](https://github.com/mattfik) |[<img alt="mateuscruz" src="https://avatars.githubusercontent.com/u/8962842?v=4&s=117" width="117">](https://github.com/mateuscruz) |
|
||||
[<img alt="mhulet" src="https://avatars.githubusercontent.com/u/293355?v=4&s=117" width="117">](https://github.com/mhulet) |[<img alt="hrsh" src="https://avatars.githubusercontent.com/u/1929359?v=4&s=117" width="117">](https://github.com/hrsh) |[<img alt="mauricioribeiro" src="https://avatars.githubusercontent.com/u/2589856?v=4&s=117" width="117">](https://github.com/mauricioribeiro) |[<img alt="matthewhartstonge" src="https://avatars.githubusercontent.com/u/6119549?v=4&s=117" width="117">](https://github.com/matthewhartstonge) |[<img alt="mjesuele" src="https://avatars.githubusercontent.com/u/871117?v=4&s=117" width="117">](https://github.com/mjesuele) |[<img alt="mattfik" src="https://avatars.githubusercontent.com/u/1638028?v=4&s=117" width="117">](https://github.com/mattfik) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[hrsh](https://github.com/hrsh) |[mauricioribeiro](https://github.com/mauricioribeiro) |[matthewhartstonge](https://github.com/matthewhartstonge) |[mjesuele](https://github.com/mjesuele) |[mattfik](https://github.com/mattfik) |[mateuscruz](https://github.com/mateuscruz) |
|
||||
[mhulet](https://github.com/mhulet) |[hrsh](https://github.com/hrsh) |[mauricioribeiro](https://github.com/mauricioribeiro) |[matthewhartstonge](https://github.com/matthewhartstonge) |[mjesuele](https://github.com/mjesuele) |[mattfik](https://github.com/mattfik) |
|
||||
|
||||
[<img alt="masumulu28" src="https://avatars.githubusercontent.com/u/49063256?v=4&s=117" width="117">](https://github.com/masumulu28) |[<img alt="masaok" src="https://avatars.githubusercontent.com/u/1320083?v=4&s=117" width="117">](https://github.com/masaok) |[<img alt="martin-brennan" src="https://avatars.githubusercontent.com/u/920448?v=4&s=117" width="117">](https://github.com/martin-brennan) |[<img alt="marcusforsberg" src="https://avatars.githubusercontent.com/u/1009069?v=4&s=117" width="117">](https://github.com/marcusforsberg) |[<img alt="marcosthejew" src="https://avatars.githubusercontent.com/u/1500967?v=4&s=117" width="117">](https://github.com/marcosthejew) |[<img alt="mperrando" src="https://avatars.githubusercontent.com/u/525572?v=4&s=117" width="117">](https://github.com/mperrando) |
|
||||
[<img alt="mateuscruz" src="https://avatars.githubusercontent.com/u/8962842?v=4&s=117" width="117">](https://github.com/mateuscruz) |[<img alt="masumulu28" src="https://avatars.githubusercontent.com/u/49063256?v=4&s=117" width="117">](https://github.com/masumulu28) |[<img alt="masaok" src="https://avatars.githubusercontent.com/u/1320083?v=4&s=117" width="117">](https://github.com/masaok) |[<img alt="martin-brennan" src="https://avatars.githubusercontent.com/u/920448?v=4&s=117" width="117">](https://github.com/martin-brennan) |[<img alt="marcusforsberg" src="https://avatars.githubusercontent.com/u/1009069?v=4&s=117" width="117">](https://github.com/marcusforsberg) |[<img alt="marcosthejew" src="https://avatars.githubusercontent.com/u/1500967?v=4&s=117" width="117">](https://github.com/marcosthejew) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[masumulu28](https://github.com/masumulu28) |[masaok](https://github.com/masaok) |[martin-brennan](https://github.com/martin-brennan) |[marcusforsberg](https://github.com/marcusforsberg) |[marcosthejew](https://github.com/marcosthejew) |[mperrando](https://github.com/mperrando) |
|
||||
[mateuscruz](https://github.com/mateuscruz) |[masumulu28](https://github.com/masumulu28) |[masaok](https://github.com/masaok) |[martin-brennan](https://github.com/martin-brennan) |[marcusforsberg](https://github.com/marcusforsberg) |[marcosthejew](https://github.com/marcosthejew) |
|
||||
|
||||
[<img alt="onhate" src="https://avatars.githubusercontent.com/u/980905?v=4&s=117" width="117">](https://github.com/onhate) |[<img alt="marc-mabe" src="https://avatars.githubusercontent.com/u/302689?v=4&s=117" width="117">](https://github.com/marc-mabe) |[<img alt="Cruaier" src="https://avatars.githubusercontent.com/u/5204940?v=4&s=117" width="117">](https://github.com/Cruaier) |[<img alt="cryptic022" src="https://avatars.githubusercontent.com/u/18145703?v=4&s=117" width="117">](https://github.com/cryptic022) |[<img alt="Ozodbek1405" src="https://avatars.githubusercontent.com/u/86141593?v=4&s=117" width="117">](https://github.com/Ozodbek1405) |[<img alt="leftdevel" src="https://avatars.githubusercontent.com/u/843337?v=4&s=117" width="117">](https://github.com/leftdevel) |
|
||||
[<img alt="mperrando" src="https://avatars.githubusercontent.com/u/525572?v=4&s=117" width="117">](https://github.com/mperrando) |[<img alt="onhate" src="https://avatars.githubusercontent.com/u/980905?v=4&s=117" width="117">](https://github.com/onhate) |[<img alt="marc-mabe" src="https://avatars.githubusercontent.com/u/302689?v=4&s=117" width="117">](https://github.com/marc-mabe) |[<img alt="ParsaArvanehPA" src="https://avatars.githubusercontent.com/u/62149413?v=4&s=117" width="117">](https://github.com/ParsaArvanehPA) |[<img alt="cryptic022" src="https://avatars.githubusercontent.com/u/18145703?v=4&s=117" width="117">](https://github.com/cryptic022) |[<img alt="Ozodbek1405" src="https://avatars.githubusercontent.com/u/86141593?v=4&s=117" width="117">](https://github.com/Ozodbek1405) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[onhate](https://github.com/onhate) |[marc-mabe](https://github.com/marc-mabe) |[Cruaier](https://github.com/Cruaier) |[cryptic022](https://github.com/cryptic022) |[Ozodbek1405](https://github.com/Ozodbek1405) |[leftdevel](https://github.com/leftdevel) |
|
||||
[mperrando](https://github.com/mperrando) |[onhate](https://github.com/onhate) |[marc-mabe](https://github.com/marc-mabe) |[ParsaArvanehPA](https://github.com/ParsaArvanehPA) |[cryptic022](https://github.com/cryptic022) |[Ozodbek1405](https://github.com/Ozodbek1405) |
|
||||
|
||||
[<img alt="nil1511" src="https://avatars.githubusercontent.com/u/2058170?v=4&s=117" width="117">](https://github.com/nil1511) |[<img alt="coreprocess" src="https://avatars.githubusercontent.com/u/1226918?v=4&s=117" width="117">](https://github.com/coreprocess) |[<img alt="nicojones" src="https://avatars.githubusercontent.com/u/6078915?v=4&s=117" width="117">](https://github.com/nicojones) |[<img alt="trungcva10a6tn" src="https://avatars.githubusercontent.com/u/18293783?v=4&s=117" width="117">](https://github.com/trungcva10a6tn) |[<img alt="naveed-ahmad" src="https://avatars.githubusercontent.com/u/701567?v=4&s=117" width="117">](https://github.com/naveed-ahmad) |[<img alt="pleasespammelater" src="https://avatars.githubusercontent.com/u/11870394?v=4&s=117" width="117">](https://github.com/pleasespammelater) |
|
||||
[<img alt="leftdevel" src="https://avatars.githubusercontent.com/u/843337?v=4&s=117" width="117">](https://github.com/leftdevel) |[<img alt="nil1511" src="https://avatars.githubusercontent.com/u/2058170?v=4&s=117" width="117">](https://github.com/nil1511) |[<img alt="coreprocess" src="https://avatars.githubusercontent.com/u/1226918?v=4&s=117" width="117">](https://github.com/coreprocess) |[<img alt="nicojones" src="https://avatars.githubusercontent.com/u/6078915?v=4&s=117" width="117">](https://github.com/nicojones) |[<img alt="trungcva10a6tn" src="https://avatars.githubusercontent.com/u/18293783?v=4&s=117" width="117">](https://github.com/trungcva10a6tn) |[<img alt="naveed-ahmad" src="https://avatars.githubusercontent.com/u/701567?v=4&s=117" width="117">](https://github.com/naveed-ahmad) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[nil1511](https://github.com/nil1511) |[coreprocess](https://github.com/coreprocess) |[nicojones](https://github.com/nicojones) |[trungcva10a6tn](https://github.com/trungcva10a6tn) |[naveed-ahmad](https://github.com/naveed-ahmad) |[pleasespammelater](https://github.com/pleasespammelater) |
|
||||
[leftdevel](https://github.com/leftdevel) |[nil1511](https://github.com/nil1511) |[coreprocess](https://github.com/coreprocess) |[nicojones](https://github.com/nicojones) |[trungcva10a6tn](https://github.com/trungcva10a6tn) |[naveed-ahmad](https://github.com/naveed-ahmad) |
|
||||
|
||||
[<img alt="marton-laszlo-attila" src="https://avatars.githubusercontent.com/u/73295321?v=4&s=117" width="117">](https://github.com/marton-laszlo-attila) |[<img alt="navruzm" src="https://avatars.githubusercontent.com/u/168341?v=4&s=117" width="117">](https://github.com/navruzm) |[<img alt="mogzol" src="https://avatars.githubusercontent.com/u/11789801?v=4&s=117" width="117">](https://github.com/mogzol) |[<img alt="shahimclt" src="https://avatars.githubusercontent.com/u/8318002?v=4&s=117" width="117">](https://github.com/shahimclt) |[<img alt="mnafees" src="https://avatars.githubusercontent.com/u/1763885?v=4&s=117" width="117">](https://github.com/mnafees) |[<img alt="boudra" src="https://avatars.githubusercontent.com/u/711886?v=4&s=117" width="117">](https://github.com/boudra) |
|
||||
[<img alt="pleasespammelater" src="https://avatars.githubusercontent.com/u/11870394?v=4&s=117" width="117">](https://github.com/pleasespammelater) |[<img alt="marton-laszlo-attila" src="https://avatars.githubusercontent.com/u/73295321?v=4&s=117" width="117">](https://github.com/marton-laszlo-attila) |[<img alt="navruzm" src="https://avatars.githubusercontent.com/u/168341?v=4&s=117" width="117">](https://github.com/navruzm) |[<img alt="mogzol" src="https://avatars.githubusercontent.com/u/11789801?v=4&s=117" width="117">](https://github.com/mogzol) |[<img alt="shahimclt" src="https://avatars.githubusercontent.com/u/8318002?v=4&s=117" width="117">](https://github.com/shahimclt) |[<img alt="mnafees" src="https://avatars.githubusercontent.com/u/1763885?v=4&s=117" width="117">](https://github.com/mnafees) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[marton-laszlo-attila](https://github.com/marton-laszlo-attila) |[navruzm](https://github.com/navruzm) |[mogzol](https://github.com/mogzol) |[shahimclt](https://github.com/shahimclt) |[mnafees](https://github.com/mnafees) |[boudra](https://github.com/boudra) |
|
||||
[pleasespammelater](https://github.com/pleasespammelater) |[marton-laszlo-attila](https://github.com/marton-laszlo-attila) |[navruzm](https://github.com/navruzm) |[mogzol](https://github.com/mogzol) |[shahimclt](https://github.com/shahimclt) |[mnafees](https://github.com/mnafees) |
|
||||
|
||||
[<img alt="achmiral" src="https://avatars.githubusercontent.com/u/10906059?v=4&s=117" width="117">](https://github.com/achmiral) |[<img alt="ken-kuro" src="https://avatars.githubusercontent.com/u/47441476?v=4&s=117" width="117">](https://github.com/ken-kuro) |[<img alt="mosi-kha" src="https://avatars.githubusercontent.com/u/35611016?v=4&s=117" width="117">](https://github.com/mosi-kha) |[<img alt="maddy-jo" src="https://avatars.githubusercontent.com/u/3241493?v=4&s=117" width="117">](https://github.com/maddy-jo) |[<img alt="mdxiaohu" src="https://avatars.githubusercontent.com/u/42248614?v=4&s=117" width="117">](https://github.com/mdxiaohu) |[<img alt="magumbo" src="https://avatars.githubusercontent.com/u/6683765?v=4&s=117" width="117">](https://github.com/magumbo) |
|
||||
[<img alt="boudra" src="https://avatars.githubusercontent.com/u/711886?v=4&s=117" width="117">](https://github.com/boudra) |[<img alt="achmiral" src="https://avatars.githubusercontent.com/u/10906059?v=4&s=117" width="117">](https://github.com/achmiral) |[<img alt="mosi-kha" src="https://avatars.githubusercontent.com/u/35611016?v=4&s=117" width="117">](https://github.com/mosi-kha) |[<img alt="maddy-jo" src="https://avatars.githubusercontent.com/u/3241493?v=4&s=117" width="117">](https://github.com/maddy-jo) |[<img alt="mdxiaohu" src="https://avatars.githubusercontent.com/u/42248614?v=4&s=117" width="117">](https://github.com/mdxiaohu) |[<img alt="magumbo" src="https://avatars.githubusercontent.com/u/6683765?v=4&s=117" width="117">](https://github.com/magumbo) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[achmiral](https://github.com/achmiral) |[ken-kuro](https://github.com/ken-kuro) |[mosi-kha](https://github.com/mosi-kha) |[maddy-jo](https://github.com/maddy-jo) |[mdxiaohu](https://github.com/mdxiaohu) |[magumbo](https://github.com/magumbo) |
|
||||
[boudra](https://github.com/boudra) |[achmiral](https://github.com/achmiral) |[mosi-kha](https://github.com/mosi-kha) |[maddy-jo](https://github.com/maddy-jo) |[mdxiaohu](https://github.com/mdxiaohu) |[magumbo](https://github.com/magumbo) |
|
||||
|
||||
[<img alt="jx-zyf" src="https://avatars.githubusercontent.com/u/26456842?v=4&s=117" width="117">](https://github.com/jx-zyf) |[<img alt="kode-ninja" src="https://avatars.githubusercontent.com/u/7857611?v=4&s=117" width="117">](https://github.com/kode-ninja) |[<img alt="sontixyou" src="https://avatars.githubusercontent.com/u/19817196?v=4&s=117" width="117">](https://github.com/sontixyou) |[<img alt="jur-ng" src="https://avatars.githubusercontent.com/u/111122756?v=4&s=117" width="117">](https://github.com/jur-ng) |[<img alt="johnmanjiro13" src="https://avatars.githubusercontent.com/u/28798279?v=4&s=117" width="117">](https://github.com/johnmanjiro13) |[<img alt="jyoungblood" src="https://avatars.githubusercontent.com/u/56104?v=4&s=117" width="117">](https://github.com/jyoungblood) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
|
|
@ -331,9 +331,9 @@ Use Uppy in your project? [Let us know](https://github.com/transloadit/uppy/issu
|
|||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[green-mike](https://github.com/green-mike) |[gaelicwinter](https://github.com/gaelicwinter) |[frederikhors](https://github.com/frederikhors) |[franckl](https://github.com/franckl) |[fingul](https://github.com/fingul) |[elliotsayes](https://github.com/elliotsayes) |
|
||||
|
||||
[<img alt="xhocquet" src="https://avatars.githubusercontent.com/u/8116516?v=4&s=117" width="117">](https://github.com/xhocquet) |[<img alt="JimmyLv" src="https://avatars.githubusercontent.com/u/4997466?v=4&s=117" width="117">](https://github.com/JimmyLv) |[<img alt="zanzlender" src="https://avatars.githubusercontent.com/u/44570474?v=4&s=117" width="117">](https://github.com/zanzlender) |[<img alt="olitomas" src="https://avatars.githubusercontent.com/u/6918659?v=4&s=117" width="117">](https://github.com/olitomas) |[<img alt="yoann-hellopret" src="https://avatars.githubusercontent.com/u/46525558?v=4&s=117" width="117">](https://github.com/yoann-hellopret) |[<img alt="vedran555" src="https://avatars.githubusercontent.com/u/38395951?v=4&s=117" width="117">](https://github.com/vedran555) |
|
||||
[<img alt="YehudaKremer" src="https://avatars.githubusercontent.com/u/946652?v=4&s=117" width="117">](https://github.com/YehudaKremer) |[<img alt="JimmyLv" src="https://avatars.githubusercontent.com/u/4997466?v=4&s=117" width="117">](https://github.com/JimmyLv) |[<img alt="zanzlender" src="https://avatars.githubusercontent.com/u/44570474?v=4&s=117" width="117">](https://github.com/zanzlender) |[<img alt="olitomas" src="https://avatars.githubusercontent.com/u/6918659?v=4&s=117" width="117">](https://github.com/olitomas) |[<img alt="yoann-hellopret" src="https://avatars.githubusercontent.com/u/46525558?v=4&s=117" width="117">](https://github.com/yoann-hellopret) |[<img alt="vedran555" src="https://avatars.githubusercontent.com/u/38395951?v=4&s=117" width="117">](https://github.com/vedran555) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[xhocquet](https://github.com/xhocquet) |[JimmyLv](https://github.com/JimmyLv) |[zanzlender](https://github.com/zanzlender) |[olitomas](https://github.com/olitomas) |[yoann-hellopret](https://github.com/yoann-hellopret) |[vedran555](https://github.com/vedran555) |
|
||||
[YehudaKremer](https://github.com/YehudaKremer) |[JimmyLv](https://github.com/JimmyLv) |[zanzlender](https://github.com/zanzlender) |[olitomas](https://github.com/olitomas) |[yoann-hellopret](https://github.com/yoann-hellopret) |[vedran555](https://github.com/vedran555) |
|
||||
|
||||
[<img alt="tusharjkhunt" src="https://avatars.githubusercontent.com/u/31904234?v=4&s=117" width="117">](https://github.com/tusharjkhunt) |[<img alt="thanhthot" src="https://avatars.githubusercontent.com/u/50633205?v=4&s=117" width="117">](https://github.com/thanhthot) |[<img alt="stduhpf" src="https://avatars.githubusercontent.com/u/28208228?v=4&s=117" width="117">](https://github.com/stduhpf) |[<img alt="slawexxx44" src="https://avatars.githubusercontent.com/u/11180644?v=4&s=117" width="117">](https://github.com/slawexxx44) |[<img alt="rtaieb" src="https://avatars.githubusercontent.com/u/35224301?v=4&s=117" width="117">](https://github.com/rtaieb) |[<img alt="rmoura-92" src="https://avatars.githubusercontent.com/u/419044?v=4&s=117" width="117">](https://github.com/rmoura-92) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
|
|
@ -343,9 +343,9 @@ Use Uppy in your project? [Let us know](https://github.com/transloadit/uppy/issu
|
|||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[rlebosse](https://github.com/rlebosse) |[rhymes](https://github.com/rhymes) |[luntta](https://github.com/luntta) |[phil714](https://github.com/phil714) |[ordago](https://github.com/ordago) |[odselsevier](https://github.com/odselsevier) |
|
||||
|
||||
[<img alt="ninesalt" src="https://avatars.githubusercontent.com/u/7952255?v=4&s=117" width="117">](https://github.com/ninesalt) |[<img alt="willycamargo" src="https://avatars.githubusercontent.com/u/5041887?v=4&s=117" width="117">](https://github.com/willycamargo) |[<img alt="weston-sankey-mark43" src="https://avatars.githubusercontent.com/u/97678695?v=4&s=117" width="117">](https://github.com/weston-sankey-mark43) |[<img alt="dwnste" src="https://avatars.githubusercontent.com/u/17119722?v=4&s=117" width="117">](https://github.com/dwnste) |[<img alt="nagyv" src="https://avatars.githubusercontent.com/u/126671?v=4&s=117" width="117">](https://github.com/nagyv) |[<img alt="stiig" src="https://avatars.githubusercontent.com/u/8639922?v=4&s=117" width="117">](https://github.com/stiig) |
|
||||
[<img alt="ninesalt" src="https://avatars.githubusercontent.com/u/7952255?v=4&s=117" width="117">](https://github.com/ninesalt) |[<img alt="xhocquet" src="https://avatars.githubusercontent.com/u/8116516?v=4&s=117" width="117">](https://github.com/xhocquet) |[<img alt="willycamargo" src="https://avatars.githubusercontent.com/u/5041887?v=4&s=117" width="117">](https://github.com/willycamargo) |[<img alt="weston-sankey-mark43" src="https://avatars.githubusercontent.com/u/97678695?v=4&s=117" width="117">](https://github.com/weston-sankey-mark43) |[<img alt="dwnste" src="https://avatars.githubusercontent.com/u/17119722?v=4&s=117" width="117">](https://github.com/dwnste) |[<img alt="nagyv" src="https://avatars.githubusercontent.com/u/126671?v=4&s=117" width="117">](https://github.com/nagyv) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[ninesalt](https://github.com/ninesalt) |[willycamargo](https://github.com/willycamargo) |[weston-sankey-mark43](https://github.com/weston-sankey-mark43) |[dwnste](https://github.com/dwnste) |[nagyv](https://github.com/nagyv) |[stiig](https://github.com/stiig) |
|
||||
[ninesalt](https://github.com/ninesalt) |[xhocquet](https://github.com/xhocquet) |[willycamargo](https://github.com/willycamargo) |[weston-sankey-mark43](https://github.com/weston-sankey-mark43) |[dwnste](https://github.com/dwnste) |[nagyv](https://github.com/nagyv) |
|
||||
|
||||
[<img alt="valentinoli" src="https://avatars.githubusercontent.com/u/23453691?v=4&s=117" width="117">](https://github.com/valentinoli) |[<img alt="vially" src="https://avatars.githubusercontent.com/u/433598?v=4&s=117" width="117">](https://github.com/vially) |[<img alt="trivikr" src="https://avatars.githubusercontent.com/u/16024985?v=4&s=117" width="117">](https://github.com/trivikr) |[<img alt="top-master" src="https://avatars.githubusercontent.com/u/31405473?v=4&s=117" width="117">](https://github.com/top-master) |[<img alt="tvaliasek" src="https://avatars.githubusercontent.com/u/8644946?v=4&s=117" width="117">](https://github.com/tvaliasek) |[<img alt="tomekp" src="https://avatars.githubusercontent.com/u/1856393?v=4&s=117" width="117">](https://github.com/tomekp) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
|
|
@ -363,105 +363,105 @@ Use Uppy in your project? [Let us know](https://github.com/transloadit/uppy/issu
|
|||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[canvasbh](https://github.com/canvasbh) |[c0b41](https://github.com/c0b41) |[avalla](https://github.com/avalla) |[arggh](https://github.com/arggh) |[alfatv](https://github.com/alfatv) |[agreene-coursera](https://github.com/agreene-coursera) |
|
||||
|
||||
[<img alt="aduh95-test-account" src="https://avatars.githubusercontent.com/u/93441190?v=4&s=117" width="117">](https://github.com/aduh95-test-account) |[<img alt="sartoshi-foot-dao" src="https://avatars.githubusercontent.com/u/99770068?v=4&s=117" width="117">](https://github.com/sartoshi-foot-dao) |[<img alt="zackbloom" src="https://avatars.githubusercontent.com/u/55347?v=4&s=117" width="117">](https://github.com/zackbloom) |[<img alt="zlawson-ut" src="https://avatars.githubusercontent.com/u/7375444?v=4&s=117" width="117">](https://github.com/zlawson-ut) |[<img alt="zachconner" src="https://avatars.githubusercontent.com/u/11339326?v=4&s=117" width="117">](https://github.com/zachconner) |[<img alt="YehudaKremer" src="https://avatars.githubusercontent.com/u/946652?v=4&s=117" width="117">](https://github.com/YehudaKremer) |
|
||||
[<img alt="aduh95-test-account" src="https://avatars.githubusercontent.com/u/93441190?v=4&s=117" width="117">](https://github.com/aduh95-test-account) |[<img alt="sartoshi-foot-dao" src="https://avatars.githubusercontent.com/u/99770068?v=4&s=117" width="117">](https://github.com/sartoshi-foot-dao) |[<img alt="zackbloom" src="https://avatars.githubusercontent.com/u/55347?v=4&s=117" width="117">](https://github.com/zackbloom) |[<img alt="zlawson-ut" src="https://avatars.githubusercontent.com/u/7375444?v=4&s=117" width="117">](https://github.com/zlawson-ut) |[<img alt="zachconner" src="https://avatars.githubusercontent.com/u/11339326?v=4&s=117" width="117">](https://github.com/zachconner) |[<img alt="yafkari" src="https://avatars.githubusercontent.com/u/41365655?v=4&s=117" width="117">](https://github.com/yafkari) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[aduh95-test-account](https://github.com/aduh95-test-account) |[sartoshi-foot-dao](https://github.com/sartoshi-foot-dao) |[zackbloom](https://github.com/zackbloom) |[zlawson-ut](https://github.com/zlawson-ut) |[zachconner](https://github.com/zachconner) |[YehudaKremer](https://github.com/YehudaKremer) |
|
||||
[aduh95-test-account](https://github.com/aduh95-test-account) |[sartoshi-foot-dao](https://github.com/sartoshi-foot-dao) |[zackbloom](https://github.com/zackbloom) |[zlawson-ut](https://github.com/zlawson-ut) |[zachconner](https://github.com/zachconner) |[yafkari](https://github.com/yafkari) |
|
||||
|
||||
[<img alt="sercraig" src="https://avatars.githubusercontent.com/u/24261518?v=4&s=117" width="117">](https://github.com/sercraig) |[<img alt="ardeois" src="https://avatars.githubusercontent.com/u/1867939?v=4&s=117" width="117">](https://github.com/ardeois) |[<img alt="CommanderRoot" src="https://avatars.githubusercontent.com/u/4395417?v=4&s=117" width="117">](https://github.com/CommanderRoot) |[<img alt="czj" src="https://avatars.githubusercontent.com/u/14306?v=4&s=117" width="117">](https://github.com/czj) |[<img alt="cbush06" src="https://avatars.githubusercontent.com/u/15720146?v=4&s=117" width="117">](https://github.com/cbush06) |[<img alt="Aarbel" src="https://avatars.githubusercontent.com/u/25119847?v=4&s=117" width="117">](https://github.com/Aarbel) |
|
||||
[<img alt="Cruaier" src="https://avatars.githubusercontent.com/u/5204940?v=4&s=117" width="117">](https://github.com/Cruaier) |[<img alt="sercraig" src="https://avatars.githubusercontent.com/u/24261518?v=4&s=117" width="117">](https://github.com/sercraig) |[<img alt="ardeois" src="https://avatars.githubusercontent.com/u/1867939?v=4&s=117" width="117">](https://github.com/ardeois) |[<img alt="CommanderRoot" src="https://avatars.githubusercontent.com/u/4395417?v=4&s=117" width="117">](https://github.com/CommanderRoot) |[<img alt="czj" src="https://avatars.githubusercontent.com/u/14306?v=4&s=117" width="117">](https://github.com/czj) |[<img alt="cbush06" src="https://avatars.githubusercontent.com/u/15720146?v=4&s=117" width="117">](https://github.com/cbush06) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[sercraig](https://github.com/sercraig) |[ardeois](https://github.com/ardeois) |[CommanderRoot](https://github.com/CommanderRoot) |[czj](https://github.com/czj) |[cbush06](https://github.com/cbush06) |[Aarbel](https://github.com/Aarbel) |
|
||||
[Cruaier](https://github.com/Cruaier) |[sercraig](https://github.com/sercraig) |[ardeois](https://github.com/ardeois) |[CommanderRoot](https://github.com/CommanderRoot) |[czj](https://github.com/czj) |[cbush06](https://github.com/cbush06) |
|
||||
|
||||
[<img alt="cfra" src="https://avatars.githubusercontent.com/u/1347051?v=4&s=117" width="117">](https://github.com/cfra) |[<img alt="csprance" src="https://avatars.githubusercontent.com/u/7902617?v=4&s=117" width="117">](https://github.com/csprance) |[<img alt="prattcmp" src="https://avatars.githubusercontent.com/u/1497950?v=4&s=117" width="117">](https://github.com/prattcmp) |[<img alt="subvertallchris" src="https://avatars.githubusercontent.com/u/4097271?v=4&s=117" width="117">](https://github.com/subvertallchris) |[<img alt="charlybillaud" src="https://avatars.githubusercontent.com/u/31970410?v=4&s=117" width="117">](https://github.com/charlybillaud) |[<img alt="Cretezy" src="https://avatars.githubusercontent.com/u/2672503?v=4&s=117" width="117">](https://github.com/Cretezy) |
|
||||
[<img alt="Aarbel" src="https://avatars.githubusercontent.com/u/25119847?v=4&s=117" width="117">](https://github.com/Aarbel) |[<img alt="cfra" src="https://avatars.githubusercontent.com/u/1347051?v=4&s=117" width="117">](https://github.com/cfra) |[<img alt="csprance" src="https://avatars.githubusercontent.com/u/7902617?v=4&s=117" width="117">](https://github.com/csprance) |[<img alt="prattcmp" src="https://avatars.githubusercontent.com/u/1497950?v=4&s=117" width="117">](https://github.com/prattcmp) |[<img alt="subvertallchris" src="https://avatars.githubusercontent.com/u/4097271?v=4&s=117" width="117">](https://github.com/subvertallchris) |[<img alt="charlybillaud" src="https://avatars.githubusercontent.com/u/31970410?v=4&s=117" width="117">](https://github.com/charlybillaud) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[cfra](https://github.com/cfra) |[csprance](https://github.com/csprance) |[prattcmp](https://github.com/prattcmp) |[subvertallchris](https://github.com/subvertallchris) |[charlybillaud](https://github.com/charlybillaud) |[Cretezy](https://github.com/Cretezy) |
|
||||
[Aarbel](https://github.com/Aarbel) |[cfra](https://github.com/cfra) |[csprance](https://github.com/csprance) |[prattcmp](https://github.com/prattcmp) |[subvertallchris](https://github.com/subvertallchris) |[charlybillaud](https://github.com/charlybillaud) |
|
||||
|
||||
[<img alt="chao" src="https://avatars.githubusercontent.com/u/55872?v=4&s=117" width="117">](https://github.com/chao) |[<img alt="cellvinchung" src="https://avatars.githubusercontent.com/u/5347394?v=4&s=117" width="117">](https://github.com/cellvinchung) |[<img alt="cartfisk" src="https://avatars.githubusercontent.com/u/8764375?v=4&s=117" width="117">](https://github.com/cartfisk) |[<img alt="cyu" src="https://avatars.githubusercontent.com/u/2431?v=4&s=117" width="117">](https://github.com/cyu) |[<img alt="radarhere" src="https://avatars.githubusercontent.com/u/3112309?v=4&s=117" width="117">](https://github.com/radarhere) |[<img alt="kergekacsa" src="https://avatars.githubusercontent.com/u/16637320?v=4&s=117" width="117">](https://github.com/kergekacsa) |
|
||||
[<img alt="Cretezy" src="https://avatars.githubusercontent.com/u/2672503?v=4&s=117" width="117">](https://github.com/Cretezy) |[<img alt="chao" src="https://avatars.githubusercontent.com/u/55872?v=4&s=117" width="117">](https://github.com/chao) |[<img alt="cellvinchung" src="https://avatars.githubusercontent.com/u/5347394?v=4&s=117" width="117">](https://github.com/cellvinchung) |[<img alt="cartfisk" src="https://avatars.githubusercontent.com/u/8764375?v=4&s=117" width="117">](https://github.com/cartfisk) |[<img alt="cyu" src="https://avatars.githubusercontent.com/u/2431?v=4&s=117" width="117">](https://github.com/cyu) |[<img alt="radarhere" src="https://avatars.githubusercontent.com/u/3112309?v=4&s=117" width="117">](https://github.com/radarhere) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[chao](https://github.com/chao) |[cellvinchung](https://github.com/cellvinchung) |[cartfisk](https://github.com/cartfisk) |[cyu](https://github.com/cyu) |[radarhere](https://github.com/radarhere) |[kergekacsa](https://github.com/kergekacsa) |
|
||||
[Cretezy](https://github.com/Cretezy) |[chao](https://github.com/chao) |[cellvinchung](https://github.com/cellvinchung) |[cartfisk](https://github.com/cartfisk) |[cyu](https://github.com/cyu) |[radarhere](https://github.com/radarhere) |
|
||||
|
||||
[<img alt="eliOcs" src="https://avatars.githubusercontent.com/u/1283954?v=4&s=117" width="117">](https://github.com/eliOcs) |[<img alt="yoldar" src="https://avatars.githubusercontent.com/u/1597578?v=4&s=117" width="117">](https://github.com/yoldar) |[<img alt="efbautista" src="https://avatars.githubusercontent.com/u/35430671?v=4&s=117" width="117">](https://github.com/efbautista) |[<img alt="emuell" src="https://avatars.githubusercontent.com/u/11521600?v=4&s=117" width="117">](https://github.com/emuell) |[<img alt="EdgarSantiago93" src="https://avatars.githubusercontent.com/u/14806877?v=4&s=117" width="117">](https://github.com/EdgarSantiago93) |[<img alt="sweetro" src="https://avatars.githubusercontent.com/u/6228717?v=4&s=117" width="117">](https://github.com/sweetro) |
|
||||
[<img alt="kergekacsa" src="https://avatars.githubusercontent.com/u/16637320?v=4&s=117" width="117">](https://github.com/kergekacsa) |[<img alt="eliOcs" src="https://avatars.githubusercontent.com/u/1283954?v=4&s=117" width="117">](https://github.com/eliOcs) |[<img alt="yoldar" src="https://avatars.githubusercontent.com/u/1597578?v=4&s=117" width="117">](https://github.com/yoldar) |[<img alt="efbautista" src="https://avatars.githubusercontent.com/u/35430671?v=4&s=117" width="117">](https://github.com/efbautista) |[<img alt="emuell" src="https://avatars.githubusercontent.com/u/11521600?v=4&s=117" width="117">](https://github.com/emuell) |[<img alt="EdgarSantiago93" src="https://avatars.githubusercontent.com/u/14806877?v=4&s=117" width="117">](https://github.com/EdgarSantiago93) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[eliOcs](https://github.com/eliOcs) |[yoldar](https://github.com/yoldar) |[efbautista](https://github.com/efbautista) |[emuell](https://github.com/emuell) |[EdgarSantiago93](https://github.com/EdgarSantiago93) |[sweetro](https://github.com/sweetro) |
|
||||
[kergekacsa](https://github.com/kergekacsa) |[eliOcs](https://github.com/eliOcs) |[yoldar](https://github.com/yoldar) |[efbautista](https://github.com/efbautista) |[emuell](https://github.com/emuell) |[EdgarSantiago93](https://github.com/EdgarSantiago93) |
|
||||
|
||||
[<img alt="jeetiss" src="https://avatars.githubusercontent.com/u/6726016?v=4&s=117" width="117">](https://github.com/jeetiss) |[<img alt="DennisKofflard" src="https://avatars.githubusercontent.com/u/8669129?v=4&s=117" width="117">](https://github.com/DennisKofflard) |[<img alt="hoangsvit" src="https://avatars.githubusercontent.com/u/11882322?v=4&s=117" width="117">](https://github.com/hoangsvit) |[<img alt="davilima6" src="https://avatars.githubusercontent.com/u/422130?v=4&s=117" width="117">](https://github.com/davilima6) |[<img alt="akizor" src="https://avatars.githubusercontent.com/u/1052439?v=4&s=117" width="117">](https://github.com/akizor) |[<img alt="KaminskiDaniell" src="https://avatars.githubusercontent.com/u/27357868?v=4&s=117" width="117">](https://github.com/KaminskiDaniell) |
|
||||
[<img alt="sweetro" src="https://avatars.githubusercontent.com/u/6228717?v=4&s=117" width="117">](https://github.com/sweetro) |[<img alt="jeetiss" src="https://avatars.githubusercontent.com/u/6726016?v=4&s=117" width="117">](https://github.com/jeetiss) |[<img alt="DennisKofflard" src="https://avatars.githubusercontent.com/u/8669129?v=4&s=117" width="117">](https://github.com/DennisKofflard) |[<img alt="hoangsvit" src="https://avatars.githubusercontent.com/u/11882322?v=4&s=117" width="117">](https://github.com/hoangsvit) |[<img alt="davilima6" src="https://avatars.githubusercontent.com/u/422130?v=4&s=117" width="117">](https://github.com/davilima6) |[<img alt="akizor" src="https://avatars.githubusercontent.com/u/1052439?v=4&s=117" width="117">](https://github.com/akizor) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[jeetiss](https://github.com/jeetiss) |[DennisKofflard](https://github.com/DennisKofflard) |[hoangsvit](https://github.com/hoangsvit) |[davilima6](https://github.com/davilima6) |[akizor](https://github.com/akizor) |[KaminskiDaniell](https://github.com/KaminskiDaniell) |
|
||||
[sweetro](https://github.com/sweetro) |[jeetiss](https://github.com/jeetiss) |[DennisKofflard](https://github.com/DennisKofflard) |[hoangsvit](https://github.com/hoangsvit) |[davilima6](https://github.com/davilima6) |[akizor](https://github.com/akizor) |
|
||||
|
||||
[<img alt="Cantabar" src="https://avatars.githubusercontent.com/u/6812207?v=4&s=117" width="117">](https://github.com/Cantabar) |[<img alt="mrboomer" src="https://avatars.githubusercontent.com/u/5942912?v=4&s=117" width="117">](https://github.com/mrboomer) |[<img alt="danilat" src="https://avatars.githubusercontent.com/u/22763?v=4&s=117" width="117">](https://github.com/danilat) |[<img alt="danschalow" src="https://avatars.githubusercontent.com/u/3527437?v=4&s=117" width="117">](https://github.com/danschalow) |[<img alt="danmichaelo" src="https://avatars.githubusercontent.com/u/434495?v=4&s=117" width="117">](https://github.com/danmichaelo) |[<img alt="functino" src="https://avatars.githubusercontent.com/u/415498?v=4&s=117" width="117">](https://github.com/functino) |
|
||||
[<img alt="KaminskiDaniell" src="https://avatars.githubusercontent.com/u/27357868?v=4&s=117" width="117">](https://github.com/KaminskiDaniell) |[<img alt="Cantabar" src="https://avatars.githubusercontent.com/u/6812207?v=4&s=117" width="117">](https://github.com/Cantabar) |[<img alt="mrboomer" src="https://avatars.githubusercontent.com/u/5942912?v=4&s=117" width="117">](https://github.com/mrboomer) |[<img alt="danilat" src="https://avatars.githubusercontent.com/u/22763?v=4&s=117" width="117">](https://github.com/danilat) |[<img alt="danschalow" src="https://avatars.githubusercontent.com/u/3527437?v=4&s=117" width="117">](https://github.com/danschalow) |[<img alt="danmichaelo" src="https://avatars.githubusercontent.com/u/434495?v=4&s=117" width="117">](https://github.com/danmichaelo) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[Cantabar](https://github.com/Cantabar) |[mrboomer](https://github.com/mrboomer) |[danilat](https://github.com/danilat) |[danschalow](https://github.com/danschalow) |[danmichaelo](https://github.com/danmichaelo) |[functino](https://github.com/functino) |
|
||||
[KaminskiDaniell](https://github.com/KaminskiDaniell) |[Cantabar](https://github.com/Cantabar) |[mrboomer](https://github.com/mrboomer) |[danilat](https://github.com/danilat) |[danschalow](https://github.com/danschalow) |[danmichaelo](https://github.com/danmichaelo) |
|
||||
|
||||
[<img alt="amitport" src="https://avatars.githubusercontent.com/u/1131991?v=4&s=117" width="117">](https://github.com/amitport) |[<img alt="tekacs" src="https://avatars.githubusercontent.com/u/63247?v=4&s=117" width="117">](https://github.com/tekacs) |[<img alt="Dogfalo" src="https://avatars.githubusercontent.com/u/2775751?v=4&s=117" width="117">](https://github.com/Dogfalo) |[<img alt="aalepis" src="https://avatars.githubusercontent.com/u/35684834?v=4&s=117" width="117">](https://github.com/aalepis) |[<img alt="alexnj" src="https://avatars.githubusercontent.com/u/683500?v=4&s=117" width="117">](https://github.com/alexnj) |[<img alt="asmt3" src="https://avatars.githubusercontent.com/u/1777709?v=4&s=117" width="117">](https://github.com/asmt3) |
|
||||
[<img alt="functino" src="https://avatars.githubusercontent.com/u/415498?v=4&s=117" width="117">](https://github.com/functino) |[<img alt="amitport" src="https://avatars.githubusercontent.com/u/1131991?v=4&s=117" width="117">](https://github.com/amitport) |[<img alt="tekacs" src="https://avatars.githubusercontent.com/u/63247?v=4&s=117" width="117">](https://github.com/tekacs) |[<img alt="Dogfalo" src="https://avatars.githubusercontent.com/u/2775751?v=4&s=117" width="117">](https://github.com/Dogfalo) |[<img alt="aalepis" src="https://avatars.githubusercontent.com/u/35684834?v=4&s=117" width="117">](https://github.com/aalepis) |[<img alt="alexnj" src="https://avatars.githubusercontent.com/u/683500?v=4&s=117" width="117">](https://github.com/alexnj) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[amitport](https://github.com/amitport) |[tekacs](https://github.com/tekacs) |[Dogfalo](https://github.com/Dogfalo) |[aalepis](https://github.com/aalepis) |[alexnj](https://github.com/alexnj) |[asmt3](https://github.com/asmt3) |
|
||||
[functino](https://github.com/functino) |[amitport](https://github.com/amitport) |[tekacs](https://github.com/tekacs) |[Dogfalo](https://github.com/Dogfalo) |[aalepis](https://github.com/aalepis) |[alexnj](https://github.com/alexnj) |
|
||||
|
||||
[<img alt="ahmadissa" src="https://avatars.githubusercontent.com/u/9936573?v=4&s=117" width="117">](https://github.com/ahmadissa) |[<img alt="adritasharma" src="https://avatars.githubusercontent.com/u/29271635?v=4&s=117" width="117">](https://github.com/adritasharma) |[<img alt="Adrrei" src="https://avatars.githubusercontent.com/u/22191685?v=4&s=117" width="117">](https://github.com/Adrrei) |[<img alt="adityapatadia" src="https://avatars.githubusercontent.com/u/1086617?v=4&s=117" width="117">](https://github.com/adityapatadia) |[<img alt="adamvigneault" src="https://avatars.githubusercontent.com/u/18236120?v=4&s=117" width="117">](https://github.com/adamvigneault) |[<img alt="ajh-sr" src="https://avatars.githubusercontent.com/u/71472057?v=4&s=117" width="117">](https://github.com/ajh-sr) |
|
||||
[<img alt="asmt3" src="https://avatars.githubusercontent.com/u/1777709?v=4&s=117" width="117">](https://github.com/asmt3) |[<img alt="ahmadissa" src="https://avatars.githubusercontent.com/u/9936573?v=4&s=117" width="117">](https://github.com/ahmadissa) |[<img alt="adritasharma" src="https://avatars.githubusercontent.com/u/29271635?v=4&s=117" width="117">](https://github.com/adritasharma) |[<img alt="Adrrei" src="https://avatars.githubusercontent.com/u/22191685?v=4&s=117" width="117">](https://github.com/Adrrei) |[<img alt="adityapatadia" src="https://avatars.githubusercontent.com/u/1086617?v=4&s=117" width="117">](https://github.com/adityapatadia) |[<img alt="adamvigneault" src="https://avatars.githubusercontent.com/u/18236120?v=4&s=117" width="117">](https://github.com/adamvigneault) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[ahmadissa](https://github.com/ahmadissa) |[adritasharma](https://github.com/adritasharma) |[Adrrei](https://github.com/Adrrei) |[adityapatadia](https://github.com/adityapatadia) |[adamvigneault](https://github.com/adamvigneault) |[ajh-sr](https://github.com/ajh-sr) |
|
||||
[asmt3](https://github.com/asmt3) |[ahmadissa](https://github.com/ahmadissa) |[adritasharma](https://github.com/adritasharma) |[Adrrei](https://github.com/Adrrei) |[adityapatadia](https://github.com/adityapatadia) |[adamvigneault](https://github.com/adamvigneault) |
|
||||
|
||||
[<img alt="adamdottv" src="https://avatars.githubusercontent.com/u/2363879?v=4&s=117" width="117">](https://github.com/adamdottv) |[<img alt="abannach" src="https://avatars.githubusercontent.com/u/43150303?v=4&s=117" width="117">](https://github.com/abannach) |[<img alt="superhawk610" src="https://avatars.githubusercontent.com/u/18172185?v=4&s=117" width="117">](https://github.com/superhawk610) |[<img alt="ajschmidt8" src="https://avatars.githubusercontent.com/u/7400326?v=4&s=117" width="117">](https://github.com/ajschmidt8) |[<img alt="bryanjswift" src="https://avatars.githubusercontent.com/u/9911?v=4&s=117" width="117">](https://github.com/bryanjswift) |[<img alt="bedgerotto" src="https://avatars.githubusercontent.com/u/4459657?v=4&s=117" width="117">](https://github.com/bedgerotto) |
|
||||
[<img alt="ajh-sr" src="https://avatars.githubusercontent.com/u/71472057?v=4&s=117" width="117">](https://github.com/ajh-sr) |[<img alt="adamdottv" src="https://avatars.githubusercontent.com/u/2363879?v=4&s=117" width="117">](https://github.com/adamdottv) |[<img alt="abannach" src="https://avatars.githubusercontent.com/u/43150303?v=4&s=117" width="117">](https://github.com/abannach) |[<img alt="superhawk610" src="https://avatars.githubusercontent.com/u/18172185?v=4&s=117" width="117">](https://github.com/superhawk610) |[<img alt="ajschmidt8" src="https://avatars.githubusercontent.com/u/7400326?v=4&s=117" width="117">](https://github.com/ajschmidt8) |[<img alt="bryanjswift" src="https://avatars.githubusercontent.com/u/9911?v=4&s=117" width="117">](https://github.com/bryanjswift) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[adamdottv](https://github.com/adamdottv) |[abannach](https://github.com/abannach) |[superhawk610](https://github.com/superhawk610) |[ajschmidt8](https://github.com/ajschmidt8) |[bryanjswift](https://github.com/bryanjswift) |[bedgerotto](https://github.com/bedgerotto) |
|
||||
[ajh-sr](https://github.com/ajh-sr) |[adamdottv](https://github.com/adamdottv) |[abannach](https://github.com/abannach) |[superhawk610](https://github.com/superhawk610) |[ajschmidt8](https://github.com/ajschmidt8) |[bryanjswift](https://github.com/bryanjswift) |
|
||||
|
||||
[<img alt="wbaaron" src="https://avatars.githubusercontent.com/u/1048988?v=4&s=117" width="117">](https://github.com/wbaaron) |[<img alt="Quorafind" src="https://avatars.githubusercontent.com/u/13215013?v=4&s=117" width="117">](https://github.com/Quorafind) |[<img alt="bducharme" src="https://avatars.githubusercontent.com/u/4173569?v=4&s=117" width="117">](https://github.com/bducharme) |[<img alt="azizk" src="https://avatars.githubusercontent.com/u/37282?v=4&s=117" width="117">](https://github.com/azizk) |[<img alt="azeemba" src="https://avatars.githubusercontent.com/u/2160795?v=4&s=117" width="117">](https://github.com/azeemba) |[<img alt="ayhankesicioglu" src="https://avatars.githubusercontent.com/u/36304312?v=4&s=117" width="117">](https://github.com/ayhankesicioglu) |
|
||||
[<img alt="bedgerotto" src="https://avatars.githubusercontent.com/u/4459657?v=4&s=117" width="117">](https://github.com/bedgerotto) |[<img alt="wbaaron" src="https://avatars.githubusercontent.com/u/1048988?v=4&s=117" width="117">](https://github.com/wbaaron) |[<img alt="Quorafind" src="https://avatars.githubusercontent.com/u/13215013?v=4&s=117" width="117">](https://github.com/Quorafind) |[<img alt="bducharme" src="https://avatars.githubusercontent.com/u/4173569?v=4&s=117" width="117">](https://github.com/bducharme) |[<img alt="azizk" src="https://avatars.githubusercontent.com/u/37282?v=4&s=117" width="117">](https://github.com/azizk) |[<img alt="azeemba" src="https://avatars.githubusercontent.com/u/2160795?v=4&s=117" width="117">](https://github.com/azeemba) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[wbaaron](https://github.com/wbaaron) |[Quorafind](https://github.com/Quorafind) |[bducharme](https://github.com/bducharme) |[azizk](https://github.com/azizk) |[azeemba](https://github.com/azeemba) |[ayhankesicioglu](https://github.com/ayhankesicioglu) |
|
||||
[bedgerotto](https://github.com/bedgerotto) |[wbaaron](https://github.com/wbaaron) |[Quorafind](https://github.com/Quorafind) |[bducharme](https://github.com/bducharme) |[azizk](https://github.com/azizk) |[azeemba](https://github.com/azeemba) |
|
||||
|
||||
[<img alt="atsawin" src="https://avatars.githubusercontent.com/u/666663?v=4&s=117" width="117">](https://github.com/atsawin) |[<img alt="ash-jc-allen" src="https://avatars.githubusercontent.com/u/39652331?v=4&s=117" width="117">](https://github.com/ash-jc-allen) |[<img alt="apuyou" src="https://avatars.githubusercontent.com/u/520053?v=4&s=117" width="117">](https://github.com/apuyou) |[<img alt="arthurdenner" src="https://avatars.githubusercontent.com/u/13774309?v=4&s=117" width="117">](https://github.com/arthurdenner) |[<img alt="Abourass" src="https://avatars.githubusercontent.com/u/39917231?v=4&s=117" width="117">](https://github.com/Abourass) |[<img alt="tyndria" src="https://avatars.githubusercontent.com/u/17138916?v=4&s=117" width="117">](https://github.com/tyndria) |
|
||||
[<img alt="ayhankesicioglu" src="https://avatars.githubusercontent.com/u/36304312?v=4&s=117" width="117">](https://github.com/ayhankesicioglu) |[<img alt="atsawin" src="https://avatars.githubusercontent.com/u/666663?v=4&s=117" width="117">](https://github.com/atsawin) |[<img alt="ash-jc-allen" src="https://avatars.githubusercontent.com/u/39652331?v=4&s=117" width="117">](https://github.com/ash-jc-allen) |[<img alt="apuyou" src="https://avatars.githubusercontent.com/u/520053?v=4&s=117" width="117">](https://github.com/apuyou) |[<img alt="arthurdenner" src="https://avatars.githubusercontent.com/u/13774309?v=4&s=117" width="117">](https://github.com/arthurdenner) |[<img alt="Abourass" src="https://avatars.githubusercontent.com/u/39917231?v=4&s=117" width="117">](https://github.com/Abourass) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[atsawin](https://github.com/atsawin) |[ash-jc-allen](https://github.com/ash-jc-allen) |[apuyou](https://github.com/apuyou) |[arthurdenner](https://github.com/arthurdenner) |[Abourass](https://github.com/Abourass) |[tyndria](https://github.com/tyndria) |
|
||||
[ayhankesicioglu](https://github.com/ayhankesicioglu) |[atsawin](https://github.com/atsawin) |[ash-jc-allen](https://github.com/ash-jc-allen) |[apuyou](https://github.com/apuyou) |[arthurdenner](https://github.com/arthurdenner) |[Abourass](https://github.com/Abourass) |
|
||||
|
||||
[<img alt="anthony0030" src="https://avatars.githubusercontent.com/u/13033263?v=4&s=117" width="117">](https://github.com/anthony0030) |[<img alt="andychongyz" src="https://avatars.githubusercontent.com/u/12697240?v=4&s=117" width="117">](https://github.com/andychongyz) |[<img alt="andrii-bodnar" src="https://avatars.githubusercontent.com/u/29282228?v=4&s=117" width="117">](https://github.com/andrii-bodnar) |[<img alt="superandrew213" src="https://avatars.githubusercontent.com/u/13059204?v=4&s=117" width="117">](https://github.com/superandrew213) |[<img alt="firesharkstudios" src="https://avatars.githubusercontent.com/u/17069637?v=4&s=117" width="117">](https://github.com/firesharkstudios) |[<img alt="kaspermeinema" src="https://avatars.githubusercontent.com/u/73821331?v=4&s=117" width="117">](https://github.com/kaspermeinema) |
|
||||
[<img alt="tyndria" src="https://avatars.githubusercontent.com/u/17138916?v=4&s=117" width="117">](https://github.com/tyndria) |[<img alt="anthony0030" src="https://avatars.githubusercontent.com/u/13033263?v=4&s=117" width="117">](https://github.com/anthony0030) |[<img alt="andychongyz" src="https://avatars.githubusercontent.com/u/12697240?v=4&s=117" width="117">](https://github.com/andychongyz) |[<img alt="andrii-bodnar" src="https://avatars.githubusercontent.com/u/29282228?v=4&s=117" width="117">](https://github.com/andrii-bodnar) |[<img alt="superandrew213" src="https://avatars.githubusercontent.com/u/13059204?v=4&s=117" width="117">](https://github.com/superandrew213) |[<img alt="firesharkstudios" src="https://avatars.githubusercontent.com/u/17069637?v=4&s=117" width="117">](https://github.com/firesharkstudios) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[anthony0030](https://github.com/anthony0030) |[andychongyz](https://github.com/andychongyz) |[andrii-bodnar](https://github.com/andrii-bodnar) |[superandrew213](https://github.com/superandrew213) |[firesharkstudios](https://github.com/firesharkstudios) |[kaspermeinema](https://github.com/kaspermeinema) |
|
||||
[tyndria](https://github.com/tyndria) |[anthony0030](https://github.com/anthony0030) |[andychongyz](https://github.com/andychongyz) |[andrii-bodnar](https://github.com/andrii-bodnar) |[superandrew213](https://github.com/superandrew213) |[firesharkstudios](https://github.com/firesharkstudios) |
|
||||
|
||||
[<img alt="tykarol" src="https://avatars.githubusercontent.com/u/9386320?v=4&s=117" width="117">](https://github.com/tykarol) |[<img alt="jvelten" src="https://avatars.githubusercontent.com/u/48118068?v=4&s=117" width="117">](https://github.com/jvelten) |[<img alt="mellow-fellow" src="https://avatars.githubusercontent.com/u/19280122?v=4&s=117" width="117">](https://github.com/mellow-fellow) |[<img alt="jmontoyaa" src="https://avatars.githubusercontent.com/u/158935?v=4&s=117" width="117">](https://github.com/jmontoyaa) |[<img alt="jcalonso" src="https://avatars.githubusercontent.com/u/664474?v=4&s=117" width="117">](https://github.com/jcalonso) |[<img alt="jbelej" src="https://avatars.githubusercontent.com/u/2229202?v=4&s=117" width="117">](https://github.com/jbelej) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[tykarol](https://github.com/tykarol) |[jvelten](https://github.com/jvelten) |[mellow-fellow](https://github.com/mellow-fellow) |[jmontoyaa](https://github.com/jmontoyaa) |[jcalonso](https://github.com/jcalonso) |[jbelej](https://github.com/jbelej) |
|
||||
|
||||
[<img alt="jszobody" src="https://avatars.githubusercontent.com/u/203749?v=4&s=117" width="117">](https://github.com/jszobody) |[<img alt="jorgeepc" src="https://avatars.githubusercontent.com/u/3879892?v=4&s=117" width="117">](https://github.com/jorgeepc) |[<img alt="jondewoo" src="https://avatars.githubusercontent.com/u/1108358?v=4&s=117" width="117">](https://github.com/jondewoo) |[<img alt="jonathanarbely" src="https://avatars.githubusercontent.com/u/18177203?v=4&s=117" width="117">](https://github.com/jonathanarbely) |[<img alt="jsanchez034" src="https://avatars.githubusercontent.com/u/761087?v=4&s=117" width="117">](https://github.com/jsanchez034) |[<img alt="Jokcy" src="https://avatars.githubusercontent.com/u/2088642?v=4&s=117" width="117">](https://github.com/Jokcy) |
|
||||
[<img alt="jbelej" src="https://avatars.githubusercontent.com/u/2229202?v=4&s=117" width="117">](https://github.com/jbelej) |[<img alt="jszobody" src="https://avatars.githubusercontent.com/u/203749?v=4&s=117" width="117">](https://github.com/jszobody) |[<img alt="jorgeepc" src="https://avatars.githubusercontent.com/u/3879892?v=4&s=117" width="117">](https://github.com/jorgeepc) |[<img alt="jondewoo" src="https://avatars.githubusercontent.com/u/1108358?v=4&s=117" width="117">](https://github.com/jondewoo) |[<img alt="jonathanarbely" src="https://avatars.githubusercontent.com/u/18177203?v=4&s=117" width="117">](https://github.com/jonathanarbely) |[<img alt="jsanchez034" src="https://avatars.githubusercontent.com/u/761087?v=4&s=117" width="117">](https://github.com/jsanchez034) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[jszobody](https://github.com/jszobody) |[jorgeepc](https://github.com/jorgeepc) |[jondewoo](https://github.com/jondewoo) |[jonathanarbely](https://github.com/jonathanarbely) |[jsanchez034](https://github.com/jsanchez034) |[Jokcy](https://github.com/Jokcy) |
|
||||
[jbelej](https://github.com/jbelej) |[jszobody](https://github.com/jszobody) |[jorgeepc](https://github.com/jorgeepc) |[jondewoo](https://github.com/jondewoo) |[jonathanarbely](https://github.com/jonathanarbely) |[jsanchez034](https://github.com/jsanchez034) |
|
||||
|
||||
[<img alt="chromacoma" src="https://avatars.githubusercontent.com/u/1535623?v=4&s=117" width="117">](https://github.com/chromacoma) |[<img alt="profsmallpine" src="https://avatars.githubusercontent.com/u/7328006?v=4&s=117" width="117">](https://github.com/profsmallpine) |[<img alt="IanVS" src="https://avatars.githubusercontent.com/u/4616705?v=4&s=117" width="117">](https://github.com/IanVS) |[<img alt="Lucklj521" src="https://avatars.githubusercontent.com/u/93632042?v=4&s=117" width="117">](https://github.com/Lucklj521) |[<img alt="lucax88x" src="https://avatars.githubusercontent.com/u/6294464?v=4&s=117" width="117">](https://github.com/lucax88x) |[<img alt="lucaperret" src="https://avatars.githubusercontent.com/u/1887122?v=4&s=117" width="117">](https://github.com/lucaperret) |
|
||||
[<img alt="Jokcy" src="https://avatars.githubusercontent.com/u/2088642?v=4&s=117" width="117">](https://github.com/Jokcy) |[<img alt="chromacoma" src="https://avatars.githubusercontent.com/u/1535623?v=4&s=117" width="117">](https://github.com/chromacoma) |[<img alt="profsmallpine" src="https://avatars.githubusercontent.com/u/7328006?v=4&s=117" width="117">](https://github.com/profsmallpine) |[<img alt="IanVS" src="https://avatars.githubusercontent.com/u/4616705?v=4&s=117" width="117">](https://github.com/IanVS) |[<img alt="Lucklj521" src="https://avatars.githubusercontent.com/u/93632042?v=4&s=117" width="117">](https://github.com/Lucklj521) |[<img alt="lucax88x" src="https://avatars.githubusercontent.com/u/6294464?v=4&s=117" width="117">](https://github.com/lucax88x) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[chromacoma](https://github.com/chromacoma) |[profsmallpine](https://github.com/profsmallpine) |[IanVS](https://github.com/IanVS) |[Lucklj521](https://github.com/Lucklj521) |[lucax88x](https://github.com/lucax88x) |[lucaperret](https://github.com/lucaperret) |
|
||||
[Jokcy](https://github.com/Jokcy) |[chromacoma](https://github.com/chromacoma) |[profsmallpine](https://github.com/profsmallpine) |[IanVS](https://github.com/IanVS) |[Lucklj521](https://github.com/Lucklj521) |[lucax88x](https://github.com/lucax88x) |
|
||||
|
||||
[<img alt="ombr" src="https://avatars.githubusercontent.com/u/857339?v=4&s=117" width="117">](https://github.com/ombr) |[<img alt="louim" src="https://avatars.githubusercontent.com/u/923718?v=4&s=117" width="117">](https://github.com/louim) |[<img alt="dolphinigle" src="https://avatars.githubusercontent.com/u/7020472?v=4&s=117" width="117">](https://github.com/dolphinigle) |[<img alt="leomelzer" src="https://avatars.githubusercontent.com/u/23313?v=4&s=117" width="117">](https://github.com/leomelzer) |[<img alt="leods92" src="https://avatars.githubusercontent.com/u/879395?v=4&s=117" width="117">](https://github.com/leods92) |[<img alt="galli-leo" src="https://avatars.githubusercontent.com/u/5339762?v=4&s=117" width="117">](https://github.com/galli-leo) |
|
||||
[<img alt="lucaperret" src="https://avatars.githubusercontent.com/u/1887122?v=4&s=117" width="117">](https://github.com/lucaperret) |[<img alt="ombr" src="https://avatars.githubusercontent.com/u/857339?v=4&s=117" width="117">](https://github.com/ombr) |[<img alt="louim" src="https://avatars.githubusercontent.com/u/923718?v=4&s=117" width="117">](https://github.com/louim) |[<img alt="dolphinigle" src="https://avatars.githubusercontent.com/u/7020472?v=4&s=117" width="117">](https://github.com/dolphinigle) |[<img alt="leomelzer" src="https://avatars.githubusercontent.com/u/23313?v=4&s=117" width="117">](https://github.com/leomelzer) |[<img alt="leods92" src="https://avatars.githubusercontent.com/u/879395?v=4&s=117" width="117">](https://github.com/leods92) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[ombr](https://github.com/ombr) |[louim](https://github.com/louim) |[dolphinigle](https://github.com/dolphinigle) |[leomelzer](https://github.com/leomelzer) |[leods92](https://github.com/leods92) |[galli-leo](https://github.com/galli-leo) |
|
||||
[lucaperret](https://github.com/lucaperret) |[ombr](https://github.com/ombr) |[louim](https://github.com/louim) |[dolphinigle](https://github.com/dolphinigle) |[leomelzer](https://github.com/leomelzer) |[leods92](https://github.com/leods92) |
|
||||
|
||||
[<img alt="dviry" src="https://avatars.githubusercontent.com/u/1230260?v=4&s=117" width="117">](https://github.com/dviry) |[<img alt="larowlan" src="https://avatars.githubusercontent.com/u/555254?v=4&s=117" width="117">](https://github.com/larowlan) |[<img alt="leaanthony" src="https://avatars.githubusercontent.com/u/1943904?v=4&s=117" width="117">](https://github.com/leaanthony) |[<img alt="hoangbits" src="https://avatars.githubusercontent.com/u/7990827?v=4&s=117" width="117">](https://github.com/hoangbits) |[<img alt="labohkip81" src="https://avatars.githubusercontent.com/u/36964869?v=4&s=117" width="117">](https://github.com/labohkip81) |[<img alt="kyleparisi" src="https://avatars.githubusercontent.com/u/1286753?v=4&s=117" width="117">](https://github.com/kyleparisi) |
|
||||
[<img alt="galli-leo" src="https://avatars.githubusercontent.com/u/5339762?v=4&s=117" width="117">](https://github.com/galli-leo) |[<img alt="dviry" src="https://avatars.githubusercontent.com/u/1230260?v=4&s=117" width="117">](https://github.com/dviry) |[<img alt="larowlan" src="https://avatars.githubusercontent.com/u/555254?v=4&s=117" width="117">](https://github.com/larowlan) |[<img alt="leaanthony" src="https://avatars.githubusercontent.com/u/1943904?v=4&s=117" width="117">](https://github.com/leaanthony) |[<img alt="hoangbits" src="https://avatars.githubusercontent.com/u/7990827?v=4&s=117" width="117">](https://github.com/hoangbits) |[<img alt="labohkip81" src="https://avatars.githubusercontent.com/u/36964869?v=4&s=117" width="117">](https://github.com/labohkip81) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[dviry](https://github.com/dviry) |[larowlan](https://github.com/larowlan) |[leaanthony](https://github.com/leaanthony) |[hoangbits](https://github.com/hoangbits) |[labohkip81](https://github.com/labohkip81) |[kyleparisi](https://github.com/kyleparisi) |
|
||||
[galli-leo](https://github.com/galli-leo) |[dviry](https://github.com/dviry) |[larowlan](https://github.com/larowlan) |[leaanthony](https://github.com/leaanthony) |[hoangbits](https://github.com/hoangbits) |[labohkip81](https://github.com/labohkip81) |
|
||||
|
||||
[<img alt="elkebab" src="https://avatars.githubusercontent.com/u/6313468?v=4&s=117" width="117">](https://github.com/elkebab) |[<img alt="kidonng" src="https://avatars.githubusercontent.com/u/44045911?v=4&s=117" width="117">](https://github.com/kidonng) |[<img alt="kevin-west-10x" src="https://avatars.githubusercontent.com/u/65194914?v=4&s=117" width="117">](https://github.com/kevin-west-10x) |[<img alt="huydod" src="https://avatars.githubusercontent.com/u/37580530?v=4&s=117" width="117">](https://github.com/huydod) |[<img alt="HussainAlkhalifah" src="https://avatars.githubusercontent.com/u/43642162?v=4&s=117" width="117">](https://github.com/HussainAlkhalifah) |[<img alt="HughbertD" src="https://avatars.githubusercontent.com/u/1580021?v=4&s=117" width="117">](https://github.com/HughbertD) |
|
||||
[<img alt="kyleparisi" src="https://avatars.githubusercontent.com/u/1286753?v=4&s=117" width="117">](https://github.com/kyleparisi) |[<img alt="elkebab" src="https://avatars.githubusercontent.com/u/6313468?v=4&s=117" width="117">](https://github.com/elkebab) |[<img alt="kidonng" src="https://avatars.githubusercontent.com/u/44045911?v=4&s=117" width="117">](https://github.com/kidonng) |[<img alt="kevin-west-10x" src="https://avatars.githubusercontent.com/u/65194914?v=4&s=117" width="117">](https://github.com/kevin-west-10x) |[<img alt="huydod" src="https://avatars.githubusercontent.com/u/37580530?v=4&s=117" width="117">](https://github.com/huydod) |[<img alt="HussainAlkhalifah" src="https://avatars.githubusercontent.com/u/43642162?v=4&s=117" width="117">](https://github.com/HussainAlkhalifah) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[elkebab](https://github.com/elkebab) |[kidonng](https://github.com/kidonng) |[kevin-west-10x](https://github.com/kevin-west-10x) |[huydod](https://github.com/huydod) |[HussainAlkhalifah](https://github.com/HussainAlkhalifah) |[HughbertD](https://github.com/HughbertD) |
|
||||
[kyleparisi](https://github.com/kyleparisi) |[elkebab](https://github.com/elkebab) |[kidonng](https://github.com/kidonng) |[kevin-west-10x](https://github.com/kevin-west-10x) |[huydod](https://github.com/huydod) |[HussainAlkhalifah](https://github.com/HussainAlkhalifah) |
|
||||
|
||||
[<img alt="hiromi2424" src="https://avatars.githubusercontent.com/u/191297?v=4&s=117" width="117">](https://github.com/hiromi2424) |[<img alt="giacomocerquone" src="https://avatars.githubusercontent.com/u/9303791?v=4&s=117" width="117">](https://github.com/giacomocerquone) |[<img alt="roenschg" src="https://avatars.githubusercontent.com/u/9590236?v=4&s=117" width="117">](https://github.com/roenschg) |[<img alt="gjungb" src="https://avatars.githubusercontent.com/u/3391068?v=4&s=117" width="117">](https://github.com/gjungb) |[<img alt="geoffappleford" src="https://avatars.githubusercontent.com/u/731678?v=4&s=117" width="117">](https://github.com/geoffappleford) |[<img alt="gabiganam" src="https://avatars.githubusercontent.com/u/28859646?v=4&s=117" width="117">](https://github.com/gabiganam) |
|
||||
[<img alt="HughbertD" src="https://avatars.githubusercontent.com/u/1580021?v=4&s=117" width="117">](https://github.com/HughbertD) |[<img alt="hiromi2424" src="https://avatars.githubusercontent.com/u/191297?v=4&s=117" width="117">](https://github.com/hiromi2424) |[<img alt="giacomocerquone" src="https://avatars.githubusercontent.com/u/9303791?v=4&s=117" width="117">](https://github.com/giacomocerquone) |[<img alt="roenschg" src="https://avatars.githubusercontent.com/u/9590236?v=4&s=117" width="117">](https://github.com/roenschg) |[<img alt="gjungb" src="https://avatars.githubusercontent.com/u/3391068?v=4&s=117" width="117">](https://github.com/gjungb) |[<img alt="geoffappleford" src="https://avatars.githubusercontent.com/u/731678?v=4&s=117" width="117">](https://github.com/geoffappleford) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[hiromi2424](https://github.com/hiromi2424) |[giacomocerquone](https://github.com/giacomocerquone) |[roenschg](https://github.com/roenschg) |[gjungb](https://github.com/gjungb) |[geoffappleford](https://github.com/geoffappleford) |[gabiganam](https://github.com/gabiganam) |
|
||||
[HughbertD](https://github.com/HughbertD) |[hiromi2424](https://github.com/hiromi2424) |[giacomocerquone](https://github.com/giacomocerquone) |[roenschg](https://github.com/roenschg) |[gjungb](https://github.com/gjungb) |[geoffappleford](https://github.com/geoffappleford) |
|
||||
|
||||
[<img alt="fuadscodes" src="https://avatars.githubusercontent.com/u/60370584?v=4&s=117" width="117">](https://github.com/fuadscodes) |[<img alt="dtrucs" src="https://avatars.githubusercontent.com/u/1926041?v=4&s=117" width="117">](https://github.com/dtrucs) |[<img alt="ferdiusa" src="https://avatars.githubusercontent.com/u/1997982?v=4&s=117" width="117">](https://github.com/ferdiusa) |[<img alt="fgallinari" src="https://avatars.githubusercontent.com/u/6473638?v=4&s=117" width="117">](https://github.com/fgallinari) |[<img alt="Gkleinereva" src="https://avatars.githubusercontent.com/u/23621633?v=4&s=117" width="117">](https://github.com/Gkleinereva) |[<img alt="epexa" src="https://avatars.githubusercontent.com/u/2198826?v=4&s=117" width="117">](https://github.com/epexa) |
|
||||
[<img alt="gabiganam" src="https://avatars.githubusercontent.com/u/28859646?v=4&s=117" width="117">](https://github.com/gabiganam) |[<img alt="fuadscodes" src="https://avatars.githubusercontent.com/u/60370584?v=4&s=117" width="117">](https://github.com/fuadscodes) |[<img alt="dtrucs" src="https://avatars.githubusercontent.com/u/1926041?v=4&s=117" width="117">](https://github.com/dtrucs) |[<img alt="ferdiusa" src="https://avatars.githubusercontent.com/u/1997982?v=4&s=117" width="117">](https://github.com/ferdiusa) |[<img alt="fgallinari" src="https://avatars.githubusercontent.com/u/6473638?v=4&s=117" width="117">](https://github.com/fgallinari) |[<img alt="Gkleinereva" src="https://avatars.githubusercontent.com/u/23621633?v=4&s=117" width="117">](https://github.com/Gkleinereva) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[fuadscodes](https://github.com/fuadscodes) |[dtrucs](https://github.com/dtrucs) |[ferdiusa](https://github.com/ferdiusa) |[fgallinari](https://github.com/fgallinari) |[Gkleinereva](https://github.com/Gkleinereva) |[epexa](https://github.com/epexa) |
|
||||
[gabiganam](https://github.com/gabiganam) |[fuadscodes](https://github.com/fuadscodes) |[dtrucs](https://github.com/dtrucs) |[ferdiusa](https://github.com/ferdiusa) |[fgallinari](https://github.com/fgallinari) |[Gkleinereva](https://github.com/Gkleinereva) |
|
||||
|
||||
[<img alt="EnricoSottile" src="https://avatars.githubusercontent.com/u/10349653?v=4&s=117" width="117">](https://github.com/EnricoSottile) |[<img alt="elliotdickison" src="https://avatars.githubusercontent.com/u/2523678?v=4&s=117" width="117">](https://github.com/elliotdickison) |[<img alt="theJoeBiz" src="https://avatars.githubusercontent.com/u/189589?v=4&s=117" width="117">](https://github.com/theJoeBiz) |[<img alt="Jmales" src="https://avatars.githubusercontent.com/u/22914881?v=4&s=117" width="117">](https://github.com/Jmales) |[<img alt="jessica-coursera" src="https://avatars.githubusercontent.com/u/35155465?v=4&s=117" width="117">](https://github.com/jessica-coursera) |[<img alt="vith" src="https://avatars.githubusercontent.com/u/3265539?v=4&s=117" width="117">](https://github.com/vith) |
|
||||
[<img alt="epexa" src="https://avatars.githubusercontent.com/u/2198826?v=4&s=117" width="117">](https://github.com/epexa) |[<img alt="EnricoSottile" src="https://avatars.githubusercontent.com/u/10349653?v=4&s=117" width="117">](https://github.com/EnricoSottile) |[<img alt="elliotdickison" src="https://avatars.githubusercontent.com/u/2523678?v=4&s=117" width="117">](https://github.com/elliotdickison) |[<img alt="theJoeBiz" src="https://avatars.githubusercontent.com/u/189589?v=4&s=117" width="117">](https://github.com/theJoeBiz) |[<img alt="Jmales" src="https://avatars.githubusercontent.com/u/22914881?v=4&s=117" width="117">](https://github.com/Jmales) |[<img alt="jessica-coursera" src="https://avatars.githubusercontent.com/u/35155465?v=4&s=117" width="117">](https://github.com/jessica-coursera) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[EnricoSottile](https://github.com/EnricoSottile) |[elliotdickison](https://github.com/elliotdickison) |[theJoeBiz](https://github.com/theJoeBiz) |[Jmales](https://github.com/Jmales) |[jessica-coursera](https://github.com/jessica-coursera) |[vith](https://github.com/vith) |
|
||||
[epexa](https://github.com/epexa) |[EnricoSottile](https://github.com/EnricoSottile) |[elliotdickison](https://github.com/elliotdickison) |[theJoeBiz](https://github.com/theJoeBiz) |[Jmales](https://github.com/Jmales) |[jessica-coursera](https://github.com/jessica-coursera) |
|
||||
|
||||
[<img alt="janwilts" src="https://avatars.githubusercontent.com/u/16721581?v=4&s=117" width="117">](https://github.com/janwilts) |[<img alt="janklimo" src="https://avatars.githubusercontent.com/u/7811733?v=4&s=117" width="117">](https://github.com/janklimo) |[<img alt="jamestiotio" src="https://avatars.githubusercontent.com/u/18364745?v=4&s=117" width="117">](https://github.com/jamestiotio) |[<img alt="jcjmcclean" src="https://avatars.githubusercontent.com/u/1822574?v=4&s=117" width="117">](https://github.com/jcjmcclean) |[<img alt="Jbithell" src="https://avatars.githubusercontent.com/u/8408967?v=4&s=117" width="117">](https://github.com/Jbithell) |[<img alt="JakubHaladej" src="https://avatars.githubusercontent.com/u/77832677?v=4&s=117" width="117">](https://github.com/JakubHaladej) |
|
||||
[<img alt="vith" src="https://avatars.githubusercontent.com/u/3265539?v=4&s=117" width="117">](https://github.com/vith) |[<img alt="janwilts" src="https://avatars.githubusercontent.com/u/16721581?v=4&s=117" width="117">](https://github.com/janwilts) |[<img alt="janklimo" src="https://avatars.githubusercontent.com/u/7811733?v=4&s=117" width="117">](https://github.com/janklimo) |[<img alt="jamestiotio" src="https://avatars.githubusercontent.com/u/18364745?v=4&s=117" width="117">](https://github.com/jamestiotio) |[<img alt="jcjmcclean" src="https://avatars.githubusercontent.com/u/1822574?v=4&s=117" width="117">](https://github.com/jcjmcclean) |[<img alt="Jbithell" src="https://avatars.githubusercontent.com/u/8408967?v=4&s=117" width="117">](https://github.com/Jbithell) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[janwilts](https://github.com/janwilts) |[janklimo](https://github.com/janklimo) |[jamestiotio](https://github.com/jamestiotio) |[jcjmcclean](https://github.com/jcjmcclean) |[Jbithell](https://github.com/Jbithell) |[JakubHaladej](https://github.com/JakubHaladej) |
|
||||
[vith](https://github.com/vith) |[janwilts](https://github.com/janwilts) |[janklimo](https://github.com/janklimo) |[jamestiotio](https://github.com/jamestiotio) |[jcjmcclean](https://github.com/jcjmcclean) |[Jbithell](https://github.com/Jbithell) |
|
||||
|
||||
[<img alt="jakemcallister" src="https://avatars.githubusercontent.com/u/1185699?v=4&s=117" width="117">](https://github.com/jakemcallister) |[<img alt="gaejabong" src="https://avatars.githubusercontent.com/u/978944?v=4&s=117" width="117">](https://github.com/gaejabong) |[<img alt="JacobMGEvans" src="https://avatars.githubusercontent.com/u/27247160?v=4&s=117" width="117">](https://github.com/JacobMGEvans) |[<img alt="mazoruss" src="https://avatars.githubusercontent.com/u/17625190?v=4&s=117" width="117">](https://github.com/mazoruss) |[<img alt="GreenJimmy" src="https://avatars.githubusercontent.com/u/39386?v=4&s=117" width="117">](https://github.com/GreenJimmy) |[<img alt="intenzive" src="https://avatars.githubusercontent.com/u/11055931?v=4&s=117" width="117">](https://github.com/intenzive) |
|
||||
[<img alt="JakubHaladej" src="https://avatars.githubusercontent.com/u/77832677?v=4&s=117" width="117">](https://github.com/JakubHaladej) |[<img alt="jakemcallister" src="https://avatars.githubusercontent.com/u/1185699?v=4&s=117" width="117">](https://github.com/jakemcallister) |[<img alt="gaejabong" src="https://avatars.githubusercontent.com/u/978944?v=4&s=117" width="117">](https://github.com/gaejabong) |[<img alt="JacobMGEvans" src="https://avatars.githubusercontent.com/u/27247160?v=4&s=117" width="117">](https://github.com/JacobMGEvans) |[<img alt="mazoruss" src="https://avatars.githubusercontent.com/u/17625190?v=4&s=117" width="117">](https://github.com/mazoruss) |[<img alt="GreenJimmy" src="https://avatars.githubusercontent.com/u/39386?v=4&s=117" width="117">](https://github.com/GreenJimmy) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[jakemcallister](https://github.com/jakemcallister) |[gaejabong](https://github.com/gaejabong) |[JacobMGEvans](https://github.com/JacobMGEvans) |[mazoruss](https://github.com/mazoruss) |[GreenJimmy](https://github.com/GreenJimmy) |[intenzive](https://github.com/intenzive) |
|
||||
[JakubHaladej](https://github.com/JakubHaladej) |[jakemcallister](https://github.com/jakemcallister) |[gaejabong](https://github.com/gaejabong) |[JacobMGEvans](https://github.com/JacobMGEvans) |[mazoruss](https://github.com/mazoruss) |[GreenJimmy](https://github.com/GreenJimmy) |
|
||||
|
||||
[<img alt="NaxYo" src="https://avatars.githubusercontent.com/u/1963876?v=4&s=117" width="117">](https://github.com/NaxYo) |[<img alt="ishendyweb" src="https://avatars.githubusercontent.com/u/10582418?v=4&s=117" width="117">](https://github.com/ishendyweb) |
|
||||
:---: |:---: |
|
||||
[NaxYo](https://github.com/NaxYo) |[ishendyweb](https://github.com/ishendyweb) |
|
||||
[<img alt="intenzive" src="https://avatars.githubusercontent.com/u/11055931?v=4&s=117" width="117">](https://github.com/intenzive) |[<img alt="NaxYo" src="https://avatars.githubusercontent.com/u/1963876?v=4&s=117" width="117">](https://github.com/NaxYo) |[<img alt="ishendyweb" src="https://avatars.githubusercontent.com/u/10582418?v=4&s=117" width="117">](https://github.com/ishendyweb) |
|
||||
:---: |:---: |:---: |
|
||||
[intenzive](https://github.com/intenzive) |[NaxYo](https://github.com/NaxYo) |[ishendyweb](https://github.com/ishendyweb) |
|
||||
|
||||
<!--/contributors-->
|
||||
|
||||
|
|
@ -472,4 +472,3 @@ We use Browserstack for manual testing <a href="https://www.browserstack.com" ta
|
|||
## License
|
||||
|
||||
[The MIT License](LICENSE).
|
||||
E).
|
||||
|
|
|
|||
3
docs/README.md
Normal file
3
docs/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Uppy documentation
|
||||
|
||||
To build the documentation, see <https://github.com/transloadit/uppy.io>.
|
||||
935
docs/companion.md
Normal file
935
docs/companion.md
Normal file
|
|
@ -0,0 +1,935 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Companion
|
||||
|
||||
Companion is an open source server application which **takes away the complexity
|
||||
of authentication and the cost of downloading files from remote sources**, such
|
||||
as Instagram, Google Drive, and others. Companion is a server-to-server
|
||||
orchestrator that streams files from a source to a destination, and files are
|
||||
never stored in Companion. Companion can run either as a standalone
|
||||
(self-hosted) application, [Transloadit-hosted](#hosted), or plugged in as an
|
||||
Express middleware into an existing application. The Uppy client requests remote
|
||||
files from Companion, which it will download and simultaneously upload to your
|
||||
[Tus server](/docs/tus), [AWS bucket](/docs/aws-s3), or any server that supports
|
||||
[PUT, POST or Multipart uploads](/docs/xhr-upload).
|
||||
|
||||
This means a user uploading a 5GB video from Google Drive from their phone isn’t
|
||||
eating into their data plans and you don’t have to worry about implementing
|
||||
OAuth.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
If you want to let users download files from [Box][], [Dropbox][], [Facebook][],
|
||||
[Google Drive][googledrive], [Instagram][], [OneDrive][], [Unsplash][], [Import
|
||||
from URL][url], or [Zoom][] — you need Companion.
|
||||
|
||||
Companion supports the same [uploaders](/docs/guides/choosing-uploader) as Uppy:
|
||||
[Tus](/docs/tus), [AWS S3](/docs/aws-s3), and [regular multipart](/docs/tus).
|
||||
But instead of manually setting a plugin, Uppy sends along a header with the
|
||||
uploader and Companion will use the same on the server. This means if you are
|
||||
using [Tus](/docs/tus) for your local uploads, you can send your remote uploads
|
||||
to the same Tus server (and likewise for your AWS S3 bucket).
|
||||
|
||||
:::note
|
||||
|
||||
Companion only deals with _remote_ files, _local_ files are still uploaded from
|
||||
the client with your upload plugin.
|
||||
|
||||
:::
|
||||
|
||||
## Hosted
|
||||
|
||||
Using [Transloadit][] services comes with a hosted version of Companion so you
|
||||
don’t have to worry about hosting your own server. Whether you are on a free or
|
||||
paid Transloadit [plan](https://transloadit.com/pricing/), you can use
|
||||
Companion. It’s not possible to rent a Companion server without a Transloadit
|
||||
plan.
|
||||
|
||||
[**Sign-up for a (free) plan**](https://transloadit.com/pricing/).
|
||||
|
||||
:::tip
|
||||
|
||||
Choosing Transloadit for your file services also comes with credentials for all
|
||||
remote providers. This means you don’t have to waste time going through the
|
||||
approval process of every app. You can still add your own credentials in the
|
||||
Transloadit admin page if you want.
|
||||
|
||||
:::
|
||||
|
||||
:::info
|
||||
|
||||
Downloading and uploading files through Companion doesn’t count towards your
|
||||
[monthly quota](https://transloadit.com/docs/faq/1gb-worth/), it’s a way for
|
||||
files to arrive at Transloadit servers, much like Uppy.
|
||||
|
||||
:::
|
||||
|
||||
## Installation & use
|
||||
|
||||
Companion is installed from npm. Depending on how you want to run Companion, the
|
||||
install process is slightly different. Companion can be integrated as middleware
|
||||
into your [Express](https://expressjs.com/) app or as a standalone server. Most
|
||||
people probably want to run it as a standalone server, while the middleware
|
||||
could be used to further customise Companion or integrate it into your own HTTP
|
||||
server code.
|
||||
|
||||
:::note
|
||||
|
||||
Since v2, you need to be running `node.js >= v10.20.1` to use Companion. More
|
||||
information in the
|
||||
[migrating to 2.0](/docs/guides/migration-guides/#migrate-from-uppy-1x-to-2x)
|
||||
guide.
|
||||
|
||||
Windows is not a supported platform right now. It may work, and we’re happy to
|
||||
accept improvements in this area, but we can’t provide support.
|
||||
|
||||
:::
|
||||
|
||||
### Standalone mode
|
||||
|
||||
You can use the standalone version if you want to run Companion as it’s own
|
||||
Node.js process. It’s a configured Express server with sessions, logging, and
|
||||
security best practices. First you’ll typically want to install it globally:
|
||||
|
||||
```bash
|
||||
npm install -g @uppy/companion
|
||||
```
|
||||
|
||||
Standalone Companion will always serve HTTP (not HTTPS) and expects a reverse
|
||||
proxy with SSL termination in front of it when running in production. See
|
||||
[`COMPANION_PROTOCOL`](#server) for more information.
|
||||
|
||||
Companion ships with an executable file (`bin/companion`) which is the
|
||||
standalone server. Unlike the middleware version, options are set via
|
||||
environment variables.
|
||||
|
||||
:::info
|
||||
|
||||
Checkout [options](#options) for the available options in JS and environment
|
||||
variable formats.
|
||||
|
||||
:::
|
||||
|
||||
You need at least these three to get started:
|
||||
|
||||
```bash
|
||||
export COMPANION_SECRET="shh!Issa Secret!"
|
||||
export COMPANION_DOMAIN="YOUR SERVER DOMAIN"
|
||||
export COMPANION_DATADIR="PATH/TO/DOWNLOAD/DIRECTORY"
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
companion
|
||||
```
|
||||
|
||||
You can also pass in the path to your JSON config file, like so:
|
||||
|
||||
```bash
|
||||
companion --config /path/to/companion.json
|
||||
```
|
||||
|
||||
You may also want to run Companion in a process manager like
|
||||
[PM2](https://pm2.keymetrics.io/) to make sure it gets restarted on upon
|
||||
crashing as well as allowing scaling to many instances.
|
||||
|
||||
### Express middleware mode
|
||||
|
||||
First install it into your Node.js project with your favorite package manager:
|
||||
|
||||
```bash
|
||||
npm install @uppy/companion
|
||||
```
|
||||
|
||||
To plug Companion into an existing server, call its `.app` method, passing in an
|
||||
[options](#options) object as a parameter. This returns a server instance that
|
||||
you can mount on a route in your Express app.
|
||||
|
||||
```js
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import session from 'express-session';
|
||||
import companion from '@uppy/companion';
|
||||
|
||||
const app = express();
|
||||
|
||||
// Companion requires body-parser and express-session middleware.
|
||||
// You can add it like this if you use those throughout your app.
|
||||
//
|
||||
// If you are using something else in your app, you can add these
|
||||
// middlewares in the same subpath as Companion instead.
|
||||
app.use(bodyParser.json());
|
||||
app.use(session({ secret: 'some secrety secret' }));
|
||||
|
||||
const companionOptions = {
|
||||
providerOptions: {
|
||||
drive: {
|
||||
key: 'GOOGLE_DRIVE_KEY',
|
||||
secret: 'GOOGLE_DRIVE_SECRET',
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: 'localhost:3020',
|
||||
protocol: 'http',
|
||||
// Default installations normally don't need a path.
|
||||
// However if you specify a `path`, you MUST specify
|
||||
// the same path in `app.use()` below,
|
||||
// e.g. app.use('/companion', companionApp)
|
||||
// path: '/companion',
|
||||
},
|
||||
filePath: '/path/to/folder/',
|
||||
};
|
||||
|
||||
const { app: companionApp } = companion.app(companionOptions);
|
||||
app.use(companionApp);
|
||||
```
|
||||
|
||||
Companion uses WebSockets to communicate progress, errors, and successes to the
|
||||
client. This is what Uppy listens to to update it’s internal state and UI.
|
||||
|
||||
Add the Companion WebSocket server using the `companion.socket` function:
|
||||
|
||||
```js
|
||||
const server = app.listen(PORT);
|
||||
|
||||
companion.socket(server);
|
||||
```
|
||||
|
||||
If WebSockets fail for some reason Uppy and Companion will fallback to HTTP
|
||||
polling.
|
||||
|
||||
### Running many instances
|
||||
|
||||
We recommend running at least two instances in production, so that if the
|
||||
Node.js event loop gets blocked by one or more requests (due to a bug or spike
|
||||
in traffic), it doesn’t also block or slow down all other requests as well (as
|
||||
Node.js is single threaded).
|
||||
|
||||
As an example for scale, one enterprise customer of Transloadit, who self-hosts
|
||||
Companion to power an education service that is used by many universities
|
||||
globally, deploys 7 Companion instances. Their earlier solution ran on 35
|
||||
instances. In our general experience Companion will saturate network interface
|
||||
cards before other resources on commodity virtual servers (`c5d.2xlarge` for
|
||||
instance).
|
||||
|
||||
Your mileage may vary, so we recommend to add observability. You can let
|
||||
Prometheus crawl the `/metrics` endpoint and graph that with Grafana for
|
||||
instance.
|
||||
|
||||
#### Using unique endpoints
|
||||
|
||||
One option is to run many instances with each instance having its own unique
|
||||
endpoint. This could be on separate ports, (sub)domain names, or IPs. With this
|
||||
setup, you can either:
|
||||
|
||||
1. Implement your own logic that will direct each upload to a specific Companion
|
||||
endpoint by setting the `companionUrl` option
|
||||
2. Setting the Companion option `COMPANION_SELF_ENDPOINT`. This option will
|
||||
cause Companion to respond with a `i-am` HTTP header containing the value
|
||||
from `COMPANION_SELF_ENDPOINT`. When Uppy’s sees this header, it will pin all
|
||||
requests for the upload to this endpoint.
|
||||
|
||||
In either case, you would then also typically configure a single Companion
|
||||
instance (one endpoint) to handle all OAuth authentication requests, so that you
|
||||
only need to specify a single OAuth callback URL. See also `oauthDomain` and
|
||||
`validHosts`.
|
||||
|
||||
#### Using a load balancer
|
||||
|
||||
The other option is to set up a load balancer in front of many Companion
|
||||
instances. Then Uppy will only see a single endpoint and send all requests to
|
||||
the associated load balancer, which will then distribute them between Companion
|
||||
instances. The companion instances coordinate their messages and events over
|
||||
Redis so that any instance can serve the client’s requests. Note that sticky
|
||||
sessions are **not** needed with this setup. Here are the requirements for this
|
||||
setup:
|
||||
|
||||
* The instances need to be connected to the same Redis server.
|
||||
* You need to set `COMPANION_SECRET` to the same value on both servers.
|
||||
* if you use the `companionKeysParams` feature (Transloadit), you also need
|
||||
`COMPANION_PREAUTH_SECRET` to be the same on each instance.
|
||||
* All other configuration needs to be the same, except if you’re running many
|
||||
instances on the same machine, then `COMPANION_PORT` should be different for
|
||||
each instance.
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
:::tip
|
||||
|
||||
The headings display the JS and environment variable options (`option`
|
||||
`ENV_OPTION`). When integrating Companion into your own server, you pass the
|
||||
options to `companion.app()`. If you are using the standalone version, you
|
||||
configure Companion using environment variables. Some options only exist as
|
||||
environment variables or only as a JS option.
|
||||
|
||||
:::
|
||||
|
||||
<details>
|
||||
<summary>Default configuration</summary>
|
||||
|
||||
```javascript
|
||||
const options = {
|
||||
server: {
|
||||
protocol: 'http',
|
||||
path: '',
|
||||
},
|
||||
providerOptions: {},
|
||||
s3: {
|
||||
endpoint: 'https://{service}.{region}.amazonaws.com',
|
||||
conditions: [],
|
||||
useAccelerateEndpoint: false,
|
||||
getKey: (req, filename) => `${crypto.randomUUID()}-${filename}`,
|
||||
expires: 800, // seconds
|
||||
},
|
||||
allowLocalUrls: false,
|
||||
logClientVersion: true,
|
||||
periodicPingUrls: [],
|
||||
streamingUpload: false,
|
||||
clientSocketConnectTimeout: 60000,
|
||||
metrics: true,
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### `filePath` `COMPANION_DATADIR`
|
||||
|
||||
Full path to the directory to which provider files will be downloaded
|
||||
temporarily.
|
||||
|
||||
#### `secret` `COMPANION_SECRET` `COMPANION_SECRET_FILE`
|
||||
|
||||
A secret string which Companion uses to generate authorization tokens. You
|
||||
should generate a long random string for this. For example:
|
||||
|
||||
```js
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
const secret = crypto.randomBytes(64).toString('hex');
|
||||
```
|
||||
|
||||
:::caution
|
||||
|
||||
Omitting the `secret` in the standalone version will generate a secret for you,
|
||||
using the above `crypto` string. But when integrating with Express you must
|
||||
provide it yourself. This is an essential security measure.
|
||||
|
||||
:::
|
||||
|
||||
:::note
|
||||
|
||||
Using a secret file means passing an absolute path to a file with any extension,
|
||||
which has only the secret, nothing else.
|
||||
|
||||
:::
|
||||
|
||||
#### `preAuthSecret` `COMPANION_PREAUTH_SECRET` `COMPANION_PREAUTH_SECRET_FILE`
|
||||
|
||||
If you are using the [Transloadit](/docs/transloadit) `companionKeysParams`
|
||||
feature (Transloadit-hosted Companion using your own custom OAuth credentials),
|
||||
set this variable to a strong randomly generated secret. See also
|
||||
`COMPANION_SECRET` (but do not use the same secret!)
|
||||
|
||||
:::note
|
||||
|
||||
Using a secret file means passing an absolute path to a file with any extension,
|
||||
which has only the secret, nothing else.
|
||||
|
||||
:::
|
||||
|
||||
#### `uploadUrls` `COMPANION_UPLOAD_URLS`
|
||||
|
||||
An allowlist (array) of strings (exact URLs) or regular expressions. Companion
|
||||
will only accept uploads to these URLs. This ensures that your Companion
|
||||
instance is only allowed to upload to your trusted servers and prevents
|
||||
[SSRF](https://en.wikipedia.org/wiki/Server-side_request_forgery) attacks.
|
||||
|
||||
#### `COMPANION_PORT`
|
||||
|
||||
The port on which to start the standalone server, defaults to 3020. This is a
|
||||
standalone-only option.
|
||||
|
||||
#### `COMPANION_COOKIE_DOMAIN`
|
||||
|
||||
Allows you to customize the domain of the cookies created for Express sessions.
|
||||
This is a standalone-only option.
|
||||
|
||||
#### `COMPANION_HIDE_WELCOME`
|
||||
|
||||
Setting this to `true` disables the welcome message shown at `/`. This is a
|
||||
standalone-only option.
|
||||
|
||||
#### `redisUrl` `COMPANION_REDIS_URL`
|
||||
|
||||
URL to running Redis server. This can be used to scale Companion horizontally
|
||||
using many instances. See [How to scale Companion](#how-to-scale-companion).
|
||||
|
||||
#### `COMPANION_REDIS_EXPRESS_SESSION_PREFIX`
|
||||
|
||||
Set a custom prefix for redis keys created by
|
||||
[connect-redis](https://github.com/tj/connect-redis). Defaults to `sess:`.
|
||||
Sessions are used for storing authentication state and for allowing thumbnails
|
||||
to be loaded by the browser via Companion. You might want to change this because
|
||||
if you run a redis with many different apps in the same redis server, it’s hard
|
||||
to know where `sess:` comes from and it might collide with other apps. **Note:**
|
||||
in the future, we plan and changing the default to `companion:` and possibly
|
||||
remove this option. This is a standalone-only option. See also
|
||||
`COMPANION_REDIS_PUBSUB_SCOPE`.
|
||||
|
||||
#### `redisOptions`
|
||||
|
||||
An object of
|
||||
[options supported by redis client](https://www.npmjs.com/package/redis#options-object-properties).
|
||||
This option can be used in place of `redisUrl`.
|
||||
|
||||
#### `redisPubSubScope` `COMPANION_REDIS_PUBSUB_SCOPE`
|
||||
|
||||
Use a scope for the companion events at the Redis server. Setting this option
|
||||
will prefix all events with the name provided and a colon. See also
|
||||
`COMPANION_REDIS_EXPRESS_SESSION_PREFIX`.
|
||||
|
||||
#### `server`
|
||||
|
||||
Configuration options for the underlying server.
|
||||
|
||||
| Key / Environment variable | Value | Description |
|
||||
| ---------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `protocol` `COMPANION_PROTOCOL` | `http` or `https` | Used to build a URL to reference the Companion instance itself, which is used for headers and cookies. Companion itself always runs as a HTTP server, so locally you should use `http`. You must to set this to `https` once you enabled SSL/HTTPS for your domain in production by running a reverse https-proxy in front of Companion, or with a built-in HTTPS feature of your hosting service. |
|
||||
| `host` `COMPANION_DOMAIN` | `String` | Your server’s publicly facing hostname (for example `example.com`). |
|
||||
| `oauthDomain` `COMPANION_OAUTH_DOMAIN` | `String` | If you have several instances of Companion with different (and perhaps dynamic) subdomains, you can set a single fixed subdomain and server (such as `sub1.example.com`) to handle your OAuth authentication for you. This would then redirect back to the correct instance with the required credentials on completion. This way you only need to configure a single callback URL for OAuth providers. |
|
||||
| `path` `COMPANION_PATH` | `String` | The server path to where the Companion app is sitting. For instance, if Companion is at `example.com/companion`, then the path would be `/companion`). |
|
||||
| `implicitPath` `COMPANION_IMPLICIT_PATH` | `String` | If the URL’s path in your reverse proxy is different from your Companion path in your express app, then you need to set this path as `implicitPath`. For instance, if your Companion URL is `example.com/mypath/companion`. Where the path `/mypath` is defined in your NGINX server, while `/companion` is set in your express app. Then you need to set the option `implicitPath` to `/mypath`, and set the `path` option to `/companion`. |
|
||||
| `validHosts` `COMPANION_DOMAINS` | `Array` | If you are setting an `oauthDomain`, you need to set a list of valid hosts, so the oauth handler can validate the host of the Uppy instance requesting the authentication. This is essentially a list of valid domains running your Companion instances. The list may also contain regex patterns. e.g `['sub2.example.com', 'sub3.example.com', '(\\w+).example.com']` |
|
||||
|
||||
#### `sendSelfEndpoint` `COMPANION_SELF_ENDPOINT`
|
||||
|
||||
This is essentially the same as the `server.host + server.path` attributes. The
|
||||
major reason for this attribute is that, when set, it adds the value as the
|
||||
`i-am` header of every request response.
|
||||
|
||||
#### `providerOptions`
|
||||
|
||||
Object to enable providers with their keys and secrets. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"drive": {
|
||||
"key": "***",
|
||||
"secret": "***"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When using the standalone version you use the corresponding environment
|
||||
variables or point to a secret file (such as `COMPANION_GOOGLE_SECRET_FILE`).
|
||||
|
||||
:::note
|
||||
|
||||
Secret files need an absolute path to a file with any extension which only has
|
||||
the secret, nothing else.
|
||||
|
||||
:::
|
||||
|
||||
| Service | Key | Environment variables |
|
||||
| ------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Box | `box` | `COMPANION_BOX_KEY`, `COMPANION_BOX_SECRET`, `COMPANION_BOX_SECRET_FILE` |
|
||||
| Dropbox | `dropbox` | `COMPANION_DROPBOX_KEY`, `COMPANION_DROPBOX_SECRET`, `COMPANION_DROPBOX_SECRET_FILE` |
|
||||
| Facebook | `facebook` | `COMPANION_FACEBOOK_KEY`, `COMPANION_FACEBOOK_SECRET`, `COMPANION_FACEBOOK_SECRET_FILE` |
|
||||
| Google Drive | `drive` | `COMPANION_GOOGLE_KEY`, `COMPANION_GOOGLE_SECRET`, `COMPANION_GOOGLE_SECRET_FILE` |
|
||||
| Instagram | `instagram` | `COMPANION_INSTAGRAM_KEY`, `COMPANION_INSTAGRAM_SECRET`, `COMPANION_INSTAGRAM_SECRET_FILE` |
|
||||
| OneDrive | `onedrive` | `COMPANION_ONEDRIVE_KEY`, `COMPANION_ONEDRIVE_SECRET`, `COMPANION_ONEDRIVE_SECRET_FILE`, `COMPANION_ONEDRIVE_DOMAIN_VALIDATION` (Settings this variable to `true` enables a route that can be used to validate your app with OneDrive) |
|
||||
| Zoom | `zoom` | `COMPANION_ZOOM_KEY`, `COMPANION_ZOOM_SECRET`, `COMPANION_ZOOM_SECRET_FILE`, `COMPANION_ZOOM_VERIFICATION_TOKEN` |
|
||||
|
||||
#### `s3`
|
||||
|
||||
Companion comes with signature endpoints for AWS S3. These can be used by the
|
||||
Uppy client to sign requests to upload files directly to S3, without exposing
|
||||
secret S3 keys in the browser. Companion also supports uploading files from
|
||||
providers like Dropbox and Instagram directly into S3.
|
||||
|
||||
##### `s3.key` `COMPANION_AWS_KEY`
|
||||
|
||||
The S3 access key ID.
|
||||
|
||||
##### `s3.secret` `COMPANION_AWS_SECRET` `COMPANION_AWS_SECRET_FILE`
|
||||
|
||||
The S3 secret access key.
|
||||
|
||||
:::note
|
||||
|
||||
Using a secret file means passing an absolute path to a file with any extension,
|
||||
which has only the secret, nothing else.
|
||||
|
||||
:::
|
||||
|
||||
##### `s3.endpoint` `COMPANION_AWS_ENDPOINT`
|
||||
|
||||
Optional URL to a custom S3 (compatible) service. Otherwise uses the default
|
||||
from the AWS SDK.
|
||||
|
||||
##### `s3.bucket` `COMPANION_AWS_BUCKET`
|
||||
|
||||
The name of the bucket to store uploaded files in.
|
||||
|
||||
It can be function that returns the name of the bucket as a `string` and takes
|
||||
the following arguments:
|
||||
|
||||
* [`http.IncomingMessage`][], the HTTP request (will be `null` for remote
|
||||
uploads)
|
||||
* metadata provided by the user for the file (will be `undefined` for local
|
||||
uploads)
|
||||
|
||||
##### `s3.region` `COMPANION_AWS_REGION`
|
||||
|
||||
The datacenter region where the target bucket is located.
|
||||
|
||||
##### `COMPANION_AWS_PREFIX`
|
||||
|
||||
An optional prefix for all uploaded keys. This is a standalone-only option. The
|
||||
same can be achieved by the `getKey` option when using the express middleware.
|
||||
|
||||
##### `s3.awsClientOptions`
|
||||
|
||||
You can supply any
|
||||
[S3 option supported by the AWS SDK](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property)
|
||||
in the `providerOptions.s3.awsClientOptions` object, _except for_ the below:
|
||||
|
||||
* `accessKeyId`. Instead, use the `providerOptions.s3.key` property. This is to
|
||||
make configuration names consistent between different Companion features.
|
||||
* `secretAccessKey`. Instead, use the `providerOptions.s3.secret` property. This
|
||||
is to make configuration names consistent between different Companion
|
||||
features.
|
||||
|
||||
Be aware that some options may cause wrong behaviour if they conflict with
|
||||
Companion’s assumptions. If you find that a particular option does not work as
|
||||
expected, please
|
||||
[open an issue on the Uppy repository](https://github.com/transloadit/uppy/issues/new)
|
||||
so we can document it here.
|
||||
|
||||
##### `s3.getKey(req, filename, metadata)`
|
||||
|
||||
Get the key name for a file. The key is the file path to which the file will be
|
||||
uploaded in your bucket. This option should be a function receiving three
|
||||
arguments:
|
||||
|
||||
* `req` [`http.IncomingMessage`][], the HTTP request, for _regular_ S3 uploads
|
||||
using the `@uppy/aws-s3` plugin. This parameter is _not_ available for
|
||||
multipart uploads using the `@uppy/aws-s3` or `@uppy/aws-s3-multipart`
|
||||
plugins. This parameter is `null` for remote uploads.
|
||||
* `filename`, the original name of the uploaded file;
|
||||
* `metadata`, user-provided metadata for the file.
|
||||
|
||||
This function should return a string `key`. The `req` parameter can be used to
|
||||
upload to a user-specific folder in your bucket, for example:
|
||||
|
||||
```js
|
||||
app.use(authenticationMiddleware);
|
||||
app.use(
|
||||
uppy.app({
|
||||
providerOptions: {
|
||||
s3: {
|
||||
getKey: (req, filename, metadata) => `${req.user.id}/${filename}`,
|
||||
/* auth options */
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
The default implementation returns the `filename`, so all files will be uploaded
|
||||
to the root of the bucket as their original file name.
|
||||
|
||||
```js
|
||||
app.use(
|
||||
uppy.app({
|
||||
providerOptions: {
|
||||
s3: {
|
||||
getKey: (req, filename, metadata) => filename,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
When signing on the client, this function will only be called for multipart
|
||||
uploads.
|
||||
|
||||
#### `COMPANION_AWS_USE_ACCELERATE_ENDPOINT`
|
||||
|
||||
Enable S3
|
||||
[Transfer Acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html).
|
||||
This is a standalone-only option.
|
||||
|
||||
#### `COMPANION_AWS_EXPIRES`
|
||||
|
||||
Set `X-Amz-Expires` query parameter in the presigned urls (in seconds, default:
|
||||
300\). This is a standalone-only option.
|
||||
|
||||
#### `COMPANION_AWS_ACL`
|
||||
|
||||
Set a
|
||||
[Canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl)
|
||||
for uploaded objects. This is a standalone-only option.
|
||||
|
||||
#### `customProviders`
|
||||
|
||||
This option enables you to add custom providers along with the already supported
|
||||
providers. See [adding custom providers](#how-to-add-custom-providers) for more
|
||||
information.
|
||||
|
||||
#### `logClientVersion`
|
||||
|
||||
A boolean flag to tell Companion whether to log its version upon startup.
|
||||
|
||||
#### `metrics` `COMPANION_HIDE_METRICS`
|
||||
|
||||
A boolean flag to tell Companion whether to provide an endpoint `/metrics` with
|
||||
Prometheus metrics (by default metrics are enabled.)
|
||||
|
||||
#### `streamingUpload` `COMPANION_STREAMING_UPLOAD`
|
||||
|
||||
A boolean flag to tell Companion whether to enable streaming uploads. If
|
||||
enabled, it will lead to _faster uploads_ because companion will start uploading
|
||||
at the same time as downloading using `stream.pipe`. If `false`, files will be
|
||||
fully downloaded first, then uploaded. Defaults to `false`, but we recommended
|
||||
enabling it, especially if you’re expecting to upload large files. In future
|
||||
versions the default might change to `true`.
|
||||
|
||||
#### `maxFileSize` `COMPANION_MAX_FILE_SIZE`
|
||||
|
||||
If this value is set, companion will limit the maximum file size to process. If
|
||||
unset, it will process files without any size limit (this is the default).
|
||||
|
||||
#### `periodicPingUrls` `COMPANION_PERIODIC_PING_URLS`
|
||||
|
||||
If this value is set, companion will periodically send POST requests to the
|
||||
specified URLs. Useful for keeping track of companion instances as a keep-alive.
|
||||
|
||||
#### `periodicPingInterval` `COMPANION_PERIODIC_PING_INTERVAL`
|
||||
|
||||
Interval for periodic ping requests (in ms).
|
||||
|
||||
#### `periodicPingStaticPayload` `COMPANION_PERIODIC_PING_STATIC_JSON_PAYLOAD`
|
||||
|
||||
A `JSON.stringify`-able JavaScript Object that will be sent as part of the JSON
|
||||
body in the period ping requests.
|
||||
|
||||
#### `allowLocalUrls` `COMPANION_ALLOW_LOCAL_URLS`
|
||||
|
||||
A boolean flag to tell Companion whether to allow requesting local URLs
|
||||
(non-internet IPs).
|
||||
|
||||
:::caution
|
||||
|
||||
Only enable this in development. **Enabling it in production is a security
|
||||
risk.**
|
||||
|
||||
:::
|
||||
|
||||
#### `corsOrigins` `COMPANION_CLIENT_ORIGINS`
|
||||
|
||||
Allowed CORS Origins (default `true`). Passed as the `origin` option in
|
||||
[cors](https://github.com/expressjs/cors#configuration-options))
|
||||
|
||||
#### `COMPANION_CLIENT_ORIGINS_REGEX`
|
||||
|
||||
Like COMPANION\_CLIENT\_ORIGINS, but allows a single regex instead.
|
||||
`COMPANION_CLIENT_ORIGINS` will be ignored if this is used. This is a
|
||||
standalone-only option.
|
||||
|
||||
#### `chunkSize` `COMPANION_CHUNK_SIZE`
|
||||
|
||||
Controls how big the uploaded chunks are for AWS S3 Multipart and Tus. Smaller
|
||||
values lead to more overhead, but larger values lead to slower retries in case
|
||||
of bad network connections. Passed to tus-js-client
|
||||
[`chunkSize`](https://github.com/tus/tus-js-client/blob/master/docs/api.md#chunksize)
|
||||
as well as
|
||||
[AWS S3 Multipart](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html)
|
||||
`partSize`.
|
||||
|
||||
#### `enableUrlEndpoint` `COMPANION_ENABLE_URL_ENDPOINT`
|
||||
|
||||
Set this to `false` to disable the
|
||||
[URL functionalily](https://uppy.io/docs/url/). Default: `true`.
|
||||
|
||||
### Events
|
||||
|
||||
The object returned by `companion.app()` also has a property `companionEmitter`
|
||||
which is an `EventEmitter` that emits the following events:
|
||||
|
||||
* `upload-start` - When an upload starts, this event is emitted with an object
|
||||
containing the property `token`, which is a unique ID for the upload.
|
||||
* **token** - The event name is the token from `upload-start`. The event has an
|
||||
object with the following properties:
|
||||
* `action` - One of the following strings:
|
||||
* `success` - When the upload succeeds.
|
||||
* `error` - When the upload fails with an error.
|
||||
* `payload` - the error or success payload.
|
||||
|
||||
Example code for using the `EventEmitter` to handle a finished file upload:
|
||||
|
||||
```js
|
||||
const companionApp = companion.app(options);
|
||||
const { companionEmitter: emitter } = companionApp;
|
||||
|
||||
emitter.on('upload-start', ({ token }) => {
|
||||
console.log('Upload started', token);
|
||||
|
||||
function onUploadEvent({ action, payload }) {
|
||||
if (action === 'success') {
|
||||
emitter.off(token, onUploadEvent); // avoid listener leak
|
||||
console.log('Upload finished', token, payload.url);
|
||||
} else if (action === 'error') {
|
||||
emitter.off(token, onUploadEvent); // avoid listener leak
|
||||
console.error('Upload failed', payload);
|
||||
}
|
||||
}
|
||||
emitter.on(token, onUploadEvent);
|
||||
});
|
||||
```
|
||||
|
||||
<!--retext-simplify ignore frequently-->
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
### Do you have a live example?
|
||||
|
||||
An example server is running at <https://companion.uppy.io>.
|
||||
|
||||
### How does the Authentication and Token mechanism work?
|
||||
|
||||
This section describes how Authentication works between Companion and Providers.
|
||||
While this behaviour is the same for all Providers (Dropbox, Instagram, Google
|
||||
Drive, etc.), we are going to be referring to Dropbox in place of any Provider
|
||||
throughout this section.
|
||||
|
||||
The following steps describe the actions that take place when a user
|
||||
Authenticates and Uploads from Dropbox through Companion:
|
||||
|
||||
* The visitor to a website with Uppy clicks `Connect to Dropbox`.
|
||||
* Uppy sends a request to Companion, which in turn sends an OAuth request to
|
||||
Dropbox (Requires that OAuth credentials from Dropbox have been added to
|
||||
Companion).
|
||||
* Dropbox asks the visitor to log in, and whether the Website should be allowed
|
||||
to access your files
|
||||
* If the visitor agrees, Companion will receive a token from Dropbox, with which
|
||||
we can temporarily download files.
|
||||
* Companion encrypts the token with a secret key and sends the encrypted token
|
||||
to Uppy (client)
|
||||
* Every time the visitor clicks on a folder in Uppy, it asks Companion for the
|
||||
new list of files, with this question, the token (still encrypted by
|
||||
Companion) is sent along.
|
||||
* Companion decrypts the token, requests the list of files from Dropbox and
|
||||
sends it to Uppy.
|
||||
* When a file is selected for upload, Companion receives the token again
|
||||
according to this procedure, decrypts it again, and thereby downloads the file
|
||||
from Dropbox.
|
||||
* As the bytes arrive, Companion uploads the bytes to the final destination
|
||||
(depending on the configuration: Apache, a Tus server, S3 bucket, etc).
|
||||
* Companion reports progress to Uppy, as if it were a local upload.
|
||||
* Completed!
|
||||
|
||||
### How to use provider redirect URIs?
|
||||
|
||||
When generating your provider API keys on their corresponding developer
|
||||
platforms (e.g
|
||||
[Google Developer Console](https://console.developers.google.com/)), you’d need
|
||||
to provide a `redirect URI` for the OAuth authorization process. In general the
|
||||
redirect URI for each provider takes the format:
|
||||
|
||||
`http(s)://$YOUR_COMPANION_HOST_NAME/$PROVIDER_NAME/redirect`
|
||||
|
||||
For example, if your Companion server is hosted on
|
||||
`https://my.companion.server.com`, then the redirect URI you would supply for
|
||||
your OneDrive provider would be:
|
||||
|
||||
`https://my.companion.server.com/onedrive/redirect`
|
||||
|
||||
Please see
|
||||
[Supported Providers](https://uppy.io/docs/companion/#Supported-providers) for a
|
||||
list of all Providers and their corresponding names.
|
||||
|
||||
### How to use Companion with Kubernetes?
|
||||
|
||||
We have a detailed
|
||||
[guide](https://github.com/transloadit/uppy/blob/main/packages/%40uppy/companion/KUBERNETES.md)
|
||||
on running Companion in Kubernetes.
|
||||
|
||||
### How to add custom providers?
|
||||
|
||||
As of now, Companion supports the
|
||||
[providers listed here](https://uppy.io/docs/companion/#Supported-providers) out
|
||||
of the box, but you may also choose to add your own custom providers. You can do
|
||||
this by passing the `customProviders` option when calling the Uppy `app` method.
|
||||
The custom provider is expected to support Oauth 1 or 2 for
|
||||
authentication/authorization.
|
||||
|
||||
```javascript
|
||||
import providerModule from './path/to/provider/module';
|
||||
|
||||
const options = {
|
||||
customProviders: {
|
||||
myprovidername: {
|
||||
config: {
|
||||
authorize_url: 'https://mywebsite.com/authorize',
|
||||
access_url: 'https://mywebsite.com/token',
|
||||
oauth: 2,
|
||||
key: '***',
|
||||
secret: '***',
|
||||
scope: ['read', 'write'],
|
||||
},
|
||||
module: providerModule,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
uppy.app(options);
|
||||
```
|
||||
|
||||
The `customProviders` option should be an object containing each custom
|
||||
provider. Each custom provider would, in turn, be an object with two keys,
|
||||
`config` and `module`. The `config` option would contain Oauth API settings,
|
||||
while the `module` would point to the provider module.
|
||||
|
||||
To work well with Companion, the **module** must be a class with the following
|
||||
methods. Note that the methods must be `async`, return a `Promise` or reject
|
||||
with an `Error`):
|
||||
|
||||
1. `async list ({ token, directory, query })` - Returns a object containing a
|
||||
list of user files (such as a list of all the files in a particular
|
||||
directory). See [example returned list data structure](#list-data). `token` -
|
||||
authorization token (retrieved from oauth process) to send along with your
|
||||
request
|
||||
* `directory` - the id/name of the directory from which data is to be
|
||||
retrieved. This may be ignored if it doesn’t apply to your provider
|
||||
* `query` - expressjs query params object received by the server (in case
|
||||
some data you need in there).
|
||||
2. `async download ({ token, id, query })` - Downloads a particular file from
|
||||
the provider. Returns an object with a single property `{ stream }` - a
|
||||
[`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable),
|
||||
which will be read from and uploaded to the destination. To prevent memory
|
||||
leaks, make sure you release your stream if you reject this method with an
|
||||
error.
|
||||
* `token` - authorization token (retrieved from oauth process) to send along
|
||||
with your request.
|
||||
* `id` - ID of the file being downloaded.
|
||||
* `query` - expressjs query params object received by the server (in case
|
||||
some data you need in there).
|
||||
3. `async size ({ token, id, query })` - Returns the byte size of the file that
|
||||
needs to be downloaded as a `Number`. If the size of the object is not known,
|
||||
`null` may be returned.
|
||||
* `token` - authorization token (retrieved from oauth process) to send along
|
||||
with your request.
|
||||
* `id` - ID of the file being downloaded.
|
||||
* `query` - expressjs query params object received by the server (in case
|
||||
some data you need in there).
|
||||
|
||||
The class must also have:
|
||||
|
||||
* A unique `static authProvider` string property - a lowercased value which
|
||||
indicates name of the [`grant`](https://github.com/simov/grant) OAuth2
|
||||
provider to use (e.g `google` for Google). If your provider doesn’t use
|
||||
OAuth2, you can omit this property.
|
||||
* A `static` property `static version = 2`, which is the current version of the
|
||||
Companion Provider API.
|
||||
|
||||
See also
|
||||
[example code with a custom provider](https://github.com/transloadit/uppy/blob/main/examples/custom-provider/server).
|
||||
|
||||
#### list data
|
||||
|
||||
```json
|
||||
{
|
||||
// username or email of the user whose provider account is being accessed
|
||||
"username": "johndoe",
|
||||
// list of files and folders in the directory. An item is considered a folder
|
||||
// if it mainly exists as a collection to contain sub-items
|
||||
"items": [
|
||||
{
|
||||
// boolean value of whether or NOT it's a folder
|
||||
"isFolder": false,
|
||||
// icon image URL
|
||||
"icon": "https://random-api.url.com/fileicon.jpg",
|
||||
// name of the item
|
||||
"name": "myfile.jpg",
|
||||
// the mime type of the item. Only relevant if the item is NOT a folder
|
||||
"mimeType": "image/jpg",
|
||||
// the id (in string) of the item
|
||||
"id": "uniqueitemid",
|
||||
// thumbnail image URL. Only relevant if the item is NOT a folder
|
||||
"thumbnail": "https://random-api.url.com/filethumbnail.jpg",
|
||||
// for folders this is typically the value that will be passed as "directory" in the list(...) method.
|
||||
// For files, this is the value that will be passed as id in the download(...) method.
|
||||
"requestPath": "file-or-folder-requestpath",
|
||||
// datetime string (in ISO 8601 format) of when this item was last modified
|
||||
"modifiedDate": "2020-06-29T19:59:58Z",
|
||||
// the size in bytes of the item. Only relevant if the item is NOT a folder
|
||||
"size": 278940,
|
||||
"custom": {
|
||||
// an object that may contain some more custom fields that you may need to send to the client. Only add this object if you have a need for it.
|
||||
"customData1": "the value",
|
||||
"customData2": "the value"
|
||||
}
|
||||
// more items here
|
||||
}
|
||||
],
|
||||
// if the "items" list is paginated, this is the request path needed to fetch the next page.
|
||||
"nextPagePath": "directory-name?cursor=cursor-to-next-page"
|
||||
}
|
||||
```
|
||||
|
||||
### How to run Companion locally?
|
||||
|
||||
1. To set up Companion for local development, please clone the Uppy repo and
|
||||
install, like so:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/transloadit/uppy
|
||||
cd uppy
|
||||
yarn install
|
||||
```
|
||||
|
||||
2. Configure your environment variables by copying the `env.example.sh` file to
|
||||
`env.sh` and edit it to its correct values.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
$EDITOR .env
|
||||
```
|
||||
|
||||
3. To start the server, run:
|
||||
|
||||
```bash
|
||||
yarn run start:companion
|
||||
```
|
||||
|
||||
This would get the Companion instance running on `http://localhost:3020`. It
|
||||
uses [nodemon](https://github.com/remy/nodemon) so it will automatically restart
|
||||
when files are changed.
|
||||
|
||||
[`http.incomingmessage`]: https://nodejs.org/api/http.html#class-httpincomingmessage
|
||||
|
||||
[box]: /docs/box
|
||||
|
||||
[dropbox]: /docs/dropbox
|
||||
|
||||
[facebook]: /docs/facebook
|
||||
|
||||
[googledrive]: /docs/google-drive
|
||||
|
||||
[instagram]: /docs/instagram
|
||||
|
||||
[onedrive]: /docs/onedrive
|
||||
|
||||
[unsplash]: /docs/unsplash
|
||||
|
||||
[url]: /docs/url
|
||||
|
||||
[zoom]: /docs/zoom
|
||||
|
||||
[transloadit]: https://transloadit.com
|
||||
125
docs/compressor.mdx
Normal file
125
docs/compressor.mdx
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
---
|
||||
sidebar_position: 12
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Compressor
|
||||
|
||||
The `@uppy/compressor` plugin optimizes images (JPEG, PNG, and any other format
|
||||
supported by the client’s browser) before upload, saving up to 60% in size
|
||||
(roughly 18 MB for 10 images). It uses [Compressor.js][] library under the hood.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
When your users are likely to upload images, potentially on mobile devices, and
|
||||
saving data and faster uploads are important.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/compressor
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/compressor
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Compressor } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Compressor, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js {7} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Compressor from '@uppy/compressor';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, {inline:true, target: '#dashboard')
|
||||
.use(Compressor);
|
||||
```
|
||||
|
||||
No action is needed from the user — Uppy will automatically optimize images,
|
||||
show an [Informer](/docs/informer) message with saved bytes, and then begin the
|
||||
upload as usual.
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
:::tip
|
||||
|
||||
You can also pass any of the [Compressor.js options][] here as well.
|
||||
|
||||
:::
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Compressor'`).
|
||||
|
||||
#### `quality`
|
||||
|
||||
The quality of the output image passed to [Compressor.js][] (`number`, default:
|
||||
`0.6`).
|
||||
|
||||
It must be a number between `0` and `1`. Be careful to use `1` as it may make
|
||||
the size of the output image become larger. In most cases, going with the
|
||||
default value is best.
|
||||
|
||||
:::note
|
||||
|
||||
This option is only available for `image/jpeg` and `image/webp` images.
|
||||
|
||||
:::
|
||||
|
||||
#### `limit`
|
||||
|
||||
Number of images that will be compressed in parallel (`number`, default: `10`).
|
||||
|
||||
You likely don’t need to change this, unless you are experiencing performance
|
||||
issues.
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
// Shown in the Status Bar
|
||||
compressingImages: 'Compressing images...',
|
||||
compressedX: 'Saved %{size} by compressing images',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
#### `compressor:complete`
|
||||
|
||||
The event is emitted when all files are compressed. You can use it for side
|
||||
effects or custom UI notifications.
|
||||
|
||||
[compressor.js]: https://github.com/fengyuanchen/compressorjs
|
||||
[compressor.js options]: https://github.com/fengyuanchen/compressorjs#options
|
||||
181
docs/form.mdx
Normal file
181
docs/form.mdx
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Form
|
||||
|
||||
The `@uppy/form` plugin integrates with an existing HTML `<form>` element to
|
||||
extract input data from it, and send along with the Uppy upload. It then appends
|
||||
the upload result back to the form via a hidden input.
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you have an existing HTML `<form>` element and you would like to:
|
||||
|
||||
- **Attach the form input values to the files.** This is useful if you want to
|
||||
associate meta data from the form (for example, name, location, id) with the
|
||||
uploaded file, so you can process it on the backend. `@uppy/form` extracts the
|
||||
input values before uploading/processing files and adds them to Uppy meta data
|
||||
state (`uppy.state.meta`), as well as and each file’s meta, and appends to the
|
||||
upload in an object with `[file input name attribute]` -> `[file input value]`
|
||||
key/values.
|
||||
- **Upload the files and put the response (such as the file URLs) into a hidden
|
||||
field** (`<input type="hidden" name="uppyResult">`). Then you can POST and
|
||||
handle the form yourself. The appended result is a stringified version of a
|
||||
result returned from calling `uppy.upload()` or listening to `complete` event.
|
||||
- **Automatically start the file upload on submit or submit the form after file
|
||||
upload.** This is off by default. See [`submitOnSuccess`](#submitOnSuccess)
|
||||
and [`triggerUploadOnSubmit`](#triggerUploadOnSubmit) options respectively for
|
||||
details.
|
||||
|
||||
:::note
|
||||
|
||||
If you are using a UI framework or library like React, Vue or Svelte, you’ll
|
||||
most likely handle form data there as well, and thus won’t need this plugin.
|
||||
Instead, pass meta data to Uppy via [`uppy.setMeta()`](/docs/uppy#setmetadata)
|
||||
and listen to [`uppy.on('complete')`](/docs/uppy#complete) to get the upload
|
||||
results back.
|
||||
|
||||
:::
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/form
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/form
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Form } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Form, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js title="app.js"
|
||||
import Uppy from '@uppy/core';
|
||||
import Form from '@uppy/form';
|
||||
import DragDrop from '@uppy/drag-drop';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/drag-drop/dist/style.min.css';
|
||||
|
||||
new Uppy().use(Form, {
|
||||
target: '#my-form',
|
||||
});
|
||||
```
|
||||
|
||||
```html title="index.html"
|
||||
<form id="my-form" action="/submit" method="get">
|
||||
<label for="name">Enter your name: </label>
|
||||
<input type="text" name="name" required />
|
||||
|
||||
<label for="dob">Date of birth: </label>
|
||||
<input type="date" name="dob" />
|
||||
|
||||
<input type="submit" value="Save" />
|
||||
</form>
|
||||
```
|
||||
|
||||
By default the code above will:
|
||||
|
||||
1. Extract meta data from the form `#my-form`.
|
||||
2. Send it with the Uppy upload.
|
||||
3. Those fields will then be added to Uppy meta data state (`uppy.state.meta`)
|
||||
and each file’s meta, and appended as (meta)data to the upload in an object
|
||||
with `[file input name attribute]` -> `[file input value]` key/values.
|
||||
4. When Uppy completes upload/processing, it will add an
|
||||
`<input type="hidden" name="uppyResult">` with the stringified upload result
|
||||
object back to the form.
|
||||
|
||||
:::note
|
||||
|
||||
You can disable both of these features, see options below.
|
||||
|
||||
:::
|
||||
|
||||
:::tip
|
||||
|
||||
`@uppy/form` can also start Uppy upload automatically once the form is
|
||||
submitted, and even submit the form after the upload is complete. This is off by
|
||||
default. See [`triggerUploadOnSubmit`](#triggerUploadOnSubmit) and
|
||||
[`submitOnSuccess`](#submitOnSuccess) options below for details.
|
||||
|
||||
:::
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Form'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element or CSS selector for the form element (`string` or `Element`,
|
||||
default: `null`).
|
||||
|
||||
This is required for the plugin to work.
|
||||
|
||||
#### `resultName`
|
||||
|
||||
The `name` attribute for the `<input type="hidden">` where the result will be
|
||||
added (`string`, default: `uppyResult`).
|
||||
|
||||
#### `getMetaFromForm`
|
||||
|
||||
Configures whether to extract metadata from the form (`boolean`, default:
|
||||
`true`).
|
||||
|
||||
When set to `true`, the Form plugin will extract all fields from a `<form>`
|
||||
element before upload begins. Those fields will then be added to Uppy meta data
|
||||
state (`uppy.state.meta`) and each file’s meta, and appended as (meta)data to
|
||||
the upload in an object with `[file input name attribute]` ->
|
||||
`[file input value]` key/values.
|
||||
|
||||
#### `addResultToForm`
|
||||
|
||||
Configures whether to add upload/encoding results back to the form in an
|
||||
`<input name="uppyResult" type="hidden">` element (`boolean`, default: `true`).
|
||||
|
||||
#### `triggerUploadOnSubmit`
|
||||
|
||||
Configures whether to start the upload when the form is submitted (`boolean`,
|
||||
default: `false`).
|
||||
|
||||
When a user submits the form (via a submit button, the <kbd>Enter</kbd> key or
|
||||
otherwise), this option will prevent form submission, and instead upload files
|
||||
via Uppy. Then you could:
|
||||
|
||||
- Set `submitOnSuccess: true` if you need the form to _actually_ be submitted
|
||||
once all files have been uploaded.
|
||||
- Listen for `uppy.on('complete')` event to do something else if the file
|
||||
uploads are all you need. For example, if the form is used for file metadata
|
||||
only.
|
||||
|
||||
#### `submitOnSuccess`
|
||||
|
||||
Configures whether to submit the form after Uppy finishes uploading/encoding
|
||||
(`boolean`, default: `false`).
|
||||
4
docs/framework-integrations/_category_.json
Normal file
4
docs/framework-integrations/_category_.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"label": "Framework integrations",
|
||||
"position": 8
|
||||
}
|
||||
108
docs/framework-integrations/angular.mdx
Normal file
108
docs/framework-integrations/angular.mdx
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
---
|
||||
slug: /angular
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Angular
|
||||
|
||||
[Angular][] components for Uppy UI plugins.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
When you are using the Angular framework and you wouldl like to use on of the UI
|
||||
components.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/angular
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/angular
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::note
|
||||
|
||||
You also need to install the UI plugin you want to use. For instance,
|
||||
`@uppy/dashboard`.
|
||||
|
||||
:::
|
||||
|
||||
## Use
|
||||
|
||||
Instead of adding a UI plugin to an Uppy instance with `.use()`, the Uppy
|
||||
instance can be passed into components as a `props` prop.
|
||||
|
||||
The following plugins are available as Angular component wrappers:
|
||||
|
||||
- `<uppy-dashboard />` renders [`@uppy/dashboard`](/docs/dashboard)
|
||||
- `<uppy-drag-drop />` renders [`@uppy/drag-drop`](/docs/drag-drop)
|
||||
- `<uppy-progress-bar />` renders [`@uppy/progress-bar`](/docs/progress-bar)
|
||||
- `<uppy-status-bar />` renders [`@uppy/status-bar`](/docs/status-bar)
|
||||
|
||||
Each component takes a `props` prop that will be passed to the UI Plugin.
|
||||
|
||||
```typescript title="app.module.ts" showLineNumbers
|
||||
import { NgModule } from '@angular/core';
|
||||
import { UppyAngularDashboardModule } from '@uppy/angular';
|
||||
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [BrowserModule, UppyAngularDashboardModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
class {}
|
||||
```
|
||||
|
||||
```html title="app.component.html" showLineNumbers
|
||||
<uppy-dashboard [uppy]="uppy"> </uppy-dashboard>
|
||||
```
|
||||
|
||||
You should initialize Uppy as a property of your component.
|
||||
|
||||
```typescript title="app.component.ts" showLineNumbers
|
||||
import { Component } from '@angular/core';
|
||||
import { Uppy } from '@uppy/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
})
|
||||
export class AppComponent {
|
||||
uppy: Uppy = new Uppy({ debug: true, autoProceed: true });
|
||||
}
|
||||
```
|
||||
|
||||
### CSS
|
||||
|
||||
All components have their own styling and should be added to your component
|
||||
decorator. You can find the CSS import statements in the docs of the UI plugin
|
||||
you want to use. For instance, for `@uppy/dashboard`:
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
// ...
|
||||
styleUrls: [
|
||||
'../node_modules/@uppy/core/dist/style.css',
|
||||
'../node_modules/@uppy/dashboard/dist/style.css',
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
[angular]: https://angular.io
|
||||
228
docs/framework-integrations/react.mdx
Normal file
228
docs/framework-integrations/react.mdx
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
---
|
||||
slug: /react
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# React
|
||||
|
||||
[React][] components for the Uppy UI plugins and a `useUppyState` hook.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/react
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/react
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::note
|
||||
|
||||
You also need to install the UI plugin you want to use. For instance,
|
||||
`@uppy/dashboard`.
|
||||
|
||||
:::
|
||||
|
||||
## Use
|
||||
|
||||
`@uppy/react` exposes component wrappers for `Dashboard`, `DragDrop`, and all
|
||||
other UI elements. The components can be used with either [React][] or
|
||||
API-compatible alternatives such as [Preact][].
|
||||
|
||||
:::caution
|
||||
|
||||
If you find yourself writing many instances of `useState` and `useEffect` to
|
||||
achieve something with Uppy in React, you are most likely breaking React best
|
||||
practices. Consider reading
|
||||
“[You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)”
|
||||
and looking at our examples below.
|
||||
|
||||
:::
|
||||
|
||||
### Components
|
||||
|
||||
The following components are exported from `@uppy/react`:
|
||||
|
||||
- `<Dashboard />` renders [`@uppy/dashboard`](/docs/dashboard)
|
||||
- `<DragDrop />` renders [`@uppy/drag-drop`](/docs/drag-drop)
|
||||
- `<ProgressBar />` renders [`@uppy/progress-bar`](/docs/progress-bar)
|
||||
- `<StatusBar />` renders [`@uppy/status-bar`](/docs/status-bar)
|
||||
|
||||
{/* prettier-ignore */}
|
||||
{/* Commented out until the hook is live
|
||||
|
||||
### Hooks
|
||||
|
||||
`useUppyState(uppy, selector)`
|
||||
|
||||
Use this hook when you need to access Uppy’s state reactively. Most of the
|
||||
times, this is needed if you are building a custom UI for Uppy in React.
|
||||
|
||||
```js
|
||||
// IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render.
|
||||
const [uppy] = useState(() => new Uppy());
|
||||
|
||||
const files = useUppyState(uppy, (state) => state.files);
|
||||
const totalProgress = useUppyState(uppy, (state) => state.totalProgress);
|
||||
// We can also get specific plugin state.
|
||||
// Note that the value on `plugins` depends on the `id` of the plugin.
|
||||
const metaFields = useUppyState(
|
||||
uppy,
|
||||
(state) => state.plugins?.Dashboard?.metaFields,
|
||||
);
|
||||
```
|
||||
|
||||
You can see all the values you can access on the
|
||||
[`State`](https://github.com/transloadit/uppy/blob/main/packages/%40uppy/core/types/index.d.ts#L190)
|
||||
type. If you are accessing plugin state, you would have to look at the types of
|
||||
the plugin.
|
||||
|
||||
\*/}
|
||||
|
||||
## Examples
|
||||
|
||||
### Example: basic component
|
||||
|
||||
Here we have a basic component which ties Uppy’s state to the component. This
|
||||
means you can render multiple instances. But be aware that as your component
|
||||
unmounts, for instance because the user navigates to a different page, Uppy’s
|
||||
state will be lost and uploads will stop.
|
||||
|
||||
:::note
|
||||
|
||||
If you render multiple instances of Uppy, make sure to give each instance a
|
||||
unique `id`.
|
||||
|
||||
:::
|
||||
|
||||
```js
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Uppy from '@uppy/core';
|
||||
import Webcam from '@uppy/webcam';
|
||||
import { Dashboard } from '@uppy/react';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
import '@uppy/webcam/dist/style.min.css';
|
||||
|
||||
function Component() {
|
||||
// IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render.
|
||||
const [uppy] = useState(() => new Uppy().use(Webcam));
|
||||
|
||||
return <Dashboard uppy={uppy} plugins={['Webcam']} />;
|
||||
}
|
||||
```
|
||||
|
||||
### Example: keep Uppy state and uploads while navigating between pages
|
||||
|
||||
When you want Uppy’s state to persist and keep uploads running between pages,
|
||||
you can
|
||||
[lift the state up](https://react.dev/learn/sharing-state-between-components#lifting-state-up-by-example).
|
||||
|
||||
```js
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Uppy from '@uppy/core';
|
||||
import { Dashboard } from '@uppy/react';
|
||||
|
||||
function Page1() {
|
||||
// ...
|
||||
}
|
||||
|
||||
function Page2({ uppy }) {
|
||||
return (
|
||||
<>
|
||||
<p>{totalProgress}</p>
|
||||
<Dashboard id="dashboard" uppy={uppy} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
// keeping the uppy instance alive above the pages the user can switch during uploading
|
||||
const [uppy] = useState(() => new Uppy());
|
||||
|
||||
return (
|
||||
// Add your router here
|
||||
<>
|
||||
<Page1 />
|
||||
<Page2 uppy={uppy} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Example: updating Uppy’s options dynamically based on props
|
||||
|
||||
```js
|
||||
// ...
|
||||
function Component(props) {
|
||||
// IMPORTANT: passing an initializer function to prevent the state from recreating.
|
||||
const [uppy] = useState(() => new Uppy().use(Webcam));
|
||||
|
||||
useEffect(() => {
|
||||
uppy.setOptions({ restrictions: props.restrictions });
|
||||
}, [props.restrictions]);
|
||||
|
||||
useEffect(() => {
|
||||
uppy.getPlugin('Webcam').setOptions({ modes: props.webcamModes });
|
||||
}, [props.webcamModes]);
|
||||
|
||||
return <Dashboard uppy={uppy} plugins={['Webcam']} />;
|
||||
}
|
||||
```
|
||||
|
||||
### Example: dynamic params and signature for Transloadit
|
||||
|
||||
When you go to production always make sure to set the `signature`. **Not using
|
||||
[Signature Authentication](https://transloadit.com/docs/topics/signature-authentication/)
|
||||
can be a security risk**. Signature Authentication is a security measure that
|
||||
can prevent outsiders from tampering with your Assembly Instructions.
|
||||
|
||||
Generating a signature should be done on the server to avoid leaking secrets. In
|
||||
React, this could get awkward with a `fetch` in a `useEffect` and setting it to
|
||||
`useState`. Instead, it’s easier to use the
|
||||
[`assemblyOptions`](/docs/transloadit#assemblyoptions) option to `fetch` the
|
||||
params.
|
||||
|
||||
```js
|
||||
// ...
|
||||
function createUppy(userId) {
|
||||
return new Uppy({ meta: { userId } }).use(Transloadit, {
|
||||
async assemblyOptions(file) {
|
||||
// You can send meta data along for use in your template.
|
||||
// https://transloadit.com/docs/topics/assembly-instructions/#form-fields-in-instructions
|
||||
const body = JSON.stringify({ userId: file.meta.userId });
|
||||
const res = await fetch('/transloadit-params', { method: 'POST', body });
|
||||
return response.json();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function Component({ userId }) {
|
||||
// IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render.
|
||||
const [uppy] = useState(() => createUppy(userId));
|
||||
|
||||
useEffect(() => {
|
||||
if (userId) {
|
||||
// Adding to global `meta` will add it to every file.
|
||||
uppy.setOptions({ meta: { userId } });
|
||||
}
|
||||
}, [uppy, userId]);
|
||||
}
|
||||
```
|
||||
|
||||
[react]: https://facebook.github.io/react
|
||||
[preact]: https://preactjs.com/
|
||||
70
docs/framework-integrations/svelte.mdx
Normal file
70
docs/framework-integrations/svelte.mdx
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
slug: /svelte
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Svelte
|
||||
|
||||
[Svelte][] components for the Uppy UI plugins.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/svelte
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/svelte
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::note
|
||||
|
||||
You also need to install the UI plugin you want to use. For instance,
|
||||
`@uppy/dashboard`.
|
||||
|
||||
:::
|
||||
|
||||
## Use
|
||||
|
||||
The following plugins are available as Svelte component wrappers:
|
||||
|
||||
- `<Dashboard />` renders [`@uppy/dashboard`](/docs/dashboard)
|
||||
- `<DragDrop />` renders [`@uppy/drag-drop`](/docs/drag-drop)
|
||||
- `<ProgressBar />` renders [`@uppy/progress-bar`](/docs/progress-bar)
|
||||
- `<StatusBar />` renders [`@uppy/status-bar`](/docs/status-bar)
|
||||
|
||||
Instead of adding a UI plugin to an Uppy instance with `.use()`, the Uppy
|
||||
instance can be passed into components as an `uppy` prop. Due to the way Svelte
|
||||
handles reactivity, you can initialize Uppy the same way you would with vanilla
|
||||
JavaScript.
|
||||
|
||||
```html
|
||||
<script>
|
||||
import { Dashboard } from '@uppy/svelte';
|
||||
import Uppy from '@uppy/core';
|
||||
import Webcam from '@uppy/webcam';
|
||||
|
||||
// Don't forget the CSS: core and UI components + plugins you are using
|
||||
import '@uppy/core/dist/style.css';
|
||||
import '@uppy/dashboard/dist/style.css';
|
||||
import '@uppy/webcam/dist/style.css';
|
||||
|
||||
const uppy = new Uppy().use(Webcam);
|
||||
</script>
|
||||
|
||||
<main><Dashboard uppy={uppy} plugins={["Webcam"]} /></main>
|
||||
```
|
||||
|
||||
[svelte]: https://svelte.dev
|
||||
73
docs/framework-integrations/vue.mdx
Normal file
73
docs/framework-integrations/vue.mdx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
slug: /vue
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Vue
|
||||
|
||||
[Vue][] components for the Uppy UI plugins.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/vue
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/vue
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::note
|
||||
|
||||
You also need to install the UI plugin you want to use. For instance,
|
||||
`@uppy/dashboard`.
|
||||
|
||||
:::
|
||||
|
||||
## Use
|
||||
|
||||
The following plugins are available as Vue component wrappers:
|
||||
|
||||
- `<Dashboard />` renders [`@uppy/dashboard`](/docs/dashboard) inline
|
||||
- `<DashboardModal />` renders [`@uppy/dashboard`](/docs/dashboard) as a modal
|
||||
- `<DragDrop />` renders [`@uppy/drag-drop`](/docs/drag-drop)
|
||||
- `<ProgressBar />` renders [`@uppy/progress-bar`](/docs/progress-bar)
|
||||
- `<StatusBar />` renders [`@uppy/status-bar`](/docs/status-bar)
|
||||
|
||||
Instead of adding a UI plugin to an Uppy instance with `.use()`, the Uppy
|
||||
instance can be passed into components as an `uppy` prop. Due to the way Vue
|
||||
handles reactivity, you can initialize Uppy the same way you would with vanilla
|
||||
JavaScript.
|
||||
|
||||
```html
|
||||
<script>
|
||||
import { Dashboard } from '@uppy/vue';
|
||||
import Uppy from '@uppy/core';
|
||||
import Webcam from '@uppy/webcam';
|
||||
|
||||
// Don't forget the CSS: core and UI components + plugins you are using
|
||||
import '@uppy/core/dist/style.css';
|
||||
import '@uppy/dashboard/dist/style.css';
|
||||
import '@uppy/webcam/dist/style.css';
|
||||
|
||||
const uppy = new Uppy().use(Webcam);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dashboard :uppy="uppy" :plugins="['Webcam']" />
|
||||
</template>
|
||||
```
|
||||
|
||||
[vue]: https://vuejs.org
|
||||
150
docs/golden-retriever.mdx
Normal file
150
docs/golden-retriever.mdx
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
---
|
||||
sidebar_position: 11
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Golden Retriever
|
||||
|
||||
The `@uppy/golden-retriever` plugin saves selected files in your browser cache,
|
||||
so that if the browser crashes, or the user accidentally closes the tab, Uppy
|
||||
can restore everything and continue uploading as if nothing happened. You can
|
||||
read more about it
|
||||
[on our blog](https://uppy.io/blog/2017/07/golden-retriever/).
|
||||
|
||||
The Golden Retriever uses three methods of browser data storage:
|
||||
|
||||
- `LocalStorage` to store file metadata and Uppy state only.
|
||||
- `IndexedDB` for small files, usually under 5MiB.
|
||||
- `Service Worker` (_optional_) for _all_ files because, unlike `IndexedDB`,
|
||||
`Service Worker` can keep references to large files. Service Worker storage is
|
||||
_quite_ temporary though, and doesn’t persist across browser crashes or
|
||||
restarts. It works well, however, for accidental refreshes or closed tabs.
|
||||
|
||||
Upon restore, the plugin first restores state from `LocalStorage` and then
|
||||
checks both file storages — `IndexedDB` and `ServiceWorker` — merging the
|
||||
results.
|
||||
|
||||
If restore is unsuccessful for certain files, they will be marked as “ghosts” in
|
||||
the Dashboard UI, and a message + button offering to re-select those files will
|
||||
be displayed.
|
||||
|
||||
Checkout the
|
||||
[storage quotas](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria#other_web_technologies)
|
||||
on MDN to see how much data can be stored depending on the device and browser.
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want extra insurance for the user-selected files. If you feel like
|
||||
users might spend some time picking files, tweaking descriptions etc, and will
|
||||
not appreciate having to do it over again if something crashes. Another use case
|
||||
might be when you think user might want to pick a few files, navigate away to do
|
||||
something else in your app or otherwise, and then come back to the same
|
||||
selection.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/golden-retriever
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/golden-retriever
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, GoldenRetriever } from "{{UPPY_JS_URL}}"
|
||||
new Uppy().use(GoldenRetriever)
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js {7} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import GoldenRetriever from '@uppy/golden-retriever';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, {inline:true, target: '#dashboard')
|
||||
.use(GoldenRetriever);
|
||||
```
|
||||
|
||||
By default, Golden Retriever will only use the `IndexedDB` storage, which is
|
||||
good enough for files up to 5 MiB. `Service Worker` is optional and requires
|
||||
setup.
|
||||
|
||||
### Enabling Service Worker
|
||||
|
||||
With the Service Worker storage, Golden Retriever will be able to temporary
|
||||
store references to large files.
|
||||
|
||||
1. Bundle your own service worker `sw.js` file with Uppy GoldenRetriever’s
|
||||
service worker.
|
||||
|
||||
:::tip
|
||||
|
||||
For Webpack, check out
|
||||
[serviceworker-webpack-plugin](https://github.com/oliviertassinari/serviceworker-webpack-plugin).
|
||||
|
||||
:::
|
||||
|
||||
```js title="sw.js"
|
||||
import('@uppy/golden-retriever/lib/ServiceWorker');
|
||||
```
|
||||
|
||||
2. Register it in your app’s entry point:
|
||||
|
||||
```js
|
||||
// you app.js entry point
|
||||
uppy.use(GoldenRetriever, { serviceWorker: true });
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register('/sw.js') // path to your bundled service worker with GoldenRetriever service worker
|
||||
.then((registration) => {
|
||||
console.log(
|
||||
'ServiceWorker registration successful with scope: ',
|
||||
registration.scope,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(`Registration failed with ${error}`);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Voilà, that’s it. Happy retrieving!
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'GoldenRetriever'`).
|
||||
|
||||
#### `expires`
|
||||
|
||||
How long to store metadata and files for. Used for `LocalStorage` and
|
||||
`IndexedDB` (`number`, default: `24 * 60 * 60 * 1000` // 24 hours).
|
||||
|
||||
#### `serviceWorker`
|
||||
|
||||
Whether to enable `Service Worker` storage (`boolean`, default: `false`).
|
||||
4
docs/guides/_category_.json
Normal file
4
docs/guides/_category_.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"label": "Guides",
|
||||
"position": 2
|
||||
}
|
||||
62
docs/guides/browser-support.mdx
Normal file
62
docs/guides/browser-support.mdx
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
---
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Supporting IE11
|
||||
|
||||
We officially support recent versions of Chrome, Firefox, Safari and Edge.
|
||||
|
||||
Internet Explorer is not officially supported, as in we don’t run tests for it,
|
||||
but you can be mostly confident it works with the right polyfills. But it does
|
||||
come with a risk of unexpected results in styling or functionality.
|
||||
|
||||
## Polyfills
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install core-js whatwg-fetch abortcontroller-polyfill md-gum-polyfill resize-observer-polyfill
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add core-js whatwg-fetch abortcontroller-polyfill md-gum-polyfill resize-observer-polyfill
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
```js
|
||||
import 'core-js';
|
||||
import 'whatwg-fetch';
|
||||
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch';
|
||||
// Order matters: AbortController needs fetch which needs Promise (provided by core-js).
|
||||
|
||||
import 'md-gum-polyfill';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
window.ResizeObserver ??= ResizeObserver;
|
||||
|
||||
export { default } from '@uppy/core';
|
||||
export * from '@uppy/core';
|
||||
```
|
||||
|
||||
## Legacy CDN bundle
|
||||
|
||||
<UppyCdnExample uppyJsName="uppy.legacy.min.js">
|
||||
{`
|
||||
import { Uppy, DragDrop, Tus } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(DragDrop, { target: '#uppy' })
|
||||
uppy.use(Tus, { endpoint: '//tusd.tusdemo.net/files/' })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
382
docs/guides/building-plugins.md
Normal file
382
docs/guides/building-plugins.md
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Building plugins
|
||||
|
||||
You can find already a few useful Uppy plugins out there, but there might come a
|
||||
time when you will want to build your own. Plugins can hook into the upload
|
||||
process or render a custom UI, typically to:
|
||||
|
||||
* Render some custom UI element, such as [StatusBar](/docs/status-bar) or
|
||||
[Dashboard](/docs/dashboard).
|
||||
* Do the actual uploading, such as [XHRUpload](/docs/xhr-upload) or
|
||||
[Tus](/docs/tus).
|
||||
* Do work before the upload, like compressing an image or calling external API.
|
||||
* Interact with a third-party service to process uploads correctly, such as
|
||||
[Transloadit](/docs/transloadit) or [AwsS3](/docs/aws-s3).
|
||||
|
||||
See a [full example of a plugin](#example-of-a-custom-plugin) below.
|
||||
|
||||
## Creating A Plugin
|
||||
|
||||
Uppy has two classes to create plugins with. `BasePlugin` for plugins that don’t
|
||||
need a user interface, and `UIPlugin` for ones that do. Each plugin has an `id`
|
||||
and a `type`. `id`s are used to uniquely identify plugins. A `type` can be
|
||||
anything—some plugins use `type`s to decide whether to do something to some
|
||||
other plugin. For example, when targeting plugins at the built-in `Dashboard`
|
||||
plugin, the Dashboard uses the `type` to figure out where to mount different UI
|
||||
elements. `'acquirer'`-type plugins are mounted into the tab bar, while
|
||||
`'progressindicator'`-type plugins are mounted into the progress bar area.
|
||||
|
||||
The plugin constructor receives the Uppy instance in the first parameter, and
|
||||
any options passed to `uppy.use()` in the second parameter.
|
||||
|
||||
```js
|
||||
import BasePlugin from '@uppy/core/lib/BasePlugin.js';
|
||||
|
||||
export default class MyPlugin extends BasePlugin {
|
||||
constructor(uppy, opts) {
|
||||
super(uppy, opts);
|
||||
this.id = opts.id || 'MyPlugin';
|
||||
this.type = 'example';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
Plugins can define methods to execute certain tasks. The most important method
|
||||
is `install()`, which is called when a plugin is `.use`d.
|
||||
|
||||
All the below methods are optional! Only define the methods you need.
|
||||
|
||||
### `BasePlugin`
|
||||
|
||||
#### `install()`
|
||||
|
||||
Called when the plugin is `.use`d. Do any setup work here, like attaching events
|
||||
or adding [upload hooks](#Upload-Hooks).
|
||||
|
||||
```js
|
||||
export default class MyPlugin extends UIPlugin {
|
||||
// ...
|
||||
install() {
|
||||
this.uppy.on('upload-progress', this.onProgress);
|
||||
this.uppy.addPostProcessor(this.afterUpload);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `uninstall()`
|
||||
|
||||
Called when the plugin is removed, or the Uppy instance is closed. This should
|
||||
undo all the work done in the `install()` method.
|
||||
|
||||
```js
|
||||
export default class MyPlugin extends UIPlugin {
|
||||
// ...
|
||||
uninstall() {
|
||||
this.uppy.off('upload-progress', this.onProgress);
|
||||
this.uppy.removePostProcessor(this.afterUpload);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `afterUpdate()`
|
||||
|
||||
Called after every state update with a debounce, after everything has mounted.
|
||||
|
||||
#### `addTarget()`
|
||||
|
||||
Use this to add your plugin to another plugin’s target. This is what
|
||||
`@uppy/dashboard` uses to add other plugins to its UI.
|
||||
|
||||
### `UIPlugin`
|
||||
|
||||
`UIPlugin` extends the `BasePlugin` class so it will also contain all the above
|
||||
methods.
|
||||
|
||||
#### `mount(target)`
|
||||
|
||||
Mount this plugin to the `target` element. `target` can be a CSS query selector,
|
||||
a DOM element, or another Plugin. If `target` is a Plugin, the source (current)
|
||||
plugin will register with the target plugin, and the latter can decide how and
|
||||
where to render the source plugin.
|
||||
|
||||
This method can be overridden to support for different render engines.
|
||||
|
||||
#### `render()`
|
||||
|
||||
Render this plugin’s UI. Uppy uses [Preact](https://preactjs.com) as its view
|
||||
engine, so `render()` should return a Preact element. `render` is automatically
|
||||
called by Uppy on each state change.
|
||||
|
||||
#### `onMount()`
|
||||
|
||||
Called after Preact has rendered the components of the plugin.
|
||||
|
||||
#### `update(state)`
|
||||
|
||||
Called on each state update. You will rarely need to use this, unless if you
|
||||
want to build a UI plugin using something other than Preact.
|
||||
|
||||
#### `onUnmount()`
|
||||
|
||||
Called after the elements have been removed from the DOM. Can be used to do some
|
||||
clean up or other side-effects.
|
||||
|
||||
## Upload Hooks
|
||||
|
||||
When creating an upload, Uppy runs files through an upload pipeline. The
|
||||
pipeline consists of three parts, each of which can be hooked into:
|
||||
Preprocessing, Uploading, and Postprocessing. Preprocessors can be used to
|
||||
configure uploader plugins, encrypt files, resize images, etc., before uploading
|
||||
them. Uploaders do the actual uploading work, such as creating an XMLHttpRequest
|
||||
object and sending the file. Postprocessors do their work after files have been
|
||||
uploaded completely. This could be anything from waiting for a file to propagate
|
||||
across a CDN, to sending another request to relate some metadata to the file.
|
||||
|
||||
Each hook is a function that receives an array containing the file IDs that are
|
||||
being uploaded, and returns a Promise to signal completion. Hooks are added and
|
||||
removed through `Uppy` methods:
|
||||
[`addPreProcessor`](/docs/uppy#addpreprocessorfn),
|
||||
[`addUploader`](/docs/uppy#adduploaderfn),
|
||||
[`addPostProcessor`](/docs/uppy#addpostprocessorfn), and their
|
||||
[`remove*`](/docs/uppy#removepreprocessorremoveuploaderremovepostprocessorfn)
|
||||
counterparts. Normally, hooks should be added during the plugin `install()`
|
||||
method, and removed during the `uninstall()` method.
|
||||
|
||||
Additionally, upload hooks can fire events to signal progress.
|
||||
|
||||
When adding hooks, make sure to bind the hook `fn` beforehand! Otherwise, it
|
||||
will be impossible to remove. For example:
|
||||
|
||||
```js
|
||||
class MyPlugin extends BasePlugin {
|
||||
constructor(uppy, opts) {
|
||||
super(uppy, opts);
|
||||
this.id = opts.id || 'MyPlugin';
|
||||
this.type = 'example';
|
||||
this.prepareUpload = this.prepareUpload.bind(this); // ← this!
|
||||
}
|
||||
|
||||
prepareUpload(fileIDs) {
|
||||
console.log(this); // `this` refers to the `MyPlugin` instance.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
install() {
|
||||
this.uppy.addPreProcessor(this.prepareUpload);
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
this.uppy.removePreProcessor(this.prepareUpload);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or you can define the method as a class field:
|
||||
|
||||
```js
|
||||
class MyPlugin extends UIPlugin {
|
||||
constructor(uppy, opts) {
|
||||
super(uppy, opts);
|
||||
this.id = opts.id || 'MyPlugin';
|
||||
this.type = 'example';
|
||||
}
|
||||
|
||||
prepareUpload = (fileIDs) => {
|
||||
// ← this!
|
||||
console.log(this); // `this` refers to the `MyPlugin` instance.
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
install() {
|
||||
this.uppy.addPreProcessor(this.prepareUpload);
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
this.uppy.removePreProcessor(this.prepareUpload);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Progress events
|
||||
|
||||
Progress events can be fired for individual files to show feedback to the user.
|
||||
For upload progress events, only emitting how many bytes are expected and how
|
||||
many have been uploaded is enough. Uppy will handle calculating progress
|
||||
percentages, upload speed, etc.
|
||||
|
||||
Preprocessing and postprocessing progress events are plugin-dependent and can
|
||||
refer to anything, so Uppy doesn’t try to be smart about them. Processing
|
||||
progress events can be of two types: determinate or indeterminate. Some
|
||||
processing does not have meaningful progress beyond “not done” and “done”. For
|
||||
example, sending a request to initialize a server-side resource that will serve
|
||||
as the upload destination. In those situations, indeterminate progress is
|
||||
suitable. Other processing does have meaningful progress. For example,
|
||||
encrypting a large file. In those situations, determinate progress is suitable.
|
||||
|
||||
Here are the relevant events:
|
||||
|
||||
* [`preprocess-progress`](/docs/uppy#preprocess-progress)
|
||||
* [`upload-progress`](/docs/uppy#upload-progress)
|
||||
* [`postprocess-progress`](/docs/uppy#postprocess-progress)
|
||||
|
||||
## JSX
|
||||
|
||||
Since Uppy uses Preact and not React, the default Babel configuration for JSX
|
||||
elements does not work. You have to import the Preact `h` function and tell
|
||||
Babel to use it by adding a `/** @jsx h */` comment at the top of the file.
|
||||
|
||||
See the Preact
|
||||
[Getting Started Guide](https://preactjs.com/guide/getting-started) for more on
|
||||
Babel and JSX.
|
||||
|
||||
<!-- eslint-disable jsdoc/check-tag-names -->
|
||||
|
||||
```jsx
|
||||
/** @jsx h */
|
||||
import { UIPlugin } from '@uppy/core';
|
||||
import { h } from 'preact';
|
||||
|
||||
class NumFiles extends UIPlugin {
|
||||
render() {
|
||||
const numFiles = Object.keys(this.uppy.state.files).length;
|
||||
|
||||
return <div>Number of files: {numFiles}</div>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Locales
|
||||
|
||||
For any user facing language that you use while writing your Plugin, please
|
||||
provide them as strings in the `defaultLocale` property like so:
|
||||
|
||||
```js
|
||||
this.defaultLocale = {
|
||||
strings: {
|
||||
youCanOnlyUploadFileTypes: 'You can only upload: %{types}',
|
||||
youCanOnlyUploadX: {
|
||||
0: 'You can only upload %{smart_count} file',
|
||||
1: 'You can only upload %{smart_count} files',
|
||||
2: 'You can only upload %{smart_count} files',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
This allows them to be overridden by Locale Packs, or directly when users pass
|
||||
`locale: { strings: youCanOnlyUploadFileTypes: 'Something else completely about %{types}'} }`.
|
||||
For this to work, it’s also required that you call `this.i18nInit()` in the
|
||||
plugin constructor.
|
||||
|
||||
## Example of a custom plugin
|
||||
|
||||
Below is a full example of a
|
||||
[small plugin](https://github.com/arturi/uppy-plugin-image-compressor) that
|
||||
compresses images before uploading them. You can replace `compressorjs` method
|
||||
with any other work you need to do. This works especially well for async stuff,
|
||||
like calling an external API.
|
||||
|
||||
<!-- eslint-disable consistent-return -->
|
||||
|
||||
```js
|
||||
import { UIPlugin } from '@uppy/core';
|
||||
import Translator from '@uppy/utils/lib/Translator';
|
||||
import Compressor from 'compressorjs/dist/compressor.esm.js';
|
||||
|
||||
class UppyImageCompressor extends UIPlugin {
|
||||
constructor(uppy, opts) {
|
||||
const defaultOptions = {
|
||||
quality: 0.6,
|
||||
};
|
||||
super(uppy, { ...defaultOptions, ...opts });
|
||||
|
||||
this.id = this.opts.id || 'ImageCompressor';
|
||||
this.type = 'modifier';
|
||||
|
||||
this.defaultLocale = {
|
||||
strings: {
|
||||
compressingImages: 'Compressing images...',
|
||||
},
|
||||
};
|
||||
|
||||
// we use those internally in `this.compress`, so they
|
||||
// should not be overridden
|
||||
delete this.opts.success;
|
||||
delete this.opts.error;
|
||||
|
||||
this.i18nInit();
|
||||
}
|
||||
|
||||
compress(blob) {
|
||||
return new Promise(
|
||||
(resolve, reject) =>
|
||||
new Compressor(blob, {
|
||||
...this.opts,
|
||||
success(result) {
|
||||
return resolve(result);
|
||||
},
|
||||
error(err) {
|
||||
return reject(err);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
prepareUpload = (fileIDs) => {
|
||||
const promises = fileIDs.map((fileID) => {
|
||||
const file = this.uppy.getFile(fileID);
|
||||
this.uppy.emit('preprocess-progress', file, {
|
||||
mode: 'indeterminate',
|
||||
message: this.i18n('compressingImages'),
|
||||
});
|
||||
|
||||
if (!file.type.startsWith('image/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.compress(file.data)
|
||||
.then((compressedBlob) => {
|
||||
this.uppy.log(
|
||||
`[Image Compressor] Image ${file.id} size before/after compression: ${file.data.size} / ${compressedBlob.size}`,
|
||||
);
|
||||
this.uppy.setFileState(fileID, { data: compressedBlob });
|
||||
})
|
||||
.catch((err) => {
|
||||
this.uppy.log(
|
||||
`[Image Compressor] Failed to compress ${file.id}:`,
|
||||
'warning',
|
||||
);
|
||||
this.uppy.log(err, 'warning');
|
||||
});
|
||||
});
|
||||
|
||||
const emitPreprocessCompleteForAll = () => {
|
||||
fileIDs.forEach((fileID) => {
|
||||
const file = this.uppy.getFile(fileID);
|
||||
this.uppy.emit('preprocess-complete', file);
|
||||
});
|
||||
};
|
||||
|
||||
// Why emit `preprocess-complete` for all files at once, instead of
|
||||
// above when each is processed?
|
||||
// Because it leads to StatusBar showing a weird “upload 6 files” button,
|
||||
// while waiting for all the files to complete pre-processing.
|
||||
return Promise.all(promises).then(emitPreprocessCompleteForAll);
|
||||
};
|
||||
|
||||
install() {
|
||||
this.uppy.addPreProcessor(this.prepareUpload);
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
this.uppy.removePreProcessor(this.prepareUpload);
|
||||
}
|
||||
}
|
||||
|
||||
export default UppyImageCompressor;
|
||||
```
|
||||
11
docs/guides/building-your-own-ui-with-uppy.md
Normal file
11
docs/guides/building-your-own-ui-with-uppy.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Building your own UI with Uppy
|
||||
|
||||
:::note
|
||||
|
||||
This guide is in progress.
|
||||
|
||||
:::
|
||||
93
docs/guides/choosing-uploader.md
Normal file
93
docs/guides/choosing-uploader.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Choosing the uploader you need
|
||||
|
||||
Versatile, reliable uploading is at the heart of Uppy. It has many configurable
|
||||
plugins to suit your needs. In this guide we will explain the different plugins,
|
||||
their strategies, and when to use them based on use cases.
|
||||
|
||||
## Use cases
|
||||
|
||||
### I want worry-free, plug-and-play uploads with Transloadit services
|
||||
|
||||
Transloadit’s strength is versatility. By doing video, audio, images, documents,
|
||||
and more, you only need one vendor for [all your file processing
|
||||
needs][transloadit-services]. The [`@uppy/transloadit`][] plugin directly
|
||||
uploads to Transloadit so you only have to worry about creating a
|
||||
[template][transloadit-concepts]. It uses
|
||||
[Tus](#i-want-reliable-resumable-uploads) under the hood so you don’t have to
|
||||
sacrifice reliable, resumable uploads for convenience.
|
||||
|
||||
You should use [`@uppy/transloadit`][] if you don’t want to host your own
|
||||
server, (optionally) need file processing, and store it in the service (such as
|
||||
S3 or GCS) of your liking. All with minimal effort.
|
||||
|
||||
### I want reliable, resumable uploads
|
||||
|
||||
[Tus][tus] is a new open protocol for resumable uploads built on HTTP. This
|
||||
means accidentally closing your tab or losing connection let’s you continue, for
|
||||
instance, your 10GB upload instead of starting all over.
|
||||
|
||||
Tus supports any language, any platform, and any network. It requires a client
|
||||
and server integration to work. You can checkout the client and server
|
||||
[implementations][tus-implementations] to find the server in your preferred
|
||||
language. You can store files on the Tus server itself, but you can also use
|
||||
service integrations (such as S3) to store files externally.
|
||||
|
||||
If you want reliable, resumable uploads: use [`@uppy/tus`][] to connect to your
|
||||
Tus server in a few lines of code.
|
||||
|
||||
:::tip
|
||||
|
||||
If you plan to let people upload _a lot_ of files, [`@uppy/tus`][] has
|
||||
exponential backoff built-in. Meaning if your server (or proxy) returns HTTP 429
|
||||
because it’s being overloaded, [`@uppy/tus`][] will find the ideal sweet spot to
|
||||
keep uploading without overloading.
|
||||
|
||||
:::
|
||||
|
||||
### I want to upload to AWS S3 (or S3-compatible storage) directly
|
||||
|
||||
When you prefer a _client-to-storage_ over a _client-to-server-to-storage_ (such
|
||||
as [Transloadit](/docs/transloadit) or [Tus](/docs/tus)) setup. This may in some
|
||||
cases be preferable, for instance, to reduce costs or the complexity of running
|
||||
a server and load balancer with [Tus](/docs/tus).
|
||||
|
||||
Uppy has two plugins to make this happen [`@uppy/aws-s3`][] and
|
||||
[`@uppy/aws-s3-multipart`][], but we are planning to merge the two plugins in
|
||||
the next major. You should use [`@uppy/aws-s3`][] with the new
|
||||
`shouldUseMultipart` option.
|
||||
|
||||
:::info
|
||||
|
||||
You can also save files in S3 with the [`/s3/store`][s3-robot] robot while still
|
||||
using the powers of Transloadit services.
|
||||
|
||||
:::
|
||||
|
||||
### I want to send regular HTTP uploads to my own server
|
||||
|
||||
[`@uppy/xhr-upload`][] handles classic HTML multipart form uploads as well as
|
||||
uploads using the HTTP `PUT` method.
|
||||
|
||||
[s3-robot]: https://transloadit.com/services/file-exporting/s3-store/
|
||||
|
||||
[transloadit-services]: https://transloadit.com/services/
|
||||
|
||||
[transloadit-concepts]: https://transloadit.com/docs/getting-started/concepts/
|
||||
|
||||
[`@uppy/transloadit`]: /docs/transloadit
|
||||
|
||||
[`@uppy/tus`]: /docs/tus
|
||||
|
||||
[`@uppy/aws-s3-multipart`]: /docs/aws-s3-multipart
|
||||
|
||||
[`@uppy/aws-s3`]: /docs/aws-s3-multipart
|
||||
|
||||
[`@uppy/xhr-upload`]: /docs/xhr-upload
|
||||
|
||||
[tus]: https://tus.io/
|
||||
|
||||
[tus-implementations]: https://tus.io/implementations.html
|
||||
136
docs/guides/custom-stores.md
Normal file
136
docs/guides/custom-stores.md
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# Custom stores
|
||||
|
||||
If your app uses a state management library such as
|
||||
[Redux](https://redux.js.org), it can be useful to have Uppy store its state
|
||||
there instead—that way, you could write custom uploader UI components in the
|
||||
same way as the other components in the application.
|
||||
|
||||
Uppy comes with two state management solutions (stores):
|
||||
|
||||
* `@uppy/store-default`, a basic object-based store.
|
||||
* `@uppy/store-redux`, a store that uses a key in a Redux store.
|
||||
|
||||
You can also use a third-party store:
|
||||
|
||||
* [uppy-store-ngrx](https://github.com/rimlin/uppy-store-ngrx/), keeping Uppy
|
||||
state in a key in an [Ngrx](https://github.com/ngrx/platform) store for use
|
||||
with Angular.
|
||||
|
||||
## Using stores
|
||||
|
||||
To use a store, pass an instance to the
|
||||
[`store` option](/docs/uppy#store-defaultstore) in the Uppy constructor:
|
||||
|
||||
```js
|
||||
import DefaultStore from '@uppy/store-default';
|
||||
|
||||
const uppy = new Uppy({
|
||||
store: new DefaultStore(),
|
||||
});
|
||||
```
|
||||
|
||||
### `DefaultStore`
|
||||
|
||||
Uppy uses the `DefaultStore` by default! You do not need to do anything to use
|
||||
it. It does not take any options.
|
||||
|
||||
### `ReduxStore`
|
||||
|
||||
The `ReduxStore` stores Uppy state on a key in an existing Redux store. The
|
||||
`ReduxStore` dispatches `uppy/STATE_UPDATE` actions to update state. When the
|
||||
state in Redux changes, it notifies Uppy. This way, you get most of the benefits
|
||||
of Redux, including support for the Redux Devtools and time traveling!
|
||||
|
||||
Checkout our
|
||||
[Redux example](https://github.com/transloadit/uppy/tree/main/examples/redux)
|
||||
for a working demo.
|
||||
|
||||
#### `opts.store`
|
||||
|
||||
Pass a Redux store instance, from `Redux.createStore`. This instance should have
|
||||
the Uppy reducer mounted somewhere already.
|
||||
|
||||
#### `opts.id`
|
||||
|
||||
By default, the `ReduxStore` assumes Uppy state is stored on a `state.uppy[id]`
|
||||
key. `id` is randomly generated by the store constructor, but can be specified
|
||||
by passing an `id` option if it should be predictable.
|
||||
|
||||
```js
|
||||
ReduxStore({
|
||||
store,
|
||||
id: 'avatarUpload',
|
||||
});
|
||||
```
|
||||
|
||||
#### `opts.selector`
|
||||
|
||||
If you’d rather not store the Uppy state under the `state.uppy` key at all, use
|
||||
the `selector` option to the `ReduxStore` constructor to tell it where to find
|
||||
state instead:
|
||||
|
||||
```js
|
||||
const uppy = new Uppy({
|
||||
store: ReduxStore({
|
||||
store,
|
||||
id: 'avatarUpload',
|
||||
selector: (state) => state.pages.profile.uppy.avatarUpload,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
Note that when specifying a custom selector, you **must** also specify a custom
|
||||
store ID. The store `id` tells the reducer in which property it should put
|
||||
Uppy’s state. The selector must then take the state from that property. In the
|
||||
example, we set the ID to `avatarUpload` and take the state from the
|
||||
`[reducer mount path].avatarUpload`.
|
||||
|
||||
If your app uses [`reselect`](https://npmjs.com/package/reselect), its selectors
|
||||
work well with this!
|
||||
|
||||
## Implementing Stores
|
||||
|
||||
An Uppy store is an object with three methods.
|
||||
|
||||
* `getState()` - Return the current state object.
|
||||
* `setState(patch)` - Merge the object `patch` into the current state.
|
||||
* `subscribe(listener)` - Call `listener` whenever the state changes. `listener`
|
||||
is a function that should receive three parameters:
|
||||
`(prevState, nextState, patch)`
|
||||
|
||||
The `subscribe()` method should return a function that “unsubscribes”
|
||||
(removes) the `listener`.
|
||||
|
||||
The default store implementation, for example, looks a bit like this:
|
||||
|
||||
```js
|
||||
function createDefaultStore() {
|
||||
let state = {};
|
||||
const listeners = new Set();
|
||||
|
||||
return {
|
||||
getState: () => state,
|
||||
setState: (patch) => {
|
||||
const prevState = state;
|
||||
const nextState = { ...prevState, ...patch };
|
||||
|
||||
state = nextState;
|
||||
|
||||
listeners.forEach((listener) => {
|
||||
listener(prevState, nextState, patch);
|
||||
});
|
||||
},
|
||||
subscribe: (listener) => {
|
||||
listeners.add(listener);
|
||||
return () => listeners.remove(listener);
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
A pattern like this, where users can pass options via a function call if
|
||||
necessary, is recommended.
|
||||
|
||||
See the
|
||||
[@uppy/store-default](https://github.com/transloadit/uppy/tree/main/packages/%40uppy/store-default)
|
||||
package for more inspiration.
|
||||
651
docs/guides/migration-guides.md
Normal file
651
docs/guides/migration-guides.md
Normal file
|
|
@ -0,0 +1,651 @@
|
|||
# Migration guides
|
||||
|
||||
These cover all the major Uppy versions and how to migrate to them.
|
||||
|
||||
## Migrate from Robodog to Uppy plugins
|
||||
|
||||
Uppy is flexible and extensible through plugins. But the integration code could
|
||||
sometimes be daunting. This is what brought Robodog to life. An alternative with
|
||||
the same features, but with a more ergonomic and minimal API.
|
||||
|
||||
But, it didn’t come with its own set of new problems:
|
||||
|
||||
* It tries to do the exact same, but it looks like a different product.
|
||||
* It’s confusing for users whether they want to use Robodog or Uppy directly.
|
||||
* Robodog is more ergonomic because it’s limited. When you hit such a limit, you
|
||||
need to refactor everything to Uppy with plugins.
|
||||
|
||||
This has now led us to deprecating Robodog and embrace Uppy for its strong
|
||||
suits; modularity and flexibility. At the same time, we also introduced
|
||||
something to take away some repetitive integration code:
|
||||
[`@uppy/remote-sources`](/docs/remote-sources).
|
||||
|
||||
To mimic the Robodog implementation with all its features, you can use the code
|
||||
snippet below. But chances are Robodog did more than you need so feel free to
|
||||
remove things or go through the [list of plugins](/docs/companion/) and install
|
||||
and use the ones you need.
|
||||
|
||||
You can also checkout how we migrated the Robodog example ourselves in this
|
||||
[commit](https://github.com/transloadit/uppy/commit/089aaed615c77bafaf905e291b6b4e82aaeb2f6f).
|
||||
|
||||
```js
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import RemoteSources from '@uppy/remote-sources';
|
||||
import Webcam from '@uppy/webcam';
|
||||
import ScreenCapture from '@uppy/screen-capture';
|
||||
import GoldenRetriever from '@uppy/golden-retriever';
|
||||
import ImageEditor from '@uppy/image-editor';
|
||||
import Audio from '@uppy/audio';
|
||||
import Transloadit, {
|
||||
COMPANION_URL,
|
||||
COMPANION_ALLOWED_HOSTS,
|
||||
} from '@uppy/transloadit';
|
||||
|
||||
import '@uppy/core/dist/style.css';
|
||||
import '@uppy/dashboard/dist/style.css';
|
||||
import '@uppy/audio/dist/style.css';
|
||||
import '@uppy/screen-capture/dist/style.css';
|
||||
import '@uppy/image-editor/dist/style.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, {
|
||||
inline: true,
|
||||
target: '#app',
|
||||
showProgressDetails: true,
|
||||
proudlyDisplayPoweredByUppy: true,
|
||||
})
|
||||
.use(RemoteSources, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
})
|
||||
.use(Webcam, {
|
||||
target: Dashboard,
|
||||
showVideoSourceDropdown: true,
|
||||
showRecordingLength: true,
|
||||
})
|
||||
.use(Audio, {
|
||||
target: Dashboard,
|
||||
showRecordingLength: true,
|
||||
})
|
||||
.use(ScreenCapture, { target: Dashboard })
|
||||
.use(ImageEditor, { target: Dashboard })
|
||||
.use(Transloadit, {
|
||||
service: 'https://api2.transloadit.com',
|
||||
async getAssemblyOptions(file) {
|
||||
// This is where you configure your auth key, auth secret, and template ID
|
||||
// /uppy/docs/transloadit/#getAssemblyOptions-file
|
||||
//
|
||||
// It is important to set the secret in production:
|
||||
// https://transloadit.com/docs/topics/signature-authentication/
|
||||
const response = await fetch('/some-endpoint');
|
||||
return response.json();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Migrate from Uppy 2.x to 3.x
|
||||
|
||||
### Uppy is pure ESM
|
||||
|
||||
Following the footsteps of many packages, we now only ship Uppy core and its
|
||||
plugins as
|
||||
[ECMAScript Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)
|
||||
(ESM). On Uppy 2.x, we were shipping CommonJS.
|
||||
|
||||
If are already using ESM yourself, or are using the CDN builds, nothing changes
|
||||
for you!
|
||||
|
||||
If you are using CommonJS, you might need to add some tooling for everything to
|
||||
work, or you might want to refactor your codebase to ESM – refer to the
|
||||
[Pure ESM package](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c)
|
||||
gist for added information and help on how to do that.
|
||||
|
||||
### Robodog is deprecated
|
||||
|
||||
See the [Robodog migration guide](#Migrate-from-Robodog-to-Uppy-plugins).
|
||||
|
||||
### `@uppy/core`
|
||||
|
||||
#### Remove `AggregateError` polyfill.
|
||||
|
||||
It’s supported by most modern browsers and
|
||||
[can be polyfilled by the user](https://github.com/transloadit/uppy/pull/3532#discussion_r818602636)
|
||||
if needed.
|
||||
|
||||
To migrate: install a `AggregateError` polyfill or use `core-js`.
|
||||
|
||||
#### Remove `reset()` method.
|
||||
|
||||
It’s a duplicate of `cancelAll`, but with a less intention revealing name.
|
||||
|
||||
To migrate: use `cancelAll`.
|
||||
|
||||
#### Remove backwards compatible exports (static properties on `Uppy`)\`
|
||||
|
||||
`Uppy`, `UIPlugin`, `BasePlugin`, and `debugLogger` used to also be accessible
|
||||
on the `Uppy` export. This has now been removed due to the transition to ESM.
|
||||
|
||||
To migrate: import the `Uppy` class by default and/or use named exports for
|
||||
everything else.
|
||||
|
||||
#### `uppy.validateRestrictions()` now returns a `RestrictionError`
|
||||
|
||||
This method used to return `{ result: false, reason: err.message }`, but that
|
||||
felt strange as it tries to mimic an error. Instead it now return a
|
||||
`RestrictionError`, which is extended `Error` class.
|
||||
|
||||
To migrate: check the return value, if it’s defined you have an error, otherwise
|
||||
all went well. Note that the error is `return`’ed, it’s not `throw`’n, so you
|
||||
don’t have to `catch` it.
|
||||
|
||||
### `@uppy/transloadit`
|
||||
|
||||
Remove export of `ALLOWED_COMPANION_PATTERN`, `COMPANION`, and
|
||||
`COMPANION_PATTERN` in favor of `COMPANION_URL` and `COMPANION_ALLOWED_HOSTS`.
|
||||
This is to have more intention revealing names, `COMPANION` sounds like the
|
||||
Companion instance, `COMPANION_URL` makes it more clear that it’s a URL.
|
||||
|
||||
These are properties can now be imported and used for remote sources plugins
|
||||
when using Transloadit:
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
|
||||
// ...
|
||||
uppy.use(Dropbox, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
});
|
||||
```
|
||||
|
||||
### `@uppy/aws-s3-multipart`
|
||||
|
||||
#### Make `headers` inside the return value of [`prepareUploadParts`](/docs/aws-s3-multipart/#prepareUploadParts-file-partData) part-indexed too.
|
||||
|
||||
This is to allow custom headers to be set per part. See this
|
||||
[issue](https://github.com/transloadit/uppy/issues/3881) for details.
|
||||
|
||||
To migrate: make headers part indexed like `presignedUrls`:
|
||||
`{ "headers": { "1": { "Content-MD5": "foo" } }}`.
|
||||
|
||||
#### Remove `client` getter and setter.
|
||||
|
||||
It’s internal usage only.
|
||||
|
||||
To migrate: use exposed options only.
|
||||
|
||||
### `@uppy/tus/`, `@uppy/aws-s3`, `@uppy/xhr-upload`
|
||||
|
||||
Rename `metaFields` option to `allowedMetaFields`. Counter intuitively,
|
||||
`metaFields` is for _filtering_ which `metaFields` to send along with the
|
||||
request, not for adding extra meta fields to a request. As a lot of people were
|
||||
confused by this, and the name overlaps with the
|
||||
[`metaFields` option from Dashboard](/docs/dashboard/#metaFields), we renamed
|
||||
it.
|
||||
|
||||
To migrate: use `allowedMetaFields`.
|
||||
|
||||
### `@uppy/react`
|
||||
|
||||
#### Uppy dependencies have become peer dependencies
|
||||
|
||||
`@uppy/dashboard`, `@uppy/drag-drop`, `@uppy/file-input`, `@uppy/progress-bar`,
|
||||
and `@uppy/status-bar` are now peer dependencies. This means you don’t install
|
||||
all these packages if you only need one.
|
||||
|
||||
To migrate: install only the packages you need. If you use the Dashboard
|
||||
component, you need `@uppy/dashboard`, and so onwards.
|
||||
|
||||
#### Don’t expose `validProps` on the exported components.
|
||||
|
||||
It’s internal usage only.
|
||||
|
||||
To migrate: use exposed options only.
|
||||
|
||||
### `@uppy/svelte`
|
||||
|
||||
`@uppy/dashboard`, `@uppy/drag-drop`, `@uppy/progress-bar`, and
|
||||
`@uppy/status-bar` are now peer dependencies. This means you don’t install all
|
||||
these packages if you only need one.
|
||||
|
||||
To migrate: install only the packages you need. If you use the Dashboard
|
||||
component, you need `@uppy/dashboard`, and so onwards.
|
||||
|
||||
### `@uppy/vue`
|
||||
|
||||
`@uppy/dashboard`, `@uppy/drag-drop`, `@uppy/file-input`, `@uppy/progress-bar`,
|
||||
and `@uppy/status-bar` are now peer dependencies. This means you don’t install
|
||||
all these packages if you only need one.
|
||||
|
||||
To migrate: install only the packages you need. If you use the Dashboard
|
||||
component, you need `@uppy/dashboard`, and so onwards.
|
||||
|
||||
### `@uppy/store-redux`
|
||||
|
||||
Remove backwards compatible exports (static properties on `ReduxStore`).
|
||||
Exports, such as `reducer`, used to also be accessible on the `ReduxStore`
|
||||
export. This has now been removed due to the transition to ESM.
|
||||
|
||||
To migrate: use named imports.
|
||||
|
||||
### `@uppy/thumbnail-generator`
|
||||
|
||||
Remove `rotateImage`, `protect`, and `canvasToBlob` from the plugin prototype.
|
||||
They are internal usage only.
|
||||
|
||||
To migrate: use exposed options only.
|
||||
|
||||
### Known issues
|
||||
|
||||
* [`ERESOLVE could not resolve` on npm install](https://github.com/transloadit/uppy/issues/4057).
|
||||
* [@uppy/svelte reports a broken dependency with the Vite bundler](https://github.com/transloadit/uppy/issues/4069).
|
||||
|
||||
## Migrate from Companion 3.x to 4.x
|
||||
|
||||
### Minimum required Node.js version is v14.20.0
|
||||
|
||||
Aligning with the Node.js
|
||||
[Long Term Support (LTS) schedule](https://nodejs.org/en/about/releases/) and to
|
||||
use modern syntax features.
|
||||
|
||||
### `companion.app()` returns `{ app, emitter }` instead of `app`
|
||||
|
||||
Companion 3.x provides the emitter as `companionEmitter` on `app`. As of 4.x, an
|
||||
object is returned with an `app` property (express middleware) and an `emitter`
|
||||
property (event emitter). This provides more flexibility in the future and
|
||||
follows best practices.
|
||||
|
||||
### Removed `searchProviders` wrapper object inside `providerOptions`
|
||||
|
||||
To use [`@uppy/unsplash`](/docs/unsplash), you had to configure Unsplash in
|
||||
Companion inside `providerOptions.searchProviders`. This is redundant, Unsplash
|
||||
is a provider as well so we removed the wrapper object.
|
||||
|
||||
### Moved the `s3` options out of `providerOptions`
|
||||
|
||||
To use AWS S3 for storage, you configured the `s3` object inside
|
||||
`providerOptions`. But as S3 is not a provider but a destination. To avoid
|
||||
confusion we moved the `s3` settings to the root settings object.
|
||||
|
||||
### Removed compatibility for legacy Custom Provider implementations
|
||||
|
||||
[Custom Provider](/docs/companion/#Adding-custom-providers) implementations must
|
||||
use the Promise API. The callback API is no longer supported.
|
||||
|
||||
### Default to no ACL for AWS S3
|
||||
|
||||
Default to no
|
||||
[ACL](https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html)
|
||||
for S3 uploads. Before the default was `public-read` but AWS now discourages
|
||||
ACLs. The environment variable `COMPANION_AWS_DISABLE_ACL` is also removed,
|
||||
instead Companion only uses `COMPANION_AWS_ACL`.
|
||||
|
||||
### `protocol` sent from Uppy in any `get` request is now required (before it would default to Multipart).
|
||||
|
||||
If you use any official Uppy plugins, then no migration is needed. For custom
|
||||
plugins that talk to Companion, make to send along the `protocol` header with a
|
||||
value of `multipart`, `s3Multipart`, or `tus`.
|
||||
|
||||
### `emitSuccess` and `emitError` are now private methods on the `Uploader` class.
|
||||
|
||||
It’s unlikely you’re using this, but it’s technically a breaking change. In
|
||||
general, don’t depend on implicitly internal methods, use exposed APIs instead.
|
||||
|
||||
### Removed `chunkSize` backwards compatibility for AWS S3 Multipart
|
||||
|
||||
`chunkSize` option will now be used as `partSize` in AWS multipart. Before only
|
||||
valid values would be respected. Invalid values would be ignored. Now any value
|
||||
will be passed on to the AWS SDK, possibly throwing an error on invalid values.
|
||||
|
||||
### Removed backwards compatibility for `/metrics` endpoint
|
||||
|
||||
The `metrics` option is a boolean flag to tell Companion whether to provide an
|
||||
endpoint `/metrics` with Prometheus metrics. Metrics will now always be served
|
||||
under `options.server.path`. Before v4.x, it would always be served under the
|
||||
root.
|
||||
|
||||
For example: if `{ options: { metrics: true, server: { path: '/companion' }}}`,
|
||||
metrics will now be served under `/companion/metrics`. In v3.x, the metrics
|
||||
would be served under `/metrics`.
|
||||
|
||||
## Migrate from Uppy 1.x to 2.x
|
||||
|
||||
### New bundle requires manual polyfilling
|
||||
|
||||
With 2.0, following in the footsteps of Microsoft, we are dropping support for
|
||||
IE11. As a result, we are able to remove all built-in polyfills, and the new
|
||||
bundle size is **25% smaller**! If you want your app to still support older
|
||||
browsers (such as IE11), you may need to add the following polyfills to your
|
||||
bundle:
|
||||
|
||||
* [abortcontroller-polyfill](https://github.com/mo/abortcontroller-polyfill)
|
||||
* [core-js](https://github.com/zloirock/core-js)
|
||||
* [md-gum-polyfill](https://github.com/mozdevs/mediaDevices-getUserMedia-polyfill)
|
||||
* [resize-observer-polyfill](https://github.com/que-etc/resize-observer-polyfill)
|
||||
* [whatwg-fetch](https://github.com/github/fetch)
|
||||
|
||||
If you’re using a bundler, you need import these before Uppy:
|
||||
|
||||
```js
|
||||
import 'core-js';
|
||||
import 'whatwg-fetch';
|
||||
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch';
|
||||
// Order matters here: AbortController needs fetch, which needs Promise (provided by core-js).
|
||||
|
||||
import 'md-gum-polyfill';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
window.ResizeObserver ??= ResizeObserver;
|
||||
|
||||
export { default } from '@uppy/core';
|
||||
export * from '@uppy/core';
|
||||
```
|
||||
|
||||
If you’re using Uppy from a CDN, we now provide two bundles: one for up-to-date
|
||||
browsers that do not include polyfills and use modern syntax, and one for legacy
|
||||
browsers. When migrating, be mindful about the types of browsers you want to
|
||||
support:
|
||||
|
||||
```html
|
||||
<!-- Modern browsers (recommended) -->
|
||||
<script src="https://releases.transloadit.com/uppy/v3.17.0/uppy.min.js"></script>
|
||||
|
||||
<!-- Legacy browsers (IE11+) -->
|
||||
<script
|
||||
nomodule
|
||||
src="https://releases.transloadit.com/uppy/v3.17.0/uppy.legacy.min.js"
|
||||
></script>
|
||||
<script type="module">
|
||||
import 'https://releases.transloadit.com/uppy/v3.17.0/uppy.min.js';
|
||||
</script>
|
||||
```
|
||||
|
||||
Please note that while you may be able to get 2.0 to work in IE11 this way, we
|
||||
do not officially support it anymore.
|
||||
|
||||
### Use `BasePlugin` or `UIPlugin` instead of `Plugin`
|
||||
|
||||
[`@uppy/core`][core] used to provide a `Plugin` class for creating plugins. This
|
||||
was used for any official plugin, but also for users who want to create their
|
||||
own custom plugin. But, `Plugin` always came bundled with Preact, even if the
|
||||
plugin itself didn’t add any UI elements.
|
||||
|
||||
`Plugin` has been replaced with `BasePlugin` and `UIPlugin`. `BasePlugin` is the
|
||||
minimum you need to create a plugin and `UIPlugin` adds Preact for rendering
|
||||
user interfaces.
|
||||
|
||||
You can import them from [`@uppy/core`][core]:
|
||||
|
||||
```js
|
||||
import { BasePlugin, UIPlugin } from '@uppy/core';
|
||||
```
|
||||
|
||||
**Note:** some bundlers will include `UIPlugin` (and thus Preact) if you import
|
||||
from `@uppy/core`. To make sure this does not happen, you can import `Uppy` and
|
||||
`BasePlugin` directly:
|
||||
|
||||
```js
|
||||
import Uppy from '@uppy/core/lib/Uppy.js';
|
||||
import BasePlugin from '@uppy/core/lib/BasePlugin.js';
|
||||
```
|
||||
|
||||
### Use the latest Preact for your Uppy plugins
|
||||
|
||||
Official plugins have already been upgraded. If you are using any custom
|
||||
plugins, upgrade Preact to the latest version. At the time of writing this is
|
||||
`10.5.13`.
|
||||
|
||||
### Set plugin titles from locales
|
||||
|
||||
Titles for plugins used to be set with the `title` property in the plugin
|
||||
options, but all other strings are set in `locale`. This has now been aligned.
|
||||
You should set your plugin title from the `locale` property.
|
||||
|
||||
Before
|
||||
|
||||
```js
|
||||
import Webcam from '@uppy/webcam';
|
||||
|
||||
uppy.use(Webcam, {
|
||||
title: 'Some title',
|
||||
});
|
||||
```
|
||||
|
||||
After
|
||||
|
||||
```js
|
||||
import Webcam from '@uppy/webcam';
|
||||
|
||||
uppy.use(Webcam, {
|
||||
locale: {
|
||||
strings: {
|
||||
title: 'Some title',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Initialize Uppy with the `new` keyword
|
||||
|
||||
The default export `Uppy` is no longer callable as a function. This means you
|
||||
construct the `Uppy` instance using the `new` keyword.
|
||||
|
||||
```js
|
||||
import Uppy from '@uppy/core';
|
||||
|
||||
const uppy = new Uppy(); // correct.
|
||||
|
||||
const otherUppy = Uppy(); // incorrect, will throw.
|
||||
```
|
||||
|
||||
### Rename `allowMultipleUploads` to `allowMultipleUploadBatches`
|
||||
|
||||
[`allowMultipleUploadBatches`](/docs/uppy/#allowmultipleuploadbatches) means
|
||||
allowing several calls to [`.upload()`](/docs/uppy/#upload), in other words, a
|
||||
user can add more files after already having uploaded some.
|
||||
|
||||
<!--retext-simplify ignore multiple-->
|
||||
|
||||
We have renamed this to be more intention revealing that this is about uploads,
|
||||
and not whether a user can choose multiple files for one upload.
|
||||
|
||||
```js
|
||||
const uppy = new Uppy({
|
||||
allowMultipleUploadBatches: true,
|
||||
});
|
||||
```
|
||||
|
||||
### New default limits for [`@uppy/xhr-upload`][xhr] and [`@uppy/tus`][tus]
|
||||
|
||||
The default limit has been changed from `0` to `5`. Setting this to `0` means no
|
||||
limit on concurrent uploads.
|
||||
|
||||
You can change the limit on the Tus and XHR plugin options.
|
||||
|
||||
```js
|
||||
uppy.use(Tus, {
|
||||
// ...
|
||||
limit: 10,
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
uppy.use(XHRUpload, {
|
||||
// ...
|
||||
limit: 10,
|
||||
});
|
||||
```
|
||||
|
||||
### TypeScript changes
|
||||
|
||||
Uppy used to have loose types by default and strict types as an opt-in. The
|
||||
default export was a function that returned the `Uppy` class, and the types came
|
||||
bundled with the default export (`Uppy.SomeType`).
|
||||
|
||||
```ts
|
||||
import Uppy from '@uppy/core';
|
||||
import Tus from '@uppy/tus';
|
||||
|
||||
const uppy = Uppy<Uppy.StrictTypes>();
|
||||
|
||||
uppy.use(Tus, {
|
||||
invalidOption: null, // this will make the compilation fail!
|
||||
});
|
||||
```
|
||||
|
||||
Uppy is now strictly typed by default and loose types have been removed.
|
||||
|
||||
```ts
|
||||
// ...
|
||||
|
||||
const uppy = new Uppy();
|
||||
|
||||
uppy.use(Tus, {
|
||||
invalidOption: null, // this will make the compilation fail!
|
||||
});
|
||||
```
|
||||
|
||||
Uppy types are now individual exports and should be imported separately.
|
||||
|
||||
<!-- eslint-disable @typescript-eslint/no-unused-vars -->
|
||||
|
||||
```ts
|
||||
import type { PluginOptions, UIPlugin, PluginTarget } from '@uppy/core';
|
||||
```
|
||||
|
||||
#### Event types
|
||||
|
||||
[`@uppy/core`][core] provides an [`.on`](/docs/uppy/#uppy-on-39-event-39-action)
|
||||
method to listen to [events](/docs/uppy/#Events). The types for these events
|
||||
were loose and allowed for invalid events to be passed, such as
|
||||
`uppy.on('upload-errrOOOoooOOOOOrrrr')`.
|
||||
|
||||
<!-- eslint-disable @typescript-eslint/no-unused-vars -->
|
||||
|
||||
```ts
|
||||
// Before:
|
||||
|
||||
type Meta = { myCustomMetadata: string };
|
||||
|
||||
// Invalid event
|
||||
uppy.on<Meta>('upload-errrOOOoooOOOOOrrrr', () => {
|
||||
// ...
|
||||
});
|
||||
|
||||
// After:
|
||||
|
||||
// Normal event signature
|
||||
uppy.on('complete', (result) => {
|
||||
const successResults = result.successful;
|
||||
});
|
||||
|
||||
// Custom signature
|
||||
type Meta = { myCustomMetadata: string };
|
||||
|
||||
// Notice how the custom type has now become the second argument
|
||||
uppy.on<'complete', Meta>('complete', (result) => {
|
||||
// The passed type is now merged into the `meta` types.
|
||||
const meta = result.successful[0].meta.myCustomMetadata;
|
||||
});
|
||||
```
|
||||
|
||||
Plugins that add their own events can merge with existing ones in `@uppy/core`
|
||||
with `declare module '@uppy/core' { ... }`. This is a TypeScript pattern called
|
||||
[module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation).
|
||||
For instance, when using [`@uppy/dashboard`][dashboard]:
|
||||
|
||||
<!-- eslint-disable @typescript-eslint/no-unused-vars -->
|
||||
|
||||
```ts
|
||||
uppy.on('dashboard:file-edit-start', (file) => {
|
||||
const fileName = file.name;
|
||||
});
|
||||
```
|
||||
|
||||
### Changes to pre-signing URLs for [`@uppy/aws-s3-multipart`][aws-s3-multipart]
|
||||
|
||||
See the Uppy 2.0.0 announcement post about the batch
|
||||
[pre-signing URLs change](/blog/2021/08/2.0/#Batch-pre-signing-URLs-for-AWS-S3-Multipart).
|
||||
|
||||
`prepareUploadPart` has been renamed to
|
||||
[`prepareUploadParts`](/docs/aws-s3-multipart/#prepareUploadParts-file-partData)
|
||||
(plural). See the documentation link on how to use this function.
|
||||
|
||||
### Removed the `.run` method from [`@uppy/core`][core]
|
||||
|
||||
The `.run` method on the `Uppy` instance has been removed. This method was
|
||||
already obsolete and only logged a warning. As of this major version, it no
|
||||
longer exists.
|
||||
|
||||
### Removed `resume` and `removeFingerprintOnSuccess` options from [`@uppy/tus`][tus]
|
||||
|
||||
Tus will now by default try to resume uploads if the upload has been started in
|
||||
the past.
|
||||
|
||||
This also means tus will store some data in localStorage for each upload, which
|
||||
will automatically be removed on success. Making `removeFingerprintOnSuccess`
|
||||
obsolete too.
|
||||
|
||||
### That’s it!
|
||||
|
||||
Uppy 1.0 will continue to receive bug fixes for three more months (until <time datetime="2021-12-01">1 December 2021</time>), security fixes for one more
|
||||
year (until <time datetime="2022-09-01">1 September 2022</time>), but no more
|
||||
new features after today. Exceptions are unlikely, but _can_ be made – to
|
||||
accommodate those with commercial support contracts, for example.
|
||||
|
||||
We hope you’ll waste no time in taking Uppy 2.0 out for a walk. When you do,
|
||||
please let us know what you thought of it on
|
||||
[Reddit](https://www.reddit.com/r/javascript/comments/penbr7/uppy_file_uploader_20_smaller_and_faster_modular/),
|
||||
[HN](https://news.ycombinator.com/item?id=28359287), ProductHunt, or
|
||||
[Twitter](https://twitter.com/uppy_io/status/1432399270846603264). We’re howling
|
||||
at the moon to hear from you!
|
||||
|
||||
## Migrate from Companion 1.x to 2.x
|
||||
|
||||
### Prerequisite
|
||||
|
||||
Since v2, you now need to be running `node.js >= v10.20.1` to use Companion.
|
||||
|
||||
### ProviderOptions
|
||||
|
||||
In v2 the `google` and `microsoft` [providerOptions](/docs/companion/#Options)
|
||||
have been changed to `drive` and `onedrive` respectively.
|
||||
|
||||
### OAuth Redirect URIs
|
||||
|
||||
On your Providers’ respective developer platforms, the OAuth redirect URIs that
|
||||
you should supply has now changed from:
|
||||
|
||||
`http(s)://$COMPANION_HOST_NAME/connect/$AUTH_PROVIDER/callback` in v1
|
||||
|
||||
to:
|
||||
|
||||
`http(s)://$COMPANION_HOST_NAME/$PROVIDER_NAME/redirect` in v2
|
||||
|
||||
#### New Redirect URIs
|
||||
|
||||
<div class="table-responsive">
|
||||
|
||||
| Provider | New Redirect URI |
|
||||
| ------------ | ------------------------------------------------- |
|
||||
| Dropbox | `https://$COMPANION_HOST_NAME/dropbox/redirect` |
|
||||
| Google Drive | `https://$COMPANION_HOST_NAME/drive/redirect` |
|
||||
| OneDrive | `https://$COMPANION_HOST_NAME/onedrive/redirect` |
|
||||
| Box | `https://$YOUR_COMPANION_HOST_NAME/box/redirect` |
|
||||
| Facebook | `https://$COMPANION_HOST_NAME/facebook/redirect` |
|
||||
| Instagram | `https://$COMPANION_HOST_NAME/instagram/redirect` |
|
||||
|
||||
</div>
|
||||
|
||||
<!-- definitions -->
|
||||
|
||||
[core]: /docs/uppy/
|
||||
|
||||
[xhr]: /docs/xhr-upload/
|
||||
|
||||
[dashboard]: /docs/dashboard/
|
||||
|
||||
[aws-s3-multipart]: /docs/aws-s3-multipart/
|
||||
|
||||
[tus]: /docs/tus/
|
||||
1562
docs/locales.mdx
Normal file
1562
docs/locales.mdx
Normal file
File diff suppressed because it is too large
Load diff
4
docs/presets/_category_.json
Normal file
4
docs/presets/_category_.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"label": "Presets",
|
||||
"position": 9
|
||||
}
|
||||
141
docs/presets/remote-sources.mdx
Normal file
141
docs/presets/remote-sources.mdx
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
slug: /remote-sources
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Remote sources
|
||||
|
||||
`@uppy/remote-sources` is a preset plugin (meaning it bundles and sets up other
|
||||
plugins) to add all the available remote sources, such Instagram, Google Drive,
|
||||
Dropbox, and others to Uppy Dashboard in one package.
|
||||
|
||||
:::note
|
||||
|
||||
Remote Sources requires Dashboard and automatically installs all its plugins to
|
||||
it.
|
||||
|
||||
:::
|
||||
|
||||
## When should I use it?
|
||||
|
||||
`@uppy/remote-sources` aims to simplify the setup for adding Companion plugins,
|
||||
when you want to share the configuration between plugins. If you want your users
|
||||
to upload files from any of the remote sources that Uppy offers, this plugin is
|
||||
recommended.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Remote Sources
|
||||
plugin to work. Companion handles authentication with the remote services (such
|
||||
as Facebook, Dropbox, etc.), downloads the files, and uploads them to the
|
||||
destination. This saves the user bandwidth, especially helpful if they are on a
|
||||
mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/remote-sources
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/remote-sources
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { RemoteSources } from "{{UPPY_JS_URL}}"
|
||||
const RemoteSources = new Uppy().use(RemoteSources)
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js {10} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import RemoteSources from '@uppy/remote-sources';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy();
|
||||
.use(Dashboard);
|
||||
.use(RemoteSources, { companionUrl: 'https://your-companion-url' });
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `RemoteSources`).
|
||||
|
||||
#### `sources`
|
||||
|
||||
List of remote sources that will be enabled (`array`, default:
|
||||
`['Box', 'Dropbox', 'Facebook', 'GoogleDrive','Instagram', 'OneDrive', 'Unsplash', 'Url', 'Zoom']`).
|
||||
|
||||
You don’t need to specify them manually or change them, but if you want to alter
|
||||
the order in which they appear in the Dashboard, or disable some sources, this
|
||||
option is for you.
|
||||
|
||||
```js
|
||||
uppy.use(RemoteSources, {
|
||||
companionUrl: 'https://your-companion-url',
|
||||
sources: ['Instagram', 'GoogleDrive', 'Unsplash', 'Url'],
|
||||
});
|
||||
```
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorized URL(s)/URL pattern(s) from which OAuth responses should
|
||||
be accepted (`string | RegExp | Array<string | RegExp>`, Default:
|
||||
`companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` object, or an array of both.
|
||||
|
||||
This is useful when you have your [Companion](/docs/companion) running on
|
||||
several hosts. Otherwise, the default value, which is `companionUrl`, should do
|
||||
fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the [`Request.credentials` value][], which tells the
|
||||
plugin whether to send cookies to [Companion](/docs/companion) (`string`,
|
||||
default: `same-origin`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string`, `Element`, `Function`, or `UIPlugin`, default: `Dashboard`).
|
||||
|
||||
[`request.credentials` value]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
|
||||
58
docs/quick-start.mdx
Normal file
58
docs/quick-start.mdx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
import { QuickStartLinks } from '../src/components/QuickStartLinks/QuickStartLinks.tsx';
|
||||
|
||||
# Quick start
|
||||
|
||||
Uppy is a sleek, modular JavaScript file uploader that integrates seamlessly
|
||||
with any application. It’s fast, has a comprehensible API and lets you worry
|
||||
about more important problems than building a file uploader.
|
||||
|
||||
:::tip
|
||||
|
||||
You can take Uppy for a walk inside CodeSandbox with a
|
||||
[minimal drag & drop](https://codesandbox.io/s/uppy-drag-drop-gyewzx?file=/src/index.js)
|
||||
experience or a
|
||||
[full featured dashboard](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
<QuickStartLinks
|
||||
items={[
|
||||
{
|
||||
name: 'I want a full featured, extendable UI',
|
||||
description: 'Learn more about the Dashboard',
|
||||
link: '/docs/dashboard',
|
||||
},
|
||||
{
|
||||
name: 'I want a minimal drag & drop UI',
|
||||
description: 'We have a lightweight plugin for that',
|
||||
link: '/docs/drag-drop',
|
||||
},
|
||||
{
|
||||
name: 'Which uploader do I need?',
|
||||
description: 'Choosing the uploader you need',
|
||||
link: '/docs/guides/choosing-uploader',
|
||||
},
|
||||
{
|
||||
name: 'I want to add files from remote sources',
|
||||
description: 'Such as Google Drive, Dropbox, Instagram',
|
||||
link: '/docs/companion',
|
||||
},
|
||||
{
|
||||
name: 'I’d like a project example',
|
||||
description: 'Try out one of our extensive example projects',
|
||||
link: 'https://github.com/transloadit/uppy/tree/main/examples',
|
||||
},
|
||||
{
|
||||
name: 'I have a question',
|
||||
description: 'Our community forum is there to help',
|
||||
link: 'https://community.transloadit.com/',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
4
docs/sources/_category_.json
Normal file
4
docs/sources/_category_.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"label": "Sources",
|
||||
"position": 6
|
||||
}
|
||||
134
docs/sources/audio.mdx
Normal file
134
docs/sources/audio.mdx
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
slug: /audio
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Audio
|
||||
|
||||
The `@uppy/audio` plugin lets you record audio using a built-in or external
|
||||
microphone, or any other audio device, on desktop and mobile. The UI shows real
|
||||
time sound wavelength when recording.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want users to record audio on their computer.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/audio
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/audio
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Dashboard, Audio } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Dashboard, { inline: true, target: 'body' })
|
||||
uppy.use(Audio, { target: Uppy.Dashboard })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js {3,7,11} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Audio from '@uppy/audio';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
import '@uppy/audio/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: 'body' })
|
||||
.use(Audio, { target: Dashboard });
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'audio'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Configures the title / name shown in the UI, for instance, on Dashboard tabs
|
||||
(`string`, default: `'Audio'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the audio into (`string` or
|
||||
`Element`, default: `null`).
|
||||
|
||||
#### `showAudioSourceDropdown`
|
||||
|
||||
Configures whether to show a dropdown to select audio device (`boolean`,
|
||||
default: `false`).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameAudio: 'Audio',
|
||||
// Used as the label for the button that starts an audio recording.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
startAudioRecording: 'Begin audio recording',
|
||||
// Used as the label for the button that stops an audio recording.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
stopAudioRecording: 'Stop audio recording',
|
||||
// Title on the “allow access” screen
|
||||
allowAudioAccessTitle: 'Please allow access to your microphone',
|
||||
// Description on the “allow access” screen
|
||||
allowAudioAccessDescription:
|
||||
'In order to record audio, please allow microphone access for this site.',
|
||||
// Title on the “device not available” screen
|
||||
noAudioTitle: 'Microphone Not Available',
|
||||
// Description on the “device not available” screen
|
||||
noAudioDescription:
|
||||
'In order to record audio, please connect a microphone or another audio input device',
|
||||
// Message about file size will be shown in an Informer bubble
|
||||
recordingStoppedMaxSize:
|
||||
'Recording stopped because the file size is about to exceed the limit',
|
||||
// Used as the label for the counter that shows recording length (`1:25`).
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
recordingLength: 'Recording length %{recording_length}',
|
||||
// Used as the label for the submit checkmark button.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
submitRecordedFile: 'Submit recorded file',
|
||||
// Used as the label for the discard cross button.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
discardRecordedFile: 'Discard recorded file',
|
||||
},
|
||||
};
|
||||
```
|
||||
5
docs/sources/companion-plugins/_category_.json
Normal file
5
docs/sources/companion-plugins/_category_.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"label": "Companion plugins",
|
||||
"position": 3,
|
||||
"collapsed": false
|
||||
}
|
||||
189
docs/sources/companion-plugins/box.mdx
Normal file
189
docs/sources/companion-plugins/box.mdx
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
---
|
||||
slug: /box
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Box
|
||||
|
||||
The `@uppy/box` plugin lets users import files from their
|
||||
[Box](https://www.box.com/en-nl/home) account.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files from their
|
||||
[Box](https://www.box.com/en-nl/home) account.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Box plugin to work.
|
||||
Companion handles authentication with Box, downloads the files, and uploads them
|
||||
to the destination. This saves the user bandwidth, especially helpful if they
|
||||
are on a mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/box
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/box
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Box } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Box, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using Box requires setup in both Uppy and Companion.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Box from '@uppy/box';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy().use(Dashboard, { inline: true, target: '#dashboard' }).use(Box, {
|
||||
target: Dashboard,
|
||||
companionUrl: 'https://your-companion.com',
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
You can create a Box App on the
|
||||
[Box Developers site](https://app.box.com/developers/console).
|
||||
|
||||
Things to note:
|
||||
|
||||
- Choose `Custom App` and select the `Standard OAuth 2.0 (User Authentication)`
|
||||
app type.
|
||||
- You must enable full write access, or you will get
|
||||
[403 when downloading files](https://support.box.com/hc/en-us/community/posts/360049195613-403-error-while-file-download-API-Call)
|
||||
|
||||
You’ll be redirected to the app page. This page lists the client ID (app key)
|
||||
and client secret (app secret), which you should use to configure Companion.
|
||||
|
||||
The app page has a `"Redirect URIs"` field. Here, add:
|
||||
|
||||
```
|
||||
https://$YOUR_COMPANION_HOST_NAME/box/redirect
|
||||
```
|
||||
|
||||
If you are using Transloadit hosted Companion:
|
||||
|
||||
```
|
||||
https://api2.transloadit.com/companion/box/redirect
|
||||
```
|
||||
|
||||
You can only use the integration with your own account initially. Make sure to
|
||||
apply for production status on the app page before you publish your app, or your
|
||||
users will not be able to sign in!
|
||||
|
||||
Configure the Box key and secret. With the standalone Companion server, specify
|
||||
environment variables:
|
||||
|
||||
```shell
|
||||
export COMPANION_BOX_KEY="Box API key"
|
||||
export COMPANION_BOX_SECRET="Box API secret"
|
||||
```
|
||||
|
||||
When using the Companion Node.js API, configure these options:
|
||||
|
||||
```js
|
||||
companion.app({
|
||||
providerOptions: {
|
||||
box: {
|
||||
key: 'Box API key',
|
||||
secret: 'Box API secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Box'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'Box'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string` or `Element`, default: `null`).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameBox: 'Box',
|
||||
},
|
||||
};
|
||||
```
|
||||
188
docs/sources/companion-plugins/dropbox.mdx
Normal file
188
docs/sources/companion-plugins/dropbox.mdx
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
---
|
||||
slug: /dropbox
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Dropbox
|
||||
|
||||
The `@uppy/dropbox` plugin lets users import files from their
|
||||
[Dropbox](https://www.dropbox.com) account.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files from their
|
||||
[Dropbox](https://www.dropbox.com) account.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Dropbox plugin to
|
||||
work. Companion handles authentication with Dropbox, downloads the files, and
|
||||
uploads them to the destination. This saves the user bandwidth, especially
|
||||
helpful if they are on a mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/dropbox
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/dropbox
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Dropbox } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Dropbox, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using Dropbox requires setup in both Uppy and Companion.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Dropbox from '@uppy/dropbox';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy().use(Dashboard, { inline: true, target: '#dashboard' }).use(Dropbox, {
|
||||
target: Dashboard,
|
||||
companionUrl: 'https://your-companion.com',
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
You can create a Dropbox App on the
|
||||
[Dropbox Developers site](https://www.dropbox.com/developers/apps/create).
|
||||
|
||||
Things to note:
|
||||
|
||||
- Choose the “Dropbox API”, not the business variant.
|
||||
- Typically you’ll want “Full Dropbox” access, unless you are absolutely certain
|
||||
that you need the other one.
|
||||
|
||||
You’ll be redirected to the app page. This page lists the app key and app
|
||||
secret, which you should use to configure Companion as shown above.
|
||||
|
||||
The app page has a “Redirect URIs” field. Here, add:
|
||||
|
||||
```
|
||||
https://$YOUR_COMPANION_HOST_NAME/dropbox/redirect
|
||||
```
|
||||
|
||||
If you are using Transloadit hosted Companion:
|
||||
|
||||
```
|
||||
https://api2.transloadit.com/companion/dropbox/redirect
|
||||
```
|
||||
|
||||
You can only use the integration with your own account initially. Make sure to
|
||||
apply for production status on the app page before you publish your app, or your
|
||||
users will not be able to sign in!
|
||||
|
||||
Configure the Dropbox key and secret. With the standalone Companion server,
|
||||
specify environment variables:
|
||||
|
||||
```shell
|
||||
export COMPANION_DROPBOX_KEY="Dropbox API key"
|
||||
export COMPANION_DROPBOX_SECRET="Dropbox API secret"
|
||||
```
|
||||
|
||||
When using the Companion Node.js API, configure these options:
|
||||
|
||||
```js
|
||||
companion.app({
|
||||
providerOptions: {
|
||||
dropbox: {
|
||||
key: 'Dropbox API key',
|
||||
secret: 'Dropbox API secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Dropbox'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'Dropbox'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string` or `Element`, default: `null`).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameDropbox: 'Dropbox',
|
||||
},
|
||||
};
|
||||
```
|
||||
187
docs/sources/companion-plugins/facebook.mdx
Normal file
187
docs/sources/companion-plugins/facebook.mdx
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
---
|
||||
slug: /facebook
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Facebook
|
||||
|
||||
The `@uppy/facebook` plugin lets users import files from their
|
||||
[Facebook](https://www.facebook.com) account.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files from their
|
||||
[Facebook](https://www.facebook.com) account.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Facebook plugin to
|
||||
work. Companion handles authentication with Facebook, downloads the files, and
|
||||
uploads them to the destination. This saves the user bandwidth, especially
|
||||
helpful if they are on a mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/facebook
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/facebook
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Facebook } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Facebook, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using Facebook requires setup in both Uppy and Companion.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Facebook from '@uppy/facebook';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: '#dashboard' })
|
||||
.use(Facebook, {
|
||||
target: Dashboard,
|
||||
companionUrl: 'https://your-companion.com',
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
You can create a Facebook App on the
|
||||
[Facebook Developers site](https://developers.facebook.com/).
|
||||
|
||||
The app page has a “Redirect URIs” field. Here, add:
|
||||
|
||||
```
|
||||
https://$YOUR_COMPANION_HOST_NAME/facebook/redirect
|
||||
```
|
||||
|
||||
If you are using Transloadit hosted Companion:
|
||||
|
||||
```
|
||||
https://api2.transloadit.com/companion/facebook/redirect
|
||||
```
|
||||
|
||||
You can only use the integration with your own account initially. Make sure to
|
||||
apply for production status on the app page before you publish your app, or your
|
||||
users will not be able to sign in!
|
||||
|
||||
You need to set up OAuth in your Facebook app for Companion to be able to
|
||||
connect to users’ Facebook accounts. You have to enable “Advanced Access” for
|
||||
the `user_photos` permission. A precondition of that is “Business Verification”
|
||||
which involves setting up a Meta Business Account and submitting documents to
|
||||
prove business ownership.
|
||||
|
||||
Configure the Facebook key and secret. With the standalone Companion server,
|
||||
specify environment variables:
|
||||
|
||||
```shell
|
||||
export COMPANION_FACEBOOK_KEY="Facebook API key"
|
||||
export COMPANION_FACEBOOK_SECRET="Facebook API secret"
|
||||
```
|
||||
|
||||
When using the Companion Node.js API, configure these options:
|
||||
|
||||
```js
|
||||
companion.app({
|
||||
providerOptions: {
|
||||
facebook: {
|
||||
key: 'Facebook API key',
|
||||
secret: 'Facebook API secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Facebook'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'Facebook'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string` or `Element`, default: `null`).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameFacebook: 'Facebook',
|
||||
},
|
||||
};
|
||||
```
|
||||
191
docs/sources/companion-plugins/google-drive.mdx
Normal file
191
docs/sources/companion-plugins/google-drive.mdx
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
---
|
||||
slug: /google-drive
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Google Drive
|
||||
|
||||
The `@uppy/google-drive` plugin lets users import files from their
|
||||
[Google Drive](https://www.drive.google.com) account.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files from their
|
||||
[Google Drive](https://www.drive.google.com) account.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Google Drive plugin
|
||||
to work. Companion handles authentication with Google Drive, downloads the
|
||||
files, and uploads them to the destination. This saves the user bandwidth,
|
||||
especially helpful if they are on a mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/google-drive
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/google-drive
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, GoogleDrive } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(GoogleDrive, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using Google Drive requires setup in both Uppy and Companion.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import GoogleDrive from '@uppy/google-drive';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: '#dashboard' })
|
||||
.use(GoogleDrive, {
|
||||
target: Dashboard,
|
||||
companionUrl: 'https://your-companion.com',
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
To sign up for API keys, go to the
|
||||
[Google Developer Console](https://console.developers.google.com/).
|
||||
|
||||
Create a project for your app if you don’t have one yet.
|
||||
|
||||
- On the project’s dashboard,
|
||||
[enable the Google Drive API](https://developers.google.com/drive/api/v3/enable-drive-api).
|
||||
- [Set up OAuth authorization](https://developers.google.com/drive/api/v3/about-auth).
|
||||
- Under scopes, add the `https://www.googleapis.com/auth/drive.readonly` Drive
|
||||
API scope.
|
||||
- Due to this being a sensitive scope, your app must complete Google’s OAuth
|
||||
app verification before being granted access. See
|
||||
[OAuth App Verification Help Center](https://support.google.com/cloud/answer/13463073)
|
||||
for more information.
|
||||
|
||||
The app page has a `"Redirect URIs"` field. Here, add:
|
||||
|
||||
```
|
||||
https://$YOUR_COMPANION_HOST_NAME/drive/redirect
|
||||
```
|
||||
|
||||
If you are using Transloadit hosted Companion:
|
||||
|
||||
```
|
||||
https://api2.transloadit.com/companion/drive/redirect
|
||||
```
|
||||
|
||||
Google will give you an OAuth client ID and client secret.
|
||||
|
||||
Configure the Google Drive key and secret in Companion. With the standalone
|
||||
Companion server, specify environment variables:
|
||||
|
||||
```shell
|
||||
export COMPANION_GOOGLE_KEY="Google Drive OAuth client ID"
|
||||
export COMPANION_GOOGLE_SECRET="Google Drive OAuth client secret"
|
||||
```
|
||||
|
||||
When using the Companion Node.js API, configure these options:
|
||||
|
||||
```js
|
||||
companion.app({
|
||||
providerOptions: {
|
||||
drive: {
|
||||
key: 'Google Drive OAuth client ID',
|
||||
secret: 'Google Drive OAuth client secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'GoogleDrive'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'GoogleDrive'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string` or `Element`, default: `null`).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameGoogleDrive: 'GoogleDrive',
|
||||
},
|
||||
};
|
||||
```
|
||||
181
docs/sources/companion-plugins/instagram.mdx
Normal file
181
docs/sources/companion-plugins/instagram.mdx
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
---
|
||||
slug: /instagram
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Instagram
|
||||
|
||||
The `@uppy/instagram` plugin lets users import files from their
|
||||
[Instagram](https://instagram.com) account.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files from their
|
||||
[Instagram](https://instagram.com) account.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Instagram plugin to
|
||||
work. Companion handles authentication with Instagram, downloads the files, and
|
||||
uploads them to the destination. This saves the user bandwidth, especially
|
||||
helpful if they are on a mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/instagram
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/instagram
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Instagram } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Instagram, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using Instagram requires setup in both Uppy and Companion.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Instagram from '@uppy/instagram';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: '#dashboard' })
|
||||
.use(Instagram, {
|
||||
target: Dashboard,
|
||||
companionUrl: 'https://your-companion.com',
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
To sign up for API keys, go to the
|
||||
[Instagram Platform from Meta](https://developers.facebook.com/products/instagram/).
|
||||
|
||||
Create a project for your app if you don’t have one yet.
|
||||
|
||||
The app page has a `"Redirect URIs"` field. Here, add:
|
||||
|
||||
```
|
||||
https://$YOUR_COMPANION_HOST_NAME/instagram/redirect
|
||||
```
|
||||
|
||||
If you are using Transloadit hosted Companion:
|
||||
|
||||
```
|
||||
https://api2.transloadit.com/companion/instagram/redirect
|
||||
```
|
||||
|
||||
Meta will give you an OAuth client ID and client secret.
|
||||
|
||||
Configure the Instagram key and secret in Companion. With the standalone
|
||||
Companion server, specify environment variables:
|
||||
|
||||
```shell
|
||||
export COMPANION_INSTAGRAM_KEY="Instagram OAuth client ID"
|
||||
export COMPANION_INSTAGRAM_SECRET="Instagram OAuth client secret"
|
||||
```
|
||||
|
||||
When using the Companion Node.js API, configure these options:
|
||||
|
||||
```js
|
||||
companion.app({
|
||||
providerOptions: {
|
||||
drive: {
|
||||
key: 'Instagram OAuth client ID',
|
||||
secret: 'Instagram OAuth client secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Instagram'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'Instagram'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string` or `Element`, default: `null`).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameInstagram: 'Instagram',
|
||||
},
|
||||
};
|
||||
```
|
||||
181
docs/sources/companion-plugins/onedrive.mdx
Normal file
181
docs/sources/companion-plugins/onedrive.mdx
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
---
|
||||
slug: /onedrive
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# OneDrive
|
||||
|
||||
The `@uppy/onedrive` plugin lets users import files from their
|
||||
[OneDrive](https://onedrive.com) account.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files from their
|
||||
[OneDrive](https://onedrive.com) account.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the OneDrive plugin to
|
||||
work. Companion handles authentication with OneDrive, downloads the files, and
|
||||
uploads them to the destination. This saves the user bandwidth, especially
|
||||
helpful if they are on a mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/onedrive
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/onedrive
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, OneDrive } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(OneDrive, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using OneDrive requires setup in both Uppy and Companion.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import OneDrive from '@uppy/onedrive';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: '#dashboard' })
|
||||
.use(OneDrive, {
|
||||
target: Dashboard,
|
||||
companionUrl: 'https://your-companion.com',
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
To sign up for API keys, go to the
|
||||
[Azure Platform from Microsoft](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade).
|
||||
|
||||
Create a project for your app if you don’t have one yet.
|
||||
|
||||
The app page has a `"Redirect URIs"` field. Here, add:
|
||||
|
||||
```
|
||||
https://$YOUR_COMPANION_HOST_NAME/onedrive/redirect
|
||||
```
|
||||
|
||||
If you are using Transloadit hosted Companion:
|
||||
|
||||
```
|
||||
https://api2.transloadit.com/companion/onedrive/redirect
|
||||
```
|
||||
|
||||
Microsoft will give you an OAuth client ID and client secret.
|
||||
|
||||
Configure the OneDrive key and secret in Companion. With the standalone
|
||||
Companion server, specify environment variables:
|
||||
|
||||
```shell
|
||||
export COMPANION_ONEDRIVE_KEY="OneDrive Application ID"
|
||||
export COMPANION_ONEDRIVE_SECRET="OneDrive OAuth client secret value"
|
||||
```
|
||||
|
||||
When using the Companion Node.js API, configure these options:
|
||||
|
||||
```js
|
||||
companion.app({
|
||||
providerOptions: {
|
||||
onedrive: {
|
||||
key: 'OneDrive Application ID',
|
||||
secret: 'OneDrive OAuth client secret value',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'OneDrive'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'OneDrive'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string` or `Element`, default: `null`).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameOneDrive: 'OneDrive',
|
||||
},
|
||||
};
|
||||
```
|
||||
166
docs/sources/companion-plugins/unsplash.mdx
Normal file
166
docs/sources/companion-plugins/unsplash.mdx
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
---
|
||||
slug: /unsplash
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Unsplash
|
||||
|
||||
The `@uppy/unsplash` plugin lets users import files from their
|
||||
[Unsplash](https://unsplash.com) account.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files from their
|
||||
[Unsplash](https://unsplash.com) account.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Unsplash plugin to
|
||||
work. Companion handles authentication with Unsplash, downloads the files, and
|
||||
uploads them to the destination. This saves the user bandwidth, especially
|
||||
helpful if they are on a mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/unsplash
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/unsplash
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Unsplash } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Unsplash, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using Unsplash requires setup in both Uppy and Companion.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Unsplash from '@uppy/unsplash';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: '#dashboard' })
|
||||
.use(Unsplash, {
|
||||
target: Dashboard,
|
||||
companionUrl: 'https://your-companion.com',
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
You can create a Unsplash App on the
|
||||
[Unsplash Developers site](https://unsplash.com/developers). You’ll be
|
||||
redirected to the app page, this page lists the app key and app secret.
|
||||
|
||||
Configure the Unsplash key and secret. With the standalone Companion server,
|
||||
specify environment variables:
|
||||
|
||||
```shell
|
||||
export COMPANION_UNSPLASH_KEY="Unsplash API key"
|
||||
export COMPANION_UNSPLASH_SECRET="Unsplash API secret"
|
||||
```
|
||||
|
||||
When using the Companion Node.js API, configure these options:
|
||||
|
||||
```js
|
||||
companion.app({
|
||||
providerOptions: {
|
||||
unsplash: {
|
||||
key: 'Unsplash API key',
|
||||
secret: 'Unsplash API secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Unsplash'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'Unsplash'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string` or `Element`, default: `null`).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameUnsplash: 'Unsplash',
|
||||
},
|
||||
};
|
||||
```
|
||||
149
docs/sources/companion-plugins/url.mdx
Normal file
149
docs/sources/companion-plugins/url.mdx
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
---
|
||||
slug: /url
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Import from URL
|
||||
|
||||
The `@uppy/url` plugin allows users to import files from the internet. Paste any
|
||||
URL and it will be added!
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files any URL.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the URL plugin to work.
|
||||
This saves the user bandwidth, especially helpful if they are on a mobile
|
||||
connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
:::note
|
||||
|
||||
Companion has
|
||||
[Server Side Request Forgery](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery)
|
||||
(SSRF) protections built-in so you don’t have to worry about the security
|
||||
implications of arbitrary URLs.
|
||||
|
||||
:::
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/url
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/url
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Url } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Url, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using `@uppy/url` only requires setup in Uppy.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Url from '@uppy/url';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy().use(Dashboard, { inline: true, target: '#dashboard' }).use(Url, {
|
||||
target: Dashboard,
|
||||
companionUrl: 'https://your-companion.com',
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
Companion supports this plugin out-of-the-box so integration is required on this
|
||||
side.
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Url'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'Url'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string` or `Element`, default: `null`).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameUrl: 'Url',
|
||||
},
|
||||
};
|
||||
```
|
||||
160
docs/sources/companion-plugins/zoom.mdx
Normal file
160
docs/sources/companion-plugins/zoom.mdx
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
---
|
||||
slug: /zoom
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Zoom
|
||||
|
||||
The `@uppy/zoom` plugin lets users import files from their
|
||||
[Zoom](https://zoom.com) account.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files from their [Zoom](https://zoom.com)
|
||||
account.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Zoom plugin to work.
|
||||
Companion handles authentication with Zoom, downloads the files, and uploads
|
||||
them to the destination. This saves the user bandwidth, especially helpful if
|
||||
they are on a mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/zoom
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/zoom
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Zoom } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Zoom, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using Zoom requires setup in both Uppy and Companion.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Zoom from '@uppy/zoom';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy().use(Dashboard, { inline: true, target: '#dashboard' }).use(Zoom, {
|
||||
target: Dashboard,
|
||||
companionUrl: 'https://your-companion.com',
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
Configure the Zoom key and secret. With the standalone Companion server, specify
|
||||
environment variables:
|
||||
|
||||
```shell
|
||||
export COMPANION_ZOOM_KEY="Zoom API key"
|
||||
export COMPANION_ZOOM_SECRET="Zoom API secret"
|
||||
```
|
||||
|
||||
When using the Companion Node.js API, configure these options:
|
||||
|
||||
```js
|
||||
companion.app({
|
||||
providerOptions: {
|
||||
zoom: {
|
||||
key: 'Zoom API key',
|
||||
secret: 'Zoom API secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Zoom'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'Zoom'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string` or `Element`, default: `null`).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameZoom: 'Zoom',
|
||||
},
|
||||
};
|
||||
```
|
||||
166
docs/sources/file-input.mdx
Normal file
166
docs/sources/file-input.mdx
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
slug: /file-input
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# File input
|
||||
|
||||
The `@uppy/file-input` plugin is the most barebones UI for selecting files — it
|
||||
shows a single button that, when clicked, opens up the browser’s file selector.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
When you want users to select files from their local machine with a minimal UI.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/file-input
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/file-input
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, FileInput } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(FileInput, { target: document.body })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import FileInput from '@uppy/file-input';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/file-input/dist/style.css';
|
||||
|
||||
new Uppy().use(FileInput, { target: '#uppy-file-input' });
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
The `@uppy/file-input` plugin includes some basic styles for use with the
|
||||
[`pretty`](#pretty-true) option, like shown in the
|
||||
[example](/examples/xhrupload). You can also choose not to use it and provide
|
||||
your own styles instead.
|
||||
|
||||
Import general Core styles from `@uppy/core/dist/style.css` first, then add the
|
||||
File Input styles from `@uppy/file-input/dist/style.css`. A minified version is
|
||||
also available as `style.min.css` at the same path. The way to do import depends
|
||||
on your build system.
|
||||
|
||||
:::
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'FileInput'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Configures the title / name shown in the UI, for instance, on Dashboard tabs
|
||||
(`string`, default: `'File Input'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the audio into (`string` or
|
||||
`Element`, default: `null`).
|
||||
|
||||
#### `pretty`
|
||||
|
||||
When true, display a styled button that, when clicked, opens the file selector
|
||||
UI. When false, a plain old browser `<input type="file">` element is shown
|
||||
(`boolean`, default: `true`).
|
||||
|
||||
#### `inputName`
|
||||
|
||||
The `name` attribute for the `<input type="file">` element (`string`, default:
|
||||
`'files[]'`).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
// The same key is used for the same purpose by @uppy/robodog's `form()` API, but our
|
||||
// locale pack scripts can't access it in Robodog. If it is updated here, it should
|
||||
// also be updated there!
|
||||
chooseFiles: 'Choose files',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Custom file inpt
|
||||
|
||||
If you don’t like the look/feel of the button rendered by `@uppy/file-input`,
|
||||
feel free to forgo the plugin and use your own custom button on a page, like so:
|
||||
|
||||
```html
|
||||
<input type="file" id="my-file-input" />
|
||||
```
|
||||
|
||||
Then add this JS to attach it to Uppy:
|
||||
|
||||
```js
|
||||
const uppy = new Uppy(/* ... */);
|
||||
const fileInput = document.querySelector('#my-file-input');
|
||||
|
||||
fileInput.addEventListener('change', (event) => {
|
||||
const files = Array.from(event.target.files);
|
||||
|
||||
files.forEach((file) => {
|
||||
try {
|
||||
uppy.addFile({
|
||||
source: 'file input',
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
data: file,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.isRestriction) {
|
||||
// handle restrictions
|
||||
console.log('Restriction error:', err);
|
||||
} else {
|
||||
// handle other errors
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Clear the `<input>` after the upload or when the file was removed
|
||||
// so the file can be uploaded again (see
|
||||
// https://github.com/transloadit/uppy/issues/2640#issuecomment-731034781).
|
||||
uppy.on('file-removed', () => {
|
||||
fileInput.value = null;
|
||||
});
|
||||
|
||||
uppy.on('complete', () => {
|
||||
fileInput.value = null;
|
||||
});
|
||||
```
|
||||
151
docs/sources/screen-capture.mdx
Normal file
151
docs/sources/screen-capture.mdx
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
---
|
||||
sidebar_position: 2
|
||||
slug: /screen-capture
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Screen capture
|
||||
|
||||
The `@uppy/screen-capture` plugin can record your screen or an application and
|
||||
save it as a video.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want users record their screen on their computer. This plugin only
|
||||
works on desktop browsers which support
|
||||
[`getDisplayMedia API`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia).
|
||||
If no support for the API is detected, Screen Capture won’t be installed, so you
|
||||
don’t have to do anything.
|
||||
|
||||
:::note
|
||||
|
||||
To use the screen capture plugin in a Chromium-based browser,
|
||||
[your site must be served over https](https://developers.google.com/web/updates/2015/10/chrome-47-webrtc#public_service_announcements).
|
||||
This restriction does not apply on `localhost`, so you don’t have to jump
|
||||
through many hoops during development.
|
||||
|
||||
:::
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/screen-capture
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/screen-capture
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Dashboard, ScreenCapture } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Dashboard, { inline: true, target: 'body' })
|
||||
uppy.use(ScreenCapture, { target: Uppy.Dashboard })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js {3,7,11} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import ScreenCapture from '@uppy/screen-capture';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
import '@uppy/screen-capture/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: 'body' })
|
||||
.use(ScreenCapture, { target: Dashboard });
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'ScreenCapture'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Configures the title / name shown in the UI, for instance, on Dashboard tabs
|
||||
(`string`, default: `'Screen Capture'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the screen capture into (`string`
|
||||
or `Element`, default: `null`).
|
||||
|
||||
#### `displayMediaConstraints`
|
||||
|
||||
Options passed to
|
||||
[`MediaDevices.getDisplayMedia()`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia)
|
||||
(`Object`, default: `null`).
|
||||
|
||||
See the
|
||||
[`MediaTrackConstraints`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)
|
||||
for a list of options.
|
||||
|
||||
#### `userMediaConstraints`
|
||||
|
||||
Options passed to
|
||||
[`MediaDevices.getUserMedia()`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia)
|
||||
(`Object`, default: `null`).
|
||||
|
||||
See the
|
||||
[`MediaTrackConstraints`](https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints)
|
||||
for a list of options.
|
||||
|
||||
#### `preferredVideoMimeType`
|
||||
|
||||
Set the preferred mime type for video recordings, for example `'video/webm'`
|
||||
(`string`, default: `null`).
|
||||
|
||||
If the browser supports the given mime type, the video will be recorded in this
|
||||
format. If the browser does not support it, it will use the browser default.
|
||||
|
||||
If no preferred video mime type is given, the ScreenCapture plugin will prefer
|
||||
types listed in the [`allowedFileTypes` restriction](/docs/uppy/#restrictions),
|
||||
if any.
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
startCapturing: 'Begin screen capturing',
|
||||
stopCapturing: 'Stop screen capturing',
|
||||
submitRecordedFile: 'Submit recorded file',
|
||||
streamActive: 'Stream active',
|
||||
streamPassive: 'Stream passive',
|
||||
micDisabled: 'Microphone access denied by user',
|
||||
recording: 'Recording',
|
||||
},
|
||||
};
|
||||
```
|
||||
253
docs/sources/webcam.mdx
Normal file
253
docs/sources/webcam.mdx
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
slug: /webcam
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Webcam
|
||||
|
||||
The `@uppy/webcam` plugin lets you take photos and record videos with a built-in
|
||||
camera on desktop and mobile devices.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use it?
|
||||
|
||||
When you want your users to able to use their camera. This plugin is published
|
||||
separately but made specifically for the [Dashboard](/docs/dashboard). You can
|
||||
technically use it without it, but it’s not officially supported.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/webcam
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/webcam
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Dashboard, Webcam } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Dashboard, { inline: true, target: 'body' })
|
||||
uppy.use(Webcam, { target: Uppy.Dashboard })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
:::note
|
||||
|
||||
To use the Webcam plugin in Chrome,
|
||||
[your site must be served over https](https://developers.google.com/web/updates/2015/10/chrome-47-webrtc#public_service_announcements).
|
||||
This restriction does not apply on `localhost`, so you don’t have to jump
|
||||
through many hoops during development.
|
||||
|
||||
:::
|
||||
|
||||
```js {3,7,11} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Webcam from '@uppy/webcam';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
import '@uppy/webcam/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: 'body' })
|
||||
.use(Webcam, { target: Dashboard });
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Webcam'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the webcam into (`string` or
|
||||
`Element`, default: `null`).
|
||||
|
||||
#### `countdown`
|
||||
|
||||
When taking a picture: the amount of seconds to wait before actually taking a
|
||||
snapshot (`boolean`, default: `false`).
|
||||
|
||||
If set to `false` or 0, the snapshot is taken right away. This also shows a
|
||||
`Smile!` message through the [Informer](/docs/informer) before the picture is
|
||||
taken.
|
||||
|
||||
#### `onBeforeSnapshot`
|
||||
|
||||
A hook function to call before a snapshot is taken (`Function`, default:
|
||||
`Promise.resolve`).
|
||||
|
||||
The Webcam plugin will wait for the returned Promise to resolve before taking
|
||||
the snapshot. This can be used to carry out variations on the `countdown` option
|
||||
for example.
|
||||
|
||||
#### `modes`
|
||||
|
||||
The types of recording modes to allow (`Array`, default: `[]`).
|
||||
|
||||
- `video-audio` - Record a video file, capturing both audio and video.
|
||||
- `video-only` - Record a video file with the webcam, but don’t record audio.
|
||||
- `audio-only` - Record an audio file with the user’s microphone.
|
||||
- `picture` - Take a picture with the webcam.
|
||||
|
||||
By default, all modes are allowed, and the Webcam plugin will show controls for
|
||||
recording video as well as taking pictures.
|
||||
|
||||
#### `mirror`
|
||||
|
||||
Configures whether to mirror preview image from the camera (`boolean`, default:
|
||||
`true`).
|
||||
|
||||
This option is useful when taking a selfie with a front camera: when you wave
|
||||
your right hand, you will see your hand on the right on the preview screen, like
|
||||
in the mirror. But when you actually take a picture, it will not be mirrored.
|
||||
This is how smartphone selfie cameras behave.
|
||||
|
||||
#### `videoConstraints`
|
||||
|
||||
Configure the [`MediaTrackConstraints`][] (`Object`, default: `{}`).
|
||||
|
||||
You can specify acceptable ranges for the resolution of the video stream using
|
||||
the [`aspectRatio`][], [`width`][], and [`height`][] properties. Each property
|
||||
takes an object with `{ min, ideal, max }` properties. For example, use
|
||||
`width: { min: 720, max: 1920, ideal: 1920 }` to allow any width between 720 and
|
||||
1920 pixels wide, while preferring the highest resolution.
|
||||
|
||||
Devices sometimes have several cameras, front and back, for example.
|
||||
[`facingMode`][] lets you specify which should be used:
|
||||
|
||||
- `user`: The video source is facing toward the user; this includes, for
|
||||
example, the front-facing camera on a smartphone.
|
||||
- `environment`: The video source is facing away from the user, thereby viewing
|
||||
their environment. This is the back camera on a smartphone.
|
||||
- `left`: The video source is facing toward the user but to their left, such as
|
||||
a camera aimed toward the user but over their left shoulder.
|
||||
- `right`: The video source is facing toward the user but to their right, such
|
||||
as a camera aimed toward the user but over their right shoulder.
|
||||
|
||||
For a full list of available properties, check out MDN documentation for
|
||||
[`MediaTrackConstraints`][].
|
||||
|
||||
[`mediatrackconstraints`]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#Properties_of_video_tracks
|
||||
[`aspectratio`]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/aspectRatio
|
||||
[`width`]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/width
|
||||
[`height`]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/height
|
||||
[`facingmode`]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints/facingMode
|
||||
|
||||
#### `showVideoSourceDropdown`
|
||||
|
||||
Configures whether to show a dropdown which enables to choose the video device
|
||||
to use (`boolean`, default: `false`).
|
||||
|
||||
This option will have priority over `facingMode` if enabled.
|
||||
|
||||
#### `showRecordingLength`
|
||||
|
||||
Configures whether to show the length of the recording while the recording is in
|
||||
progress (`boolean`, default: `false`).
|
||||
|
||||
#### `preferredVideoMimeType`
|
||||
|
||||
Set the preferred mime type for video recordings, for example `'video/webm'`
|
||||
(`string`, default: `null`).
|
||||
|
||||
If the browser supports the given mime type, the video will be recorded in this
|
||||
format. If the browser does not support it, it will use the browser default. If
|
||||
no preferred video mime type is given, the Webcam plugin will prefer types
|
||||
listed in the [`allowedFileTypes` restriction](/docs/uppy/#restrictions), if
|
||||
any.
|
||||
|
||||
#### `preferredImageMimeType`
|
||||
|
||||
Set the preferred mime type for images, for example `'image/png'` (`string`,
|
||||
default: `image/jpeg`).
|
||||
|
||||
If the browser supports rendering the given mime type, the image will be stored
|
||||
in this format. Else `image/jpeg` is used by default. If no preferred image mime
|
||||
type is given, the Webcam plugin will prefer types listed in the
|
||||
[`allowedFileTypes` restriction](/docs/uppy/#restrictions), if any.
|
||||
|
||||
#### `mobileNativeCamera`
|
||||
|
||||
Replaces Uppy’s custom camera UI on mobile and tablet with the native device
|
||||
camera (`Function: boolean` or `boolean`, default: `isMobile()`).
|
||||
|
||||
This will show the “Take Picture” and / or “Record Video” buttons, which ones
|
||||
show depends on the [`modes`](#modes) option.
|
||||
|
||||
You can set a boolean to forcefully enable / disable this feature, or a function
|
||||
which returns a boolean. By default we use the
|
||||
[`is-mobile`](https://github.com/juliangruber/is-mobile) package.
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameCamera: 'Camera',
|
||||
noCameraTitle: 'Camera Not Available',
|
||||
noCameraDescription:
|
||||
'In order to take pictures or record video, please connect a camera device',
|
||||
recordingStoppedMaxSize:
|
||||
'Recording stopped because the file size is about to exceed the limit',
|
||||
submitRecordedFile: 'Submit recorded file',
|
||||
discardRecordedFile: 'Discard recorded file',
|
||||
// Shown before a picture is taken when the `countdown` option is set.
|
||||
smile: 'Smile!',
|
||||
// Used as the label for the button that takes a picture.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
takePicture: 'Take a picture',
|
||||
// Used as the label for the button that starts a video recording.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
startRecording: 'Begin video recording',
|
||||
// Used as the label for the button that stops a video recording.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
stopRecording: 'Stop video recording',
|
||||
// Used as the label for the recording length counter. See the showRecordingLength option.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
recordingLength: 'Recording length %{recording_length}',
|
||||
// Title on the “allow access” screen
|
||||
allowAccessTitle: 'Please allow access to your camera',
|
||||
// Description on the “allow access” screen
|
||||
allowAccessDescription:
|
||||
'In order to take pictures or record video with your camera, please allow camera access for this site.',
|
||||
},
|
||||
};
|
||||
```
|
||||
4
docs/uploader/_category_.json
Normal file
4
docs/uploader/_category_.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"label": "Uploaders",
|
||||
"position": 7
|
||||
}
|
||||
522
docs/uploader/aws-s3-multipart.mdx
Normal file
522
docs/uploader/aws-s3-multipart.mdx
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
slug: /aws-s3-multipart
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# AWS S3
|
||||
|
||||
The `@uppy/aws-s3` plugin can be used to upload files directly to a S3 bucket or
|
||||
a S3-compatible provider, such as Google Cloud Storage or DigitalOcean Spaces.
|
||||
Uploads can be signed using either [Companion][companion docs], temporary
|
||||
credentials, or a custom signing function.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
:::tip
|
||||
|
||||
Not sure which uploader is best for you? Read
|
||||
“[Choosing the uploader you need](/docs/guides/choosing-uploader)”.
|
||||
|
||||
:::
|
||||
|
||||
You can use this plugin when you prefer a _client-to-storage_ over a
|
||||
_client-to-server-to-storage_ (such as [Transloadit](/docs/transloadit) or
|
||||
[Tus](/docs/tus)) setup. This may in some cases be preferable, for instance, to
|
||||
reduce costs or the complexity of running a server and load balancer with
|
||||
[Tus](/docs/tus).
|
||||
|
||||
Multipart uploads start to become valuable for larger files (100 MiB+) as
|
||||
it uploads a single object as a set of parts. This has certain benefits, such as
|
||||
improved throughput (uploading parts in parallel) and quick recovery from
|
||||
network issues (only the failed parts need to be retried). The downside is
|
||||
request overhead, as it needs to do creation, signing (unless you are [signing
|
||||
on the client][]), and completion requests besides the upload requests. For
|
||||
example, if you are uploading files that are only a couple kilobytes with a
|
||||
100ms roundtrip latency, you are spending 400ms on overhead and only a few
|
||||
milliseconds on uploading.
|
||||
|
||||
**In short**
|
||||
|
||||
- We recommend to set [`shouldUseMultipart`][] to enable multipart uploads only
|
||||
for large files.
|
||||
- If you prefer to have less overhead (+20% upload speed) you can use temporary
|
||||
S3 credentials with [`getTemporarySecurityCredentials`][]. This means users
|
||||
get a single token which allows them to do bucket operations for longer,
|
||||
instead of short lived signed URL per resource. This is a security trade-off.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/aws-s3
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/aws-s3
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, AwsS3 } from "{{UPPY_JS_URL}}"
|
||||
new Uppy().use(AwsS3, { /* see options */ })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
### Setting up your S3 bucket
|
||||
|
||||
To use this plugin with S3 we need to setup a bucket with the right permissions
|
||||
and CORS settings.
|
||||
|
||||
S3 buckets do not allow public uploads for security reasons. To allow Uppy and
|
||||
the browser to upload directly to a bucket, its CORS permissions need to be
|
||||
configured.
|
||||
|
||||
CORS permissions can be found in the
|
||||
[S3 Management Console](https://console.aws.amazon.com/s3/home). Click the
|
||||
bucket that will receive the uploads, then go into the `Permissions` tab and
|
||||
select the `CORS configuration` button. A JSON document will be shown that
|
||||
defines the CORS configuration. (AWS used to use XML but now only allow JSON).
|
||||
More information about the
|
||||
[S3 CORS format here](https://docs.amazonaws.cn/en_us/AmazonS3/latest/userguide/ManageCorsUsing.html).
|
||||
|
||||
The configuration required for Uppy and Companion is this:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"AllowedOrigins": ["https://my-app.com"],
|
||||
"AllowedMethods": ["GET", "PUT"],
|
||||
"MaxAgeSeconds": 3000,
|
||||
"AllowedHeaders": [
|
||||
"Authorization",
|
||||
"x-amz-date",
|
||||
"x-amz-content-sha256",
|
||||
"content-type"
|
||||
],
|
||||
"ExposeHeaders": ["ETag", "Location"]
|
||||
},
|
||||
{
|
||||
"AllowedOrigins": ["*"],
|
||||
"AllowedMethods": ["GET"],
|
||||
"MaxAgeSeconds": 3000
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
A good practice is to use two CORS rules: one for viewing the uploaded files,
|
||||
and one for uploading files. This is done above where the first object in the
|
||||
array defines the rules for uploading, and the second for viewing. The example
|
||||
above **makes files publicly viewable**. You can change it according to your
|
||||
needs.
|
||||
|
||||
If you are using an IAM policy to allow access to the S3 bucket, the policy must
|
||||
have at least the `s3:PutObject` and `s3:PutObjectAcl` permissions scoped to the
|
||||
bucket in question. In-depth documentation about CORS rules is available on the
|
||||
[AWS documentation site](https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html).
|
||||
|
||||
### Use with your own server
|
||||
|
||||
The recommended approach is to integrate `@uppy/aws-s3` with your own server.
|
||||
You will need to do the following things:
|
||||
|
||||
1. [Setup a S3 bucket](#setting-up-your-s3-bucket).
|
||||
2. [Setup your server](https://github.com/transloadit/uppy/blob/main/examples/aws-nodejs/index.js)
|
||||
3. [Setup Uppy client](https://github.com/transloadit/uppy/blob/main/examples/aws-nodejs/public/index.html).
|
||||
|
||||
### Use with Companion
|
||||
|
||||
[Companion](/docs/companion) has S3 routes built-in for a plug-and-play
|
||||
experience with Uppy.
|
||||
|
||||
:::caution
|
||||
|
||||
Generally it’s better for access control, observability, and scaling to
|
||||
integrate `@uppy/aws-s3` with your own server. You may want to use
|
||||
[Companion](/docs/companion) for creating, signing, and completing your S3
|
||||
uploads if you already need Companion for remote files (such as from Google
|
||||
Drive). Otherwise it’s not worth the hosting effort.
|
||||
|
||||
:::
|
||||
|
||||
```js {10} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import AwsS3 from '@uppy/aws-s3';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
const uppy = new Uppy()
|
||||
.use(Dashboard, { inline: true, target: 'body' })
|
||||
.use(AwsS3, {
|
||||
shouldUseMultipart: (file) => file.size > 100 * 2 ** 20,
|
||||
companionUrl: 'https://companion.uppy.io',
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `shouldUseMultipart(file)`
|
||||
|
||||
:::warning
|
||||
|
||||
Until the next major version, not setting this option uses the
|
||||
[legacy version of this plugin](../aws-s3/). This is a suboptimal experience for
|
||||
some of your user’s uploads. It’s best for speed and stability to upload large
|
||||
(100 MiB+) files with multipart and small files with regular uploads.
|
||||
|
||||
:::
|
||||
|
||||
A boolean, or a function that returns a boolean which is called for each file
|
||||
that is uploaded with the corresponding `UppyFile` instance as argument.
|
||||
|
||||
By default, all files are uploaded as multipart. In a future version, all files
|
||||
with a `file.size` ≤ 100 MiB will be uploaded in a single chunk, all files
|
||||
larger than that as multipart.
|
||||
|
||||
Here’s how to use it:
|
||||
|
||||
```js
|
||||
uppy.use(AwsS3, {
|
||||
shouldUseMultipart(file) {
|
||||
// Use multipart only for files larger than 100MiB.
|
||||
return file.size > 100 * 2 ** 20;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### `limit`
|
||||
|
||||
The maximum amount of files to upload in parallel (`number`, default: `6`).
|
||||
|
||||
Note that the amount of files is not the same as the amount of concurrent
|
||||
connections. Multipart uploads can use many requests per file. For example, for
|
||||
a 100 MiB file with a part size of 5 MiB:
|
||||
|
||||
- 1 `createMultipartUpload` request
|
||||
- 100/5 = 20 sign requests (unless you are [signing on the client][])
|
||||
- 100/5 = 20 upload requests
|
||||
- 1 `completeMultipartUpload` request
|
||||
|
||||
:::caution
|
||||
|
||||
Unless you have a good reason and are well informed about the average internet
|
||||
speed of your users, do not set this higher. S3 uses HTTP/1.1, which means a
|
||||
limit to concurrent connections and your uploads may expire before they are
|
||||
uploaded.
|
||||
|
||||
:::
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `retryDelays`
|
||||
|
||||
`retryDelays` are the intervals in milliseconds used to retry a failed chunk
|
||||
(`array`, default: `[0, 1000, 3000, 5000]`).
|
||||
|
||||
This is also used for [`signPart()`](#signpartfile-partdata). Set to `null` to
|
||||
disable automatic retries, and fail instantly if any chunk fails to upload.
|
||||
|
||||
#### `getChunkSize(file)`
|
||||
|
||||
A function that returns the minimum chunk size to use when uploading the given
|
||||
file.
|
||||
|
||||
The S3 Multipart plugin uploads files in chunks. Chunks are sent in batches to
|
||||
have presigned URLs generated with [`signPart()`](#signpartfile-partdata). To
|
||||
reduce the amount of requests for large files, you can choose a larger chunk
|
||||
size, at the cost of having to re-upload more data if one chunk fails to upload.
|
||||
|
||||
S3 requires a minimum chunk size of 5MiB, and supports at most 10,000 chunks per
|
||||
multipart upload. If `getChunkSize()` returns a size that’s too small, Uppy will
|
||||
increase it to S3’s minimum requirements.
|
||||
|
||||
#### `getUploadParameters(file, options)`
|
||||
|
||||
:::note
|
||||
|
||||
When using [Companion][companion docs] to sign S3 uploads, you should not define
|
||||
this option.
|
||||
|
||||
:::
|
||||
|
||||
A function that will be called for each non-multipart upload.
|
||||
|
||||
- `file`: `UppyFile` the file that will be uploaded
|
||||
- `options`: `object`
|
||||
- `signal`: `AbortSignal`
|
||||
- **Returns:** `object | Promise<object>`
|
||||
- `method`: `string`, the HTTP method to be used for the upload. This should
|
||||
be one of either `PUT` or `POST`, depending on the type of upload used.
|
||||
- `url`: `string`, the URL to which the upload request will be sent. When
|
||||
using a presigned PUT upload, this should be the URL to the S3 object with
|
||||
signing parameters included in the query string. When using a POST upload
|
||||
with a policy document, this should be the root URL of the bucket.
|
||||
- `fields` `object`, an object with form fields to send along with the upload
|
||||
request. For presigned PUT uploads (which are default), this should be left
|
||||
empty.
|
||||
- `headers`: `object`, an object with request headers to send along with the
|
||||
upload request. When using a presigned PUT upload, it’s a good idea to
|
||||
provide `headers['content-type']`. That will make sure that the request uses
|
||||
the same content-type that was used to generate the signature. Without it,
|
||||
the browser may decide on a different content-type instead, causing S3 to
|
||||
reject the upload.
|
||||
|
||||
#### `createMultipartUpload(file)`
|
||||
|
||||
A function that calls the S3 Multipart API to create a new upload.
|
||||
|
||||
`file` is the file object from Uppy’s state. The most relevant keys are
|
||||
`file.name` and `file.type`.
|
||||
|
||||
Return a Promise for an object with keys:
|
||||
|
||||
- `uploadId` - The UploadID returned by S3.
|
||||
- `key` - The object key for the file. This needs to be returned to allow it to
|
||||
be different from the `file.name`.
|
||||
|
||||
The default implementation calls out to Companion’s S3 signing endpoints.
|
||||
|
||||
#### `listParts(file, { uploadId, key })`
|
||||
|
||||
A function that calls the S3 Multipart API to list the parts of a file that have
|
||||
already been uploaded.
|
||||
|
||||
Receives the `file` object from Uppy’s state, and an object with keys:
|
||||
|
||||
- `uploadId` - The UploadID of this Multipart upload.
|
||||
- `key` - The object key of this Multipart upload.
|
||||
|
||||
Return a Promise for an array of S3 Part objects, as returned by the S3
|
||||
Multipart API. Each object has keys:
|
||||
|
||||
- `PartNumber` - The index in the file of the uploaded part.
|
||||
- `Size` - The size of the part in bytes.
|
||||
- `ETag` - The ETag of the part, used to identify it when completing the
|
||||
multipart upload and combining all parts into a single file.
|
||||
|
||||
The default implementation calls out to Companion’s S3 signing endpoints.
|
||||
|
||||
#### `signPart(file, partData)`
|
||||
|
||||
A function that generates a signed URL for the specified part number. The
|
||||
`partData` argument is an object with the keys:
|
||||
|
||||
- `uploadId` - The UploadID of this Multipart upload.
|
||||
- `key` - The object key in the S3 bucket.
|
||||
- `partNumber` - can’t be zero.
|
||||
- `body` – The data that will be signed.
|
||||
- `signal` – An `AbortSignal` that may be used to abort an ongoing request.
|
||||
|
||||
This function should return a object, or a promise that resolves to an object,
|
||||
with the following keys:
|
||||
|
||||
- `url` – the presigned URL, as a `string`.
|
||||
- `headers` – **(Optional)** Custom headers to send along with the request to S3
|
||||
endpoint.
|
||||
|
||||
An example of what the return value should look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "https://bucket.region.amazonaws.com/path/to/file.jpg?partNumber=1&...",
|
||||
"headers": { "Content-MD5": "foo" }
|
||||
}
|
||||
```
|
||||
|
||||
#### `abortMultipartUpload(file, { uploadId, key })`
|
||||
|
||||
A function that calls the S3 Multipart API to abort a Multipart upload, and
|
||||
removes all parts that have been uploaded so far.
|
||||
|
||||
Receives the `file` object from Uppy’s state, and an object with keys:
|
||||
|
||||
- `uploadId` - The UploadID of this Multipart upload.
|
||||
- `key` - The object key of this Multipart upload.
|
||||
|
||||
This is typically called when the user cancels an upload. Cancellation cannot
|
||||
fail in Uppy, so the result of this function is ignored.
|
||||
|
||||
The default implementation calls out to Companion’s S3 signing endpoints.
|
||||
|
||||
#### `completeMultipartUpload(file, { uploadId, key, parts })`
|
||||
|
||||
A function that calls the S3 Multipart API to complete a Multipart upload,
|
||||
combining all parts into a single object in the S3 bucket.
|
||||
|
||||
Receives the `file` object from Uppy’s state, and an object with keys:
|
||||
|
||||
- `uploadId` - The UploadID of this Multipart upload.
|
||||
- `key` - The object key of this Multipart upload.
|
||||
- `parts` - S3-style list of parts, an array of objects with `ETag` and
|
||||
`PartNumber` properties. This can be passed straight to S3’s Multipart API.
|
||||
|
||||
Return a Promise for an object with properties:
|
||||
|
||||
- `location` - **(Optional)** A publicly accessible URL to the object in the S3
|
||||
bucket.
|
||||
|
||||
The default implementation calls out to Companion’s S3 signing endpoints.
|
||||
|
||||
#### `allowedMetaFields: null`
|
||||
|
||||
Pass an array of field names to limit the metadata fields that will be added to
|
||||
upload as query parameters.
|
||||
|
||||
- Set this to `['name']` to only send the `name` field.
|
||||
- Set this to `null` (the default) to send _all_ metadata fields.
|
||||
- Set this to an empty array `[]` to not send any fields.
|
||||
|
||||
<details>
|
||||
<summary>Deprecated options</summary>
|
||||
|
||||
#### `prepareUploadParts(file, partData)`
|
||||
|
||||
A function that generates a batch of signed URLs for the specified part numbers.
|
||||
|
||||
Receives the `file` object from Uppy’s state. The `partData` argument is an
|
||||
object with keys:
|
||||
|
||||
- `uploadId` - The UploadID of this Multipart upload.
|
||||
- `key` - The object key in the S3 bucket.
|
||||
- `parts` - An array of objects with the part number and chunk
|
||||
(`Array<{ number: number, chunk: blob }>`). `number` can’t be zero.
|
||||
|
||||
`prepareUploadParts` should return a `Promise` with an `Object` with keys:
|
||||
|
||||
- `presignedUrls` - A JavaScript object with the part numbers as keys and the
|
||||
presigned URL for each part as the value.
|
||||
- `headers` - **(Optional)** Custom headers to send along with every request per
|
||||
part (`{ 1: { 'Content-MD5': 'hash' }}`). These are (1-based) indexed by part
|
||||
number too so you can for instance send the MD5 hash validation for each part
|
||||
to S3.
|
||||
|
||||
An example of what the return value should look like:
|
||||
|
||||
```json
|
||||
{
|
||||
"presignedUrls": {
|
||||
"1": "https://bucket.region.amazonaws.com/path/to/file.jpg?partNumber=1&...",
|
||||
"2": "https://bucket.region.amazonaws.com/path/to/file.jpg?partNumber=2&...",
|
||||
"3": "https://bucket.region.amazonaws.com/path/to/file.jpg?partNumber=3&..."
|
||||
},
|
||||
"headers": {
|
||||
"1": { "Content-MD5": "foo" },
|
||||
"2": { "Content-MD5": "bar" },
|
||||
"3": { "Content-MD5": "baz" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If an error occurred, reject the `Promise` with an `Object` with the following
|
||||
keys:
|
||||
|
||||
```json
|
||||
{ "source": { "status": 500 } }
|
||||
```
|
||||
|
||||
`status` is the HTTP code and is required for determining whether to retry the
|
||||
request. `prepareUploadParts` will be retried if the code is `0`, `409`, `423`,
|
||||
or between `500` and `600`.
|
||||
|
||||
</details>
|
||||
|
||||
#### `getTemporarySecurityCredentials(options)`
|
||||
|
||||
:::note
|
||||
|
||||
When using [Companion][companion docs] as a backend, you can pass `true` instead
|
||||
of a function. Setting up Companion will not simplify the process of getting
|
||||
signing on the client.
|
||||
|
||||
:::
|
||||
|
||||
A boolean (when using Companion), or an (async) function to retrieve temporary
|
||||
security credentials used for all uploads instead of signing every part. This
|
||||
results in less request overhead which can lead to around 20% faster uploads.
|
||||
This is a security tradeoff. We recommend to not use this option unless you are
|
||||
familiar with the security implications of temporary credentials, and how to
|
||||
setup your bucket to make it work. See the
|
||||
[Requesting temporary security credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html)
|
||||
AWS guide for more information.
|
||||
|
||||
It’s strongly recommended to have some sort of caching process to avoid
|
||||
requesting more temporary token than necessary.
|
||||
|
||||
- `options`: `object`
|
||||
- `signal`: `AbortSignal`
|
||||
- **Returns:** `object | Promise<object>`
|
||||
- `credentials`: `object`
|
||||
- `AccessKeyId`: `string`
|
||||
- `SecretAccessKey`: `string`
|
||||
- `SessionToken`: `string`
|
||||
- `Expiration`: `string`
|
||||
- `bucket`: `string`
|
||||
- `region`: `string`
|
||||
|
||||
If you are using Companion (for example because you want to support remote
|
||||
upload sources), you can pass a boolean:
|
||||
|
||||
```js
|
||||
uppy.use(AwsS3, {
|
||||
// This is an example using Companion:
|
||||
companionUrl: 'http://companion.uppy.io',
|
||||
getTemporarySecurityCredentials: true,
|
||||
shouldUseMultipart: (file) => file.size > 100 * 2 ** 20,
|
||||
});
|
||||
```
|
||||
|
||||
In the most common case, you are using a different backend, in which case you
|
||||
need to specify a function:
|
||||
|
||||
```js
|
||||
uppy.use(AwsS3, {
|
||||
// This is an example not using Companion:
|
||||
async getTemporarySecurityCredentials({ signal }) {
|
||||
const response = await fetch('/sts-token', { signal });
|
||||
if (!response.ok)
|
||||
throw new Error('Failed to fetch STS', { cause: response });
|
||||
return response.json();
|
||||
},
|
||||
shouldUseMultipart: (file) => file.size > 100 * 2 ** 20,
|
||||
});
|
||||
```
|
||||
|
||||
[`gettemporarysecuritycredentials`]: #gettemporarysecuritycredentialsoptions
|
||||
[`shouldusemultipart`]: #shouldusemultipartfile
|
||||
[companion docs]: /docs/companion
|
||||
[signing on the client]: #gettemporarysecuritycredentialsoptions
|
||||
463
docs/uploader/aws-s3.mdx
Normal file
463
docs/uploader/aws-s3.mdx
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
slug: /aws-s3
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# AWS S3 (legacy)
|
||||
|
||||
The `@uppy/aws-s3` plugin can be used to upload files directly to a S3 bucket or
|
||||
a S3-compatible provider, such as Google Cloud Storage or DigitalOcean Spaces.
|
||||
Uploads can be signed using either [Companion][companion docs] or a custom
|
||||
signing function.
|
||||
|
||||
This documents the legacy version of this plugin that we plan to remove on the
|
||||
next version.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
:::tip
|
||||
|
||||
Not sure which uploader is best for you? Read
|
||||
“[Choosing the uploader you need](/docs/guides/choosing-uploader)”.
|
||||
|
||||
:::
|
||||
|
||||
:::warning
|
||||
|
||||
This plugin is deprecated, you should switch to using the
|
||||
[modern version of this plugin](/docs/aws-s3-multipart).
|
||||
|
||||
:::
|
||||
|
||||
You can use this plugin when you prefer a _client-to-storage_ over a
|
||||
_client-to-server-to-storage_ (such as [Transloadit](/docs/transloadit) or
|
||||
[Tus](/docs/tus)) setup. This may in some cases be preferable, for instance, to
|
||||
reduce costs or the complexity of running a server and load balancer with
|
||||
[Tus](/docs/tus).
|
||||
|
||||
This plugin can be used with AWS S3, DigitalOcean Spaces, Google Cloud Storage,
|
||||
or any S3-compatible provider. Although all S3-compatible providers are
|
||||
supported, we don’t test against them, this plugin was developed against S3 so a
|
||||
small risk is involved in using the others.
|
||||
|
||||
`@uppy/aws-s3` is best suited for small files and/or lots of files. If you are
|
||||
planning to upload mostly large files (100 MB+), consider using
|
||||
[`@uppy/aws-s3-multipart`](/docs/aws-s3-multipart).
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/aws-s3
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/aws-s3
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, AwsS3 } from "{{UPPY_JS_URL}}"
|
||||
new Uppy().use(AwsS3, { /* see options */ })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
A quick overview of the complete API.
|
||||
|
||||
```js {10} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import AwsS3 from '@uppy/aws-s3';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
const uppy = new Uppy()
|
||||
.use(Dashboard, { inline: true, target: 'body' })
|
||||
.use(AwsS3, { companionUrl: 'http://companion.uppy.io' });
|
||||
```
|
||||
|
||||
### With a AWS S3 bucket
|
||||
|
||||
To use this plugin with S3 we need to setup a bucket with the right permissions
|
||||
and CORS settings.
|
||||
|
||||
S3 buckets do not allow public uploads for security reasons. To allow Uppy and
|
||||
the browser to upload directly to a bucket, its CORS permissions need to be
|
||||
configured.
|
||||
|
||||
CORS permissions can be found in the
|
||||
[S3 Management Console](https://console.aws.amazon.com/s3/home). Click the
|
||||
bucket that will receive the uploads, then go into the `Permissions` tab and
|
||||
select the `CORS configuration` button. A JSON document will be shown that
|
||||
defines the CORS configuration. (AWS used to use XML but now only allow JSON).
|
||||
More information about the
|
||||
[S3 CORS format here](https://docs.amazonaws.cn/en_us/AmazonS3/latest/userguide/ManageCorsUsing.html).
|
||||
|
||||
The configuration required for Uppy and Companion is this:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"AllowedOrigins": ["https://my-app.com"],
|
||||
"AllowedMethods": ["GET", "POST"],
|
||||
"MaxAgeSeconds": 3000,
|
||||
"AllowedHeaders": [
|
||||
"Authorization",
|
||||
"x-amz-date",
|
||||
"x-amz-content-sha256",
|
||||
"content-type"
|
||||
]
|
||||
},
|
||||
{
|
||||
"AllowedOrigins": ["*"],
|
||||
"AllowedMethods": ["GET"],
|
||||
"MaxAgeSeconds": 3000
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
A good practice is to use two CORS rules: one for viewing the uploaded files,
|
||||
and one for uploading files. This is done above where the first object in the
|
||||
array defines the rules for uploading, and the second for viewing. The example
|
||||
above **makes files publicly viewable**. You can change it according to your
|
||||
needs.
|
||||
|
||||
If you are using an IAM policy to allow access to the S3 bucket, the policy must
|
||||
have at least the `s3:PutObject` and `s3:PutObjectAcl` permissions scoped to the
|
||||
bucket in question. In-depth documentation about CORS rules is available on the
|
||||
[AWS documentation site](https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html).
|
||||
|
||||
### With a DigitalOcean Spaces bucket
|
||||
|
||||
:::tip
|
||||
|
||||
Checkout the
|
||||
[DigitalOcean Spaces example](https://github.com/transloadit/uppy/tree/main/examples/digitalocean-spaces)
|
||||
in the Uppy repository for a complete, runnable example.
|
||||
|
||||
:::
|
||||
|
||||
DigitalOcean Spaces is S3-compatible so you only need to change the endpoint and
|
||||
bucket. Make sure you have a `key` and `secret`. If not, refer to
|
||||
“[How To Create a DigitalOcean Space and API Key](https://www.digitalocean.com/community/tutorials/how-to-create-a-digitalocean-space-and-api-key)”.
|
||||
|
||||
When using [Companion](/docs/companion) as standalone, you can set these as
|
||||
environment variables:
|
||||
|
||||
```bash
|
||||
export COMPANION_AWS_KEY="xxx"
|
||||
export COMPANION_AWS_SECRET="xxx"
|
||||
export COMPANION_AWS_REGION="us-east-1"
|
||||
export COMPANION_AWS_ENDPOINT="https://{region}.digitaloceanspaces.com"
|
||||
export COMPANION_AWS_BUCKET="my-space-name"
|
||||
```
|
||||
|
||||
The `{region}` string will be replaced by the contents of the
|
||||
`COMPANION_AWS_REGION` environment variable.
|
||||
|
||||
When using [Companion](/docs/companion) as an Express integration, configure the
|
||||
`s3` options:
|
||||
|
||||
```js
|
||||
const options = {
|
||||
s3: {
|
||||
key: 'xxx',
|
||||
secret: 'xxx',
|
||||
bucket: 'my-space-name',
|
||||
region: 'us-east-1',
|
||||
endpoint: 'https://{region}.digitaloceanspaces.com',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### With a Google Cloud Storage bucket
|
||||
|
||||
For the `@uppy/aws-s3` plugin to be able to upload to a GCS bucket, it needs the
|
||||
Interoperability setting enabled. You can enable the Interoperability setting
|
||||
and
|
||||
[generate interoperable storage access keys](https://cloud.google.com/storage/docs/migrating#keys)
|
||||
by going to [Google Cloud Storage](https://console.cloud.google.com/storage) »
|
||||
Settings » Interoperability. Then set the environment variables for Companion
|
||||
like this:
|
||||
|
||||
```bash
|
||||
export COMPANION_AWS_ENDPOINT="https://storage.googleapis.com"
|
||||
export COMPANION_AWS_BUCKET="YOUR-GCS-BUCKET-NAME"
|
||||
export COMPANION_AWS_KEY="GOOGxxxxxxxxx" # The Access Key
|
||||
export COMPANION_AWS_SECRET="YOUR-GCS-SECRET" # The Secret
|
||||
```
|
||||
|
||||
You do not need to configure the region with GCS.
|
||||
|
||||
You also need to configure CORS with their HTTP API. If you haven’t done this
|
||||
already, see
|
||||
[Configuring CORS on a Bucket](https://cloud.google.com/storage/docs/configuring-cors#Configuring-CORS-on-a-Bucket)
|
||||
in the GCS documentation, or follow the steps below to do it using Google’s API
|
||||
playground.
|
||||
|
||||
The JSON format consists of an array of CORS configuration objects. For
|
||||
instance:
|
||||
|
||||
```json
|
||||
{
|
||||
"cors": [
|
||||
{
|
||||
"origin": ["https://my-app.com"],
|
||||
"method": ["GET", "POST"],
|
||||
"maxAgeSeconds": 3000
|
||||
},
|
||||
{
|
||||
"origin": ["*"],
|
||||
"method": ["GET"],
|
||||
"maxAgeSeconds": 3000
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
When using presigned `PUT` uploads, replace the `"POST"` method by `"PUT"` in
|
||||
the first entry.
|
||||
|
||||
If you have the [gsutil](https://cloud.google.com/storage/docs/gsutil)
|
||||
command-line tool, you can apply this configuration using the
|
||||
[gsutil cors](https://cloud.google.com/storage/docs/configuring-cors#configure-cors-bucket)
|
||||
command.
|
||||
|
||||
```bash
|
||||
gsutil cors set THAT-FILE.json gs://BUCKET-NAME
|
||||
```
|
||||
|
||||
Otherwise, you can manually apply it through the OAuth playground:
|
||||
|
||||
1. Get a temporary API token from the
|
||||
[Google OAuth2.0 playground](https://developers.google.com/oauthplayground/)
|
||||
2. Select the `Cloud Storage JSON API v1` » `devstorage.full_control` scope
|
||||
3. Press `Authorize APIs` and allow access
|
||||
4. Click `Step 3 - Configure request to API`
|
||||
5. Configure it as follows:
|
||||
1. HTTP Method: PATCH
|
||||
2. Request URI: `https://www.googleapis.com/storage/v1/b/YOUR_BUCKET_NAME`
|
||||
3. Content-Type: application/json (should be the default)
|
||||
4. Press `Enter request body` and input your CORS configuration
|
||||
6. Press `Send the request`.
|
||||
|
||||
### Use with your own server
|
||||
|
||||
The recommended approach is to integrate `@uppy/aws-s3` with your own server.
|
||||
You will need to do the following things:
|
||||
|
||||
1. Setup a bucket
|
||||
2. Create endpoints in your server. You can create them as edge functions (such
|
||||
as AWS Lambdas), inside Next.js as an API route, or wherever your server runs
|
||||
- `POST` > `/uppy/s3`: get upload parameters
|
||||
3. [Setup Uppy](https://github.com/transloadit/uppy/blob/main/examples/aws-nodejs/public/index.html)
|
||||
|
||||
### Use with Companion
|
||||
|
||||
[Companion](/docs/companion) has S3 routes built-in for a plug-and-play
|
||||
experience with Uppy.
|
||||
|
||||
:::caution
|
||||
|
||||
Generally it’s better for access control, observability, and scaling to
|
||||
integrate `@uppy/aws-s3` with your own server. You may want to use
|
||||
[Companion](/docs/companion) for creating, signing, and completing your S3
|
||||
uploads if you already need Companion for remote files (such as from Google
|
||||
Drive). Otherwise it’s not worth the hosting effort.
|
||||
|
||||
:::
|
||||
|
||||
```js {10} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import AwsS3 from '@uppy/aws-s3';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
const uppy = new Uppy.use(Dashboard, { inline: true, target: 'body' }).use(
|
||||
AwsS3,
|
||||
{ companionUrl: 'http://companion.uppy.io' },
|
||||
);
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
The `@uppy/aws-s3` plugin has the following configurable options:
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'AwsS3'`).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
Companion instance to use for signing S3 uploads (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `allowedMetaFields`
|
||||
|
||||
Pass an array of field names to limit the metadata fields that will be added to
|
||||
upload as query parameters (`Array`, default: `null`).
|
||||
|
||||
- Set this to `['name']` to only send the `name` field.
|
||||
- Set this to `null` (the default) to send _all_ metadata fields.
|
||||
- Set this to an empty array `[]` to not send any fields.
|
||||
|
||||
#### `getUploadParameters(file)`
|
||||
|
||||
:::note
|
||||
|
||||
When using [Companion][companion docs] to sign S3 uploads, do not define this
|
||||
option.
|
||||
|
||||
:::
|
||||
|
||||
A function that returns upload parameters for a file (`Promise`, default:
|
||||
`null`).
|
||||
|
||||
Parameters should be returned as an object, or as a `Promise` that fulfills with
|
||||
an object, with keys `{ method, url, fields, headers }`.
|
||||
|
||||
- The `method` field is the HTTP method to be used for the upload. This should
|
||||
be one of either `PUT` or `POST`, depending on the type of upload used.
|
||||
- The `url` field is the URL to which the upload request will be sent. When
|
||||
using a presigned PUT upload, this should be the URL to the S3 object with
|
||||
signing parameters included in the query string. When using a POST upload with
|
||||
a policy document, this should be the root URL of the bucket.
|
||||
- The `fields` field is an object with form fields to send along with the upload
|
||||
request. For presigned PUT uploads, this should be left empty.
|
||||
- The `headers` field is an object with request headers to send along with the
|
||||
upload request. When using a presigned PUT upload, it’s a good idea to provide
|
||||
`headers['content-type']`. That will make sure that the request uses the same
|
||||
content-type that was used to generate the signature. Without it, the browser
|
||||
may decide on a different content-type instead, causing S3 to reject the
|
||||
upload.
|
||||
|
||||
#### `timeout`
|
||||
|
||||
When no upload progress events have been received for this amount of
|
||||
milliseconds, assume the connection has an issue and abort the upload (`number`,
|
||||
default: `30_000`).
|
||||
|
||||
This is passed through to [XHRUpload](/docs/xhr-upload#timeout-30-1000); see its
|
||||
documentation page for details. Set to `0` to disable this check.
|
||||
|
||||
#### `limit`
|
||||
|
||||
Limit the amount of uploads going on at the same time (`number`, default: `5`).
|
||||
|
||||
Setting this to `0` means no limit on concurrent uploads, but we recommend a
|
||||
value between `5` and `20`.
|
||||
|
||||
#### `getResponseData(responseText, response)`
|
||||
|
||||
:::note
|
||||
|
||||
This is an advanced option intended for use with _almost_ S3-compatible storage
|
||||
solutions.
|
||||
|
||||
:::
|
||||
|
||||
Customize response handling once an upload is completed. This passes the
|
||||
function through to @uppy/xhr-upload, see its
|
||||
[documentation](https://uppy.io/docs/xhr-upload/#getResponseData-responseText-response)
|
||||
for API details.
|
||||
|
||||
This option is useful when uploading to an S3-like service that doesn’t reply
|
||||
with an XML document, but with something else such as JSON.
|
||||
|
||||
#### `locale: {}`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
timedOut: 'Upload stalled for %{seconds} seconds, aborting.',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### How can I generate a presigned URL server-side?
|
||||
|
||||
The `getUploadParameters` function can return a `Promise`, so upload parameters
|
||||
can be prepared server-side. That way, no private keys to the S3 bucket need to
|
||||
be shared on the client. For example, there could be a PHP server endpoint that
|
||||
prepares a presigned URL for a file:
|
||||
|
||||
```js
|
||||
uppy.use(AwsS3, {
|
||||
getUploadParameters(file) {
|
||||
// Send a request to our PHP signing endpoint.
|
||||
return fetch('/s3-sign.php', {
|
||||
method: 'post',
|
||||
// Send and receive JSON.
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
filename: file.name,
|
||||
contentType: file.type,
|
||||
}),
|
||||
})
|
||||
.then((response) => {
|
||||
// Parse the JSON response.
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
// Return an object in the correct shape.
|
||||
return {
|
||||
method: data.method,
|
||||
url: data.url,
|
||||
fields: data.fields,
|
||||
// Provide content type header required by S3
|
||||
headers: {
|
||||
'Content-Type': file.type,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
See either the
|
||||
[aws-nodejs](https://github.com/transloadit/uppy/tree/HEAD/examples/aws-nodejs)
|
||||
or [aws-php](https://github.com/transloadit/uppy/tree/HEAD/examples/aws-php)
|
||||
examples in the uppy repository for a demonstration of how to implement handling
|
||||
of presigned URLs on both the server-side and client-side.
|
||||
|
||||
### How can I retrieve the presigned parameters of the uploaded file?
|
||||
|
||||
Once the file is uploaded, it’s possible to retrieve the parameters that were
|
||||
generated in `getUploadParameters(file)` via the `file.meta` field:
|
||||
|
||||
```js
|
||||
uppy.on('upload-success', (file, data) => {
|
||||
const s3Key = file.meta['key']; // the S3 object key of the uploaded file
|
||||
});
|
||||
```
|
||||
|
||||
[companion docs]: /docs/companion
|
||||
684
docs/uploader/transloadit.mdx
Normal file
684
docs/uploader/transloadit.mdx
Normal file
|
|
@ -0,0 +1,684 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
slug: /transloadit
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Transloadit
|
||||
|
||||
The `@uppy/transloadit` plugin can be used to upload files directly to
|
||||
[Transloadit](https://transloadit.com/) for all kinds of processing, such as
|
||||
transcoding video, resizing images, zipping/unzipping, [and much
|
||||
more][transloadit-services].
|
||||
|
||||
## When should I use it?
|
||||
|
||||
:::tip
|
||||
|
||||
Not sure which uploader is best for you? Read
|
||||
“[Choosing the uploader you need](/docs/guides/choosing-uploader)”.
|
||||
|
||||
:::
|
||||
|
||||
Transloadit’s strength is versatility. By doing video, audio, images, documents,
|
||||
and more, you only need one vendor for [all your file processing
|
||||
needs][transloadit-services]. The `@uppy/transloadit` plugin directly uploads to
|
||||
Transloadit so you only have to worry about creating a
|
||||
[Template][transloadit-concepts]. Transloadit accepts the files, processes
|
||||
according to the instructions in the Template, and stores the results in storage
|
||||
of your choosing, such as a self-owned S3 bucket. The Transloadit plugin uses
|
||||
[Tus](/docs/tus) under the hood so you don’t have to sacrifice reliable,
|
||||
resumable uploads.
|
||||
|
||||
You should use `@uppy/transloadit` if you don’t want to host your own Tus or
|
||||
Companion servers, (optionally) need file processing, and store it in the
|
||||
service (such as S3 or GCS) of your liking. All with minimal effort.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/transloadit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/transloadit
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Transloadit } from "{{UPPY_JS_URL}}"
|
||||
new Uppy().use(Transloadit, { /* see options */ })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
A quick overview of the complete API.
|
||||
|
||||
```js {10-17} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Transloadit from '@uppy/transloadit';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
const uppy = new Uppy()
|
||||
.use(Dashboard, { inline: true, target: 'body' })
|
||||
.use(Transloadit, {
|
||||
assemblyOptions: {
|
||||
params: {
|
||||
auth: { key: 'your-transloadit-key' },
|
||||
template_id: 'your-template-id',
|
||||
},
|
||||
},
|
||||
});
|
||||
// Optionally listen to events
|
||||
uppy.on('transloadit:assembly-created', (assembly, fileIDs) => {});
|
||||
uppy.on('transloadit:upload', (file, assembly) => {});
|
||||
uppy.on('transloadit:assembly-executing', (assembly) => {});
|
||||
uppy.on('transloadit:result', (stepName, result, assembly) => {});
|
||||
uppy.on('transloadit:complete', (assembly) => {});
|
||||
```
|
||||
|
||||
### Use with Companion
|
||||
|
||||
:::note
|
||||
|
||||
All [Transloadit plans](https://transloadit/pricing/) come with a hosted version
|
||||
of Companion.
|
||||
|
||||
:::
|
||||
|
||||
You can use this plugin together with Transloadit’s hosted Companion service to
|
||||
let your users import files from third party sources across the web. To do so
|
||||
each provider plugin must be configured with Transloadit’s Companion URLs:
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import Dropbox from '@uppy/dropbox';
|
||||
|
||||
uppy.use(Dropbox, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
});
|
||||
```
|
||||
|
||||
This will already work. Transloadit’s OAuth applications are used to
|
||||
authenticate your users by default. Your users will be asked to provide
|
||||
Transloadit access to their files. Since your users are probably not aware of
|
||||
Transloadit, this may be confusing or decrease trust. You may also hit rate
|
||||
limits, because the OAuth application is shared between everyone using
|
||||
Transloadit.
|
||||
|
||||
To solve that, you can use your own OAuth keys with Transloadit’s hosted
|
||||
Companion servers by using Transloadit Template Credentials. [Create a Template
|
||||
Credential][template-credentials] on the Transloadit site. Select “Companion
|
||||
OAuth” for the service, and enter the key and secret for the provider you want
|
||||
to use. Then you can pass the name of the new credentials to that provider:
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import Dropbox from '@uppy/dropbox';
|
||||
|
||||
uppy.use(Dropbox, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
companionKeysParams: {
|
||||
key: 'YOUR_TRANSLOADIT_API_KEY',
|
||||
credentialsName: 'my_companion_dropbox_creds',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Transloadit'`).
|
||||
|
||||
#### `service`
|
||||
|
||||
The Transloadit API URL to use (`string`, default:
|
||||
`https://api2.transloadit.com`).
|
||||
|
||||
The default will try to route traffic efficiently based on the location of your
|
||||
users. You could for instance set it to `https://api2-us-east-1.transloadit.com`
|
||||
if you need the traffic to stay inside a particular region.
|
||||
|
||||
#### `limit`
|
||||
|
||||
Limit the amount of uploads going on at the same time (`number`, default: `20`).
|
||||
|
||||
Setting this to `0` means no limit on concurrent uploads, but we recommend a
|
||||
value between `5` and `20`. This option is passed through to the
|
||||
[`@uppy/tus`](/docs/tus) plugin, which this plugin uses internally.
|
||||
|
||||
#### `assemblyOptions`
|
||||
|
||||
Configure the
|
||||
[Assembly Instructions](https://transloadit.com/docs/topics/assembly-instructions/),
|
||||
the fields to send along to the assembly, and authentication
|
||||
(`object | function`, default: `null`).
|
||||
|
||||
The object you can pass or return from a function has this structure:
|
||||
|
||||
```js
|
||||
{
|
||||
params: {
|
||||
auth: { key: 'key-from-transloadit' },
|
||||
template_id: 'id-from-transloadit',
|
||||
steps: {
|
||||
// Overruling Template at runtime
|
||||
},
|
||||
notify_url: 'https://your-domain.com/assembly-status',
|
||||
},
|
||||
signature: 'generated-signature',
|
||||
fields: {
|
||||
// Dynamic or static fields to send along
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- `params` is used to authenticate with Transloadit and using your desired
|
||||
[template](https://transloadit.com/docs/topics/templates/).
|
||||
- `auth.key` _(required)_ is your authentication key which you can find on the
|
||||
“Credentials” page of your account.
|
||||
- `template_id` _(required)_ is the unique identifier to use the right
|
||||
template from your account.
|
||||
- `steps` _(optional)_ can be used to
|
||||
[overrule Templates at runtime](https://transloadit.com/docs/topics/templates/#overruling-templates-at-runtime).
|
||||
A typical use case might be changing the storage path on the fly based on
|
||||
the session user id. For most use cases, we recommend to let your Templates
|
||||
handle dynamic cases (they can accept `fields` and execute arbitrary
|
||||
JavaScript as well), and not pass in `steps` from a browser. The template
|
||||
editor also has extra validations and context.
|
||||
- `notify_url` _(optional)_ is a pingback with the assembly status as JSON.
|
||||
For instance, if you don’t want to block the user experience by letting them
|
||||
wait for your template to complete with
|
||||
[`waitForEncoding`](#waitForEncoding), but you do want to want to
|
||||
asynchrounously have an update, you can provide an URL which will be
|
||||
“pinged” with the assembly status.
|
||||
- `signature` _(optional, but recommended)_ is a cryptographic signature to
|
||||
provide further trust in unstrusted environments. Refer to
|
||||
“[Signature Authentication”](https://transloadit.com/docs/topics/signature-authentication/)
|
||||
for more information.
|
||||
- `fields` _(optional)_ can be used to to send along key/value pairs, which can
|
||||
be
|
||||
[used dynamically in your template](https://transloadit.com/docs/topics/assembly-instructions/#form-fields-in-instructions).
|
||||
|
||||
<details>
|
||||
<summary>Examples</summary>
|
||||
|
||||
**As a function**
|
||||
|
||||
A custom `assemblyOptions()` option should return an object or a promise for an
|
||||
object.
|
||||
|
||||
```js
|
||||
uppy.use(Transloadit, {
|
||||
assemblyOptions(file) {
|
||||
return {
|
||||
params: {
|
||||
auth: { key: 'TRANSLOADIT_AUTH_KEY_HERE' },
|
||||
template_id: 'xyz',
|
||||
},
|
||||
fields: {
|
||||
caption: file.meta.caption,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The `${fields.caption}` variable will be available in the Assembly spawned from
|
||||
Template `xyz`. You can use this to dynamically watermark images for example.
|
||||
|
||||
`assemblyOptions()` may also return a Promise, so it could retrieve signed
|
||||
Assembly parameters from a server. For example, assuming an endpoint
|
||||
`/transloadit-params` that responds with a JSON object with
|
||||
`{ params, signature }` properties:
|
||||
|
||||
```js
|
||||
uppy.use(Transloadit, {
|
||||
async assemblyOptions(file) {
|
||||
const res = await fetch('/transloadit-params');
|
||||
return response.json();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**As an object**
|
||||
|
||||
If you don’t need to change anything dynamically, you can also pass an object
|
||||
directly.
|
||||
|
||||
```js
|
||||
uppy.use(Transloadit, {
|
||||
assemblyOptions: {
|
||||
params: { auth: { key: 'transloadit-key' } },
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Use with @uppy/form**
|
||||
|
||||
Combine the `assemblyOptions()` option with the [Form](/docs/form) plugin to
|
||||
pass user input from a `<form>` to a Transloadit Assembly:
|
||||
|
||||
```js
|
||||
// This will add form field values to each file's `.meta` object:
|
||||
uppy.use(Form, { getMetaFromForm: true });
|
||||
uppy.use(Transloadit, {
|
||||
getAssemblyOptions(file) {
|
||||
return {
|
||||
params: {
|
||||
/* ... */
|
||||
},
|
||||
// Pass through the fields you need:
|
||||
fields: {
|
||||
message: file.meta.message,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
:::caution
|
||||
|
||||
When you go to production always make sure to set the `signature`. **Not using
|
||||
[Signature Authentication](https://transloadit.com/docs/topics/signature-authentication/)
|
||||
can be a security risk**. Signature Authentication is a security measure that
|
||||
can prevent outsiders from tampering with your Assembly Instructions. While
|
||||
Signature Authentication is not implemented (yet), we recommend to disable
|
||||
`allow_steps_override` in your Templates to avoid outsiders being able to pass
|
||||
in any Instructions and storage targets on your behalf.
|
||||
|
||||
:::
|
||||
|
||||
#### `waitForEncoding`
|
||||
|
||||
Wait for the template to finish, rather than only the upload, before marking the
|
||||
upload complete (`boolean`, default: `false`).
|
||||
|
||||
- When `false`, the Assemblies will complete (or error) in the background but
|
||||
Uppy won’t know or care about it. You may have to let Transloadit ping you via
|
||||
a `notify_url` and asynchronously inform your user (email, in-app
|
||||
notification).
|
||||
- When `true`, the Transloadit plugin waits for Assemblies to complete before
|
||||
the files are marked as completed. This means users have to wait for a
|
||||
potentially long time, depending on how complicated your Assembly instructions
|
||||
are. But, you can receive the final status and transcoding results on the
|
||||
client side with less effort.
|
||||
|
||||
When this is enabled, you can listen for the
|
||||
[`transloadit:result`](#transloaditresult) and
|
||||
[`transloadit:complete`](#transloaditcomplete) events.
|
||||
|
||||
#### `waitForMetadata`
|
||||
|
||||
Wait for Transloadit’s backend to catch early errors, not the entire Assembly to
|
||||
complete. (`boolean`, default: `false`)
|
||||
|
||||
When set to `true`, the Transloadit plugin waits for Transloadit’s backend to
|
||||
extract metadata from all the uploaded files. This is mostly handy if you want
|
||||
to have a quick user experience (so your users don’t necessarily need to wait
|
||||
for all the encoding to complete), but you do want to let users know about some
|
||||
types of errors that can be caught early on, like file format issues.
|
||||
|
||||
You you can listen for the [`transloadit:upload`](#transloaditupload) event when
|
||||
this or `waitForEncoding` is enabled.
|
||||
|
||||
#### `importFromUploadURLs`
|
||||
|
||||
Allow another plugin to upload files, and then import those files into the
|
||||
Transloadit Assembly (`boolean`, default: `false`).
|
||||
|
||||
When enabling this option, Transloadit will _not_ configure the Tus plugin to
|
||||
upload to Transloadit. Instead, a separate upload plugin must be used. Once the
|
||||
upload completes, the Transloadit plugin adds the uploaded file to the Assembly.
|
||||
|
||||
For example, to upload files to an S3 bucket and then transcode them:
|
||||
|
||||
```js
|
||||
uppy.use(AwsS3, {
|
||||
getUploadParameters(file) {
|
||||
return {
|
||||
/* upload parameters */
|
||||
};
|
||||
},
|
||||
});
|
||||
uppy.use(Transloadit, {
|
||||
importFromUploadURLs: true,
|
||||
assemblyOptions: {
|
||||
params: {
|
||||
auth: { key: 'YOUR_API_KEY' },
|
||||
template_id: 'YOUR_TEMPLATE_ID',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Tranloadit will download the files and expose them to your Template as
|
||||
`:original`, as if they were directly uploaded from the Uppy client.
|
||||
:::note
|
||||
|
||||
For this to work, the upload plugin must assign a publicly accessible
|
||||
`uploadURL` property to the uploaded file object. The Tus and S3 plugins both do
|
||||
this automatically, but you must configure your S3 bucket to have publicly
|
||||
readable objects. For the XHRUpload plugin, you may have to specify a custom
|
||||
`getResponseData` function.
|
||||
|
||||
:::
|
||||
|
||||
#### `alwaysRunAssembly`
|
||||
|
||||
Always create and run an Assembly when `uppy.upload()` is called, even if no
|
||||
files were selected (`boolean`, default: `false`).
|
||||
|
||||
This allows running Assemblies that do not receive files, but instead use a
|
||||
robot like [`/s3/import`](https://transloadit.com/docs/transcoding/#s3-import)
|
||||
to download the files from elsewhere, for example, for a bulk transcoding job.
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
// Shown while Assemblies are being created for an upload.
|
||||
creatingAssembly: 'Preparing upload...',
|
||||
// Shown if an Assembly could not be created.
|
||||
creatingAssemblyFailed: 'Transloadit: Could not create Assembly',
|
||||
// Shown after uploads have succeeded, but when the Assembly is still executing.
|
||||
// This only shows if `waitForMetadata` or `waitForEncoding` was enabled.
|
||||
encoding: 'Encoding...',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### `clientName`
|
||||
|
||||
Append a custom client name to the `Transloadit-Client` header field when
|
||||
creating an Assembly (`string`, default: `null`).
|
||||
|
||||
The `Transloadit-Client` header includes by default information about the used
|
||||
SDK and is included in the Assembly Status under the `transloadit_client`
|
||||
property. By providing a value, such as `homepage-file-uploader`, you can
|
||||
identify the client and SDK that created a given Assembly.
|
||||
|
||||
<details>
|
||||
<summary>Deprecated options</summary>
|
||||
|
||||
These options have been deprecated in favor of
|
||||
[`assemblyOptions`](#assemblyoptions), which we now recommend for all use cases.
|
||||
You can still use these options, but they will be removed in the next major
|
||||
version.
|
||||
|
||||
#### `getAssemblyOptions`
|
||||
|
||||
This function behaves the same as passing a function to
|
||||
[`assemblyOptions`](#assemblyoptions).
|
||||
|
||||
#### `params`
|
||||
|
||||
The Assembly parameters to use for the upload (`object`, default: `null`) See
|
||||
the Transloadit documentation on
|
||||
[Assembly Instructions](https://transloadit.com/docs/#14-assembly-instructions)
|
||||
for further information.
|
||||
|
||||
The `auth.key` Assembly parameter is required. You can also use the `steps` or
|
||||
`template_id` options here as described in the Transloadit documentation.
|
||||
|
||||
```js
|
||||
uppy.use(Transloadit, {
|
||||
params: {
|
||||
auth: { key: 'YOUR_TRANSLOADIT_KEY' },
|
||||
steps: {
|
||||
encode: {
|
||||
robot: '/video/encode',
|
||||
use: {
|
||||
steps: [':original'],
|
||||
fields: ['file_input_field2'],
|
||||
},
|
||||
preset: 'iphone',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### `signature`
|
||||
|
||||
An optional signature for the Assembly parameters. See the Transloadit
|
||||
documentation on
|
||||
[Signature Authentication](https://transloadit.com/docs/#26-signature-authentication)
|
||||
for further information.
|
||||
|
||||
If a `signature` is provided, `params` should be a JSON string instead of a
|
||||
JavaScript object, as otherwise the generated JSON in the browser may be
|
||||
different from the JSON string that was used to generate the signature.
|
||||
|
||||
#### `fields`
|
||||
|
||||
An object of form fields to send along to the Assembly. Keys are field names,
|
||||
and values are field values. See also the Transloadit documentation on
|
||||
[Form Fields In Instructions](https://transloadit.com/docs/#23-form-fields-in-instructions).
|
||||
|
||||
```js
|
||||
uppy.use(Transloadit, {
|
||||
// ...
|
||||
fields: {
|
||||
message: 'This is a form field',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can also pass an array of field names to send global or file metadata along
|
||||
to the Assembly. Global metadata is set using the
|
||||
[`meta` option](/docs/uppy/#meta) in the Uppy constructor, or using the
|
||||
[`setMeta` method](/docs/uppy/#uppy-setMeta-data). File metadata is set using
|
||||
the [`setFileMeta`](/docs/uppy/#uppy-setFileMeta-fileID-data) method. The
|
||||
[Form](/docs/form) plugin also sets global metadata based on the values of
|
||||
`<input />`s in the form, providing a handy way to use values from HTML form
|
||||
fields:
|
||||
|
||||
```js
|
||||
uppy.use(Form, { target: 'form#upload-form', getMetaFromForm: true });
|
||||
uppy.use(Transloadit, {
|
||||
fields: ['field_name', 'other_field_name'],
|
||||
params: {
|
||||
/* ... */
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Form fields can also be computed dynamically using custom logic, by using the
|
||||
[`getAssemblyOptions(file)`](/docs/transloadit/#getAssemblyOptions-file) option.
|
||||
|
||||
</details>
|
||||
|
||||
### Static exports
|
||||
|
||||
#### `COMPANION_URL`
|
||||
|
||||
The main endpoint for Transloadit’s hosted companions. You can use this constant
|
||||
in remote provider options, like so:
|
||||
|
||||
```js
|
||||
import Dropbox from '@uppy/dropbox';
|
||||
import { COMPANION_URL } from '@uppy/transloadit';
|
||||
|
||||
uppy.use(Dropbox, {
|
||||
companionUrl: COMPANION_URL,
|
||||
});
|
||||
```
|
||||
|
||||
When using `COMPANION_URL`, you should also configure
|
||||
[`companionAllowedHosts`](#companion_allowed_hosts).
|
||||
|
||||
The value of this constant is `https://api2.transloadit.com/companion`. If you
|
||||
are using a custom [`service`](#service) option, you should also set a custom
|
||||
host option in your provider plugins, by taking a Transloadit API url and
|
||||
appending `/companion`:
|
||||
|
||||
```js
|
||||
uppy.use(Dropbox, {
|
||||
companionUrl: 'https://api2-us-east-1.transloadit.com/companion',
|
||||
});
|
||||
```
|
||||
|
||||
#### `COMPANION_ALLOWED_HOSTS`
|
||||
|
||||
A RegExp pattern matching Transloadit’s hosted companion endpoints. The pattern
|
||||
is used in remote provider `companionAllowedHosts` options, to make sure that
|
||||
third party authentication messages cannot be faked by an attacker’s page but
|
||||
can only originate from Transloadit’s servers.
|
||||
|
||||
Use it whenever you use `companionUrl: COMPANION_URL`, like so:
|
||||
|
||||
```js
|
||||
import Dropbox from '@uppy/dropbox';
|
||||
import { COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
|
||||
uppy.use(Dropbox, {
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
});
|
||||
```
|
||||
|
||||
The value of this constant covers _all_ Transloadit’s Companion servers, so it
|
||||
does not need to be changed if you are using a custom [`service`](#service)
|
||||
option. But, if you are not using the Transloadit Companion servers at
|
||||
`*.transloadit.com`, make sure to set the `companionAllowedHosts` option to
|
||||
something that matches what you do use.
|
||||
|
||||
### Events
|
||||
|
||||
#### `transloadit:assembly-created`
|
||||
|
||||
Fired when an Assembly is created.
|
||||
|
||||
**Parameters**
|
||||
|
||||
- `assembly` - The initial [Assembly Status][assembly-status].
|
||||
- `fileIDs` - The IDs of the files that will be uploaded to this Assembly.
|
||||
|
||||
```js
|
||||
uppy.on('transloadit:assembly-created', (assembly, fileIDs) => {
|
||||
console.group('Created', assembly.assembly_id, 'for files:');
|
||||
for (const id of fileIDs) {
|
||||
console.log(uppy.getFile(id).name);
|
||||
}
|
||||
console.groupEnd();
|
||||
});
|
||||
```
|
||||
|
||||
#### `transloadit:upload`
|
||||
|
||||
Fired when Transloadit has received an upload. Requires
|
||||
[`waitForMetadata`](#waitformetadata) to be set.
|
||||
|
||||
**Parameters**
|
||||
|
||||
- `file` - The Transloadit file object that was uploaded.
|
||||
- `assembly` - The [Assembly Status][assembly-status] of the Assembly to which
|
||||
the file was uploaded.
|
||||
|
||||
#### `transloadit:assembly-executing`
|
||||
|
||||
Fired when Transloadit has received all uploads, and is executing the Assembly.
|
||||
|
||||
**Parameters**
|
||||
|
||||
- `assembly` - The
|
||||
[Assembly Status](https://transloadit.com/docs/api/#assembly-status-response)
|
||||
of the Assembly that is executing.
|
||||
|
||||
#### `transloadit:result`
|
||||
|
||||
Fired when a result came in from an Assembly. Requires
|
||||
[`waitForEncoding`](#waitforencoding) to be set.
|
||||
|
||||
**Parameters**
|
||||
|
||||
- `stepName` - The name of the Assembly step that generated this result.
|
||||
- `result` - The result object from Transloadit. This result object has one more
|
||||
property, namely `localId`. This is the ID of the file in Uppy’s local state,
|
||||
and can be used with `uppy.getFile(id)`.
|
||||
- `assembly` - The [Assembly Status][assembly-status] of the Assembly that
|
||||
generated this result.
|
||||
|
||||
```js
|
||||
uppy.on('transloadit:result', (stepName, result) => {
|
||||
const file = uppy.getFile(result.localId);
|
||||
document.body.appendChild(html`
|
||||
<div>
|
||||
<h2>From ${file.name}</h2>
|
||||
<a href=${result.ssl_url}> View </a>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
```
|
||||
|
||||
#### `transloadit:complete`
|
||||
|
||||
Fired when an Assembly completed. Requires [`waitForEncoding`](#waitForEncoding)
|
||||
to be set.
|
||||
|
||||
**Parameters**
|
||||
|
||||
- `assembly` - The final [Assembly Status][assembly-status] of the completed
|
||||
Assembly.
|
||||
|
||||
```js
|
||||
uppy.on('transloadit:complete', (assembly) => {
|
||||
// Could do something fun with this!
|
||||
console.log(assembly.results);
|
||||
});
|
||||
```
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### Accessing the assembly when an error occurred
|
||||
|
||||
If an error occurs when an Assembly has already started, you can find the
|
||||
Assembly Status on the error object’s `assembly` property.
|
||||
|
||||
```js
|
||||
uppy.on('error', (error) => {
|
||||
if (error.assembly) {
|
||||
console.log(`Assembly ID ${error.assembly.assembly_id} failed!`);
|
||||
console.log(error.assembly);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Assembly behavior when Uppy is closed
|
||||
|
||||
When integrating `@uppy/transloadit` with `@uppy/dashboard`, closing the
|
||||
dashboard will result in continuing assemblies on the server. When the user
|
||||
manually cancels the upload any running assemblies will be cancelled.
|
||||
|
||||
[assembly-status]: https://transloadit.com/docs/api/#assembly-status-response
|
||||
[template-credentials]:
|
||||
https://transloadit.com/docs/#how-to-create-template-credentials
|
||||
[transloadit-services]: https://transloadit.com/services/
|
||||
[transloadit-concepts]: https://transloadit.com/docs/getting-started/concepts/
|
||||
308
docs/uploader/tus.mdx
Normal file
308
docs/uploader/tus.mdx
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
---
|
||||
sidebar_position: 2
|
||||
slug: /tus
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Tus
|
||||
|
||||
The `@uppy/tus` plugin brings resumable file uploading with [Tus](http://tus.io)
|
||||
to Uppy by wrapping the [`tus-js-client`][].
|
||||
|
||||
## When should I use it?
|
||||
|
||||
:::tip
|
||||
|
||||
Not sure which uploader is best for you? Read
|
||||
“[Choosing the uploader you need](/docs/guides/choosing-uploader)”.
|
||||
|
||||
:::
|
||||
|
||||
[Tus][tus] is an open protocol for resumable uploads built on HTTP. This means
|
||||
accidentally closing your tab or losing connection let’s you continue, for
|
||||
instance, your 10GB upload instead of starting all over.
|
||||
|
||||
Tus supports any language, any platform, and any network. It requires a client
|
||||
and server integration to work. You can checkout the client and server
|
||||
[implementations][] to find the server in your preferred language. You can store
|
||||
files on the Tus server itself, but you can also use service integrations (such
|
||||
as S3) to store files externally. If you don’t want to host your own server, see
|
||||
“[Are there hosted Tus servers?](#are-there-hosted-tus-servers)”.
|
||||
|
||||
If you want reliable, resumable uploads: use `@uppy/tus` to connect to your Tus
|
||||
server in a few lines of code.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/tus
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/tus
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Tus } from "{{UPPY_JS_URL}}"
|
||||
new Uppy().use(Tus, { endpoint: 'https://tusd.tusdemo.net/files' })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
A quick overview of the complete API.
|
||||
|
||||
```js {10} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Tus from '@uppy/tus';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: 'body' })
|
||||
.use(Tus, { endpoint: 'https://tusd.tusdemo.net/files/' });
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
:::info
|
||||
|
||||
All options are passed to `tus-js-client` and we document the ones here that are
|
||||
required, added, or changed. This means you can also pass functions like
|
||||
[`onAfterResponse`](https://github.com/tus/tus-js-client/blob/master/docs/api.md#onafterresponse).
|
||||
|
||||
We recommended taking a look at the
|
||||
[API reference](https://github.com/tus/tus-js-client/blob/master/docs/api.md)
|
||||
from `tus-js-client` to know what is supported.
|
||||
|
||||
:::
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Tus'`).
|
||||
|
||||
#### `endpoint`
|
||||
|
||||
URL of the tus server (`string`, default: `null`).
|
||||
|
||||
#### `headers`
|
||||
|
||||
An object or function returning an object with HTTP headers to send along
|
||||
requests (`object | function`, default: `null`).
|
||||
|
||||
Keys are header names, values are header values.
|
||||
|
||||
```js
|
||||
const headers = {
|
||||
authorization: `Bearer ${window.getCurrentUserToken()}`,
|
||||
};
|
||||
```
|
||||
|
||||
Header values can also be derived from file data by providing a function. The
|
||||
function receives an [Uppy file][] and must return an object where the keys are
|
||||
header names, and values are header values.
|
||||
|
||||
```js
|
||||
const headers = (file) => {
|
||||
return {
|
||||
authorization: `Bearer ${window.getCurrentUserToken()}`,
|
||||
expires: file.meta.expires,
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
#### `chunkSize`
|
||||
|
||||
A number indicating the maximum size of a `PATCH` request body in bytes
|
||||
(`number`, default: `Infinity`). Note that this option only affects local
|
||||
browser uploads. If you need a max chunk size for remote (Companion) uploads,
|
||||
you must set the `chunkSize` Companion option as well.
|
||||
|
||||
:::caution
|
||||
|
||||
Do not set this value unless you are forced to. The two valid reasons are
|
||||
described in the
|
||||
[`tus-js-client` docs](https://github.com/tus/tus-js-client/blob/master/docs/api.md#chunksize).
|
||||
|
||||
:::
|
||||
|
||||
#### `withCredentials`
|
||||
|
||||
Configure the requests to send Cookies using the
|
||||
[`xhr.withCredentials`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials)
|
||||
property (`boolean`, default: `false`).
|
||||
|
||||
The remote server must accept CORS and credentials.
|
||||
|
||||
#### `retryDelays`
|
||||
|
||||
When uploading a chunk fails, automatically try again after the defined
|
||||
millisecond intervals (`Array<number>`, default: `[0, 1000, 3000, 5000]`).
|
||||
|
||||
By default, we first retry instantly; if that fails, we retry after 1 second; if
|
||||
that fails, we retry after 3 seconds, etc.
|
||||
|
||||
Set to `null` to disable automatic retries, and fail instantly if any chunk
|
||||
fails to upload.
|
||||
|
||||
#### `onBeforeRequest(req, file)`
|
||||
|
||||
Behaves like the
|
||||
[`onBeforeRequest`](https://github.com/tus/tus-js-client/blob/master/docs/api.md#onbeforerequest)
|
||||
function from `tus-js-client` but with the added `file` argument.
|
||||
|
||||
#### `onShouldRetry: (err, retryAttempt, options, next)`
|
||||
|
||||
When an upload fails `onShouldRetry` is called with the error and the default
|
||||
retry logic as the last argument (`function`).
|
||||
|
||||
The default retry logic is an
|
||||
[exponential backoff](https://en.wikipedia.org/wiki/Exponential_backoff)
|
||||
algorithm triggered on HTTP 429 (Too Many Requests) errors. Meaning if your
|
||||
server (or proxy) returns HTTP 429 because it’s being overloaded, @uppy/tus will
|
||||
find the ideal sweet spot to keep uploading without overloading.
|
||||
|
||||
If you want to extend this functionality, for instance to retry on unauthorized
|
||||
requests (to retrieve a new authentication token):
|
||||
|
||||
```js
|
||||
import Uppy from '@uppy/core';
|
||||
import Tus from '@uppy/tus';
|
||||
new Uppy().use(Tus, {
|
||||
endpoint: '',
|
||||
async onBeforeRequest(req) {
|
||||
const token = await getAuthToken();
|
||||
req.setHeader('Authorization', `Bearer ${token}`);
|
||||
},
|
||||
onShouldRetry(err, retryAttempt, options, next) {
|
||||
if (err?.originalResponse?.getStatus() === 401) {
|
||||
return true;
|
||||
}
|
||||
return next(err);
|
||||
},
|
||||
async onAfterResponse(req, res) {
|
||||
if (res.getStatus() === 401) {
|
||||
await refreshAuthToken();
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### `allowedMetaFields`
|
||||
|
||||
Pass an array of field names to limit the metadata fields that will be added to
|
||||
uploads as
|
||||
[Tus Metadata](https://tus.io/protocols/resumable-upload.html#upload-metadata)
|
||||
(`Array`, default: `null`).
|
||||
|
||||
- Set this to `['name']` to only send the `name` field.
|
||||
- Set this to `null` (the default) to send _all_ metadata fields.
|
||||
- Set this to an empty array `[]` to not send any fields.
|
||||
|
||||
#### `limit`
|
||||
|
||||
Limit the amount of uploads going on at the same time (`number`, default: `20`).
|
||||
|
||||
Setting this to `0` means no limit on concurrent uploads (not recommended).
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
:::info
|
||||
|
||||
The Tus website has extensive [FAQ section](https://tus.io/faq.html), we
|
||||
recommend taking a look there as well if something is unclear.
|
||||
|
||||
:::
|
||||
|
||||
### How is file meta data stored?
|
||||
|
||||
Tus uses unique identifiers for the file names to prevent naming collisions. To
|
||||
still keep the meta data in place, Tus also uploads an extra `.info` file with
|
||||
the original file name and other meta data:
|
||||
|
||||
```json
|
||||
{
|
||||
"ID": "00007a99d16d4eeb5a3e3c080b6f69da+JHZavdqPSK4VMtarg2yYcNiP8t_kDjN51lBYMJdEyr_wqEotVl8ZBRBSTnWKWenZBwHvbLNz5tQXYp2N7Vdol.04ysQAuw__suTJ4IsCljj0rjyWA6LvV4IwF5P2oom2",
|
||||
"Size": 1679852,
|
||||
"SizeIsDeferred": false,
|
||||
"Offset": 0,
|
||||
"MetaData": {
|
||||
"filename": "cat.jpg",
|
||||
"filetype": "image/jpeg"
|
||||
},
|
||||
"IsPartial": false,
|
||||
"IsFinal": false,
|
||||
"PartialUploads": null,
|
||||
"Storage": {
|
||||
"Bucket": "your-bucket",
|
||||
"Key": "some-key",
|
||||
"Type": "s3store"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### How do I change files before sending them?
|
||||
|
||||
If you want to change the file names, you want to do that in
|
||||
[`onBeforeFileAdded`](/docs/uppy#onbeforefileaddedfile-files).
|
||||
|
||||
If you want to send extra headers with the request, use [`headers`](#headers) or
|
||||
[`onBeforeRequest`](#onbeforerequestreq-file).
|
||||
|
||||
### How do I change (or move) files after sending them?
|
||||
|
||||
If you want to preserve files names, extract meta data, or move files to a
|
||||
different place you generally can with hooks or events. It depends on the Tus
|
||||
server you use how it’s done exactly. [`tusd`][], for instance, exposes
|
||||
[hooks](https://github.com/tus/tusd/blob/master/docs/hooks.md) and
|
||||
[`tus-node-server`](https://github.com/tus/tus-node-server) has
|
||||
[events](https://github.com/tus/tus-node-server#events).
|
||||
|
||||
### Which server do you recommend?
|
||||
|
||||
[Transloadit](https://transloadit.com) runs [`tusd`][] in production, where it
|
||||
serves millions of requests globally. So we recommend `tusd` as battle-tested
|
||||
from our side, but other companies have had success with other
|
||||
[implementations][] so it depends on your needs.
|
||||
|
||||
### Are there hosted Tus servers?
|
||||
|
||||
All [Transloadit plans](https://transloadit.com/pricing) come with a hosted
|
||||
[`tusd`][] server. You don’t have to do anything to leverage it, using
|
||||
[`@uppy/transloadit`](/docs/transloadit) automatically uses Tus under the hood.
|
||||
|
||||
### Why Tus instead of directly uploading to AWS S3?
|
||||
|
||||
First: reliable, resumable uploads. This means accidentally closing your tab or
|
||||
losing connection let’s you continue, for instance, your 10GB upload instead of
|
||||
starting all over.
|
||||
|
||||
Tus is also efficient with lots of files (such as 8K) and large files. Uploading
|
||||
to AWS S3 directly from the client also introduces quite a bit of overhead, as
|
||||
more requests are needed for the flow to work.
|
||||
|
||||
[`tus-js-client`]: https://github.com/tus/tus-js-client
|
||||
[uppy file]: /docs/uppy#working-with-uppy-files
|
||||
[tus]: https://tus.io/
|
||||
[`tusd`]: https://github.com/tus/tusd
|
||||
[implementations]: https://tus.io/implementations.html
|
||||
399
docs/uploader/xhr.mdx
Normal file
399
docs/uploader/xhr.mdx
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
---
|
||||
sidebar_position: 5
|
||||
slug: /xhr-upload
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# XHR
|
||||
|
||||
The `@uppy/xhr-upload` plugin is for regular uploads to a HTTP server.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
:::tip
|
||||
|
||||
Not sure which uploader is best for you? Read
|
||||
“[Choosing the uploader you need](/docs/guides/choosing-uploader)”.
|
||||
|
||||
:::
|
||||
|
||||
When you have an existing HTTP server and you don’t need Transloadit services or
|
||||
want to run a [tus][] server. Note that it’s still possible to use [tus][]
|
||||
without running an extra server by integrating tus into your existing one. For
|
||||
instance, if you have a Node.js server (or server-side framework like Next.js)
|
||||
you could integrate [tus-node-server][].
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/xhr-upload
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/xhr-upload
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, XHRUpload } from "{{UPPY_JS_URL}}"
|
||||
new Uppy().use(XHRUpload, { endpoint: 'https://tusd.tusdemo.net/files' })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
A quick overview of the complete API.
|
||||
|
||||
```js {10} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import XHR from '@uppy/xhr-upload';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: 'body' })
|
||||
.use(XHR, { endpoint: 'https://your-domain.com/upload' });
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'XHRUpload'`).
|
||||
|
||||
#### `endpoint`
|
||||
|
||||
URL of the HTTP server (`string`, default: `null`).
|
||||
|
||||
#### `method`
|
||||
|
||||
Configures which HTTP method to use for the upload (`string`, default:
|
||||
`'post'`).
|
||||
|
||||
#### `formData`
|
||||
|
||||
Configures whether to use a multipart form upload, using [FormData][]
|
||||
(`boolean`, default: `true`).
|
||||
|
||||
This works similarly to using a `<form>` element with an `<input type="file">`
|
||||
for uploads. When set to `true`, file metadata is also sent to the endpoint as
|
||||
separate form fields. When set to `false`, only the file contents are sent.
|
||||
|
||||
#### `fieldName`
|
||||
|
||||
When [`formData`](#formData-true) is set to true, this is used as the form field
|
||||
name for the file to be uploaded.
|
||||
|
||||
It defaults to `'files[]'` if `bundle` option is set to `true`, otherwise it
|
||||
defaults to `'file'`.
|
||||
|
||||
#### `allowedMetaFields`
|
||||
|
||||
Pass an array of field names to limit the metadata fields that will be added to
|
||||
upload.
|
||||
|
||||
- Set this to an empty array `[]` to not send any fields.
|
||||
- Set this to `['name']` to only send the `name` field.
|
||||
- Set this to `null` (the default) to send _all_ metadata fields.
|
||||
|
||||
If the [`formData`](#formData-true) option is set to false, `metaFields` is
|
||||
ignored.
|
||||
|
||||
#### `headers`
|
||||
|
||||
An object containing HTTP headers to use for the upload request. Keys are header
|
||||
names, values are header values.
|
||||
|
||||
```js
|
||||
const headers = {
|
||||
authorization: `Bearer ${window.getCurrentUserToken()}`,
|
||||
};
|
||||
```
|
||||
|
||||
Header values can also be derived from file data by providing a function. The
|
||||
function receives an [Uppy file][] and must return an object where the keys are
|
||||
header names, and values are header values.
|
||||
|
||||
```js
|
||||
const headers = (file) => {
|
||||
return {
|
||||
authorization: `Bearer ${window.getCurrentUserToken()}`,
|
||||
expires: file.meta.expires,
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
The function syntax is not available when [`bundle`](#bundle) is set to `true`.
|
||||
|
||||
:::
|
||||
|
||||
#### `bundle`
|
||||
|
||||
Send all files in a single multipart request (`boolean`, default: `false`).
|
||||
|
||||
All files will be appended to the provided `fieldName` field in the request.
|
||||
|
||||
:::caution
|
||||
|
||||
When `bundle` is set to `true`:
|
||||
|
||||
- [`formData`](#formData-true) must also be set to `true`.
|
||||
- Uppy won’t be able to bundle remote files (such as Google Drive) and will
|
||||
throw an error in this case.
|
||||
- Only [global uppy metadata](/docs/uppy/#meta) is sent to the endpoint.
|
||||
Individual per-file metadata is ignored.
|
||||
|
||||
:::
|
||||
|
||||
To upload files on different fields, use
|
||||
[`uppy.setFileState()`](/docs/uppy#uppy-setFileState-fileID-state) to set the
|
||||
`xhrUpload.fieldName` property on the file:
|
||||
|
||||
```js
|
||||
uppy.setFileState(fileID, {
|
||||
xhrUpload: { fieldName: 'pic0' },
|
||||
});
|
||||
```
|
||||
|
||||
#### `validateStatus`
|
||||
|
||||
Check if the response was successful (`function`, default:
|
||||
`(status, responseText, response) => boolean`).
|
||||
|
||||
- By default, responses with a 2xx HTTP status code are considered successful.
|
||||
- When `true`, [`getResponseData()`](#getResponseData-responseText-response)
|
||||
will be called and the upload will be marked as successful.
|
||||
- When `false`, both
|
||||
[`getResponseData()`](#getResponseData-responseText-response) and
|
||||
[`getResponseError()`](#getResponseError-responseText-response) will be called
|
||||
and the upload will be marked as unsuccessful.
|
||||
|
||||
##### Parameters
|
||||
|
||||
- The `statusCode` is the numeric HTTP status code returned by the endpoint.
|
||||
- The `responseText` is the XHR endpoint response as a string.
|
||||
- `response` is the [XMLHttpRequest][] object.
|
||||
|
||||
:::note
|
||||
|
||||
This option is only used for **local** uploads. Uploads from remote providers
|
||||
like Google Drive or Instagram do not support this and will always use the
|
||||
default.
|
||||
|
||||
:::
|
||||
|
||||
#### `getResponseData`
|
||||
|
||||
Extract the response data from the successful upload (`function`, default:
|
||||
`(responseText, response) => void`).
|
||||
|
||||
- `responseText` is the XHR endpoint response as a string.
|
||||
- `response` is the [XMLHttpRequest][] object.
|
||||
|
||||
JSON is handled automatically, so you should only use this if the endpoint
|
||||
responds with a different format. For example, an endpoint that responds with an
|
||||
XML document:
|
||||
|
||||
```js
|
||||
function getResponseData(responseText, response) {
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(responseText, 'text/xml');
|
||||
return {
|
||||
url: xmlDoc.querySelector('Location').textContent,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
This response data will be available on the file’s `.response` property and will
|
||||
be emitted in the [`upload-success`][uppy.upload-success] event.
|
||||
|
||||
:::
|
||||
|
||||
:::note
|
||||
|
||||
When uploading files from remote providers such as Dropbox or Instagram,
|
||||
Companion sends upload response data to the client. This is made available in
|
||||
the `getResponseData()` function as well. The `response` object from Companion
|
||||
has some properties named after their [XMLHttpRequest][] counterparts.
|
||||
|
||||
:::
|
||||
|
||||
#### `getResponseError`
|
||||
|
||||
Extract the error from the failed upload (`function`, default:
|
||||
`(responseText, response) => void`).
|
||||
|
||||
For example, if the endpoint responds with a JSON object containing a
|
||||
`{ message }` property, this would show that message to the user:
|
||||
|
||||
```js
|
||||
function getResponseError(responseText, response) {
|
||||
return new Error(JSON.parse(responseText).message);
|
||||
}
|
||||
```
|
||||
|
||||
#### `responseUrlFieldName`
|
||||
|
||||
The field name containing the location of the uploaded file (`string`, default:
|
||||
`'url'`).
|
||||
|
||||
This is returned by [`getResponseData()`](#getResponseData).
|
||||
|
||||
#### `timeout: 30 * 1000`
|
||||
|
||||
Abort the connection if no upload progress events have been received for this
|
||||
milliseconds amount (`number`, default: `30_000`).
|
||||
|
||||
Note that unlike the [`XMLHttpRequest.timeout`][xhr.timeout] property, this is a
|
||||
timer between progress events: the total upload can take longer than this value.
|
||||
Set to `0` to disable this check.
|
||||
|
||||
#### `limit`
|
||||
|
||||
The maximum amount of files to upload in parallel (`number`, default: `5`).
|
||||
|
||||
#### `responseType`
|
||||
|
||||
The response type expected from the server, determining how the `xhr.response`
|
||||
property should be filled (`string`, default: `'text'`).
|
||||
|
||||
The `xhr.response` property can be accessed in a custom
|
||||
[`getResponseData()`](#getResponseData-responseText-response) callback. This
|
||||
option sets the [`XMLHttpRequest.responseType`][xhr.responsetype] property. Only
|
||||
`''`, `'text'`, `'arraybuffer'`, `'blob'` and `'document'` are widely supported
|
||||
by browsers, so it’s recommended to use one of those.
|
||||
|
||||
#### `withCredentials`
|
||||
|
||||
Indicates whether cross-site Access-Control requests should be made using
|
||||
credentials (`boolean`, default: `false`).
|
||||
|
||||
#### `locale: {}`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
// Shown in the Informer if an upload is being canceled because it stalled for too long.
|
||||
timedOut: 'Upload stalled for %{seconds} seconds, aborting.',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### How to send along meta data with the upload?
|
||||
|
||||
When using XHRUpload with [`formData: true`](#formData-true), file metadata is
|
||||
sent along with each upload request. You can set metadata for a file using
|
||||
[`uppy.setFileMeta(fileID, data)`](/docs/uppy#uppy-setFileMeta-fileID-data), or
|
||||
for all files simultaneously using
|
||||
[`uppy.setMeta(data)`](/docs/uppy#uppy-setMeta-data).
|
||||
|
||||
It may be useful to set metadata depending on some file properties, such as the
|
||||
size. You can use the [`file-added`](/docs/uppy/#file-added) event and the
|
||||
[`uppy.setFileMeta(fileID, data)`](/docs/uppy#uppy-setFileMeta-fileID-data)
|
||||
method to do this:
|
||||
|
||||
```js
|
||||
uppy.on('file-added', (file) => {
|
||||
uppy.setFileMeta(file.id, {
|
||||
size: file.size,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Now, a form field named `size` will be sent along to the
|
||||
[`endpoint`](#endpoint-39-39) once the upload starts.
|
||||
|
||||
By default, all metadata is sent, including Uppy’s default `name` and `type`
|
||||
metadata. If you do not want the `name` and `type` metadata properties to be
|
||||
sent to your upload endpoint, you can use the [`metaFields`](#metaFields-null)
|
||||
option to restrict the field names that should be sent.
|
||||
|
||||
```js
|
||||
uppy.use(XHRUpload, {
|
||||
// Only send our own `size` metadata field.
|
||||
allowedMetaFields: ['size'],
|
||||
});
|
||||
```
|
||||
|
||||
### How to upload to a PHP server?
|
||||
|
||||
The XHRUpload plugin works similarly to a `<form>` upload. You can use the
|
||||
`$_FILES` variable on the server to work with uploaded files. See the PHP
|
||||
documentation on [Handling file uploads][php.file-upload].
|
||||
|
||||
The default form field for file uploads is `files[]`, which means you have to
|
||||
access the `$_FILES` array as described in [Uploading many files][php.multiple]:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// upload.php
|
||||
$files = $_FILES['files'];
|
||||
$file_path = $files['tmp_name'][0]; // temporary upload path of the first file
|
||||
$file_name = $_POST['name']; // desired name of the file
|
||||
move_uploaded_file($file_path, './img/' . basename($file_name)); // save the file in `img/`
|
||||
```
|
||||
|
||||
Note how we are using `$_POST['name']` instead of `$my_file['name']`.
|
||||
`$my_file['name']` has the original name of the file on the user’s device.
|
||||
`$_POST['name']` has the `name` metadata value for the uploaded file, which can
|
||||
be edited by the user using the [Dashboard](/docs/dashboard).
|
||||
|
||||
Set a custom `fieldName` to make working with the `$_FILES` array a bit less
|
||||
convoluted:
|
||||
|
||||
```js
|
||||
// app.js
|
||||
uppy.use(XHRUpload, {
|
||||
endpoint: '/upload.php',
|
||||
fieldName: 'my_file',
|
||||
});
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
// upload.php
|
||||
$my_file = $_FILES['my_file'];
|
||||
$file_path = $my_file['tmp_name']; // temporary upload path of the file
|
||||
$file_name = $_POST['name']; // desired name of the file
|
||||
move_uploaded_file($file_path, $_SERVER['DOCUMENT_ROOT'] . '/img/' . basename($file_name)); // save the file at `img/FILE_NAME`
|
||||
```
|
||||
|
||||
[formdata]: https://developer.mozilla.org/en-US/docs/Web/API/FormData
|
||||
[xmlhttprequest]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
|
||||
[xhr.timeout]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout
|
||||
[xhr.responsetype]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType
|
||||
[uppy.upload-success]: /docs/uppy/#upload-success
|
||||
[uppy file]: /docs/uppy#working-with-uppy-files
|
||||
[php.file-upload]: https://secure.php.net/manual/en/features.file-upload.php
|
||||
[php.multiple]:
|
||||
https://secure.php.net/manual/en/features.file-upload.multiple.php
|
||||
[tus-node-server]: https://github.com/tus/tus-node-server
|
||||
[tus]: https://tus.io/
|
||||
1628
docs/uppy-core.mdx
Normal file
1628
docs/uppy-core.mdx
Normal file
File diff suppressed because it is too large
Load diff
4
docs/user-interfaces/_category_.json
Normal file
4
docs/user-interfaces/_category_.json
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"label": "User interfaces",
|
||||
"position": 5
|
||||
}
|
||||
753
docs/user-interfaces/dashboard.mdx
Normal file
753
docs/user-interfaces/dashboard.mdx
Normal file
|
|
@ -0,0 +1,753 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
slug: /dashboard
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Dashboard
|
||||
|
||||
The all you need Dashboard — powerful, responsive, and pluggable. Kickstart your
|
||||
uploading experience and gradually add more functionality. Add files from
|
||||
[remote sources](/docs/companion), [edit images](/docs/image-editor),
|
||||
[generate thumbnails](/docs/thumbnail-generator), and more.
|
||||
|
||||
Checkout [integrations](#integrations) for the full list of plugins you can
|
||||
integrate.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example with all plugins](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-dashboard-xpxuhd).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
There could be many reasons why you may want to use the Dashboard, but some
|
||||
could be:
|
||||
|
||||
- when you need a battle tested plug-and-play uploading UI to save time.
|
||||
- when your users need to add files from [remote sources](/docs/companion), such
|
||||
[Google Drive](/docs/google-drive), [Dropbox](/docs/dropbox), and others.
|
||||
- when you need to collect [meta data](#metafields) from your users per file.
|
||||
- when your users want to take a picture with their [webcam](/docs/webcam) or
|
||||
[capture their screen](/docs/screen-capture).
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/core @uppy/dashboard
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/core @uppy/dashboard
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Dashboard } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Dashboard, { target: '#uppy', inline: true })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy().use(Dashboard, { inline: true, target: '#uppy-dashboard' });
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
The `@uppy/dashboard` plugin includes CSS for the Dashboard itself, and the
|
||||
various plugins used by the Dashboard, such as
|
||||
([`@uppy/status-bar`](/docs/status-bar) and [`@uppy/informer`](/docs/informer)).
|
||||
If you also use the `@uppy/status-bar` or `@uppy/informer` plugin directly, you
|
||||
should not include their CSS files, but instead only use the one from the
|
||||
`@uppy/dashboard` plugin.
|
||||
|
||||
:::
|
||||
|
||||
:::note
|
||||
|
||||
Styles for Provider plugins, like Google Drive and Instagram, are also bundled
|
||||
with Dashboard styles. Styles for other plugins, such as `@uppy/url` and
|
||||
`@uppy/webcam`, are not included. If you are using those, please see their docs
|
||||
and make sure to include styles for them as well.
|
||||
|
||||
:::
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Dashboard'`).
|
||||
|
||||
Plugins that are added by the Dashboard get unique IDs based on this ID, like
|
||||
`'Dashboard:StatusBar'` and `'Dashboard:Informer'`.
|
||||
|
||||
#### `target`
|
||||
|
||||
Where to render the Dashboard (`string` or `Element`, default: `'body'`).
|
||||
|
||||
You can pass an element, class, or id as a string. Dashboard is rendered into
|
||||
`body`, because it’s hidden by default and only opened as a modal when `trigger`
|
||||
is clicked.
|
||||
|
||||
#### `inline`
|
||||
|
||||
Render the Dashboard as a modal or inline (`boolean`, default: `false`).
|
||||
|
||||
When `false`, Dashboard is opened by clicking on [`trigger`](#trigger). If
|
||||
`inline: true`, Dashboard will be rendered into [`target`](#target) and fit
|
||||
right in.
|
||||
|
||||
#### `trigger`
|
||||
|
||||
A CSS selector for a button that will trigger opening the Dashboard modal
|
||||
(`string`, default: `null`).
|
||||
|
||||
Several buttons or links can be used, as long as they are selected using the
|
||||
same selector (`.select-file-button`, for example).
|
||||
|
||||
#### `width`
|
||||
|
||||
Width of the Dashboard in pixels (`number`, default: `750`). Used when
|
||||
`inline: true`.
|
||||
|
||||
#### `height`
|
||||
|
||||
Height of the Dashboard in pixels (`number`, default: `550`). Used when
|
||||
`inline: true`.
|
||||
|
||||
#### `waitForThumbnailsBeforeUpload`
|
||||
|
||||
Whether to wait for all thumbnails from `@uppy/thumbnail-generator` to be ready
|
||||
before starting the upload (`boolean`, default `false`).
|
||||
|
||||
If set to `true`, Thumbnail Generator will envoke Uppy’s internal processing
|
||||
stage, displaying “Generating thumbnails...” message, and wait for
|
||||
`thumbnail:all-generated` event, before proceeding to the uploading stage.
|
||||
|
||||
This is useful because Thumbnail Generator also adds EXIF data to images, and if
|
||||
we wait until it’s done processing, this data will be available on the server
|
||||
after the upload.
|
||||
|
||||
#### `showLinkToFileUploadResult`
|
||||
|
||||
Turn the file icon and thumbnail in the Dashboard into a link to the uploaded
|
||||
file (`boolean`, default: `false`).
|
||||
|
||||
Please make sure to return the `url` key (or the one set via
|
||||
`responseUrlFieldName`) from your server.
|
||||
|
||||
#### `showProgressDetails`
|
||||
|
||||
Show or hide progress details in the status bar (`boolean`, default: `false`).
|
||||
|
||||
By default, progress in Status Bar is shown as a percentage. If you would like
|
||||
to also display remaining upload size and time, set this to `true`.
|
||||
|
||||
`showProgressDetails: false`: Uploading: 45%
|
||||
|
||||
`showProgressDetails: true`: Uploading: 45%・43 MB of 101 MB・8s left
|
||||
|
||||
#### `hideUploadButton`
|
||||
|
||||
Show or hide the upload button (`boolean`, default: `false`).
|
||||
|
||||
Use this if you are providing a custom upload button somewhere, and are using
|
||||
the `uppy.upload()` API.
|
||||
|
||||
#### `hideRetryButton`
|
||||
|
||||
Hide the retry button in the status bar and on each individual file (`boolean`,
|
||||
default: `false`).
|
||||
|
||||
Use this if you are providing a custom retry button somewhere and if you are
|
||||
using the `uppy.retryAll()` or `uppy.retryUpload(fileID)` API.
|
||||
|
||||
#### `hidePauseResumeButton`
|
||||
|
||||
Hide the pause/resume button (for resumable uploads, via [tus](http://tus.io),
|
||||
for example) in the status bar and on each individual file (`boolean`, default:
|
||||
`false`).
|
||||
|
||||
Use this if you are providing custom cancel or pause/resume buttons somewhere,
|
||||
and using the `uppy.pauseResume(fileID)` or `uppy.removeFile(fileID)` API.
|
||||
|
||||
#### `hideCancelButton`
|
||||
|
||||
Hide the cancel button in status bar and on each individual file (`boolean`,
|
||||
default: `false`).
|
||||
|
||||
Use this if you are providing a custom retry button somewhere, and using the
|
||||
`uppy.cancelAll()` API.
|
||||
|
||||
#### `hideProgressAfterFinish`
|
||||
|
||||
Hide the status bar after the upload has finished (`boolean`, default: `false`).
|
||||
|
||||
#### `doneButtonHandler`
|
||||
|
||||
This option is passed to the status bar and will render a “Done” button in place
|
||||
of pause/resume/cancel buttons, once the upload/encoding is done. The behaviour
|
||||
of this “Done” button is defined by this handler function, for instance to close
|
||||
the file picker modals or clear the upload state.
|
||||
|
||||
This is what the Dashboard sets by default:
|
||||
|
||||
```js
|
||||
const doneButtonHandler = () => {
|
||||
this.uppy.cancelAll();
|
||||
this.requestCloseModal();
|
||||
};
|
||||
```
|
||||
|
||||
Set to `null` to disable the “Done” button.
|
||||
|
||||
#### `showSelectedFiles`
|
||||
|
||||
Show the list of added files with a preview and file information (`boolean`,
|
||||
default: `true`).
|
||||
|
||||
In case you are showing selected files in your own app’s UI and want the Uppy
|
||||
Dashboard to only be a picker, the list can be hidden with this option.
|
||||
|
||||
See also [`disableStatusBar`](#disablestatusbar) option, which can hide the
|
||||
progress and upload button.
|
||||
|
||||
#### `showRemoveButtonAfterComplete`
|
||||
|
||||
Show the remove button on every file after a successful upload (`boolean`,
|
||||
default: `false`).
|
||||
|
||||
Enabling this option only shows the remove `X` button in the Dashboard UI, but
|
||||
to actually send a request you should listen to
|
||||
[`file-removed`](https://uppy.io/docs/uppy/#file-removed) event and add your
|
||||
logic there.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
uppy.on('file-removed', (file, reason) => {
|
||||
if (reason === 'removed-by-user') {
|
||||
sendDeleteRequestForFile(file);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
For an implementation example, please see
|
||||
[#2301](https://github.com/transloadit/uppy/issues/2301#issue-628931176).
|
||||
|
||||
#### `singleFileFullScreen`
|
||||
|
||||
When only one file is selected, its preview and meta information will be
|
||||
centered and enlarged (`boolean`, default: `true`).
|
||||
|
||||
Often times Uppy used for photo / profile image uploads, or maybe a single
|
||||
document. Then it makes sense to occupy the whole space of the available
|
||||
Dashboard UI, giving the stage to this one file. This feature is automatically
|
||||
disabled when Dashboard is small in height, since there’s not enough room.
|
||||
|
||||
#### `note`
|
||||
|
||||
A string of text to be placed in the Dashboard UI (`string`, default: `null`).
|
||||
|
||||
This could for instance be used to explain any [`restrictions`](#restrictions)
|
||||
that are put in place. For example:
|
||||
`'Images and video only, 2–3 files, up to 1 MB'`.
|
||||
|
||||
#### `metaFields`
|
||||
|
||||
Create text or custom input fields for the user to fill in (`Array<Object>` or
|
||||
`Function`, default: `null`).
|
||||
|
||||
This will be shown when a user clicks the “edit” button on that file.
|
||||
|
||||
:::note
|
||||
|
||||
The meta data will only be set on a file object if it’s entered by the user. If
|
||||
the user doesn’t edit a file’s metadata, it will not have default values;
|
||||
instead everything will be `undefined`. If you want to set a certain meta field
|
||||
to each file regardless of user actions, set
|
||||
[`meta` in the Uppy constructor options](/docs/uppy/#meta).
|
||||
|
||||
:::
|
||||
|
||||
Each object can contain:
|
||||
|
||||
- `id`. The name of the meta field. This will also be used in CSS/HTML as part
|
||||
of the `id` attribute, so it’s better to
|
||||
[avoid using characters like periods, semicolons, etc](https://stackoverflow.com/a/79022).
|
||||
- `name`. The label shown in the interface.
|
||||
- `placeholder`. The text shown when no value is set in the field. (Not needed
|
||||
when a custom render function is provided)
|
||||
- `render: ({value, onChange, required, form}, h) => void` (optional). A
|
||||
function for rendering a custom form element.
|
||||
- `value` is the current value of the meta field
|
||||
- `onChange: (newVal) => void` is a function saving the new value and `h` is
|
||||
the `createElement` function from
|
||||
[Preact](https://preactjs.com/guide/v10/api-reference#h--createelement).
|
||||
- `required` is a boolean that’s true if the field `id` is in the
|
||||
`restrictedMetaFields` restriction
|
||||
- `form` is the `id` of the associated `<form>` element.
|
||||
- `h` can be useful when using Uppy from plain JavaScript, where you cannot
|
||||
write JSX.
|
||||
|
||||
<details>
|
||||
<summary>Example: meta fields configured as an `Array`</summary>
|
||||
|
||||
```js
|
||||
uppy.use(Dashboard, {
|
||||
trigger: '#pick-files',
|
||||
metaFields: [
|
||||
{ id: 'name', name: 'Name', placeholder: 'file name' },
|
||||
{ id: 'license', name: 'License', placeholder: 'specify license' },
|
||||
{
|
||||
id: 'caption',
|
||||
name: 'Caption',
|
||||
placeholder: 'describe what the image is about',
|
||||
},
|
||||
{
|
||||
id: 'public',
|
||||
name: 'Public',
|
||||
render({ value, onChange, required, form }, h) {
|
||||
return h('input', {
|
||||
type: 'checkbox',
|
||||
required,
|
||||
form,
|
||||
onChange: (ev) => onChange(ev.target.checked ? 'on' : ''),
|
||||
defaultChecked: value === 'on',
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Example: dynamic meta fields based on file type with a `Function`</summary>
|
||||
|
||||
```js
|
||||
uppy.use(Dashboard, {
|
||||
trigger: '#pick-files',
|
||||
metaFields: (file) => {
|
||||
const fields = [{ id: 'name', name: 'File name' }];
|
||||
if (file.type.startsWith('image/')) {
|
||||
fields.push({ id: 'location', name: 'Photo Location' });
|
||||
fields.push({ id: 'alt', name: 'Alt text' });
|
||||
fields.push({
|
||||
id: 'public',
|
||||
name: 'Public',
|
||||
render: ({ value, onChange, required, form }, h) => {
|
||||
return h('input', {
|
||||
type: 'checkbox',
|
||||
onChange: (ev) => onChange(ev.target.checked ? 'on' : ''),
|
||||
defaultChecked: value === 'on',
|
||||
required,
|
||||
form,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
return fields;
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### `closeModalOnClickOutside`
|
||||
|
||||
Set to true to automatically close the modal when the user clicks outside of it
|
||||
(`boolean`, default: `false`).
|
||||
|
||||
#### `closeAfterFinish`
|
||||
|
||||
Set to true to automatically close the modal when all current uploads are
|
||||
complete (`boolean`, default: `false`).
|
||||
|
||||
With this option, the modal is only automatically closed when uploads are
|
||||
complete _and successful_. If some uploads failed, the modal stays open so the
|
||||
user can retry failed uploads or cancel the current batch and upload an entirely
|
||||
different set of files instead.
|
||||
|
||||
:::info
|
||||
|
||||
You can use this together with the
|
||||
[`allowMultipleUploads: false`](/docs/uppy/#allowmultipleuploads) option in Uppy
|
||||
Core to create a smooth experience when uploading a single (batch of) file(s).
|
||||
|
||||
This is recommended. With several upload batches, the auto-closing behavior can
|
||||
be quite confusing for users.
|
||||
|
||||
:::
|
||||
|
||||
#### `disablePageScrollWhenModalOpen`
|
||||
|
||||
Disable page scroll when the modal is open (`boolean`, default: `true`).
|
||||
|
||||
Page scrolling is disabled by default when the Dashboard modal is open, so when
|
||||
you scroll a list of files in Uppy, the website in the background stays still.
|
||||
Set to false to override this behaviour and leave page scrolling intact.
|
||||
|
||||
#### `animateOpenClose`
|
||||
|
||||
Add animations when the modal dialog is opened or closed, for a more satisfying
|
||||
user experience (`boolean`, default: `true`).
|
||||
|
||||
#### `fileManagerSelectionType`
|
||||
|
||||
Configure the type of selections allowed when browsing your file system via the
|
||||
file manager selection window (`string`, default: `'files'`).
|
||||
|
||||
May be either `'files'`, `'folders'`, or `'both'`. Selecting entire folders for
|
||||
upload may not be supported on all
|
||||
[browsers](https://caniuse.com/#feat=input-file-directory).
|
||||
|
||||
#### `proudlyDisplayPoweredByUppy`
|
||||
|
||||
Show the Uppy logo with a link (`boolean`, default: `true`).
|
||||
|
||||
Uppy is provided to the world for free by the team behind
|
||||
[Transloadit](https://transloadit.com). In return, we ask that you consider
|
||||
keeping a tiny Uppy logo at the bottom of the Dashboard, so that more people can
|
||||
discover and use Uppy.
|
||||
|
||||
#### `disableStatusBar`
|
||||
|
||||
Disable the status bar completely (`boolean`, default: `false`).
|
||||
|
||||
Dashboard ships with the `StatusBar` plugin that shows upload progress and
|
||||
pause/resume/cancel buttons. If you want, you can disable the StatusBar to
|
||||
provide your own custom solution.
|
||||
|
||||
#### `disableInformer`
|
||||
|
||||
Disable informer (shows notifications in the form of toasts) completely
|
||||
(`boolean`, default: `false`).
|
||||
|
||||
Dashboard ships with the `Informer` plugin that notifies when the browser is
|
||||
offline, or when it’s time to say cheese if `Webcam` is taking a picture. If you
|
||||
want, you can disable the Informer and/or provide your own custom solution.
|
||||
|
||||
#### `disableThumbnailGenerator`
|
||||
|
||||
Disable the thumbnail generator completely (`boolean`, default: `false`).
|
||||
|
||||
Dashboard ships with the `ThumbnailGenerator` plugin that adds small resized
|
||||
image thumbnails to images, for preview purposes only. If you want, you can
|
||||
disable the `ThumbnailGenerator` and/or provide your own custom solution.
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
strings: {
|
||||
// When `inline: false`, used as the screen reader label for the button that closes the modal.
|
||||
closeModal: 'Close Modal',
|
||||
// Used as the screen reader label for the plus (+) button that shows the “Add more files” screen
|
||||
addMoreFiles: 'Add more files',
|
||||
addingMoreFiles: 'Adding more files',
|
||||
// Used as the header for import panels, e.g., “Import from Google Drive”.
|
||||
importFrom: 'Import from %{name}',
|
||||
// When `inline: false`, used as the screen reader label for the dashboard modal.
|
||||
dashboardWindowTitle: 'Uppy Dashboard Window (Press escape to close)',
|
||||
// When `inline: true`, used as the screen reader label for the dashboard area.
|
||||
dashboardTitle: 'Uppy Dashboard',
|
||||
// Shown in the Informer when a link to a file was copied to the clipboard.
|
||||
copyLinkToClipboardSuccess: 'Link copied to clipboard.',
|
||||
// Used when a link cannot be copied automatically — the user has to select the text from the
|
||||
// input element below this string.
|
||||
copyLinkToClipboardFallback: 'Copy the URL below',
|
||||
// Used as the hover title and screen reader label for buttons that copy a file link.
|
||||
copyLink: 'Copy link',
|
||||
back: 'Back',
|
||||
// Used as the screen reader label for buttons that remove a file.
|
||||
removeFile: 'Remove file',
|
||||
// Used as the screen reader label for buttons that open the metadata editor panel for a file.
|
||||
editFile: 'Edit file',
|
||||
// Shown in the panel header for the metadata editor. Rendered as “Editing image.png”.
|
||||
editing: 'Editing %{file}',
|
||||
// Used as the screen reader label for the button that saves metadata edits and returns to the
|
||||
// file list view.
|
||||
finishEditingFile: 'Finish editing file',
|
||||
saveChanges: 'Save changes',
|
||||
// Used as the label for the tab button that opens the system file selection dialog.
|
||||
myDevice: 'My Device',
|
||||
dropHint: 'Drop your files here',
|
||||
// Used as the hover text and screen reader label for file progress indicators when
|
||||
// they have been fully uploaded.
|
||||
uploadComplete: 'Upload complete',
|
||||
uploadPaused: 'Upload paused',
|
||||
// Used as the hover text and screen reader label for the buttons to resume paused uploads.
|
||||
resumeUpload: 'Resume upload',
|
||||
// Used as the hover text and screen reader label for the buttons to pause uploads.
|
||||
pauseUpload: 'Pause upload',
|
||||
// Used as the hover text and screen reader label for the buttons to retry failed uploads.
|
||||
retryUpload: 'Retry upload',
|
||||
// Used as the hover text and screen reader label for the buttons to cancel uploads.
|
||||
cancelUpload: 'Cancel upload',
|
||||
// Used in a title, how many files are currently selected
|
||||
xFilesSelected: {
|
||||
0: '%{smart_count} file selected',
|
||||
1: '%{smart_count} files selected',
|
||||
},
|
||||
uploadingXFiles: {
|
||||
0: 'Uploading %{smart_count} file',
|
||||
1: 'Uploading %{smart_count} files',
|
||||
},
|
||||
processingXFiles: {
|
||||
0: 'Processing %{smart_count} file',
|
||||
1: 'Processing %{smart_count} files',
|
||||
},
|
||||
// The "powered by Uppy" link at the bottom of the Dashboard.
|
||||
poweredBy: 'Powered by %{uppy}',
|
||||
addMore: 'Add more',
|
||||
editFileWithFilename: 'Edit file %{file}',
|
||||
save: 'Save',
|
||||
cancel: 'Cancel',
|
||||
dropPasteFiles: 'Drop files here or %{browseFiles}',
|
||||
dropPasteFolders: 'Drop files here or %{browseFolders}',
|
||||
dropPasteBoth: 'Drop files here, %{browseFiles} or %{browseFolders}',
|
||||
dropPasteImportFiles: 'Drop files here, %{browseFiles} or import from:',
|
||||
dropPasteImportFolders: 'Drop files here, %{browseFolders} or import from:',
|
||||
dropPasteImportBoth:
|
||||
'Drop files here, %{browseFiles}, %{browseFolders} or import from:',
|
||||
importFiles: 'Import files from:',
|
||||
browseFiles: 'browse files',
|
||||
browseFolders: 'browse folders',
|
||||
recoveredXFiles: {
|
||||
0: 'We could not fully recover 1 file. Please re-select it and resume the upload.',
|
||||
1: 'We could not fully recover %{smart_count} files. Please re-select them and resume the upload.',
|
||||
},
|
||||
recoveredAllFiles: 'We restored all files. You can now resume the upload.',
|
||||
sessionRestored: 'Session restored',
|
||||
reSelect: 'Re-select',
|
||||
missingRequiredMetaFields: {
|
||||
0: 'Missing required meta field: %{fields}.',
|
||||
1: 'Missing required meta fields: %{fields}.',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### `theme`
|
||||
|
||||
Light or dark theme for the Dashboard (`string`, default: `'light'`).
|
||||
|
||||
Uppy Dashboard supports “Dark Mode”. You can try it live on
|
||||
[the Dashboard example page](https://uppy.io/examples/).
|
||||
|
||||
It supports the following values:
|
||||
|
||||
- `light` — the default
|
||||
- `dark`
|
||||
- `auto` — will respect the user’s system settings and switch automatically
|
||||
|
||||
#### `autoOpen`
|
||||
|
||||
Automatically open file editor for the file user just dropped/selected.
|
||||
If one file is added, editor opens for that file; if 10 files are added, editor
|
||||
opens only for the first file.
|
||||
|
||||
This option supports the following values:
|
||||
|
||||
- `null` - the default
|
||||
- `"metaEditor"` - open the meta fields editor if
|
||||
[meta fields](/docs/dashboard/#metafields) are enabled.
|
||||
- `"imageEditor"` - open [`@uppy/image-editor`](/docs/image-editor) if the
|
||||
plugin is enabled.
|
||||
|
||||
#### `disabled`
|
||||
|
||||
Enabling this option makes the Dashboard grayed-out and non-interactive
|
||||
(`boolean`, default: `false`).
|
||||
|
||||
Users won’t be able to click on buttons or drop files. Useful when you need to
|
||||
conditionally enable/disable file uploading or manipulation, based on a
|
||||
condition in your app. Can be set on init or via API:
|
||||
|
||||
```js
|
||||
const dashboard = uppy.getPlugin('Dashboard');
|
||||
dashboard.setOptions({ disabled: true });
|
||||
|
||||
userNameInput.addEventListener('change', () => {
|
||||
dashboard.setOptions({ disabled: false });
|
||||
});
|
||||
```
|
||||
|
||||
#### `disableLocalFiles`
|
||||
|
||||
Disable local files (`boolean`, default: `false`).
|
||||
|
||||
Enabling this option will disable drag & drop, hide the “browse” and “My Device”
|
||||
button, allowing only uploads from plugins, such as Webcam, Screen Capture,
|
||||
Google Drive, Instagram.
|
||||
|
||||
#### `onDragOver(event)`
|
||||
|
||||
Callback for the [`ondragover`][ondragover] event handler.
|
||||
|
||||
#### `onDrop(event)`
|
||||
|
||||
Callback for the [`ondrop`][ondrop] event handler.
|
||||
|
||||
#### `onDragLeave(event)`
|
||||
|
||||
Callback for the [`ondragleave`][ondragleave] event handler.
|
||||
|
||||
### Methods
|
||||
|
||||
:::info
|
||||
|
||||
Dashboard also has the methods described in
|
||||
[`UIPlugin`](/docs/uppy#new-uipluginuppy-options) and
|
||||
[`BasePlugin`](/docs/uppy#new-basepluginuppy-options).
|
||||
|
||||
:::
|
||||
|
||||
#### `openModal()`
|
||||
|
||||
Shows the Dashboard modal. Use it like this:
|
||||
|
||||
`uppy.getPlugin('Dashboard').openModal()`
|
||||
|
||||
#### `closeModal()`
|
||||
|
||||
Hides the Dashboard modal. Use it like this:
|
||||
|
||||
`uppy.getPlugin('Dashboard').closeModal()`
|
||||
|
||||
#### `isModalOpen()`
|
||||
|
||||
Returns `true` if the Dashboard modal is open, `false` otherwise.
|
||||
|
||||
```js
|
||||
const dashboard = uppy.getPlugin('Dashboard');
|
||||
if (dashboard.isModalOpen()) {
|
||||
dashboard.closeModal();
|
||||
}
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
:::info
|
||||
|
||||
You can use [`on`](/docs/uppy#onevent-action) and
|
||||
[`once`](/docs/uppy#onceevent-action) to listen to these events.
|
||||
|
||||
:::
|
||||
|
||||
#### `dashboard:modal-open`
|
||||
|
||||
Fired when the Dashboard modal is open.
|
||||
|
||||
```js
|
||||
uppy.on('dashboard:modal-open', () => {
|
||||
console.log('Modal is open');
|
||||
});
|
||||
```
|
||||
|
||||
#### `dashboard:modal-closed`
|
||||
|
||||
Fired when the Dashboard modal is closed.
|
||||
|
||||
#### `dashboard:file-edit-start`
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `file` — The [File Object](https://uppy.io/docs/uppy/#File-Objects)
|
||||
representing the file that was opened for editing.
|
||||
|
||||
Fired when the user clicks “edit” icon next to a file in the Dashboard. The
|
||||
FileCard panel is then open with file metadata available for editing.
|
||||
|
||||
#### `dashboard:file-edit-complete`
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `file` — The [File Object](https://uppy.io/docs/uppy/#File-Objects)
|
||||
representing the file that was edited.
|
||||
|
||||
Fired when the user finished editing the file metadata.
|
||||
|
||||
## Integrations
|
||||
|
||||
These are the plugins specifically made for the Dashboard. This is not a list of
|
||||
all Uppy plugins.
|
||||
|
||||
### Sources
|
||||
|
||||
- [`@uppy/audio`](/docs/audio) — record audio.
|
||||
- [`@uppy/box`](/docs/box) — import files from
|
||||
[Box](https://www.box.com/en-nl/home).
|
||||
- [`@uppy/dropbox`](/docs/dropbox) — import from [Dropbox](https://dropbox.com).
|
||||
- [`@uppy/facebook`](/docs/facebook) — import from
|
||||
[Facebook](https://facebook.com).
|
||||
- [`@uppy/google-drive`](/docs/google-drive) — import from
|
||||
[Google Drive](https://drive.google.com).
|
||||
- [`@uppy/instagram`](/docs/instagram) — import from
|
||||
[Instagram](https://instagram.com).
|
||||
- [`@uppy/onedrive`](/docs/onedrive) — import from
|
||||
[OneDrive](https://www.microsoft.com/en-us/microsoft-365/onedrive/online-cloud-storage).
|
||||
- [`@uppy/screen-capture`](/docs/screen-capture) — Record your screen, including
|
||||
(optionally) your microphone.
|
||||
- [`@uppy/unsplash`](/docs/unsplash) — import files from
|
||||
[Unsplash](https://unsplash.com/)
|
||||
- [`@uppy/url`](/docs/url) — import files from any URL.
|
||||
- [`@uppy/webcam`](/docs/webcam) — Record or make a picture with your webcam.
|
||||
- [`@uppy/zoom`](/docs/zoom) — import files from [Zoom](https://zoom.us).
|
||||
|
||||
### UI
|
||||
|
||||
- [`@uppy/image-editor`](/docs/image-editor) — allows users to crop, rotate,
|
||||
zoom and flip images that are added to Uppy.
|
||||
- [`@uppy/informer`](/docs/informer) — show notifications.
|
||||
- [`@uppy/status-bar`](/docs/status-bar) — advanced upload progress status bar.
|
||||
- [`@uppy/thumbnail-generator`](/docs/thumbnail-generator) — generate preview
|
||||
thumbnails for images to be uploaded.
|
||||
|
||||
### Frameworks
|
||||
|
||||
- [`@uppy/angular`](/docs/angular) — Dashboard component for
|
||||
[Angular](https://angular.io/).
|
||||
- [`@uppy/react`](/docs/react) — Dashboard component for
|
||||
[React](https://reactjs.org/).
|
||||
- [`@uppy/svelte`](/docs/svelte) — Dashboard component for
|
||||
[Svelte](https://svelte.dev/).
|
||||
- [`@uppy/vue`](/docs/vue) — Dashboard component for [Vue](https://vuejs.org/).
|
||||
|
||||
[ondragover]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragover
|
||||
[ondragleave]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragleave
|
||||
[ondrop]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondrop
|
||||
149
docs/user-interfaces/drag-drop.mdx
Normal file
149
docs/user-interfaces/drag-drop.mdx
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
---
|
||||
sidebar_position: 2
|
||||
slug: /drag-drop
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Drag & Drop
|
||||
|
||||
The `@uppy/drag-drop` plugin renders a drag and drop area for file selection.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-drag-drop-gyewzx?file=/src/index.js).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
It can be useful when you only want the local device as a file source, don’t
|
||||
need file previews and a UI for metadata editing, or the
|
||||
[Dashboard](/docs/dashboard/) is too much. But it can be too minimal too. By
|
||||
default it doesn’t show that a file has been added nor is there a progress bar.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/core @uppy/drag-drop
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/core @uppy/drag-drop
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, DragDrop } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(DragDrop, { target: '#uppy' })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import DragDrop from '@uppy/drag-drop';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/drag-drop/dist/style.min.css';
|
||||
|
||||
new Uppy().use(DragDrop, { target: '#drag-drop' });
|
||||
```
|
||||
|
||||
:::info
|
||||
|
||||
Certain [restrictions](/docs/uppy#restrictions) set in Uppy’s options, namely
|
||||
`maxNumberOfFiles` and `allowedFileTypes`, affect the system file picker dialog.
|
||||
If `maxNumberOfFiles: 1`, users will only be able to select one file, and
|
||||
`allowedFileTypes: ['video/*', '.gif']` means only videos or gifs (files with
|
||||
`.gif` extension) will be selectable.
|
||||
|
||||
:::
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, Default: `'DragDrop'`).
|
||||
|
||||
Use this if you need to add several DragDrop instances.
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string` or `Element`, default: `null`).
|
||||
|
||||
#### `width`
|
||||
|
||||
Drag and drop area width (`string`, default: `'100%'`).
|
||||
|
||||
Set in inline CSS, so feel free to use percentage, pixels or other values that
|
||||
you like.
|
||||
|
||||
#### `height`
|
||||
|
||||
Drag and drop area height (`string`, default: `'100%'`).
|
||||
|
||||
Set in inline CSS, so feel free to use percentage, pixels or other values that
|
||||
you like.
|
||||
|
||||
#### `note`
|
||||
|
||||
Optionally, specify a string of text that explains something about the upload
|
||||
for the user (`string`, default: `null`).
|
||||
|
||||
This is a place to explain any `restrictions` that are put in place. For
|
||||
example: `'Images and video only, 2–3 files, up to 1 MB'`.
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
// Text to show on the droppable area.
|
||||
// `%{browse}` is replaced with a link that opens the system file selection dialog.
|
||||
dropHereOr: 'Drop here or %{browse}',
|
||||
// Used as the label for the link that opens the system file selection dialog.
|
||||
browse: 'browse',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### `onDragOver(event)`
|
||||
|
||||
Callback for the [`ondragover`][ondragover] event handler.
|
||||
|
||||
#### `onDragLeave(event)`
|
||||
|
||||
Callback for the [`ondragleave`][ondragleave] event handler.
|
||||
|
||||
#### `onDrop(event)`
|
||||
|
||||
Callback for the [`ondrop`][ondrop] event handler.
|
||||
|
||||
[ondragover]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragover
|
||||
[ondragleave]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondragleave
|
||||
[ondrop]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/ondrop
|
||||
5
docs/user-interfaces/elements/_category_.json
Normal file
5
docs/user-interfaces/elements/_category_.json
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"label": "Elements",
|
||||
"position": 3,
|
||||
"collapsed": false
|
||||
}
|
||||
113
docs/user-interfaces/elements/drop-target.mdx
Normal file
113
docs/user-interfaces/elements/drop-target.mdx
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
---
|
||||
sidebar_position: 2
|
||||
slug: /drop-target
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Drop target
|
||||
|
||||
The `@uppy/drop-target` plugin lets your users drag-and-drop files on any
|
||||
element on the page, for example the whole page, `document.body`.
|
||||
|
||||
Can be used together with Uppy Dashboard or Drag & Drop plugins, or your custom
|
||||
solution targeting any DOM element.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-drag-drop-gyewzx?file=/src/index.js).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to allow users to drag and drop files in your own UI, rather than
|
||||
in the [`Dashboard`](/docs/dashboard) UI, or catch dropped files from anywhere
|
||||
on the page.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/drop-target
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/drop-target
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { DropTarget } from "{{UPPY_JS_URL}}"
|
||||
const DropTarget = new Uppy().use(DropTarget)
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
This module has one default export: the `DropTarget` plugin class.
|
||||
|
||||
```js {8-10} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import DropTarget from '@uppy/drop-target';
|
||||
|
||||
import '@uppy/core/dist/style.css';
|
||||
import '@uppy/drop-target/dist/style.css';
|
||||
|
||||
const uppy = new Uppy();
|
||||
uppy.use(DropTarget, {
|
||||
target: document.body,
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `onDragLeave`
|
||||
|
||||
Event listener for the [`dragleave` event][].
|
||||
|
||||
```js {3-5} showLineNumbers
|
||||
uppy.use(DropTarget, {
|
||||
target: document.body,
|
||||
onDragLeave: (event) => {
|
||||
event.stopPropagation();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### `onDragOver`
|
||||
|
||||
Event listener for the [`dragover` event][].
|
||||
|
||||
#### `onDrop`
|
||||
|
||||
Event listener for the [`drop` event][].
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string`, `Element`, `Function`, or `UIPlugin`, default: `null`).
|
||||
|
||||
[`dragover` event]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragover_event
|
||||
[`dragleave` event]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dragleave_event
|
||||
[`drop` event]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event
|
||||
179
docs/user-interfaces/elements/image-editor.mdx
Normal file
179
docs/user-interfaces/elements/image-editor.mdx
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
slug: /image-editor
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Image editor
|
||||
|
||||
Image editor. Designed to be used with the Dashboard UI.
|
||||
|
||||
<div style={{ maxWidth: 500 }}>
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to allow users to crop, rotate, zoom and flip images that are
|
||||
added to Uppy.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/core @uppy/dashboard @uppy/image-editor
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/core @uppy/dashboard @uppy/image-editor
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Dashboard, ImageEditor } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Dashboard, { target: '#uppy', inline: true })
|
||||
uppy.use(ImageEditor, { target: Uppy.Dashboard })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js {3,7,11} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import ImageEditor from '@uppy/image-editor';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
import '@uppy/image-editor/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: '#dashboard' })
|
||||
.use(ImageEditor, { target: Dashboard });
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
:::info
|
||||
|
||||
If you automatically want to open the image editor when an image is added, see
|
||||
the [`autoOpen`](/docs/dashboard#autoopen) Dashboard option.
|
||||
|
||||
:::
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'ImageEditor'`).
|
||||
|
||||
#### `quality`
|
||||
|
||||
Quality Of the resulting blob that will be saved in Uppy after editing/cropping
|
||||
(`number`, default: `0.8`).
|
||||
|
||||
#### `cropperOptions`
|
||||
|
||||
Image Editor is using the excellent
|
||||
[Cropper.js](https://fengyuanchen.github.io/cropperjs/). `cropperOptions` will
|
||||
be directly passed to `Cropper` and thus can expect the same values as
|
||||
documented in their
|
||||
[README](https://github.com/fengyuanchen/cropperjs/blob/HEAD/README.md#options),
|
||||
with the addition of `croppedCanvasOptions`, which will be passed to
|
||||
[`getCroppedCanvas`](https://github.com/fengyuanchen/cropperjs/blob/HEAD/README.md#getcroppedcanvasoptions).
|
||||
|
||||
#### `actions`
|
||||
|
||||
Show action buttons (`Object` or `boolean`).
|
||||
|
||||
If you you’d like to hide all actions, pass `false` to it. By default all the
|
||||
actions are visible. Or enable/disable them individually:
|
||||
|
||||
```js
|
||||
{
|
||||
revert: true,
|
||||
rotate: true,
|
||||
granularRotate: true,
|
||||
flip: true,
|
||||
zoomIn: true,
|
||||
zoomOut: true,
|
||||
cropSquare: true,
|
||||
cropWidescreen: true,
|
||||
cropWidescreenVertical: true,
|
||||
}
|
||||
```
|
||||
|
||||
#### `locale: {}`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
revert: 'Revert',
|
||||
rotate: 'Rotate',
|
||||
zoomIn: 'Zoom in',
|
||||
zoomOut: 'Zoom out',
|
||||
flipHorizontal: 'Flip horizontal',
|
||||
aspectRatioSquare: 'Crop square',
|
||||
aspectRatioLandscape: 'Crop landscape (16:9)',
|
||||
aspectRatioPortrait: 'Crop portrait (9:16)',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Events
|
||||
|
||||
:::info
|
||||
|
||||
You can use [`on`](/docs/uppy#onevent-action) and
|
||||
[`once`](/docs/uppy#onceevent-action) to listen to these events.
|
||||
|
||||
:::
|
||||
|
||||
#### `file-editor:start`
|
||||
|
||||
Emitted when `selectFile(file)` is called.
|
||||
|
||||
```js
|
||||
uppy.on('file-editor:start', (file) => {
|
||||
console.log(file);
|
||||
});
|
||||
```
|
||||
|
||||
#### `file-editor:complete`
|
||||
|
||||
Emitted after `save(blob)` is called.
|
||||
|
||||
```js
|
||||
uppy.on('file-editor:complete', (updatedFile) => {
|
||||
console.log(updatedFile);
|
||||
});
|
||||
```
|
||||
|
||||
#### `file-editor:cancel`
|
||||
|
||||
Emitted when `uninstall` is called or when the current image editing changes are
|
||||
discarded.
|
||||
|
||||
```js
|
||||
uppy.on('file-editor:cancel', (file) => {
|
||||
console.log(file);
|
||||
});
|
||||
```
|
||||
94
docs/user-interfaces/elements/informer.mdx
Normal file
94
docs/user-interfaces/elements/informer.mdx
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
slug: /informer
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Informer
|
||||
|
||||
The `@uppy/informer` plugin is a pop-up bar for showing notifications for the
|
||||
[Dashboard](/docs/dashboard). When plugins have some exciting news (or errors)
|
||||
to share, they can with Informer
|
||||
|
||||
## When should I use it?
|
||||
|
||||
When you use the [Dashboard](/docs/dashboard) it’s already included by default.
|
||||
This plugin is published separately but made specifically for the Dashboard. You
|
||||
can technically use it without it, but it’s not officially supported.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/informer
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/informer
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Informer } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Informer, { target: '#informer' })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js
|
||||
import Uppy from '@uppy/core';
|
||||
import Informer from '@uppy/informer';
|
||||
|
||||
// The `@uppy/informer` plugin includes some basic styles.
|
||||
// You can also choose not to use it and provide your own styles instead.
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/informer/dist/style.min.css';
|
||||
|
||||
new Uppy().use(Informer, { target: '#informer' });
|
||||
```
|
||||
|
||||
Informer gets its data from `uppy.state.info`, which is updated by various
|
||||
plugins via [`uppy.info`](/docs/uppy#infomessage-type-duration) method.
|
||||
|
||||
In the [compressor](/docs/compressor) plugin we use it like this for instance:
|
||||
|
||||
```js
|
||||
const size = prettierBytes(totalCompressedSize);
|
||||
this.uppy.info(this.i18n('compressedX', { size }), 'info');
|
||||
```
|
||||
|
||||
When calling `uppy.info`, [Uppy](/docs/uppy) emits
|
||||
[`info-visible`](/docs/uppy#info-visible) and will emit
|
||||
[`info-hidden`](/docs/uppy#info-hidden) after the timeout.
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Informer'`).
|
||||
|
||||
Use this if you need several `Informer` instances.
|
||||
|
||||
### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to mount the Informer into (`string` or
|
||||
`Element`, default: `null`).
|
||||
91
docs/user-interfaces/elements/progress-bar.mdx
Normal file
91
docs/user-interfaces/elements/progress-bar.mdx
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
---
|
||||
sidebar_position: 5
|
||||
slug: /progress-bar
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Progress bar
|
||||
|
||||
`@uppy/progress-bar` is a minimalist plugin that shows the current upload
|
||||
progress in a thin bar element, like the ones used by YouTube and GitHub when
|
||||
navigating between pages.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
When you need a minimalistec progress indicator and you’re
|
||||
[building your own UI](/docs/guides/building-your-own-ui-with-uppy). For most
|
||||
cases, [Dashboard](/docs/dashboard) or [Drag & Drop](/docs/drag-drop) (with
|
||||
[Status Bar](/docs/status-bar)) is more likely what you need.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/progress-bar
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/progress-bar
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, ProgressBar } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(ProgressBar, { target: '#progress-bar' })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js
|
||||
import Uppy from '@uppy/core';
|
||||
import ProgressBar from '@uppy/progress-bar';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/progress-bar/dist/style.min.css';
|
||||
|
||||
new Uppy().use(ProgressBar, { target: '#progress-bar' });
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this Progress Bar (`string`, default: `'ProgressBar'`).
|
||||
|
||||
Use this if you need to add several ProgressBar instances.
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to mount the Progress Bar into (`Element`,
|
||||
`string`, default: `null`).
|
||||
|
||||
#### `fixed`
|
||||
|
||||
Show the progress bar at the top of the page with `position: fixed` (`boolean`,
|
||||
default: `false`).
|
||||
|
||||
When set to false, show the progress bar inline wherever it’s mounted.
|
||||
|
||||
#### `hideAfterFinish`
|
||||
|
||||
Hides the progress bar after the upload has finished (`boolean`, default:
|
||||
`true`).
|
||||
201
docs/user-interfaces/elements/status-bar.mdx
Normal file
201
docs/user-interfaces/elements/status-bar.mdx
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
slug: /status-bar
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Status bar
|
||||
|
||||
The `@uppy/status-bar` plugin shows upload progress and speed, estimated time,
|
||||
pre- and post-processing information, and allows users to control
|
||||
(pause/resume/cancel) the upload.
|
||||
|
||||
:::tip
|
||||
|
||||
Try it out together with [`@uppy/drag-drop`](/docs/drag-drop) in
|
||||
[CodeSandbox](https://codesandbox.io/s/uppy-drag-drop-gyewzx?file=/src/index.js)
|
||||
|
||||
:::
|
||||
|
||||
## When should I use it?
|
||||
|
||||
When you use the [Dashboard](/docs/dashboard) it’s already included by default.
|
||||
This plugin is published separately but made specifically for the Dashboard. You
|
||||
can still use it without it but it may need some (CSS) tweaking for your use
|
||||
case.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/status-bar
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/status-bar
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, StatusBar } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(StatusBar, { target: '#status-bar' })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import StatusBar from '@uppy/status-bar';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/status-bar/dist/style.min.css';
|
||||
|
||||
new Uppy().use(StatusBar, { target: '#status-bar' });
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this Status Bar (`string`, default: `'StatusBar'`).
|
||||
|
||||
Use this if you need to add several StatusBar instances.
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to mount the Status Bar into (`Element`,
|
||||
`string`, `UIPlugin`, default: `'body'`).
|
||||
|
||||
#### `hideAfterFinish`
|
||||
|
||||
Hide the Status Bar after the upload is complete (`boolean`, default: `true`).
|
||||
|
||||
#### `showProgressDetails`
|
||||
|
||||
Display remaining upload size and estimated time (`boolean`, default: `false`)
|
||||
|
||||
:::note
|
||||
|
||||
`false`: Uploading: 45%
|
||||
|
||||
`true`: Uploading: 45%・43 MB of 101 MB・8s left
|
||||
|
||||
:::
|
||||
|
||||
#### `hideUploadButton`
|
||||
|
||||
Hide the upload button (`boolean`, default: `false`).
|
||||
|
||||
Use this if you are providing a custom upload button somewhere, and using the
|
||||
`uppy.upload()` API.
|
||||
|
||||
#### `hideRetryButton`
|
||||
|
||||
Hide the retry button (`boolean`, default: `false`).
|
||||
|
||||
Use this if you are providing a custom retry button somewhere, and using the
|
||||
`uppy.retryAll()` or `uppy.retryUpload(fileID)` API.
|
||||
|
||||
#### `hidePauseResumeButton`
|
||||
|
||||
Hide pause/resume buttons (for resumable uploads, via [tus](http://tus.io), for
|
||||
example) (`boolean`, default: `false`).
|
||||
|
||||
Use this if you are providing custom cancel or pause/resume buttons somewhere,
|
||||
and using the `uppy.pauseResume(fileID)` or `uppy.removeFile(fileID)` API.
|
||||
|
||||
#### `hideCancelButton`
|
||||
|
||||
Hide the cancel button (`boolean`, default: `false`).
|
||||
|
||||
Use this if you are providing a custom retry button somewhere, and using the
|
||||
`uppy.cancelAll()` API.
|
||||
|
||||
#### `doneButtonHandler`
|
||||
|
||||
Status Bar will render a “Done” button in place of pause/resume/cancel buttons,
|
||||
once the upload/encoding is done (`Function`, default: `null`).
|
||||
|
||||
The behaviour of this “Done” button is defined by the handler function — can be
|
||||
used to close file picker modals or clear the upload state. This is what the
|
||||
Dashboard plugin, which uses Status Bar internally, sets:
|
||||
|
||||
```js
|
||||
const doneButtonHandler = () => {
|
||||
this.uppy.reset();
|
||||
this.requestCloseModal();
|
||||
};
|
||||
```
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
// Shown in the status bar while files are being uploaded.
|
||||
uploading: 'Uploading',
|
||||
// Shown in the status bar once all files have been uploaded.
|
||||
complete: 'Complete',
|
||||
// Shown in the status bar if an upload failed.
|
||||
uploadFailed: 'Upload failed',
|
||||
// Shown in the status bar while the upload is paused.
|
||||
paused: 'Paused',
|
||||
// Used as the label for the button that retries an upload.
|
||||
retry: 'Retry',
|
||||
// Used as the label for the button that cancels an upload.
|
||||
cancel: 'Cancel',
|
||||
// Used as the label for the button that pauses an upload.
|
||||
pause: 'Pause',
|
||||
// Used as the label for the button that resumes an upload.
|
||||
resume: 'Resume',
|
||||
// Used as the label for the button that resets the upload state after an upload
|
||||
done: 'Done',
|
||||
// When `showProgressDetails` is set, shows the number of files that have been fully uploaded so far.
|
||||
filesUploadedOfTotal: {
|
||||
0: '%{complete} of %{smart_count} file uploaded',
|
||||
1: '%{complete} of %{smart_count} files uploaded',
|
||||
},
|
||||
// When `showProgressDetails` is set, shows the amount of bytes that have been uploaded so far.
|
||||
dataUploadedOfTotal: '%{complete} of %{total}',
|
||||
// When `showProgressDetails` is set, shows an estimation of how long the upload will take to complete.
|
||||
xTimeLeft: '%{time} left',
|
||||
// Used as the label for the button that starts an upload.
|
||||
uploadXFiles: {
|
||||
0: 'Upload %{smart_count} file',
|
||||
1: 'Upload %{smart_count} files',
|
||||
},
|
||||
// Used as the label for the button that starts an upload, if another upload has been started in the past
|
||||
// and new files were added later.
|
||||
uploadXNewFiles: {
|
||||
0: 'Upload +%{smart_count} file',
|
||||
1: 'Upload +%{smart_count} files',
|
||||
},
|
||||
upload: 'Upload',
|
||||
retryUpload: 'Retry upload',
|
||||
xMoreFilesAdded: {
|
||||
0: '%{smart_count} more file added',
|
||||
1: '%{smart_count} more files added',
|
||||
},
|
||||
showErrorDetails: 'Show error details',
|
||||
},
|
||||
};
|
||||
```
|
||||
168
docs/user-interfaces/elements/thumbnail-generator.mdx
Normal file
168
docs/user-interfaces/elements/thumbnail-generator.mdx
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
sidebar_position: 2
|
||||
slug: /thumbnail-generator
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Thumbnail generator
|
||||
|
||||
`@uppy/thumbnail-generator` generates proportional thumbnails (file previews)
|
||||
for images that are added to Uppy.
|
||||
|
||||
## When should I use this?
|
||||
|
||||
This plugin is included by default with the [Dashboard](/docs/dashboard) plugin,
|
||||
and can also be useful to display image previews in a custom UI.
|
||||
|
||||
:::note
|
||||
|
||||
Thumbnails are only generated for _local_ files. Remote files through
|
||||
[Companion](/docs/companion) generally won’t have thumbnails because they are
|
||||
downloaded on the server. Some providers, such as Google Drive, have baked in
|
||||
thumbnails into their API responses.
|
||||
|
||||
:::
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/thumbnail-generator
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/thumbnail-generator
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, ThumbnailGenerator } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(ThumbnailGenerator)
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
If you use the [Dashboard](/docs/dashboard) then it’s already installed. If you
|
||||
want to use it programmatically for your own UI:
|
||||
|
||||
```js showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import ThumbnailGenerator from '@uppy/thumbnail-generator';
|
||||
|
||||
const uppy = new Uppy();
|
||||
|
||||
uppy.use(ThumbnailGenerator);
|
||||
uppy.on('thumbnail:generated', (file, preview) => doSomething(file, preview));
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'ThumbnailGenerator'`).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
generatingThumbnails: 'Generating thumbnails...',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### `thumbnailWidth`
|
||||
|
||||
Width of the resulting thumbnail (`number`, default: `200`).
|
||||
|
||||
Thumbnails are always proportional and not cropped. If width is provided, height
|
||||
is calculated automatically to match ratio. If both width and height are given,
|
||||
only width is taken into account.
|
||||
|
||||
#### `thumbnailHeight`
|
||||
|
||||
Height of the resulting thumbnail (`number`, default: `200`).
|
||||
|
||||
Thumbnails are always proportional and not cropped. If height is provided, width
|
||||
is calculated automatically to match ratio. If both width and height are given,
|
||||
only width is taken into account.
|
||||
|
||||
:::note
|
||||
|
||||
Produce a 300px height thumbnail with calculated width to match ratio:
|
||||
|
||||
```js
|
||||
uppy.use(ThumbnailGenerator, { thumbnailHeight: 300 });
|
||||
```
|
||||
|
||||
Produce a 300px width thumbnail with calculated height to match ratio (and
|
||||
ignore the given height):
|
||||
|
||||
```js
|
||||
uppy.use(ThumbnailGenerator, { thumbnailWidth: 300, thumbnailHeight: 300 });
|
||||
```
|
||||
|
||||
See issue [#979](https://github.com/transloadit/uppy/issues/979) and
|
||||
[#1096](https://github.com/transloadit/uppy/pull/1096) for details on this
|
||||
feature.
|
||||
|
||||
:::
|
||||
|
||||
#### `thumbnailType`
|
||||
|
||||
MIME type of the resulting thumbnail (`string`, default: `'image/jpeg'`).
|
||||
|
||||
This is useful if you want to support transparency in your thumbnails by
|
||||
switching to `image/png`.
|
||||
|
||||
#### `waitForThumbnailsBeforeUpload`
|
||||
|
||||
Whether to wait for all thumbnails to be ready before starting the upload
|
||||
(`boolean`, default: `false`).
|
||||
|
||||
If set to `true`, Thumbnail Generator will invoke Uppy’s internal processing
|
||||
stage and wait for `thumbnail:all-generated` event, before proceeding to the
|
||||
uploading stage. This is useful because Thumbnail Generator also adds EXIF data
|
||||
to images, and if we wait until it’s done processing, this data will be
|
||||
available on the server after the upload.
|
||||
|
||||
### Events
|
||||
|
||||
:::info
|
||||
|
||||
You can use [`on`](/docs/uppy#onevent-action) and
|
||||
[`once`](/docs/uppy#onceevent-action) to listen to these events.
|
||||
|
||||
:::
|
||||
|
||||
#### `thumbnail:generated`
|
||||
|
||||
Emitted with `file` and `preview` local url as arguments:
|
||||
|
||||
```js
|
||||
uppy.on('thumbnail:generated', (file, preview) => {
|
||||
const img = document.createElement('img');
|
||||
img.src = preview;
|
||||
img.width = 100;
|
||||
document.body.appendChild(img);
|
||||
});
|
||||
```
|
||||
|
|
@ -1,25 +1,35 @@
|
|||
/** @jsx h */
|
||||
|
||||
import { UIPlugin } from '@uppy/core'
|
||||
import { Provider } from '@uppy/companion-client'
|
||||
import { Provider, getAllowedHosts, tokenStorage } from '@uppy/companion-client'
|
||||
import { ProviderViews } from '@uppy/provider-views'
|
||||
import { h } from 'preact'
|
||||
|
||||
const defaultOptions = {}
|
||||
|
||||
export default class MyCustomProvider extends UIPlugin {
|
||||
constructor (uppy, opts) {
|
||||
constructor(uppy, opts) {
|
||||
super(uppy, opts)
|
||||
this.type = 'acquirer'
|
||||
this.id = this.opts.id || 'MyCustomProvider'
|
||||
Provider.initPlugin(this, opts)
|
||||
this.type = 'acquirer'
|
||||
this.storage = this.opts.storage || tokenStorage
|
||||
this.files = []
|
||||
|
||||
this.icon = () => (
|
||||
<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z" fill="#000000" fillRule="nonzero" />
|
||||
<path
|
||||
d="M10 9V0h12v9H10zm12 5h10v18H0V14h10v9h12v-9z"
|
||||
fill="#000000"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
this.opts.companionAllowedHosts = getAllowedHosts(
|
||||
this.opts.companionAllowedHosts,
|
||||
this.opts.companionUrl,
|
||||
)
|
||||
this.provider = new Provider(uppy, {
|
||||
companionUrl: this.opts.companionUrl,
|
||||
companionHeaders: this.opts.companionHeaders,
|
||||
|
|
@ -44,7 +54,7 @@ export default class MyCustomProvider extends UIPlugin {
|
|||
this.files = []
|
||||
}
|
||||
|
||||
install () {
|
||||
install() {
|
||||
this.view = new ProviderViews(this, {
|
||||
provider: this.provider,
|
||||
})
|
||||
|
|
@ -55,16 +65,16 @@ export default class MyCustomProvider extends UIPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
uninstall () {
|
||||
uninstall() {
|
||||
this.view.tearDown()
|
||||
this.unmount()
|
||||
}
|
||||
|
||||
onFirstRender () {
|
||||
onFirstRender() {
|
||||
return this.view.getFolder()
|
||||
}
|
||||
|
||||
render (state) {
|
||||
render(state) {
|
||||
return this.view.render(state)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,13 @@ Included in: Uppy v4.0.0-beta.1
|
|||
- @uppy/dashboard: refactor to TypeScript (Antoine du Hamel / #4984)
|
||||
- @uppy/dashboard: refactor to stable lifecycle method (Antoine du Hamel / #4999)
|
||||
|
||||
## 3.8.1
|
||||
|
||||
Released: 2024-04-16
|
||||
Included in: Uppy v3.24.3
|
||||
|
||||
- @uppy/dashboard: add missing `x-zip-compress` archive type (Younes / #5081)
|
||||
|
||||
## 3.8.0
|
||||
|
||||
Released: 2024-03-27
|
||||
|
|
|
|||
|
|
@ -193,6 +193,7 @@ export default function getIconByMime(fileType: $TSFixMe): $TSFixMe {
|
|||
const archiveTypes = [
|
||||
'zip',
|
||||
'x-7z-compressed',
|
||||
'x-zip-compressed',
|
||||
'x-rar-compressed',
|
||||
'x-tar',
|
||||
'x-gzip',
|
||||
|
|
|
|||
|
|
@ -11,6 +11,13 @@ Included in: Uppy v4.0.0-beta.1
|
|||
- @uppy/utils: fix `AbortablePromise` type (Antoine du Hamel / #4988)
|
||||
- @uppy/utils: migrate RateLimitedQueue to TS (Merlijn Vos / #4981)
|
||||
|
||||
## 5.8.0
|
||||
|
||||
Released: 2024-04-16
|
||||
Included in: Uppy v3.24.3
|
||||
|
||||
- @uppy/utils: add fetcher (Merlijn Vos / #5073)
|
||||
|
||||
## 5.7.5
|
||||
|
||||
Released: 2024-03-27
|
||||
|
|
|
|||
145
packages/@uppy/utils/src/fetcher.ts
Normal file
145
packages/@uppy/utils/src/fetcher.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import NetworkError from './NetworkError.ts'
|
||||
import ProgressTimeout from './ProgressTimeout.ts'
|
||||
|
||||
const noop = (): void => {}
|
||||
|
||||
export type FetcherOptions = {
|
||||
/** The HTTP method to use for the request. Default is 'GET'. */
|
||||
method?: string
|
||||
|
||||
/** The request payload, if any. Default is null. */
|
||||
body?: Document | XMLHttpRequestBodyInit | null
|
||||
|
||||
/** Milliseconds between XMLHttpRequest upload progress events before the request is aborted. Default is 30000 ms. */
|
||||
timeout?: number
|
||||
|
||||
/** Sets the withCredentials property of the XMLHttpRequest object. Default is false. */
|
||||
withCredentials?: boolean
|
||||
|
||||
/** Sets the responseType property of the XMLHttpRequest object. Default is an empty string. */
|
||||
responseType?: XMLHttpRequestResponseType
|
||||
|
||||
/** An object representing any headers to send with the request. */
|
||||
headers?: Record<string, string>
|
||||
|
||||
/** The number of retry attempts to make if the request fails. Default is 3. */
|
||||
retries?: number
|
||||
|
||||
/** Called before the request is made. */
|
||||
onBeforeRequest?: (
|
||||
xhr: XMLHttpRequest,
|
||||
retryCount: number,
|
||||
) => void | Promise<void>
|
||||
|
||||
/** Function for tracking upload progress. */
|
||||
onUploadProgress?: (event: ProgressEvent) => void
|
||||
|
||||
/** A function to determine whether to retry the request. */
|
||||
shouldRetry?: (xhr: XMLHttpRequest) => boolean
|
||||
|
||||
/** Called after the response has succeeded or failed but before the promise is resolved. */
|
||||
onAfterRequest?: (
|
||||
xhr: XMLHttpRequest,
|
||||
retryCount: number,
|
||||
) => void | Promise<void>
|
||||
|
||||
/** Called when no XMLHttpRequest upload progress events have been received for `timeout` ms. */
|
||||
onTimeout?: () => void
|
||||
|
||||
/** Signal to abort the upload. */
|
||||
signal?: AbortSignal
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches data from a specified URL using XMLHttpRequest, with optional retry functionality and progress tracking.
|
||||
*
|
||||
* @param url The URL to send the request to.
|
||||
* @param options Optional settings for the fetch operation.
|
||||
*/
|
||||
export function fetcher(
|
||||
url: string,
|
||||
options: FetcherOptions = {},
|
||||
): Promise<XMLHttpRequest> {
|
||||
const {
|
||||
body = null,
|
||||
headers = {},
|
||||
method = 'GET',
|
||||
onBeforeRequest = noop,
|
||||
onUploadProgress = noop,
|
||||
shouldRetry = () => true,
|
||||
onAfterRequest = noop,
|
||||
onTimeout = noop,
|
||||
responseType,
|
||||
retries = 3,
|
||||
signal = null,
|
||||
timeout = 30_000,
|
||||
withCredentials = false,
|
||||
} = options
|
||||
|
||||
// 300 ms, 600 ms, 1200 ms, 2400 ms, 4800 ms
|
||||
const delay = (attempt: number): number => 0.3 * 2 ** (attempt - 1) * 1000
|
||||
const timer = new ProgressTimeout(timeout, onTimeout)
|
||||
|
||||
function requestWithRetry(retryCount = 0): Promise<XMLHttpRequest> {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
|
||||
xhr.open(method, url, true)
|
||||
xhr.withCredentials = withCredentials
|
||||
if (responseType) {
|
||||
xhr.responseType = responseType
|
||||
}
|
||||
|
||||
signal?.addEventListener('abort', () => {
|
||||
xhr.abort()
|
||||
// Using DOMException for abort errors aligns with
|
||||
// the convention established by the Fetch API.
|
||||
reject(new DOMException('Aborted', 'AbortError'))
|
||||
})
|
||||
|
||||
xhr.onload = async () => {
|
||||
await onAfterRequest(xhr, retryCount)
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
timer.done()
|
||||
resolve(xhr)
|
||||
} else if (shouldRetry(xhr) && retryCount < retries) {
|
||||
setTimeout(() => {
|
||||
requestWithRetry(retryCount + 1).then(resolve, reject)
|
||||
}, delay(retryCount))
|
||||
} else {
|
||||
timer.done()
|
||||
reject(new NetworkError(xhr.statusText, xhr))
|
||||
}
|
||||
}
|
||||
|
||||
xhr.onerror = () => {
|
||||
if (shouldRetry(xhr) && retryCount < retries) {
|
||||
setTimeout(() => {
|
||||
requestWithRetry(retryCount + 1).then(resolve, reject)
|
||||
}, delay(retryCount))
|
||||
} else {
|
||||
timer.done()
|
||||
reject(new NetworkError(xhr.statusText, xhr))
|
||||
}
|
||||
}
|
||||
|
||||
xhr.upload.onprogress = (event: ProgressEvent) => {
|
||||
timer.progress()
|
||||
onUploadProgress(event)
|
||||
}
|
||||
|
||||
if (headers) {
|
||||
Object.keys(headers).forEach((key) => {
|
||||
xhr.setRequestHeader(key, headers[key])
|
||||
})
|
||||
}
|
||||
|
||||
await onBeforeRequest(xhr, retryCount)
|
||||
xhr.send(body)
|
||||
})
|
||||
}
|
||||
|
||||
return requestWithRetry()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue