import React from 'react' import ReactCrop, { makeAspectCrop } from 'react-image-crop' import { useLocalStorage } from 'actionsack' import RandomImage from './RandomImage' import PhotoCredit from './PhotoCredit' import Input from './Input' import Toggle from './Toggle' import { Link } from './Meta' import { fileToDataURL } from '../lib/util' import ApiContext from './ApiContext' const getCroppedImg = (imageDataURL, pixelCrop) => { const canvas = document.createElement('canvas') canvas.width = pixelCrop.width canvas.height = pixelCrop.height const ctx = canvas.getContext('2d') return new Promise(resolve => { const image = new Image() image.src = imageDataURL image.onload = () => { ctx.drawImage( image, pixelCrop.x, pixelCrop.y, pixelCrop.width, pixelCrop.height, 0, 0, pixelCrop.width, pixelCrop.height ) resolve(canvas.toDataURL('image/jpeg')) } }) } const INITIAL_STATE = { mode: 'file', crop: null, imageAspectRatio: null, pixelCrop: null, photographer: null, dataURL: null } export default class ImagePicker extends React.Component { static contextType = ApiContext constructor(props) { super(props) this.state = INITIAL_STATE this.selectMode = this.selectMode.bind(this) this.handleURLInput = this.handleURLInput.bind(this) this.uploadImage = this.uploadImage.bind(this) this.selectImage = this.selectImage.bind(this) this.removeImage = this.removeImage.bind(this) this.onImageLoaded = this.onImageLoaded.bind(this) this.onCropChange = this.onCropChange.bind(this) this.onDragEnd = this.onDragEnd.bind(this) } static getDerivedStateFromProps(nextProps, state) { if (state.crop) { // update crop for editor container aspect-ratio change return { crop: makeAspectCrop( { ...state.crop, aspect: nextProps.aspectRatio }, state.imageAspectRatio ) } } return null } selectMode(mode) { this.setState({ mode }) } async onDragEnd() { if (this.state.pixelCrop) { const croppedImg = await getCroppedImg(this.state.dataURL, this.state.pixelCrop) this.props.onChange({ backgroundImageSelection: croppedImg }) } } onCropChange(crop, pixelCrop) { this.setState({ crop: { ...crop, aspect: this.props.aspectRatio }, pixelCrop }) } onImageLoaded(image) { const imageAspectRatio = image.width / image.height const initialCrop = { x: 0, y: 0, width: 100, aspect: this.props.aspectRatio } this.setState({ imageAspectRatio, crop: makeAspectCrop(initialCrop, imageAspectRatio) }) } handleImageChange = (url, dataURL, photographer) => { this.setState({ dataURL, photographer }, () => { this.props.onChange({ backgroundImage: url, backgroundImageSelection: null, photographer }) }) } handleURLInput(e) { e.preventDefault() const url = e.target[0].value return this.context .downloadThumbnailImage({ url }) .then(res => res.dataURL) .then(dataURL => this.handleImageChange(url, dataURL)) .catch(err => { if (err.message.indexOf('Network Error') > -1) { this.setState({ error: 'Fetching the image failed. This is probably a CORS-related issue. You can either enable CORS in your browser, or use another image.' }) } }) } async uploadImage(e) { const dataURL = await fileToDataURL(e.target.files[0]) return this.handleImageChange(dataURL, dataURL) } async selectImage(image) { // TODO use React suspense for loading this asset const { dataURL } = await this.context.downloadThumbnailImage(image) this.handleImageChange(image.url, dataURL, image.photographer) if (image.palette && image.palette.length && this.generateColorPalette) { /* * Background is first, which is either the lightest or darkest color * and the rest are sorted by highest contrast w/ the background */ const palette = image.palette.map(c => c.hex) /* * Contributors, please feel free to adjust this algorithm to create the most * readible or aesthetically pleasing syntax highlighting. */ this.props.updateHighlights({ background: palette[0], text: palette[1], variable: palette[2], attribute: palette[3], definition: palette[4], keyword: palette[5], property: palette[6], string: palette[7], number: palette[8], operator: palette[9], meta: palette[10], tag: palette[11], comment: palette[12] }) } } removeImage() { this.setState(INITIAL_STATE, () => { this.props.onChange({ backgroundImage: null, backgroundImageSelection: null }) }) } render() { let content = (