From cf1e92b8cbabcb101a611de6fcfe4050077e862a Mon Sep 17 00:00:00 2001 From: Jake Dexheimer Date: Sat, 7 Apr 2018 19:19:01 -0500 Subject: [PATCH] Use Unsplash API (#301) * Add unsplash-js, isomorphic-fetch * /unsplash route/handling * RandomImage use unsplash api * Implement Photographer accredidation * Add referral link * Fetch photographer profile_url from unsplash api * Change credit copy * Add PhotoCredit component --- components/Editor.js | 11 +++++-- components/ImagePicker.js | 14 +++++++-- components/PhotoCredit.js | 27 ++++++++++++++++ components/RandomImage.js | 65 ++++++++++++++++++++------------------- handlers/unsplash.js | 40 ++++++++++++++++++++++++ package.json | 9 ++++-- server.js | 11 ++++--- yarn.lock | 34 +++++++++++++++++++- 8 files changed, 167 insertions(+), 44 deletions(-) create mode 100644 components/PhotoCredit.js create mode 100644 handlers/unsplash.js diff --git a/components/Editor.js b/components/Editor.js index 982affc..2d4fafe 100644 --- a/components/Editor.js +++ b/components/Editor.js @@ -175,8 +175,15 @@ class Editor extends React.Component { this.updateSetting('language', language.mime || language.mode) } - updateBackground(changes, cb) { - this.setState(changes, cb) + updateBackground({ photographer, ...changes }) { + if (photographer) { + this.setState(({ code = DEFAULT_CODE }) => ({ + ...changes, + code: code + `\n\n// Photo by ${photographer.name} on Unsplash` + })) + } else { + this.setState(changes) + } } render() { diff --git a/components/ImagePicker.js b/components/ImagePicker.js index 1102049..681b934 100644 --- a/components/ImagePicker.js +++ b/components/ImagePicker.js @@ -2,6 +2,7 @@ import React from 'react' import ReactCrop, { makeAspectCrop } from 'react-image-crop' import RandomImage from './RandomImage' +import PhotoCredit from './PhotoCredit' import { fileToDataURL } from '../lib/util' const getCroppedImg = (imageDataURL, pixelCrop) => { @@ -31,7 +32,7 @@ const getCroppedImg = (imageDataURL, pixelCrop) => { }) } -const INITIAL_STATE = { crop: null, imageAspectRatio: null, pixelCrop: null } +const INITIAL_STATE = { crop: null, imageAspectRatio: null, pixelCrop: null, photographer: null } export default class extends React.Component { constructor(props) { @@ -88,11 +89,17 @@ export default class extends React.Component { }) } - selectImage(e) { + selectImage(e, { photographer }) { const file = e.target ? e.target.files[0] : e return fileToDataURL(file).then(dataURL => - this.props.onChange({ backgroundImage: dataURL, backgroundImageSelection: null }) + this.setState({ photographer }, () => { + this.props.onChange({ + backgroundImage: dataURL, + backgroundImageSelection: null, + photographer + }) + }) ) } @@ -173,6 +180,7 @@ export default class extends React.Component { minWidth={10} keepSelection /> + {this.state.photographer && } + +) diff --git a/components/RandomImage.js b/components/RandomImage.js index 458e1ed..1d3624f 100644 --- a/components/RandomImage.js +++ b/components/RandomImage.js @@ -2,25 +2,33 @@ import React from 'react' import axios from 'axios' import Spinner from 'react-spinner' -import { range, fileToDataURL } from '../lib/util' - -const RAND_RANGE = 1000000 -const UPDATE_SIZE = 20 -const WALLPAPER_COLLECTION_ID = 136026 -const RANDOM_WALLPAPER_URL = `https://source.unsplash.com/collection/${WALLPAPER_COLLECTION_ID}/240x320` +import PhotoCredit from './PhotoCredit' +import { fileToDataURL } from '../lib/util' + +const downloadThumbnailImage = img => { + return axios + .get(img.url, { responseType: 'blob' }) + .then(res => res.data) + .then(fileToDataURL) + .then(dataURL => Object.assign(img, { dataURL })) +} -const largerImage = url => url.replace(/w=\d+/, 'w=1920').replace(/&h=\d+/, '') +const getImageDownloadUrl = img => + axios.get(`/unsplash/download/${img.id}`).then(res => res.data.url) -export default class RandomImage extends React.Component { +class RandomImage extends React.Component { constructor(props) { super(props) this.state = { cacheIndex: 0, loading: false } this.selectImage = this.selectImage.bind(this) this.updateCache = this.updateCache.bind(this) - this.getImage = this.getImage.bind(this) + this.getImages = this.getImages.bind(this) this.nextImage = this.nextImage.bind(this) } + cache = [] + imageUrls = {} + // fetch images in browser (we require window.FileReader) componentDidMount() { // clear cache when remounted @@ -28,38 +36,25 @@ export default class RandomImage extends React.Component { this.updateCache() } - async getImage() { - // circumvent browser caching - const sig = Math.floor(Math.random() * RAND_RANGE) - - const res = await axios.get(`${RANDOM_WALLPAPER_URL}?sig=${sig}`, { responseType: 'blob' }) - - // image already in cache? - if (this.imageUrls[res.request.responseURL]) return undefined - - this.imageUrls[res.request.responseURL] = true - return { - url: res.request.responseURL, - dataURL: await fileToDataURL(res.data) - } + async getImages() { + const imageUrls = await axios.get('/unsplash/random') + return Promise.all(imageUrls.data.map(downloadThumbnailImage)) } - cache = [] - imageUrls = {} - selectImage() { + const image = this.cache[this.state.cacheIndex] + this.setState({ loading: true }) - axios - .get(largerImage(this.cache[this.state.cacheIndex].url), { responseType: 'blob' }) + getImageDownloadUrl(image) + .then(url => axios.get(url, { responseType: 'blob' })) .then(res => res.data) - .then(this.props.onChange) + .then(blob => this.props.onChange(blob, image)) .then(() => this.setState({ loading: false })) } updateCache() { this.setState({ loading: true }) - Promise.all(range(UPDATE_SIZE).map(this.getImage)) - .then(imgs => imgs.filter(img => img)) // remove null + this.getImages() .then(imgs => (this.cache = this.cache.concat(imgs))) .then(() => this.setState({ loading: false })) } @@ -75,6 +70,8 @@ export default class RandomImage extends React.Component { } render() { + const photographer = + this.cache[this.state.cacheIndex] && this.cache[this.state.cacheIndex].photographer const bgImage = this.cache[this.state.cacheIndex] && this.cache[this.state.cacheIndex].dataURL return ( @@ -84,14 +81,16 @@ export default class RandomImage extends React.Component { Try Another
{this.state.loading && }
+ {photographer && }