Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make @uppy/unsplash production ready #3196

Merged
merged 25 commits into from Oct 1, 2021
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8839c8b
Resolve eslint warnings in `companion/src/server/provider/unsplash/in…
Murderlon Sep 13, 2021
b1359bd
Fix photos always being disabled
Murderlon Sep 13, 2021
cbdab4d
Call download attribution endpoint in companion
Murderlon Sep 13, 2021
ef21a2a
Use older syntax to fix CI
Murderlon Sep 13, 2021
e6b327d
Try again
Murderlon Sep 13, 2021
c54dd80
Clean props in `Browser`, `ProviderView`, and `SearchProviderView`
Murderlon Sep 14, 2021
4f21687
Inline `Itemlist` in `Browser`
Murderlon Sep 14, 2021
5067aea
Show author of image in UI
Murderlon Sep 14, 2021
afc0ed7
Remove `.bind` in core
Murderlon Sep 15, 2021
b5da629
Fix empty state and tests
Murderlon Sep 15, 2021
f177df6
Use app referral in author links
Murderlon Sep 15, 2021
262848b
Always show underline in artist link to indicate it is a link
Murderlon Sep 15, 2021
35c4b72
Always show underline in artist link to indicate it is a link
Murderlon Sep 15, 2021
3937634
Merge branch 'unsplash' of https://github.com/transloadit/uppy into u…
Murderlon Sep 15, 2021
02150f8
Add `docs/unsplash.md`
Murderlon Sep 15, 2021
6e8a89a
More subtle author links
nqst Sep 16, 2021
fa1f35b
Don't underline author links by default again if you don't mind @Murd…
nqst Sep 16, 2021
2853116
Create base provider class View
Murderlon Sep 16, 2021
dcaf200
Merge branch 'main' into unsplash
Murderlon Sep 20, 2021
6c2a4c3
Add author attribution in dashboard file overview
Murderlon Sep 20, 2021
908eb40
Remove redundant private `#isHandlingScroll`
Murderlon Sep 28, 2021
ecf75a8
"By {author}" --> "{author} * Unsplash"
Murderlon Sep 28, 2021
dd5200a
Apply suggestions from code review
Murderlon Sep 28, 2021
5ecbe88
Remove unused `by` key
Murderlon Sep 28, 2021
39324d4
Merge branch 'unsplash' of https://github.com/transloadit/uppy into u…
Murderlon Sep 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -49,3 +49,7 @@ exports.getNextPageQuery = (currentQuery) => {
delete query.q
return querystring.stringify(query)
}

exports.getAuthor = (item) => {
return { name: item.user.name, url: item.user.links.html }
}
108 changes: 57 additions & 51 deletions packages/@uppy/companion/src/server/provider/unsplash/index.js
Expand Up @@ -7,6 +7,33 @@ const { ProviderApiError } = require('../error')

const BASE_URL = 'https://api.unsplash.com'

function adaptData (body, currentQuery) {
const pagesCount = body.total_pages
const currentPage = Number(currentQuery.cursor || 1)
const hasNextPage = currentPage < pagesCount
const subList = adapter.getItemSubList(body) || []

return {
searchedFor: currentQuery.q,
username: null,
items: subList.map((item) => ({
isFolder: adapter.isFolder(item),
icon: adapter.getItemIcon(item),
name: adapter.getItemName(item),
mimeType: adapter.getMimeType(item),
id: adapter.getItemId(item),
thumbnail: adapter.getItemThumbnailUrl(item),
requestPath: adapter.getItemRequestPath(item),
modifiedDate: adapter.getItemModifiedDate(item),
author: adapter.getAuthor(item),
size: null,
})),
nextPageQuery: hasNextPage
? adapter.getNextPageQuery(currentQuery)
: null,
}
}

/**
* Adapter for API https://api.unsplash.com
*/
Expand All @@ -31,11 +58,11 @@ class Unsplash extends SearchProvider {

request(reqOpts, (err, resp, body) => {
if (err || resp.statusCode !== 200) {
err = this._error(err, resp)
logger.error(err, 'provider.unsplash.list.error')
return done(err)
const error = this.error(err, resp)
logger.error(error, 'provider.unsplash.list.error')
return done(error)
}
done(null, this.adaptData(body, query))
return done(null, adaptData(body, query))
})
}

Expand All @@ -48,28 +75,33 @@ class Unsplash extends SearchProvider {
Authorization: `Client-ID ${token}`,
},
}

request(reqOpts, (err, resp, body) => {
if (err || resp.statusCode !== 200) {
err = this._error(err, resp)
logger.error(err, 'provider.unsplash.download.error')
onData(err)
const error = this.error(err, resp)
logger.error(error, 'provider.unsplash.download.error')
onData(error)
return
}

const url = body.links.download
request.get(url)
.on('response', (resp) => {
if (resp.statusCode !== 200) {
onData(this._error(null, resp))

request
.get(url)
.on('response', (response) => {
if (response.statusCode !== 200) {
onData(this.error(null, response))
} else {
resp.on('data', (chunk) => onData(null, chunk))
response.on('data', (chunk) => onData(null, chunk))
}
})
.on('end', () => onData(null, null))
.on('error', (err) => {
logger.error(err, 'provider.unsplash.download.url.error')
onData(err)
// To attribute the author of the image, we call the `download_location`
// endpoint to increment the download count on Unsplash.
// https://help.unsplash.com/en/articles/2511258-guideline-triggering-a-download
.on('complete', () => request({ ...reqOpts, url: body.links.download_location }))
.on('error', (error) => {
logger.error(error, 'provider.unsplash.download.url.error')
onData(error)
})
})
}
Expand All @@ -86,53 +118,27 @@ class Unsplash extends SearchProvider {

request(reqOpts, (err, resp, body) => {
if (err || resp.statusCode !== 200) {
err = this._error(err, resp)
logger.error(err, 'provider.unsplash.size.error')
done(err)
const error = this.error(err, resp)
logger.error(error, 'provider.unsplash.size.error')
done(error)
return
}

getURLMeta(body.links.download)
.then(({ size }) => done(null, size))
.catch((err) => {
logger.error(err, 'provider.unsplash.size.error')
.catch((error) => {
logger.error(error, 'provider.unsplash.size.error')
done()
})
})
}

adaptData (body, currentQuery) {
const data = {
searchedFor: currentQuery.q,
username: null,
items: [],
}
const items = adapter.getItemSubList(body)
items.forEach((item) => {
data.items.push({
isFolder: adapter.isFolder(item),
icon: adapter.getItemIcon(item),
name: adapter.getItemName(item),
mimeType: adapter.getMimeType(item),
id: adapter.getItemId(item),
thumbnail: adapter.getItemThumbnailUrl(item),
requestPath: adapter.getItemRequestPath(item),
modifiedDate: adapter.getItemModifiedDate(item),
size: null,
})
})

const pagesCount = body.total_pages
const currentPage = parseInt(currentQuery.cursor || 1)
const hasNextPage = currentPage < pagesCount
data.nextPageQuery = hasNextPage ? adapter.getNextPageQuery(currentQuery) : null
return data
}

_error (err, resp) {
// eslint-disable-next-line class-methods-use-this
error (err, resp) {
if (resp) {
const fallbackMessage = `request to Unsplash returned ${resp.statusCode}`
const msg = resp.body && resp.body.errors ? `${resp.body.errors}` : fallbackMessage
const msg
= resp.body && resp.body.errors ? `${resp.body.errors}` : fallbackMessage
return new ProviderApiError(msg, resp.statusCode)
}

Expand Down
90 changes: 56 additions & 34 deletions packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.js
Expand Up @@ -3,49 +3,67 @@ const prettierBytes = require('@transloadit/prettier-bytes')
const truncateString = require('@uppy/utils/lib/truncateString')

const renderFileName = (props) => {
// Take up at most 2 lines on any screen
let maxNameLength
// For very small mobile screens
if (props.containerWidth <= 352) {
maxNameLength = 35
// For regular mobile screens
} else if (props.containerWidth <= 576) {
maxNameLength = 60
// For desktops
} else {
maxNameLength = 30
const { author, name } = props.file.meta

function getMaxNameLength () {
if (props.containerWidth <= 352) {
return 35
}
if (props.containerWidth <= 576) {
return 60
}
// When `author` is present, we want to make sure
// the file name fits on one line so we can place
// the author on the second line.
return author ? 20 : 30
}

return (
<div className="uppy-Dashboard-Item-name" title={props.file.meta.name}>
{truncateString(props.file.meta.name, maxNameLength)}
<div className="uppy-Dashboard-Item-name" title={name}>
{truncateString(name, getMaxNameLength())}
</div>
)
}

const renderFileSize = (props) => (
props.file.size
&& (
<div className="uppy-Dashboard-Item-statusSize">
{prettierBytes(props.file.size)}
const renderAuthor = (props) => {
const { author } = props.file.meta

if (!author) {
return null
}

return (
<div className="uppy-Dashboard-Item-author">
{props.i18n('by')}
Murderlon marked this conversation as resolved.
Show resolved Hide resolved
{' '}
<a
href={`${author.url}?utm_source=Companion&utm_medium=referral`}
Murderlon marked this conversation as resolved.
Show resolved Hide resolved
target="_blank"
rel="noreferrer"
Murderlon marked this conversation as resolved.
Show resolved Hide resolved
>
{truncateString(author.name, 20)}
</a>
</div>
)
)
}

const renderFileSize = (props) => props.file.size && (
<div className="uppy-Dashboard-Item-statusSize">
{prettierBytes(props.file.size)}
</div>
)

const ReSelectButton = (props) => (
props.file.isGhost
&& (
<span>
{' \u2022 '}
<button
className="uppy-u-reset uppy-c-btn uppy-Dashboard-Item-reSelect"
type="button"
onClick={props.toggleAddFilesPanel}
>
{props.i18n('reSelect')}
</button>
</span>
)
const ReSelectButton = (props) => props.file.isGhost && (
<span>
{' \u2022 '}
<button
className="uppy-u-reset uppy-c-btn uppy-Dashboard-Item-reSelect"
type="button"
onClick={props.toggleAddFilesPanel}
>
{props.i18n('reSelect')}
</button>
</span>
)

const ErrorButton = ({ file, onClick }) => {
Expand All @@ -68,10 +86,14 @@ const ErrorButton = ({ file, onClick }) => {

module.exports = function FileInfo (props) {
return (
<div className="uppy-Dashboard-Item-fileInfo" data-uppy-file-source={props.file.source}>
<div
className="uppy-Dashboard-Item-fileInfo"
data-uppy-file-source={props.file.source}
>
{renderFileName(props)}
<div className="uppy-Dashboard-Item-status">
{renderFileSize(props)}
{renderAuthor(props)}
{ReSelectButton(props)}
<ErrorButton
file={props.file}
Expand Down
Expand Up @@ -17,6 +17,19 @@
}
}

.uppy-Dashboard-Item-author {
color: $gray-600;
vertical-align: bottom;
font-size: 11px;
font-weight: normal;
display: inline-block;
line-height: 1;

a {
color: $gray-600;
}
}

.uppy-Dashboard-Item-status {
color: $gray-600;
font-weight: normal;
Expand Down
1 change: 1 addition & 0 deletions packages/@uppy/dashboard/src/index.js
Expand Up @@ -105,6 +105,7 @@ module.exports = class Dashboard extends UIPlugin {
sessionRestored: 'Session restored',
reSelect: 'Re-select',
poweredBy: 'Powered by %{uppy}',
by: 'By',
},
}

Expand Down
1 change: 1 addition & 0 deletions packages/@uppy/locales/src/en_US.js
Expand Up @@ -21,6 +21,7 @@ en_US.strings = {
browse: 'browse',
browseFiles: 'browse files',
browseFolders: 'browse folders',
by: 'By',
cancel: 'Cancel',
cancelUpload: 'Cancel upload',
chooseFiles: 'Choose files',
Expand Down
1 change: 1 addition & 0 deletions packages/@uppy/locales/src/fr_FR.js
Expand Up @@ -152,6 +152,7 @@ fr_FR.strings = {
'1': 'Vous devez sélectionner au moins %{smart_count} fichiers',
'2': 'Vous devez sélectionner au moins %{smart_count} fichiers',
},
by: 'Par',
}

fr_FR.pluralize = function pluralize (n) {
Expand Down
1 change: 1 addition & 0 deletions packages/@uppy/locales/src/nl_NL.js
Expand Up @@ -128,6 +128,7 @@ nl_NL.strings = {
selectFileNamed: 'Selecteer bestand %{name}',
unselectFileNamed: 'Deselecteer bestand %{name}',
openFolderNamed: 'Open map %{name}',
by: 'Door',
}

nl_NL.pluralize = function pluralize (n) {
Expand Down