read all api calls from context (#691)

main
Michael Fix 6 years ago committed by GitHub
parent a40f016fb4
commit 223bccd6b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -23,7 +23,8 @@ module.exports = {
'react/jsx-uses-react': 'error', 'react/jsx-uses-react': 'error',
'react/jsx-uses-vars': 'error', 'react/jsx-uses-vars': 'error',
'jsx-a11y/click-events-have-key-events': 'off', 'jsx-a11y/click-events-have-key-events': 'off',
'react-hooks/rules-of-hooks': 'error' 'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error'
}, },
settings: { settings: {
react: { react: {

@ -0,0 +1,4 @@
import React from 'react'
import api from '../lib/api'
export default React.createContext(api)

@ -6,6 +6,7 @@ import dynamic from 'next/dynamic'
import Dropzone from 'dropperx' import Dropzone from 'dropperx'
// Ours // Ours
import ApiContext from './ApiContext'
import Dropdown from './Dropdown' import Dropdown from './Dropdown'
import Settings from './Settings' import Settings from './Settings'
import Toolbar from './Toolbar' import Toolbar from './Toolbar'
@ -38,6 +39,7 @@ const BackgroundSelect = dynamic(() => import('./BackgroundSelect'), {
}) })
class Editor extends React.Component { class Editor extends React.Component {
static contextType = ApiContext
constructor(props) { constructor(props) {
super(props) super(props)
this.state = { this.state = {
@ -65,8 +67,8 @@ class Editor extends React.Component {
const initialState = Object.keys(queryParams).length ? queryParams : {} const initialState = Object.keys(queryParams).length ? queryParams : {}
try { try {
// TODO fix this hack // TODO fix this hack
if (this.props.api.getGist && path.length >= 19 && path.indexOf('.') === -1) { if (this.context.gist && path.length >= 19 && path.indexOf('.') === -1) {
const { content, language } = await this.props.api.getGist(path) const { content, language } = await this.context.gist.get(path)
if (language) { if (language) {
initialState.language = language.toLowerCase() initialState.language = language.toLowerCase()
} }
@ -115,9 +117,9 @@ class Editor extends React.Component {
) { ) {
// if safari, get image from api // if safari, get image from api
const isPNG = format !== 'svg' const isPNG = format !== 'svg'
if (this.props.api.image && this.isSafari && isPNG) { if (this.context.image && this.isSafari && isPNG) {
const encodedState = serializeState(this.state) const encodedState = serializeState(this.state)
return this.props.api.image(encodedState) return this.context.image(encodedState)
} }
const node = this.carbonNode.current const node = this.carbonNode.current
@ -226,7 +228,7 @@ class Editor extends React.Component {
upload() { upload() {
this.getCarbonImage({ format: 'png' }).then( this.getCarbonImage({ format: 'png' }).then(
this.props.api.tweet.bind(null, this.state.code || DEFAULT_CODE) this.context.tweet.bind(null, this.state.code || DEFAULT_CODE)
) )
} }
@ -322,7 +324,7 @@ class Editor extends React.Component {
getCarbonImage={this.getCarbonImage} getCarbonImage={this.getCarbonImage}
/> />
<div className="buttons"> <div className="buttons">
{this.props.api.tweet && <TweetButton onClick={this.upload} />} <TweetButton onClick={this.upload} />
<ExportMenu <ExportMenu
onChange={this.updateSetting} onChange={this.updateSetting}
export={this.export} export={this.export}
@ -385,7 +387,6 @@ function isImage(file) {
} }
Editor.defaultProps = { Editor.defaultProps = {
api: {},
onUpdate: () => {}, onUpdate: () => {},
onReset: () => {} onReset: () => {}
} }

@ -1,11 +1,12 @@
import React from 'react' import React from 'react'
import ReactCrop, { makeAspectCrop } from 'react-image-crop' import ReactCrop, { makeAspectCrop } from 'react-image-crop'
import RandomImage, { downloadThumbnailImage } from './RandomImage' import RandomImage from './RandomImage'
import PhotoCredit from './PhotoCredit' import PhotoCredit from './PhotoCredit'
import Input from './Input' import Input from './Input'
import { Link } from './Meta' import { Link } from './Meta'
import { fileToDataURL } from '../lib/util' import { fileToDataURL } from '../lib/util'
import ApiContext from './ApiContext'
const getCroppedImg = (imageDataURL, pixelCrop) => { const getCroppedImg = (imageDataURL, pixelCrop) => {
const canvas = document.createElement('canvas') const canvas = document.createElement('canvas')
@ -43,6 +44,7 @@ const INITIAL_STATE = {
} }
export default class ImagePicker extends React.Component { export default class ImagePicker extends React.Component {
static contextType = ApiContext
constructor(props) { constructor(props) {
super(props) super(props)
this.state = INITIAL_STATE this.state = INITIAL_STATE
@ -103,7 +105,8 @@ export default class ImagePicker extends React.Component {
handleURLInput(e) { handleURLInput(e) {
e.preventDefault() e.preventDefault()
const url = e.target[0].value const url = e.target[0].value
return downloadThumbnailImage({ url }) return this.context
.downloadThumbnailImage({ url })
.then(({ dataURL }) => .then(({ dataURL }) =>
this.props.onChange({ this.props.onChange({
backgroundImage: dataURL, backgroundImage: dataURL,

@ -2,47 +2,29 @@ import React from 'react'
import Spinner from 'react-spinner' import Spinner from 'react-spinner'
import { useAsyncCallback } from '@dawnlabs/tacklebox' import { useAsyncCallback } from '@dawnlabs/tacklebox'
import api from '../lib/api' import ApiContext from './ApiContext'
import PhotoCredit from './PhotoCredit' import PhotoCredit from './PhotoCredit'
import { fileToDataURL } from '../lib/util'
export const downloadThumbnailImage = img => {
return api.client
.get(img.url.replace('http://', 'https://'), { responseType: 'blob' })
.then(res => res.data)
.then(fileToDataURL)
.then(dataURL => Object.assign(img, { dataURL }))
}
const getImageDownloadUrl = img =>
api.client.get(`/unsplash/download/${img.id}`).then(res => res.data.url)
async function getImages() {
const imageUrls = await api.client.get('/unsplash/random')
return Promise.all(imageUrls.data.map(downloadThumbnailImage))
}
function RandomImage(props) { function RandomImage(props) {
const { current: cache } = React.useRef([]) const { current: cache } = React.useRef([])
const [cacheIndex, updateIndex] = React.useState(0) const [cacheIndex, updateIndex] = React.useState(0)
const api = React.useContext(ApiContext)
const [selectImage, { loading: selecting }] = useAsyncCallback(() => { const [selectImage, { loading: selecting }] = useAsyncCallback(() => {
const image = cache[cacheIndex] const image = cache[cacheIndex]
return getImageDownloadUrl(image) return api.unsplash.download(image.id).then(blob => props.onChange(blob, image))
.then(url => api.client.get(url, { responseType: 'blob' }))
.then(res => res.data)
.then(blob => props.onChange(blob, image))
}) })
const [updateCache, { loading: updating, data: imgs }] = useAsyncCallback(getImages) const [updateCache, { loading: updating, error, data: imgs }] = useAsyncCallback(
api.unsplash.random
)
React.useEffect(() => { React.useEffect(() => {
if (cacheIndex === 0 || cacheIndex > cache.length - 2) { if (!error && !updating && (!imgs || cacheIndex > cache.length - 2)) {
updateCache() updateCache()
} }
}, [cacheIndex, cache.length, updateCache]) }, [error, updating, imgs, cacheIndex, cache.length, updateCache])
React.useEffect(() => { React.useEffect(() => {
if (imgs) { if (imgs) {

@ -1,14 +1,15 @@
import React from 'react' import React from 'react'
import { useAsyncCallback } from '@dawnlabs/tacklebox' import { useAsyncCallback } from '@dawnlabs/tacklebox'
import ApiContext from './ApiContext'
import Button from './Button' import Button from './Button'
function useWindowListener(key, fn) { function useWindowListener(key, fn) {
const callback = React.useRef(fn) const { current: callback } = React.useRef(fn)
React.useEffect(() => { React.useEffect(() => {
window.addEventListener(key, callback.current) window.addEventListener(key, callback)
return () => window.removeEventListener(key, callback.current) return () => window.removeEventListener(key, callback)
}, [key, callback]) }, [key, callback])
} }
@ -30,9 +31,14 @@ function useOnlineListener() {
} }
function TweetButton(props) { function TweetButton(props) {
const api = React.useContext(ApiContext)
const online = useOnlineListener() const online = useOnlineListener()
const [onClick, { loading }] = useAsyncCallback(props.onClick) const [onClick, { loading }] = useAsyncCallback(props.onClick)
if (!api || !api.tweet) {
return null
}
if (!online) { if (!online) {
return null return null
} }

@ -2,6 +2,8 @@ import axios from 'axios'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import ms from 'ms' import ms from 'ms'
import { fileToDataURL } from './util'
const client = axios.create({ const client = axios.create({
baseURL: `${ baseURL: `${
process.env.API_URL || process.env.NODE_ENV === 'production' ? '' : 'http://localhost:4000' process.env.API_URL || process.env.NODE_ENV === 'production' ? '' : 'http://localhost:4000'
@ -68,9 +70,34 @@ function checkIfRateLimited(err) {
throw err throw err
} }
const downloadThumbnailImage = img => {
return client
.get(img.url.replace('http://', 'https://'), { responseType: 'blob' })
.then(res => res.data)
.then(fileToDataURL)
.then(dataURL => Object.assign(img, { dataURL }))
}
const unsplash = {
download(id) {
return client
.get(`/unsplash/download/${id}`)
.then(res => res.data.url)
.then(url => client.get(url, { responseType: 'blob' }))
.then(res => res.data)
},
async random() {
const imageUrls = await client.get('/unsplash/random')
return Promise.all(imageUrls.data.map(downloadThumbnailImage))
}
}
export default { export default {
client, gist: {
getGist, get: getGist
},
tweet: debounce(tweet, ms('5s'), { leading: true, trailing: false }), tweet: debounce(tweet, ms('5s'), { leading: true, trailing: false }),
image: debounce(image, ms('5s'), { leading: true, trailing: false }) image: debounce(image, ms('5s'), { leading: true, trailing: false }),
unsplash,
downloadThumbnailImage
} }

@ -60,7 +60,7 @@
"eslint-plugin-import": "^2.16.0", "eslint-plugin-import": "^2.16.0",
"eslint-plugin-jsx-a11y": "^6.2.1", "eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-react": "^7.12.3", "eslint-plugin-react": "^7.12.3",
"eslint-plugin-react-hooks": "^1.1.0-rc.0", "eslint-plugin-react-hooks": "^1.4.0",
"husky": "^1.3.1", "husky": "^1.3.1",
"lint-staged": "^8.1.3", "lint-staged": "^8.1.3",
"now": "^14.0.0", "now": "^14.0.0",

@ -7,7 +7,6 @@ import debounce from 'lodash.debounce'
import Editor from '../components/Editor' import Editor from '../components/Editor'
import Page from '../components/Page' import Page from '../components/Page'
import { MetaLinks } from '../components/Meta' import { MetaLinks } from '../components/Meta'
import api from '../lib/api'
import { updateQueryString } from '../lib/routing' import { updateQueryString } from '../lib/routing'
import { saveSettings, clearSettings, omit } from '../lib/util' import { saveSettings, clearSettings, omit } from '../lib/util'
@ -30,12 +29,7 @@ class Index extends React.Component {
return ( return (
<Page enableHeroText={true}> <Page enableHeroText={true}>
<MetaLinks /> <MetaLinks />
<Editor <Editor router={this.props.router} onUpdate={this.onEditorUpdate} onReset={onReset} />
router={this.props.router}
onUpdate={this.onEditorUpdate}
api={api}
onReset={onReset}
/>
</Page> </Page>
) )
} }

@ -2722,10 +2722,10 @@ eslint-plugin-jsx-a11y@^6.2.1:
has "^1.0.3" has "^1.0.3"
jsx-ast-utils "^2.0.1" jsx-ast-utils "^2.0.1"
eslint-plugin-react-hooks@^1.1.0-rc.0: eslint-plugin-react-hooks@^1.4.0:
version "1.2.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.2.0.tgz#a1c78e792b8d7d3e9c2a2aad28df80b9b5cd1101" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.4.0.tgz#ad86001e05519368e55a888b1b31004b8e2ae8f6"
integrity sha512-pb/pwyHg0K3Ss/8loSwCGRSXIsvPBHWfzcP/6jeei0SgWBOyXRbcKFpGxolg0xSmph0jQKLyM27B74clbZM/YQ== integrity sha512-fMGlzztW/5hSQT0UBnlPwulao0uF8Kyp0Uv6PA81lzmcDz2LBtthkWQaE8Wz2F2kEe7mSRDgK8ABEFK1ipeDxw==
eslint-plugin-react@^7.12.3: eslint-plugin-react@^7.12.3:
version "7.12.4" version "7.12.4"

Loading…
Cancel
Save