Merge pull request #1135 from transloadit/feature/tl-preset

Add Robodog: Transloadit browser SDK
This commit is contained in:
Artur Paikin 2019-02-04 23:56:14 +03:00 committed by GitHub
commit 407b4bc54c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
71 changed files with 7226 additions and 272 deletions

View file

@ -1,4 +1,3 @@
var path = require('path')
var fs = require('fs')
var chalk = require('chalk')
var mkdirp = require('mkdirp')
@ -7,18 +6,12 @@ var tinyify = require('tinyify')
var browserify = require('browserify')
var exorcist = require('exorcist')
var distPath = './packages/uppy/dist'
var srcPath = './packages/uppy'
function handleErr (err) {
console.error(chalk.red('✗ Error:'), chalk.red(err.message))
}
function buildUppyBundle (minify) {
var src = path.join(srcPath, 'bundle.js')
var bundleFile = minify ? 'uppy.min.js' : 'uppy.js'
var b = browserify(src, { debug: true, standalone: 'Uppy' })
function buildBundle (srcFile, bundleFile, { minify = false } = {}) {
var b = browserify(srcFile, { debug: true, standalone: 'Uppy' })
if (minify) {
b.plugin(tinyify)
}
@ -27,8 +20,8 @@ function buildUppyBundle (minify) {
return new Promise(function (resolve, reject) {
b.bundle()
.pipe(exorcist(path.join(distPath, bundleFile + '.map')))
.pipe(fs.createWriteStream(path.join(distPath, bundleFile), 'utf8'))
.pipe(exorcist(bundleFile + '.map'))
.pipe(fs.createWriteStream(bundleFile), 'utf8')
.on('error', handleErr)
.on('finish', function () {
if (minify) {
@ -41,9 +34,28 @@ function buildUppyBundle (minify) {
})
}
mkdirp.sync(distPath)
mkdirp.sync('./packages/uppy/dist')
mkdirp.sync('./packages/@uppy/robodog/dist')
Promise.all([buildUppyBundle(), buildUppyBundle(true)])
.then(function () {
console.info(chalk.yellow('✓ JS Bundle 🎉'))
})
Promise.all([
buildBundle(
'./packages/uppy/bundle.js',
'./packages/uppy/dist/uppy.js'
),
buildBundle(
'./packages/uppy/bundle.js',
'./packages/uppy/dist/uppy.min.js',
{ minify: true }
),
buildBundle(
'./packages/@uppy/robodog/bundle.js',
'./packages/@uppy/robodog/dist/transloadit.js'
),
buildBundle(
'./packages/@uppy/robodog/bundle.js',
'./packages/@uppy/robodog/dist/transloadit.min.js',
{ minify: true }
)
]).then(function () {
console.info(chalk.yellow('✓ JS Bundle 🎉'))
})

View file

@ -0,0 +1,130 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://transloadit.edgly.net/releases/uppy/v0.29.1/dist/uppy.css">
<style>
body {
font-family: Roboto, Open Sans;
background: #f4f5f6;
}
h1, h2, h3, h4, h5, h6 {
font-family: Oxygen, Raleway, Open Sans;
}
header {
width: 800px;
margin: auto;
}
header h1 { text-align: center; }
main {
background: white;
margin: auto;
width: 800px;
border: 2px solid #e8eaee;
padding: 36px;
border-radius: 5px;
}
main h2 { margin-top: 0 }
label {
display: flex;
padding-bottom: 16px;
border-bottom: 1px solid #e8eaee;
margin-bottom: 16px;
}
.label-text {
width: 25%;
text-transform: uppercase;
color: #888888;
line-height: 34px;
}
label input, label textarea {
flex-grow: 1;
height: 34px;
}
label textarea {
box-sizing: border-box;
padding: 6px;
border-radius: 5px;
border: 1px solid #b7b7b4;
font-family: Roboto, Open Sans;
min-height: 160px;
width: 100%;
resize: vertical;
}
.form-createSnippet {
border: 1px solid #2e6da4;
border-bottom-width: 3px;
padding: 8px;
border-radius: 5px;
background-color: #337ab7;
color: white;
width: 100%;
}
.mdtxt {
width: 100%;
height: 100%;
}
.mdtxt-controls {
}
.mdtxt-upload {
box-sizing: border-box;
width: 100%;
border-radius: 0 0 5px 5px;
border: 1px solid #b7b7b4;
border-top-width: 0;
padding: 4px 8px;
cursor: pointer;
}
.mdtxt textarea {
margin-bottom: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.mdtxt-upload .error {
border-color: red;
color: red;
}
.mdtxt-upload .error .message {
margin-right: 8px;
}
.snippet {
margin-top: 25px;
}
</style>
</head>
<body>
<header>
<h1>Markdown Bin</h1>
</header>
<main>
<form id="new">
<h2>Create a new snippet</h2>
<label>
<span class="label-text">Snippet Title</span>
<input type="text" name="title" placeholder="Snippet Title">
</label>
<label>
<textarea name="snippet"></textarea>
</label>
<p>
<button class="form-createSnippet" type="submit">
Create
</button>
</p>
</form>
<div id="snippets"></div>
</main>
<template id="snippet">
<div class="snippet">
<h3 class="snippet-title"></h3>
<div class="snippet-content"></div>
</div>
</template>
<script src="bundle.js"></script>
</body>
</html>

View file

@ -0,0 +1,268 @@
/* eslint-env browser */
const marked = require('marked')
const dragdrop = require('drag-drop')
const transloadit = require('@uppy/robodog')
// TOP SECRET!!!!!!!
const TRANSLOADIT_KEY = '05a61ed019fe11e783fdbd1f56c73eb0'
const THUMB_SIZE = [400, 300]
/* eslint-disable no-template-curly-in-string */
const IMAGE_FILTER = ['${file.mime}', 'regex', 'image/']
const VIDEO_FILTER = ['${file.mime}', 'regex', 'video/']
const AUDIO_FILTER = ['${file.mime}', 'regex', 'audio/']
/* eslint-enable no-template-curly-in-string */
const transloaditSteps = {
':original': {
robot: '/upload/handle'
},
// Separate source files
images: {
use: [':original'],
robot: '/file/filter',
result: true,
accepts: [IMAGE_FILTER]
},
videos: {
use: [':original'],
robot: '/file/filter',
result: true,
accepts: [VIDEO_FILTER]
},
audios: {
use: [':original'],
robot: '/file/filter',
result: true,
accepts: [AUDIO_FILTER]
},
others: {
use: [':original'],
robot: '/file/filter',
result: true,
rejects: [IMAGE_FILTER, VIDEO_FILTER, AUDIO_FILTER]
},
// Generate thumbs for different types of files
audio_thumbnails: {
use: ['audios'],
robot: '/audio/artwork'
},
resized_thumbnails: {
use: ['images', 'audio_thumbnails'],
robot: '/image/resize',
imagemagick_stack: 'v1.0.0',
width: THUMB_SIZE[0],
height: THUMB_SIZE[1],
resize_strategy: 'fit',
zoom: false
},
video_thumbnails: {
use: ['videos'],
robot: '/video/thumbs',
ffmpeg_stack: 'v2.2.3',
count: 1,
offsets: ['50%'],
format: 'jpeg',
width: THUMB_SIZE[0],
height: THUMB_SIZE[1],
resize_strategy: 'fit'
},
// Optimize thumbnails for decent file size
thumbnails: {
use: ['resized_thumbnails', 'video_thumbnails'],
robot: '/image/optimize'
},
// Store all the things away
store_sources: {
use: ['images', 'videos', 'audios', 'others'],
robot: '/s3/store',
credentials: 'uppy_test_s3',
// eslint-disable-next-line no-template-curly-in-string
path: 'markdownbin/sources/${unique_prefix}/${file.url_name}',
result: true
},
store_thumbnails: {
use: ['thumbnails'],
robot: '/s3/store',
credentials: 'uppy_test_s3',
// eslint-disable-next-line no-template-curly-in-string
path: 'markdownbin/thumbs/${file.md5hash}',
result: true
}
}
class MarkdownTextarea {
constructor (element) {
this.element = element
this.controls = document.createElement('div')
this.controls.classList.add('mdtxt-controls')
this.uploadLine = document.createElement('div')
this.uploadLine.classList.add('mdtxt-upload')
this.uploadLine.appendChild(
document.createTextNode('Upload an attachment'))
}
install () {
const { element } = this
const wrapper = document.createElement('div')
wrapper.classList.add('mdtxt')
element.parentNode.replaceChild(wrapper, element)
wrapper.appendChild(this.controls)
wrapper.appendChild(element)
wrapper.appendChild(this.uploadLine)
this.setupUploadLine()
}
setupTextareaDrop () {
dragdrop(this.element, (files) => {
this.uploadFiles(files)
})
}
setupUploadLine () {
this.uploadLine.addEventListener('click', () => {
this.pickFiles()
})
}
reportUploadError (err) {
this.uploadLine.classList.add('error')
const message = document.createElement('span')
message.appendChild(document.createTextNode(err.message))
this.uploadLine.insertChild(message, this.uploadLine.firstChild)
}
unreportUploadError () {
this.uploadLine.classList.remove('error')
const message = this.uploadLine.querySelector('message')
if (message) {
this.uploadLine.removeChild(message)
}
}
insertAttachments (attachments) {
attachments.forEach((attachment) => {
const { file, thumb } = attachment
const link = `\n[LABEL](${file.ssl_url})\n`
const labelText = `View File ${file.basename}`
if (thumb) {
this.element.value += link.replace('LABEL', `![${labelText}](${thumb.ssl_url})`)
} else {
this.element.value += link.replace('LABEL', labelText)
}
})
}
matchFilesAndThumbs (results) {
const filesById = {}
const thumbsById = {}
results.forEach((result) => {
if (result.stepName === 'thumbnails') {
thumbsById[result.original_id] = result
} else {
filesById[result.original_id] = result
}
})
return Object.keys(filesById).reduce((acc, key) => {
const file = filesById[key]
const thumb = thumbsById[key]
acc.push({ file, thumb })
return acc
}, [])
}
uploadFiles (files) {
transloadit.upload({
waitForEncoding: true,
params: {
auth: { key: TRANSLOADIT_KEY },
steps: transloaditSteps
}
}).then((result) => {
this.insertAttachments(
this.matchFilesAndThumbs(result.results)
)
}).catch((err) => {
console.error(err)
this.reportUploadError(err)
})
}
pickFiles () {
transloadit.pick({
waitForEncoding: true,
params: {
auth: { key: TRANSLOADIT_KEY },
steps: transloaditSteps
}
}).then((result) => {
this.insertAttachments(
this.matchFilesAndThumbs(result.results)
)
}).catch((err) => {
console.error(err)
this.reportUploadError(err)
})
}
}
const textarea = new MarkdownTextarea(
document.querySelector('#new textarea'))
textarea.install()
function renderSnippet (title, text) {
const template = document.querySelector('#snippet')
const newSnippet = document.importNode(template.content, true)
const titleEl = newSnippet.querySelector('.snippet-title')
const contentEl = newSnippet.querySelector('.snippet-content')
titleEl.appendChild(document.createTextNode(title))
contentEl.innerHTML = marked(text)
const list = document.querySelector('#snippets')
list.insertBefore(newSnippet, list.firstChild)
}
function saveSnippet (title, text) {
const id = parseInt(localStorage.numSnippets || 0, 10)
localStorage[`snippet_${id}`] = JSON.stringify({ title, text })
localStorage.numSnippets = id + 1
}
function loadSnippets () {
for (let id = 0; localStorage[`snippet_${id}`] != null; id += 1) {
const { title, text } = JSON.parse(localStorage[`snippet_${id}`])
renderSnippet(title, text)
}
}
document.querySelector('#new').addEventListener('submit', (event) => {
event.preventDefault()
const title = event.target.querySelector('input[name="title"]').value ||
'Unnamed Snippet'
const text = textarea.element.value
saveSnippet(title, text)
renderSnippet(title, text)
event.target.querySelector('input').value = ''
event.target.querySelector('textarea').value = ''
})
window.addEventListener('DOMContentLoaded', () => {
loadSnippets()
})

View file

@ -0,0 +1,19 @@
{
"aliasify": {
"aliases": {
"@uppy": "../../packages/@uppy"
}
},
"dependencies": {
"drag-drop": "^4.2.0",
"marked": "^0.6.0"
},
"devDependencies": {
"aliasify": "^2.1.0",
"browserify": "^16.2.3",
"ecstatic": "^3.0.0"
},
"scripts": {
"start": "browserify -g aliasify main.js > bundle.js && ecstatic"
}
}

1
examples/transloadit/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
uppy.min.css

View file

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Uppy :tl: playground</title>
</head>
<body>
<style>
html {
background: #f1f1f1;
}
main {
padding: 20px;
font: 12pt sans-serif;
background: white;
width: 800px;
margin: auto;
}
hr {
border: 0;
background-color: #111e33;
height: 1px;
}
.hidden { display: none; }
.error { color: red; }
#logo { height: 1em; vertical-align: middle; }
</style>
<main>
<h1>Uppy <img src="https://transloadit.edgly.net/assets/images/logo-small.svg" alt="Transloadit" id="logo"> playground</h1>
<hr>
<h2>transloadit.form()</h2>
<form id="test-form" method="post" action="http://localhost:9967/test">
<p><strong>leave a message</strong>
<p>
<label>name:
<input type="text" name="name">
</label>
<p>
<label>message: <br>
<textarea name="message"></textarea>
</label>
<p>
<label>
attachments:
<input type="file" name="files" multiple>
</label>
<div class="progress"></div>
<p>
<button type="submit">
Upload
</button>
<span class="error"></span>
</form>
<hr>
<h2>transloadit.form() with dashboard</h2>
<form id="dashboard-form" method="post" action="http://localhost:9967/test">
<p><strong>leave a message</strong>
<p>
<label>name:
<input type="text" name="name">
</label>
<p>
<label>message: <br>
<textarea name="message"></textarea>
</label>
<p>
<label>
attachments:
<span class="dashboard"></span>
</label>
<p>
<button type="submit">
Upload
</button>
<span class="error"></span>
</form>
<hr>
<h2>transloadit.pick()</h2>
<p>
<button onclick="openModal()">Open</button>
<hr>
<h2>transloadit.upload()</h2>
<p>
An &lt;input type=file&gt; backed by `transloadit.upload`:
<p>
<input type="file" multiple onchange="doUpload(event)">
<p id="upload-result">
<p id="upload-error" class="error">
</main>
<link href="uppy.min.css" rel="stylesheet">
<script src="bundle.js"></script>
</body>
</html>

View file

@ -0,0 +1,99 @@
const { inspect } = require('util')
const transloadit = require('@uppy/robodog')
/**
* transloadit.form
*/
const formUppy = transloadit.form('#test-form', {
debug: true,
fields: ['message'],
restrictions: {
allowedFileTypes: ['.png']
},
waitForEncoding: true,
params: {
auth: { key: '05a61ed019fe11e783fdbd1f56c73eb0' },
template_id: 'be001500a56011e889f9cddd88df842c'
},
modal: true,
progressBar: '#test-form .progress'
})
formUppy.on('error', (err) => {
document.querySelector('#test-form .error')
.textContent = err.message
})
formUppy.on('upload-error', (file, err) => {
document.querySelector('#test-form .error')
.textContent = err.message
})
window.formUppy = formUppy
const formUppyWithDashboard = transloadit.form('#dashboard-form', {
debug: true,
fields: ['message'],
restrictions: {
allowedFileTypes: ['.png']
},
waitForEncoding: true,
params: {
auth: { key: '05a61ed019fe11e783fdbd1f56c73eb0' },
template_id: 'be001500a56011e889f9cddd88df842c'
},
dashboard: '#dashboard-form .dashboard'
})
window.formUppyWithDashboard = formUppyWithDashboard
/**
* transloadit.modal
*/
function openModal () {
transloadit.pick({
restrictions: {
allowedFileTypes: ['.png']
},
waitForEncoding: true,
params: {
auth: { key: '05a61ed019fe11e783fdbd1f56c73eb0' },
template_id: 'be001500a56011e889f9cddd88df842c'
},
providers: [
'webcam'
]
// if providers need custom config
// webcam: {
// option: 'whatever'
// }
}).then(console.log, console.error)
}
window.openModal = openModal
/**
* transloadit.upload
*/
window.doUpload = (event) => {
const resultEl = document.querySelector('#upload-result')
const errorEl = document.querySelector('#upload-error')
transloadit.upload(event.target.files, {
waitForEncoding: true,
params: {
auth: { key: '05a61ed019fe11e783fdbd1f56c73eb0' },
template_id: 'be001500a56011e889f9cddd88df842c'
}
}).then((result) => {
resultEl.classList.remove('hidden')
errorEl.classList.add('hidden')
resultEl.textContent = inspect(result.results)
}, (err) => {
resultEl.classList.add('hidden')
errorEl.classList.remove('hidden')
errorEl.textContent = err.message
})
}

4797
examples/transloadit/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,23 @@
{
"private": true,
"name": "uppy-robodog-example",
"scripts": {
"css": "cp ../../packages/uppy/dist/uppy.min.css .",
"start": "npm run css && (node server & budo main.js:bundle.js -- -t babelify -g aliasify & wait)"
},
"aliasify": {
"aliases": {
"@uppy": "../../packages/@uppy"
}
},
"dependencies": {
"he": "^1.2.0"
},
"devDependencies": {
"aliasify": "^2.1.0",
"babel-core": "^6.26.3",
"babelify": "^8.0.0",
"budo": "^11.3.2",
"express": "^4.16.4"
}
}

View file

@ -0,0 +1,13 @@
# Multiple Instances
This example uses Uppy with the RestoreFiles plugin.
It has two instances on the same page, side-by-side, but with different `id`s so their stored files don't interfere with each other.
## Run it
Move into this directory, then:
```bash
npm install
npm start
```

View file

@ -0,0 +1,122 @@
const http = require('http')
const qs = require('querystring')
const e = require('he').encode
/**
* A very haxxor server that outputs some of the data it receives in a POST form parameter.
*/
const server = http.createServer(onrequest)
server.listen(9967)
function onrequest (req, res) {
if (req.url !== '/test') {
res.writeHead(404, {'content-type': 'text/html'})
res.end('404')
return
}
let body = ''
req.on('data', (chunk) => { body += chunk })
req.on('end', () => {
onbody(body)
})
function onbody (body) {
const fields = qs.parse(body)
const assemblies = JSON.parse(fields.transloadit)
res.setHeader('content-type', 'text/html')
res.write(Header())
res.write(FormFields(fields))
assemblies.forEach((assembly) => {
res.write(AssemblyResult(assembly))
})
res.end(Footer())
}
}
function Header () {
return `
<!DOCTYPE html>
<html>
<head>
<style>
body { background: #f1f1f1; }
main {
padding: 20px;
font: 12pt sans-serif;
background: white;
width: 800px;
margin: auto;
}
</style>
</head>
<body>
<main>
`
}
function Footer () {
return `
</main>
</body>
</html>
`
}
function FormFields (fields) {
return `
<h1>Form Fields</h1>
<dl>
${Object.entries(fields).map(Field).join('\n')}
</dl>
`
function Field ([name, value]) {
if (name === 'transloadit') return ''
return `
<dt>${e(name)}</dt>
<dd>${e(value)}</dd>
`
}
}
function AssemblyResult (assembly) {
return `
<h1>${e(assembly.assembly_id)} (${e(assembly.ok)})</h1>
${UploadsList(assembly.uploads)}
${ResultsList(assembly.results)}
`
}
function UploadsList (uploads) {
return `
<ul>
${uploads.map(Upload).join('\n')}
</ul>
`
function Upload (upload) {
return `<li>${e(upload.name)}</li>`
}
}
function ResultsList (results) {
return Object.keys(results)
.map(ResultsSection)
.join('\n')
function ResultsSection (stepName) {
return `
<h2>${e(stepName)}</h2>
<ul>
${results[stepName].map(Result).join('\n')}
</ul>
`
}
function Result (result) {
return `<li>${e(result.name)} <a href="${result.ssl_url}" target="_blank">View</a></li>`
}
}

603
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -648,6 +648,7 @@ class Uppy {
})
if (inProgress.length === 0) {
this.emit('progress', 0)
this.setState({ totalProgress: 0 })
return
}
@ -684,6 +685,7 @@ class Uppy {
: Math.round(uploadedSize / totalSize * 100)
this.setState({ totalProgress })
this.emit('progress', totalProgress)
}
/**

View file

@ -32,6 +32,15 @@ const FOCUSABLE_ELEMENTS = [
const TAB_KEY = 9
const ESC_KEY = 27
function createPromise () {
const o = {}
o.promise = new Promise((resolve, reject) => {
o.resolve = resolve
o.reject = reject
})
return o
}
/**
* Dashboard UI with previews, metadata editing, tabs for various services and more
*/
@ -302,6 +311,7 @@ module.exports = class Dashboard extends Plugin {
}
openModal () {
const { promise, resolve } = createPromise()
// save scroll position
this.savedScrollPosition = window.scrollY
// save active element, so we can restore focus when modal is closed
@ -317,12 +327,14 @@ module.exports = class Dashboard extends Plugin {
isHidden: false
})
this.el.removeEventListener('animationend', handler, false)
resolve()
}
this.el.addEventListener('animationend', handler, false)
} else {
this.setPluginState({
isHidden: false
})
resolve()
}
if (this.opts.browserBackButtonClose) {
@ -334,6 +346,8 @@ module.exports = class Dashboard extends Plugin {
// this.rerender(this.uppy.getState())
this.setFocusToBrowse()
return promise
}
closeModal (opts = {}) {
@ -347,6 +361,8 @@ module.exports = class Dashboard extends Plugin {
return
}
const { promise, resolve } = createPromise()
if (this.opts.disablePageScrollWhenModalOpen) {
document.body.classList.remove('uppy-Dashboard-isFixed')
}
@ -361,12 +377,14 @@ module.exports = class Dashboard extends Plugin {
isClosing: false
})
this.el.removeEventListener('animationend', handler, false)
resolve()
}
this.el.addEventListener('animationend', handler, false)
} else {
this.setPluginState({
isHidden: true
})
resolve()
}
// handle ESC and TAB keys in modal dialog
@ -383,6 +401,8 @@ module.exports = class Dashboard extends Plugin {
}
}
}
return promise
}
isModalOpen () {

View file

@ -72,6 +72,7 @@ module.exports = class FileInput extends Plugin {
}
const restrictions = this.uppy.opts.restrictions
const accept = restrictions.allowedFileTypes ? restrictions.allowedFileTypes.join(',') : null
// empty value="" on file input, so that the input is cleared after a file is selected,
// because Uppy will be handling the upload and so we can select same file
@ -83,7 +84,7 @@ module.exports = class FileInput extends Plugin {
name={this.opts.inputName}
onchange={this.handleInputChange}
multiple={restrictions.maxNumberOfFiles !== 1}
accept={restrictions.allowedFileTypes}
accept={accept}
ref={(input) => { this.input = input }}
value="" />
{this.opts.pretty &&

View file

@ -1,5 +1,6 @@
const { Plugin } = require('@uppy/core')
const findDOMElement = require('@uppy/utils/lib/findDOMElement')
const toArray = require('@uppy/utils/lib/toArray')
// Rollup uses get-form-data's ES modules build, and rollup-plugin-commonjs automatically resolves `.default`.
// So, if we are being built using rollup, this require() won't have a `.default` property.
const getFormData = require('get-form-data').default || require('get-form-data')
@ -53,7 +54,25 @@ module.exports = class Form extends Plugin {
handleFormSubmit (ev) {
if (this.opts.triggerUploadOnSubmit) {
ev.preventDefault()
this.uppy.upload().catch((err) => {
const elements = toArray(ev.target.elements)
const disabledByUppy = []
elements.forEach((el) => {
const isButton = el.tagName === 'BUTTON' || (el.tagName === 'INPUT' && el.type === 'submit')
if (isButton && !el.disabled) {
el.disabled = true
disabledByUppy.push(el)
}
})
this.uppy.upload().then(() => {
disabledByUppy.forEach((button) => {
button.disabled = false
})
}, (err) => {
disabledByUppy.forEach((button) => {
button.disabled = false
})
return Promise.reject(err)
}).catch((err) => {
this.uppy.log(err.stack || err.message || err)
})
}

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Transloadit
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,81 @@
# @uppy/transloadit
<img src="https://uppy.io/images/logos/uppy-dog-head-arrow.svg" width="120" alt="Uppy logo: a superman puppy in a pink suit" align="right">
<a href="https://www.npmjs.com/package/@uppy/transloadit"><img src="https://img.shields.io/npm/v/@uppy/transloadit.svg?style=flat-square"></a>
<a href="https://travis-ci.org/transloadit/uppy"><img src="https://img.shields.io/travis/transloadit/uppy/master.svg?style=flat-square" alt="Build Status"></a>
The Transloadit plugin can be used to upload files to Transloadit for all kinds of processing, such as transcoding video, resizing images, zipping/unzipping, [and more](https://transloadit.com/services/).
[Try it live →](https://uppy.io/examples/transloadit/)
Uppy is being developed by the folks at [Transloadit](https://transloadit.com), a versatile file encoding service.
## Example
`transloadit.form` attaches Transloadit to an existing HTML form.
It could act like the jQuery SDK using the `@uppy/file-input` plugin,
or it could also add the `@uppy/dashboard`.
Uploads files on form submission, adds results to a hidden input,
then really submits the form.
```js
const transloadit = require('@uppy/robodog')
transloadit.form('#form', {
params: {
auth: { key: '' },
template_id: ''
}
})
```
Adding Dashboard could be optional, eg
```js
transloadit.form('#form', {
...
dashboard: true // or css selector, true means input[type=file]
})
```
The file input would be replaced by a button that opens the dashboard modal.
Needs:
- a way of having a 'Done' button instead of 'Upload' that closes the modal but doesn't trigger upload.
`transloadit.modal` opens the Dashboard and allows the user to select files.
When the user is done, presses 'upload', files are uploaded and the modal closes.
Promise resolves with results.
Needs:
- `{multi: false}` option in core, so that no new files can be added once `upload()` was called
- `{autoClose: true}` option in dashboard, that closes it once upload is complete
```js
transloadit.modal({
params: {
auth: { key: '' },
template_id: ''
}
}).then(({ successful, failed }) => {
// successful, failed are uppy.upload() result
// perhaps it could be assembly status or assembly results instead
})
```
## Installation
```bash
$ npm install @uppy/transloadit --save
```
We recommend installing from npm and then using a module bundler such as [Webpack](http://webpack.github.io/), [Browserify](http://browserify.org/) or [Rollup.js](http://rollupjs.org/).
Alternatively, you can also use this plugin in a pre-built bundle from Transloadit's CDN: Edgly. In that case `Uppy` will attach itself to the global `window.Uppy` object. See the [main Uppy documentation](https://uppy.io/docs/#Installation) for instructions.
## Documentation
Documentation for this plugin can be found on the [Uppy website](https://uppy.io/docs/transloadit).
## License
[The MIT License](./LICENSE).

View file

@ -0,0 +1,4 @@
require('es6-promise/auto')
require('whatwg-fetch')
module.exports = require('./')

View file

@ -0,0 +1,45 @@
{
"name": "@uppy/robodog",
"description": "Transloadit SDK for browsers based on Uppy",
"version": "0.0.0",
"license": "MIT",
"main": "lib/index.js",
"jsnext:main": "src/index.js",
"types": "types/index.d.ts",
"keywords": [
"file uploader",
"transloadit",
"file encoding",
"encoding",
"file processing",
"video encoding",
"crop",
"resize",
"watermark",
"uppy",
"uppy-plugin"
],
"homepage": "https://uppy.io",
"bugs": {
"url": "https://github.com/transloadit/uppy/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/transloadit/uppy.git"
},
"dependencies": {
"@uppy/core": "0.29.0",
"@uppy/dashboard": "0.29.0",
"@uppy/dropbox": "0.29.0",
"@uppy/form": "0.29.0",
"@uppy/google-drive": "0.29.0",
"@uppy/instagram": "0.29.0",
"@uppy/status-bar": "0.29.0",
"@uppy/transloadit": "0.29.0",
"@uppy/url": "0.29.0",
"@uppy/utils": "0.29.0",
"@uppy/webcam": "0.29.0",
"es6-promise": "^4.2.5",
"whatwg-fetch": "^3.0.0"
}
}

View file

@ -0,0 +1,77 @@
const { Plugin } = require('@uppy/core')
const toArray = require('@uppy/utils/lib/toArray')
const findDOMElement = require('@uppy/utils/lib/findDOMElement')
/**
* Add files from existing file inputs to Uppy.
*/
class AttachFileInputs extends Plugin {
constructor (uppy, opts) {
super(uppy, opts)
this.id = opts.id || 'AttachFileInputs'
this.type = 'acquirer'
this.handleChange = this.handleChange.bind(this)
this.inputs = null
}
handleChange (event) {
this.addFiles(event.target)
}
addFiles (input) {
const files = toArray(input.files)
files.forEach((file) => {
try {
this.uppy.addFile({
source: this.id,
name: file.name,
type: file.type,
data: file
})
} catch (err) {
// Nothing, restriction errors handled in Core
}
})
}
install () {
this.el = findDOMElement(this.opts.target)
if (!this.el) {
throw new Error('[AttachFileInputs] Target form does not exist')
}
const { restrictions } = this.uppy.opts
this.inputs = this.el.querySelectorAll('input[type="file"]')
this.inputs.forEach((input) => {
input.addEventListener('change', this.handleChange)
if (!input.hasAttribute('multiple')) {
if (restrictions.maxNumberOfFiles !== 1) {
input.setAttribute('multiple', 'multiple')
} else {
input.removeAttribute('multiple')
}
}
if (!input.hasAttribute('accept') && restrictions.allowedFileTypes) {
input.setAttribute('accept', restrictions.allowedFileTypes.join(','))
}
// Check if this input already contains files (eg. user selected them before Uppy loaded,
// or the page was refreshed and the browser kept files selected)
this.addFiles(input)
})
}
uninstall () {
this.inputs.forEach((input) => {
input.removeEventListener('change', this.handleChange)
})
this.inputs = null
}
}
module.exports = AttachFileInputs

View file

@ -0,0 +1,53 @@
const { Plugin } = require('@uppy/core')
const findDOMElement = require('@uppy/utils/lib/findDOMElement')
/**
* After an upload completes, inject result data from Transloadit in a hidden input.
*
* Must be added _after_ the Transloadit plugin.
*/
class TransloaditFormResult extends Plugin {
constructor (uppy, opts) {
super(uppy, opts)
this.id = opts.id || 'TransloaditFormResult'
this.type = 'modifier'
this.handleUpload = this.handleUpload.bind(this)
}
getAssemblyStatuses (fileIDs) {
const assemblyIds = []
fileIDs.forEach((fileID) => {
const file = this.uppy.getFile(fileID)
const assembly = file.transloadit && file.transloadit.assembly
if (assembly && assemblyIds.indexOf(assembly) === -1) {
assemblyIds.push(assembly)
}
})
const tl = this.uppy.getPlugin(this.opts.transloaditPluginId || 'Transloadit')
return assemblyIds.map((id) => tl.getAssembly(id))
}
handleUpload (fileIDs) {
const assemblies = this.getAssemblyStatuses(fileIDs)
const input = document.createElement('input')
input.type = 'hidden'
input.name = this.opts.name
input.value = JSON.stringify(assemblies)
const target = findDOMElement(this.opts.target)
target.appendChild(input)
}
install () {
this.uppy.addPostProcessor(this.handleUpload)
}
uninstall () {
this.uppy.removePostProcessor(this.handleUpload)
}
}
module.exports = TransloaditFormResult

View file

@ -0,0 +1,46 @@
const { Plugin } = require('@uppy/core')
/**
* Add a `results` key to the upload result data, containing all Transloadit Assembly results.
*/
class TransloaditResultsPlugin extends Plugin {
constructor (uppy, opts) {
super(uppy, opts)
this.type = 'modifier'
this.id = 'TransloaditResultsPlugin'
this._afterUpload = this._afterUpload.bind(this)
}
install () {
this.uppy.addPostProcessor(this._afterUpload)
}
_afterUpload (fileIDs, uploadID) {
const { currentUploads } = this.uppy.getState()
const { result } = currentUploads[uploadID]
const assemblies = result && Array.isArray(result.transloadit) ? result.transloadit : []
// Merge the assembly.results[*] arrays and add `stepName` and
// `assemblyId` properties.
const assemblyResults = []
assemblies.forEach((assembly) => {
Object.keys(assembly.results).forEach((stepName) => {
const results = assembly.results[stepName]
results.forEach((result) => {
assemblyResults.push({
...result,
assemblyId: assembly.assembly_id,
stepName
})
})
})
})
this.uppy.addResultData(uploadID, {
results: assemblyResults
})
}
}
module.exports = TransloaditResultsPlugin

View file

@ -0,0 +1,80 @@
const Transloadit = require('@uppy/transloadit')
const remoteProviders = {
dropbox: require('@uppy/dropbox'),
'google-drive': require('@uppy/google-drive'),
instagram: require('@uppy/instagram'),
url: require('@uppy/url')
}
const localProviders = {
webcam: require('@uppy/webcam')
}
const remoteProviderOptionNames = [
'serverUrl',
'serverPattern',
'serverHeaders',
'target'
]
// No shared options.
const localProviderOptionNames = [
'target'
]
function addRemoteProvider (uppy, name, opts) {
const Provider = remoteProviders[name]
const providerOptions = {
// Default to the :tl: Companion servers.
serverUrl: Transloadit.COMPANION,
serverPattern: Transloadit.COMPANION_PATTERN
}
remoteProviderOptionNames.forEach((name) => {
if (opts.hasOwnProperty(name)) providerOptions[name] = opts[name]
})
// Apply overrides for a specific provider plugin.
if (typeof opts[name] === 'object') {
Object.assign(providerOptions, opts[name])
}
uppy.use(Provider, providerOptions)
}
function addLocalProvider (uppy, name, opts) {
const Provider = localProviders[name]
const providerOptions = {}
localProviderOptionNames.forEach((name) => {
if (opts.hasOwnProperty(name)) providerOptions[name] = opts[name]
})
// Apply overrides for a specific provider plugin.
if (typeof opts[name] === 'object') {
Object.assign(providerOptions, opts[name])
}
uppy.use(Provider, providerOptions)
}
function addProviders (uppy, names, opts = {}) {
names.forEach((name) => {
if (remoteProviders.hasOwnProperty(name)) {
addRemoteProvider(uppy, name, opts)
} else if (localProviders.hasOwnProperty(name)) {
addLocalProvider(uppy, name, opts)
} else {
const validNames = [
...Object.keys(remoteProviders),
...Object.keys(localProviders)
]
const expectedNameString = validNames
.sort()
.map((validName) => `'${validName}'`)
.join(', ')
throw new Error(`Unexpected provider '${name}', expected one of [${expectedNameString}]`)
}
})
}
module.exports = addProviders

View file

@ -0,0 +1,29 @@
const Transloadit = require('@uppy/transloadit')
const TransloaditResults = require('./TransloaditResultsPlugin')
const transloaditOptionNames = [
'service',
'waitForEncoding',
'waitForMetadata',
'alwaysRunAssembly',
'importFromUploadURLs',
'signature',
'params',
'fields',
'getAssemblyOptions'
]
function addTransloaditPlugin (uppy, opts) {
const transloaditOptions = {}
transloaditOptionNames.forEach((name) => {
if (opts.hasOwnProperty(name)) transloaditOptions[name] = opts[name]
})
uppy.use(Transloadit, transloaditOptions)
// Adds a `results` key to the upload result data containing a flat array of all results from all Assemblies.
if (transloaditOptions.waitForEncoding) {
uppy.use(TransloaditResults)
}
}
module.exports = addTransloaditPlugin

View file

@ -0,0 +1,64 @@
const Uppy = require('@uppy/core')
const eventNames = {
// File management events
onFileAdded: 'file-added',
onFileRemoved: 'file-removed',
// Transloadit events
onImportError: 'transloadit:import-error',
onAssemblyCreated: 'transloadit:assembly-created',
onAssemblyExecuting: 'transloadit:assembly-executing',
onAssemblyError: 'transloadit:assembly-error',
onAssemblyComplete: 'transloadit:complete',
onResult: 'transloadit:result',
// Upload events
onStart: 'upload',
onPause: 'pause-all',
onFilePause: 'upload-pause',
onCancel: 'cancel-all',
onError: 'error', // mostly akin to promise rejection
onFileCancel: 'upload-cancel',
onFileProgress: 'upload-progress',
onFileError: 'upload-error',
onUploaded: 'transloadit:upload',
onComplete: 'complete' // mostly akin to promise resolution
}
const uppyOptionNames = [
'autoProceed',
'restrictions',
'meta',
'onBeforeFileAdded',
'onBeforeUpload'
]
function createUppy (opts, overrides = {}) {
const uppyOptions = {}
uppyOptionNames.forEach((name) => {
if (opts.hasOwnProperty(name)) uppyOptions[name] = opts[name]
})
Object.assign(uppyOptions, overrides)
const uppy = Uppy(uppyOptions)
// Builtin event aliases
Object.keys(eventNames).forEach((optionName) => {
const eventName = eventNames[optionName]
if (typeof opts[optionName] === 'function') {
uppy.on(eventName, opts[optionName])
}
})
// Custom events (these should probably be added to core)
if (typeof opts.onProgress === 'function') {
uppy.on('upload-progress', () => {
const { totalProgress } = uppy.getState()
opts.onProgress.call(uppy, totalProgress)
})
}
return uppy
}
module.exports = createUppy

View file

@ -0,0 +1,77 @@
const Uppy = require('@uppy/core')
const Form = require('@uppy/form')
const StatusBar = require('@uppy/status-bar')
const Dashboard = require('@uppy/dashboard')
const findDOMElement = require('@uppy/utils/lib/findDOMElement')
const AttachFileInputs = require('./AttachFileInputs')
const TransloaditFormResult = require('./TransloaditFormResult')
const addTransloaditPlugin = require('./addTransloaditPlugin')
const addProviders = require('./addProviders')
function form (target, opts) {
const uppy = Uppy(opts)
addTransloaditPlugin(uppy, opts)
uppy.use(TransloaditFormResult, {
target,
transloaditPluginId: 'Transloadit',
name: 'transloadit'
})
uppy.use(Form, {
target,
triggerUploadOnSubmit: true,
submitOnSuccess: true,
addResultToForm: false // using custom implementation instead
})
const useDashboard = opts.dashboard || opts.modal
if (useDashboard) {
const dashboardTarget = findDOMElement(opts.dashboard) || document.body
const dashboardId = 'form:Dashboard'
const dashboardOpts = {
id: dashboardId,
target: dashboardTarget
}
if (opts.modal) {
const trigger = 'input[type="file"]'
const button = document.createElement('button')
button.textContent = 'Select files'
button.type = 'button'
const old = findDOMElement(trigger, findDOMElement(target))
old.parentNode.replaceChild(button, old)
dashboardOpts.trigger = button
} else {
dashboardOpts.inline = true
dashboardOpts.hideUploadButton = true
}
uppy.use(Dashboard, dashboardOpts)
if (Array.isArray(opts.providers)) {
addProviders(uppy, opts.providers, {
...opts,
target: uppy.getPlugin(dashboardId)
})
}
} else {
uppy.use(AttachFileInputs, { target })
}
if (opts.statusBar) {
uppy.use(StatusBar, {
target: opts.statusBar,
// hide most of the things to keep our api simple,
// we can change this in the future if someone needs it
hideUploadButton: true,
hideAfterFinish: true,
hideRetryButton: true,
hidePauseResumeButtons: true,
hideCancelButtons: true
})
}
return uppy
}
module.exports = form

View file

@ -0,0 +1,9 @@
const form = require('./form')
const pick = require('./pick')
const upload = require('./upload')
module.exports = {
form,
pick,
upload
}

View file

@ -0,0 +1,52 @@
const Dashboard = require('@uppy/dashboard')
const createUppy = require('./createUppy')
const addTransloaditPlugin = require('./addTransloaditPlugin')
const addProviders = require('./addProviders')
const CANCEL = {}
function pick (opts = {}) {
const target = opts.target || document.body
const pluginId = 'pick'
const uppy = createUppy(opts, {
allowMultipleUploads: false
})
addTransloaditPlugin(uppy, opts)
uppy.use(Dashboard, {
id: pluginId,
target,
closeAfterFinish: true
})
if (Array.isArray(opts.providers)) {
addProviders(uppy, opts.providers, {
...opts,
// Install providers into the Dashboard.
target: uppy.getPlugin(pluginId)
})
}
return new Promise((resolve, reject) => {
uppy.on('complete', (result) => {
if (result.failed.length === 0) {
resolve(result)
}
})
uppy.on('error', reject)
uppy.on('cancel-all', () => reject(CANCEL))
uppy.getPlugin(pluginId)
.openModal()
}).then((result) => {
return result
}, (err) => {
if (err === CANCEL) {
uppy.getPlugin(pluginId)
.requestCloseModal()
return null
}
throw err
})
}
module.exports = pick

View file

@ -0,0 +1,6 @@
@import '@uppy/core/src/_utils.scss';
@import '@uppy/core/src/_variables.scss';
@import '@uppy/status-bar/src/style.scss';
@import '@uppy/url/src/style.scss';
@import '@uppy/webcam/src/style.scss';
@import '@uppy/dashboard/src/style.scss';

View file

@ -0,0 +1,28 @@
const toArray = require('@uppy/utils/lib/toArray')
const createUppy = require('./createUppy')
const addTransloaditPlugin = require('./addTransloaditPlugin')
function upload (files, opts = {}) {
if (!Array.isArray(files) && typeof files.length === 'number') {
files = toArray(files)
}
const uppy = createUppy(opts, {
allowMultipleUploads: false
})
addTransloaditPlugin(uppy, opts)
files.forEach((file) => {
uppy.addFile({
data: file,
type: file.type,
name: file.name,
meta: file.meta || {}
})
})
return uppy.upload()
}
module.exports = upload

View file

@ -133,4 +133,5 @@ class AssemblyOptions {
}
}
module.exports = Object.assign(AssemblyOptions, { validateParams })
module.exports = AssemblyOptions
module.exports.validateParams = validateParams

View file

@ -52,7 +52,10 @@ module.exports = class Transloadit extends Plugin {
locale: defaultLocale
}
this.opts = Object.assign({}, defaultOptions, opts)
this.opts = {
...defaultOptions,
...opts
}
// i18n
this.translator = new Translator([ defaultLocale, this.uppy.locale, this.opts.locale ])
@ -268,13 +271,14 @@ module.exports = class Transloadit extends Plugin {
return
}
this.setPluginState({
files: Object.assign({}, state.files, {
files: {
...state.files,
[uploadedFile.id]: {
assembly: assemblyId,
id: file.id,
uploadedFile
}
})
}
})
this.uppy.emit('transloadit:upload', uploadedFile, this.getAssembly(assemblyId))
}
@ -316,9 +320,10 @@ module.exports = class Transloadit extends Plugin {
this.client.getAssemblyStatus(url).then((finalStatus) => {
const state = this.getPluginState()
this.setPluginState({
assemblies: Object.assign({}, state.assemblies, {
assemblies: {
...state.assemblies,
[finalStatus.assembly_id]: finalStatus
})
}
})
this.uppy.emit('transloadit:complete', finalStatus)
})

View file

@ -6,9 +6,9 @@ const isDOMElement = require('./isDOMElement')
* @param {Node|string} element
* @return {Node|null}
*/
module.exports = function findDOMElement (element) {
module.exports = function findDOMElement (element, context = document) {
if (typeof element === 'string') {
return document.querySelector(element)
return context.querySelector(element)
}
if (typeof element === 'object' && isDOMElement(element)) {

View file

@ -1,6 +1,6 @@
---
type: docs
order: 43
order: 53
title: "AWS S3 Multipart"
module: "@uppy/aws-s3-multipart"
permalink: docs/aws-s3-multipart/

View file

@ -1,6 +1,6 @@
---
type: docs
order: 42
order: 52
title: "AWS S3"
module: "@uppy/aws-s3"
permalink: docs/aws-s3/

View file

@ -1,7 +1,7 @@
---
title: "Contributing"
type: docs
order: 4
order: 5
---
## Uppy development

View file

@ -1,6 +1,6 @@
---
type: docs
order: 20
order: 30
title: "Dashboard"
module: "@uppy/dashboard"
permalink: docs/dashboard/
@ -159,7 +159,7 @@ Hide the retry button. Use this if you are providing a custom retry button somew
### `hidePauseResumeButton: false`
Passed to the Status Bar plugin used in the Dashboard.
Passed to the Status Bar plugin used in the Dashboard.
Hide pause/resume buttons (for resumable uploads, via [tus](http://tus.io), for example). Use this if you are providing custom cancel or pause/resume buttons somewhere, and using the `uppy.pauseResume(fileID)` or `uppy.removeFile(fileID)` API.

View file

@ -1,6 +1,6 @@
---
type: docs
order: 21
order: 31
title: "Drag & Drop"
module: "@uppy/drag-drop"
permalink: docs/drag-drop/

View file

@ -1,6 +1,6 @@
---
type: docs
order: 31
order: 41
title: "Dropbox"
module: "@uppy/dropbox"
permalink: docs/dropbox/

View file

@ -1,6 +1,6 @@
---
type: docs
order: 22
order: 32
title: "File Input"
module: "@uppy/file-input"
permalink: docs/file-input/

View file

@ -1,6 +1,6 @@
---
type: docs
order: 70
order: 80
title: "Form"
module: "@uppy/form"
permalink: docs/form/

View file

@ -3,7 +3,7 @@ title: "Golden Retriever"
module: "@uppy/golden-retriever"
type: docs
permalink: docs/golden-retriever/
order: 71
order: 81
---
The `@uppy/golden-retriever` plugin saves selected files in your browser cache, so that if the browser crashes, 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/).

View file

@ -1,6 +1,6 @@
---
type: docs
order: 32
order: 42
title: "Google Drive"
module: "@uppy/google-drive"
permalink: docs/google-drive/

View file

@ -1,6 +1,6 @@
---
type: docs
order: 52
order: 62
title: "Informer"
module: "@uppy/informer"
permalink: docs/informer/

View file

@ -1,6 +1,6 @@
---
type: docs
order: 33
order: 43
title: "Instagram"
module: "@uppy/instagram"
permalink: docs/instagram/

View file

@ -2,7 +2,7 @@
title: "List & Common Options"
type: docs
permalink: docs/plugins/
order: 10
order: 20
---
Plugins are what makes Uppy useful: they help select, manipulate and upload files.

View file

@ -1,6 +1,6 @@
---
type: docs
order: 51
order: 61
title: "Progress Bar"
module: "@uppy/progress-bar"
permalink: docs/progress-bar/

View file

@ -2,14 +2,14 @@
title: "Provider Plugins"
type: docs
permalink: docs/providers/
order: 30
order: 40
---
The Provider plugins help you connect to your accounts with remote file providers such as [Dropbox](https://dropbox.com), [Google Drive](https://drive.google.com), [Instagram](https://instagram.com) and remote URLs (importing a file by pasting a direct link to it). Because this requires server-to-server communication, they work tightly with [Companion](https://github.com/transloadit/companion) to manage the server-to-server authorization for your account. Almost all of the communication (file download/upload) is done on the server-to-server end, so this saves you the stress and bills of data consumption on the client.
As of now, the supported providers are [**Dropbox**](/docs/dropbox), [**GoogleDrive**](/docs/google-drive), [**Instagram**](/docs/instagram), and [**URL**](/docs/url).
Usage of the Provider plugins is not that different from any other *acquirer* plugin, except that it takes an extra option `serverUrl`, which specifies the URL to the Companion that you are running. This allows Uppy to know what server to connect to when datacenter operations are required by the provider plugin.
Usage of the Provider plugins is not that different from any other *acquirer* plugin, except that it takes an extra option `serverUrl`, which specifies the URL to the Companion that you are running. This allows Uppy to know what server to connect to when datacenter operations are required by the provider plugin.
Here's a quick example:

View file

@ -2,7 +2,7 @@
title: "&lt;DashboardModal />"
type: docs
permalink: docs/react/dashboard-modal/
order: 85
order: 95
---
The `<DashboardModal />` component wraps the [`@uppy/dashboard`][] plugin, allowing control over the modal `open` state using a prop.

View file

@ -2,7 +2,7 @@
title: "&lt;Dashboard />"
type: docs
permalink: docs/react/dashboard/
order: 84
order: 94
---
The `<Dashboard />` component wraps the [`@uppy/dashboard`][] plugin. It only renders the Dashboard inline. To use the Dashboard modal (`inline: false`), use the [`<DashboardModal />`](/docs/react/dashboard-modal) component.

View file

@ -3,7 +3,7 @@ title: "&lt;DragDrop />"
type: docs
permalink: docs/react/drag-drop/
alias: docs/react/dragdrop/
order: 82
order: 92
---
The `<DragDrop />` component wraps the [`@uppy/drag-drop`][] plugin.

View file

@ -3,7 +3,7 @@ title: "&lt;ProgressBar />"
type: docs
permalink: docs/react/progress-bar/
alias: docs/react/progressbar/
order: 83
order: 93
---
The `<ProgressBar />` component wraps the [`@uppy/progress-bar`][] plugin.

View file

@ -3,7 +3,7 @@ title: "&lt;StatusBar />"
type: docs
permalink: docs/react/status-bar/
alias: docs/react/statusbar/
order: 81
order: 91
---
The `<StatusBar />` component wraps the [`@uppy/status-bar`][] plugin.

View file

@ -2,7 +2,7 @@
title: "Introduction"
type: docs
permalink: docs/react/
order: 80
order: 90
---
Uppy provides [React][] components for the included UI plugins.

View file

@ -2,7 +2,7 @@
title: "Redux"
type: docs
permalink: docs/redux/
order: 87
order: 97
---
Uppy supports the popular [Redux](https://redux.js.org/) state management library in two ways:

View file

@ -0,0 +1,159 @@
---
type: docs
order: 12
title: "Robodog: Form API"
menu: "Form"
permalink: docs/robodog/form/
---
Add resumable uploads and Transloadit's processing to your existing HTML upload forms. Selected files will be uploaded to Transloadit, and the Assembly information will be submitted to your form endpoint.
```html
<form id="myForm" method="POST" action="/upload">
<input type="file" multiple>
...
</form>
<script>
transloadit.form('form#myForm', {
params: {
auth: { key: '' },
template_id: ''
}
})
</script>
```
When the user submits the form, we intercept it and send the files to Transloadit instead. This creates one or more Assemblies depending on configuration. Then, we put the status JSON object(s) in a hidden input field named `transloadit`.
```html
<input type="hidden" name="transloadit" value='[{"ok": "ASSEMBLY_EXECUTING",...}]'>
```
Finally, we _really_ submit the form—without files, but with those Assembly status objects. You can then handle that in your backend.
## Transloadit
All the options to the [Transloadit][transloadit] plugin are supported.
## Restrictions
Set rules and conditions to limit the type and/or number of files that can be selected. Restrictions are configured by the `restrictions` option.
### `restrictions.maxFileSize`
Maximum file size in bytes for each individual file.
### `restrictions.maxNumberOfFiles`
The total number of files that can be selected. If this is larger than 1, the `multiple` attribute will be added to `<input type="file">` fields.
### `restrictions.minNumberOfFiles`
The minimum number of files that must be selected before the upload. The upload will fail and the form will not be submitted if fewer files were selected.
### `restrictions.allowedFileTypes`
Array of mime type wildcards `image/*`, exact mime types `image/jpeg`, or file extensions `.jpg`: `['image/*', '.jpg', '.jpeg', '.png', '.gif']`.
If provided, the [`<input accept>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Limiting_accepted_file_types) attribute will be added to `<input type="file">` fields, so only acceptable files can be selected in the system file dialog.
## Progress Reporting
Uploads using HTML forms have no builtin progress reporting. With the Robodog, you can use the `statusBar` option to show an [@uppy/status-bar](/docs/status-bar): an element styled like a progress bar, reporting both upload and Assembly execution progress.
Point it to an element or a CSS selector:
```html
<form id="my-form" ...>
<div class="progress"></div>
</form>
<script>
transloadit.form('form#my-form', {
statusBar: '#my-form .progress'
// ...
})
</script>
```
The progress bar will be inserted _into_ that element (thus _not_ replace it).
<!--
## Dashboard
**TODO have an option to replace the inputs with a Dashboard modal button?**
-->
## Migrating From the jQuery SDK
We now recommend using Uppy over the jQuery SDK. Uppy is framework- and library-agnostic, and much more extensible.
Like the Transloadit jQuery SDK, this API enhances an existing form. That makes this a good candidate for migration. Most of the jQuery SDK options have a direct equivalent in the Robodog.
First, change your import URLs and initialization code:
```html
<!-- The old jQuery way… -->
<script src="//assets.transloadit.com/js/jquery.transloadit2-v3-latest.js"></script>
<script>
$(selector).transloadit({
...options
})
</script>
```
```html
<!-- The new Robodog way! -->
<script src="//transloadit.edgly.net/robodog/v1.0.0/dist/transloadit.js"></script>
<script>
transloadit.form(selector, {
...options
})
</script>
```
The equivalent options are listed below.
### Options
| jQuery option | Robodog option |
|---------------|---------------------------|
| `service` | `service` |
| `region` | Not supported, instead set the `service` option to `https://api2-regionname.transloadit.com` |
| `wait: true` | `waitForEncoding: true` |
| `requireUploadMetadata: true` | `waitForMetadata: true` |
| `params` | `params` |
| `signature` | `signature` |
| `modal` | Currently unavailable |
| `autoSubmit` | `submitOnSuccess` |
| `triggerUploadOnFileSelection` | `autoProceed: true` |
| `processZeroFiles` | `alwaysRunAssembly` |
| `maxNumberOfUploadedFiles` | Use [restrictions](#Restrictions) instead. `restrictions.maxNumberOfFiles`. |
| `locale` | No longer supported, this will be addressed by the equivalent to the `translations` option instead. |
| `translations` | Currently unavailable |
| `exclude` | Currently unavailable |
| `fields` | `fields`. The CSS selector format is no longer supported. Instead, specify an array of form field names. `['field1']` instead of `'input[name=field1]`. |
| `debug` | Obsolete, as Transloadit's backend has improved error reporting. |
As for the options that are unavailable:
- `modal` will be added in the future.
- `exclude` is intended to exclude certain `<input type="file">` inputs from Transloadit processing. It will likely not be added, but we'll perhaps have a `include` CSS selector option instead.
- `debug` will not be added.
### Events
The `transloadit.form()` method returns an Uppy object, so you can listen to events there. There are no `on*()` _options_ anymore, but an `.on('*')` method is provided instead.
| jQuery option | Robodog Event |
|---------------|--------------------------|
| `onStart` | `onAssemblyCreated` |
| `onExecuting` | `onAssemblyExecuting` |
| `onFileSelect` | `onFileAdded` |
| `onProgress` | `onProgress` |
| `onUpload` | `onUpload` |
| `onResult` | `onResult` |
| `onCancel` | `onCancel` (or `onFileCancel` for individual files) |
| `onError` | `onError` |
| `onSuccess` | `onComplete` |
| `onDisconnect` | Currently unavailable, use something like [`is-offline`](https://www.npmjs.com/package/is-offline) |
| `onReconnect` | Currently unavailable, use something like [`is-offline`](https://www.npmjs.com/package/is-offline) |

View file

@ -0,0 +1,117 @@
---
type: docs
title: "Robodog: File Picker API"
menu: "File Picker"
permalink: docs/robodog/picker/
order: 11
---
Show a modal UI that allows users to pick files from their device and from the web. It uploads files to Transloadit for processing.
```js
const resultPromise = transloadit.pick({
params: {
auth: { key: '' },
template_id: ''
}
})
```
`resultPromise` is a [Promise][promise] that resolves with an object:
- `successful` - An array containing data about files that were uploaded successfully
- `failed` - An array containing data about files that failed to upload
- `transloadit` - An array of Assembly statuses
- `results` - An array of results produced by the assembly, if `waitForEncoding` was used
## `options.target`
DOM element or CSS selector to place the modal element in. `document.body` is usually fine in this case because the modal is absolutely positioned on top of everything anyway.
## Transloadit
All the options to the [Transloadit][transloadit] plugin are supported.
The Promise resolution value has a `transloadit` and `results` key.
`result.transloadit` contains an array of Assembly statuses. Assembly statuses are objects as described in the [Transloadit documentation][assembly-status]. There may be multiple Assembly statuses if the `getAssemblyOptions` option was used, because different files may be processed by different Assemblies.
`result.results` contains an array of results produced by the Assemblies. Each result has an `assemblyId` property containing the string ID of the Assembly that produced it, and a `stepName` property containing the string name of the Assembly step that produced it.
## Restrictions
Set rules and conditions to limit the type and/or number of files that can be selected. Restrictions are configured by the `restrictions` option.
### `restrictions.maxFileSize`
Maximum file size in bytes for each individual file.
### `restrictions.maxNumberOfFiles`
The total number of files that can be selected. If this is equal to 1, users can only select a single file in system dialogs; else they can select multiple.
### `restrictions.minNumberOfFiles`
The minimum number of files that must be selected before the upload.
### `restrictions.allowedFileTypes`
Array of mime type wildcards `image/*`, exact mime types `image/jpeg`, or file extensions `.jpg`: `['image/*', '.jpg', '.jpeg', '.png', '.gif']`.
If provided, the [`<input accept>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Limiting_accepted_file_types) attribute will be used for the internal file input field, so only acceptable files can be selected in the system file dialog.
## Providers
Providers import files from third party services using [Uppy Companion][companion] or from local sources like the device camera.
By default, the Picker will use Transloadit's [Uppy Companion][companion] servers for imports from third party service. You can self-host your own instances as well.
### `providers: []`
Array of providers to use. Each entry is the name of a provider. The available ones are:
- `'dropbox'` Import files from Dropbox using [Uppy Companion][companion].
- `'google-drive'` Import files from Google Drive using [Uppy Companion][companion].
- `'instagram'` Import files from Instagram using [Uppy Companion][companion].
- `'url'` Import files from public Web URLs using [Uppy Companion][companion].
- `'webcam'` Take photos and record videos using thee user's device camera.
### `serverUrl: Transloadit.COMPANION`
The URL to a [Uppy Companion][companion] server to use.
### `serverPattern: Transloadit.COMPANION_PATTERN`
The valid and authorised URL(s) from which OAuth responses should be accepted.
This value can be a `String`, a `Regex` pattern, or an `Array` of both.
This is useful when you have your [Uppy Companion][companion] running on multiple hosts. Otherwise, the default value should do just fine.
### `serverHeaders: {}`
Custom headers to send to [Uppy Companion][companion].
### `dropbox: {}`
Specific options for the [Dropbox](/docs/dropbox) provider.
### `googleDrive: {}`
Specific options for the [Google Drive](/docs/google-drive) provider.
### `instagram: {}`
Specific options for the [Instagram](/docs/instagram) provider.
### `url: {}`
Specific options for the [URL](/docs/url) provider.
### `webcam: {}`
Specific options for the [Webcam](/docs/webcam) provider.
[companion]: /docs/companion
[transloadit]: /docs/transloadit#options
[assembly-status]: https://transloadit.com/docs/api/#assembly-status-response

View file

@ -0,0 +1,39 @@
---
type: docs
title: "Robodog: Upload API"
menu: "Upload"
permalink: docs/robodog/upload/
order: 13
---
Upload files straight to Transloadit from your own custom UI. Give us an array of files, and we'll give you an array of results!
```js
const resultPromise = transloadit.upload(files, {
params: {
auth: { key: '' },
template_id: ''
}
})
```
`resultPromise` is a [Promise][promise] that resolves with an object:
- `successful` - An array containing data about files that were uploaded successfully
- `failed` - An array containing data about files that failed to upload
- `transloadit` - An array of Assembly statuses
## `files`
An array of [File][file] objects, obtained from an `<input type="file">` or elsewhere.
These can also be [Blob][blob]s with a `.name` property. That way you can upload files that were created using JavaScript.
## Transloadit
All the options to the [Transloadit][tl-options] plugin are supported.
[file]: https://developer.mozilla.org/en-US/docs/Web/API/File
[blob]: https://developer.mozilla.org/en-US/docs/Web/API/Blob
[promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
[tl-options]: /docs/transloadit#options

113
website/src/docs/robodog.md Normal file
View file

@ -0,0 +1,113 @@
---
type: docs
order: 10
title: "Robodog"
menu: "Introduction"
module: "@uppy/robodog"
permalink: docs/robodog/
---
[Transloadit][transloadit] is a service that helps you handle file uploads, resize, crop and watermark your images, make GIFs, transcode your videos, extract thumbnails, generate audio waveforms, and so much more. In short, [Transloadit][transloadit] is the Swiss Army Knife for your files.
Robodog is an Uppy-based library that helps you talk to the Transloadit API. It includes a modal UI file picker with support for imports from third-party services, integration with HTML forms, and more. Because it's based on Uppy, you can add any existing Uppy plugin to add more functionality.
## Install
Robodog can be downloaded from npm:
```shell
npm install --save @uppy/robodog
```
Then, with a bundler such as [webpack][webpack] or [Browserify][browserify], do:
```js
const transloadit = require('@uppy/robodog')
```
If you are not using a bundler, you can also import the Robodog using an HTML script tag.
```html
<link rel="stylesheet" href="https://transloadit.edgly.net/releases/robodog/v1.0.0/dist/style.min.css">
<script src="https://transloadit.edgly.net/releases/robodog/v1.0.0/dist/transloadit.min.js"></script>
```
## Methods
Robodog has several methods for different use cases.
If you want to have a modal UI that users can use to select files from their local device or from third party sources like Instagram, use the [File Picker API](#File-Picker). This can be used for one-off uploads _outside_ an HTML form, like profile avatars or images to embed in a blog post.
If you already have an HTML form, you can use the [Form API](#Form) to add Transloadit's encoding capabilities to it. Files will be uploaded to Transloadit, and the form will submit JSON information about the files and encoding results. You can also optionally show upload progress using Uppy's Status Bar UI, or even use the advanced Dashboard UI so users can import files from third party sources as well.
Finally, you can use the [Programmatic Upload API](#Programmatic-Uploads) with your custom UI implementation.
And if any of these methods are not flexible enough, you can always replace them with a custom Uppy setup using the [Transloadit Plugin](/docs/transloadit) instead!
## File Picker
Show a modal UI that allows users to pick files from their device and from the web. It uploads files to Transloadit for processing.
```js
const resultPromise = transloadit.pick({
target: 'body',
params: {
auth: { key: '' },
template_id: ''
}
})
resultPromise.then((bundle) => {
bundle.transloadit // Array of Assembly statuses
bundle.results // Array of all Assembly results
})
```
<img src="/images/temp-robodog-demo.gif" alt="Robodog File Picker Demo GIF">
<a class="MoreButton" href="/docs/robodog/picker">View Documentation</a>
## Form
Add resumable uploads and Transloadit's processing to your existing HTML upload forms. Selected files will be uploaded to Transloadit, and the Assembly information will be submitted to your form endpoint.
```html
<form id="myForm" method="POST" action="/upload">
<input type="file" multiple>
<!-- Will be inserted by `transloadit.form()`: -->
<!-- <input type="hidden" name="transloadit" value="{...json...}"> -->
<button type="submit">Upload</button>
</form>
<script>
transloadit.form('form#myForm', {
params: {
auth: { key: '' },
template_id: ''
}
})
</script>
```
<a class="MoreButton" href="/docs/robodog/form">View Documentation</a>
## Programmatic Uploads
Upload files straight to Transloadit from your own custom UI. Give us an array of files, and we'll give you an array of results!
```js
const resultPromise = transloadit.upload(files, {
params: {
auth: { key: '' },
template_id: ''
}
})
resultPromise.then((bundle) => {
bundle.transloadit // Array of Assembly statuses
bundle.results // Array of all Assembly results
})
```
<a class="MoreButton" href="/docs/robodog/upload">View Documentation</a>
[transloadit]: https://transloadit.com/
[browserify]: https://browserify.org
[webpack]: https://webpack.js.org

View file

@ -1,6 +1,6 @@
---
type: docs
order: 50
order: 60
title: "Status Bar"
module: "@uppy/status-bar"
permalink: docs/status-bar/

View file

@ -1,6 +1,6 @@
---
type: docs
order: 60
order: 70
title: "Transloadit"
module: "@uppy/transloadit"
permalink: docs/transloadit/

View file

@ -1,6 +1,6 @@
---
type: docs
order: 40
order: 50
title: "Tus"
module: "@uppy/tus"
permalink: docs/tus/

View file

@ -1,6 +1,6 @@
---
type: docs
order: 34
order: 44
title: "Import From URL"
module: "@uppy/url"
permalink: docs/url/

View file

@ -1,6 +1,6 @@
---
type: docs
order: 26
order: 36
title: "Webcam"
module: "@uppy/webcam"
permalink: docs/webcam/

View file

@ -2,7 +2,7 @@
type: docs
title: "Writing Plugins"
permalink: docs/writing-plugins/
order: 11
order: 21
---
There are 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:

View file

@ -1,6 +1,6 @@
---
type: docs
order: 41
order: 51
title: "XHR Upload"
module: "@uppy/xhr-upload"
permalink: docs/xhr-upload/

Binary file not shown.

After

Width:  |  Height:  |  Size: 653 KiB

View file

@ -3,8 +3,8 @@ title: Stats
type: docs
layout: stats
permalink: docs/stats/
alias:
alias:
- guide/stats/
- stats/
order: 5
order: 6
---

View file

@ -1,20 +1,22 @@
<%
var categories = [
// order: 1x
{ path: 'docs/plugins/', name: 'Plugins', link: true },
{ path: 'docs/robodog/', name: 'Robodog', link: true },
// order: 2x
{ path: 'docs/dashboard/', name: 'Local Sources', link: false },
{ path: 'docs/plugins/', name: 'Plugins', link: true },
// order: 3x
{ path: 'docs/providers/', name: 'Remote Providers', link: true },
{ path: 'docs/dashboard/', name: 'Local Sources', link: false },
// order: 4x
{ path: 'docs/tus/', name: 'Uploaders', link: false },
{ path: 'docs/providers/', name: 'Remote Providers', link: true },
// order: 5x
{ path: 'docs/status-bar/', name: 'UI Elements', link: false },
{ path: 'docs/tus/', name: 'Uploaders', link: false },
// order: 6x
{ path: 'docs/transloadit/', name: 'File Processing', link: false },
{ path: 'docs/status-bar/', name: 'UI Elements', link: false },
// order: 7x
{ path: 'docs/form/', name: 'Miscellaneous', link: false },
{ path: 'docs/transloadit/', name: 'File Processing', link: false },
// order: 8x
{ path: 'docs/form/', name: 'Miscellaneous', link: false },
// order: 9x
{ path: 'docs/react/', name: 'React Components', link: true },
]
%>
@ -30,6 +32,7 @@ var categories = [
</h2>
<ul class="menu-root">
<% site.pages.find({ type }).sort('order').each((p) => { %>
<% if (p.hidden) return; %>
<% var path = p.path.replace(/index\.html$/, ''); %>
<% var category = categories.find((c) => c.path === path) %>
<% // see https://github.com/vuejs/vuejs.org/blob/master/themes/vue/layout/partials/toc.ejs %>
@ -42,11 +45,11 @@ var categories = [
<% } %>
</li>
<li>
<a href="/<%- path %>" class="sidebar-link<%- page.title === p.title ? ' current' : '' %><%- p.is_new ? ' new' : '' %>"><%- p.title %></a>
<a href="/<%- path %>" class="sidebar-link<%- page.title === p.title ? ' current' : '' %><%- p.is_new ? ' new' : '' %>"><%- p.menu || p.title %></a>
</li>
<% } else { %>
<li>
<a href="/<%- path %>" class="sidebar-link<%- page.title === p.title ? ' current' : '' %><%- p.is_new ? ' new' : '' %>"><%- p.title %></a>
<a href="/<%- path %>" class="sidebar-link<%- page.title === p.title ? ' current' : '' %><%- p.is_new ? ' new' : '' %>"><%- p.menu || p.title %></a>
</li>
<% } %>
<% }) %>

View file

@ -331,6 +331,16 @@
font-size: 14px;
}
}
table {
width: 100%;
text-align: left;
td {
vertical-align: top;
padding: 8px;
border-bottom: 1px solid #eee;
}
}
}
.PostAuthor {
@ -373,8 +383,8 @@
font-size: .9em;
}
.TryButton,
a.TryButton {
.TryButton, a.TryButton,
.MoreButton, a.MoreButton {
@include reset-button;
display: inline-block;
font-size: 0.8em;