Skip to content

Instantly share code, notes, and snippets.

@firegoby
Created September 3, 2015 15:01
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save firegoby/960980d6e24efcb83c7a to your computer and use it in GitHub Desktop.
Save firegoby/960980d6e24efcb83c7a to your computer and use it in GitHub Desktop.
Meteor React Jcrop Slingshot Semantic UI Component - User locally crops an image and uploads direct to S3 via Slingshot with upload progress
let stages = {
DISABLED: 'disabled',
ACTIVE: 'active',
SAVING: 'saving',
WARNING: 'warning',
ERROR: 'error',
SUCCESS: 'success'
}
let stage = new ReactiveVar(stages.DISABLED)
let uploader = new ReactiveVar(null)
let jcropAPI = null
let labels = {}
labels[stages.DISABLED] = '',
labels[stages.ACTIVE] = 'Uploading photo...',
labels[stages.SAVING] = 'Saving photo...',
labels[stages.WARNING] = "Your photo doesn't meet the image requirements, choose another",
labels[stages.ERROR] = 'An error occurred while uploading your photo',
labels[stages.SUCCESS] = 'Photo uploaded successfully!'
Cropper = React.createClass({
propTypes: {
slingshot: React.PropTypes.string, // slingshot Directive *required
width: React.PropTypes.string, // upload width
height: React.PropTypes.string, // upload height
imageType: React.PropTypes.string, // image MIME type, compatible with canvas.toBlob()
imageQuality: React.PropTypes.number, // image quality for JPG or WEBP, number 0.0 to 1.0
options: React.PropTypes.object, // Jcrop options
onError: React.PropTypes.func, // on upload error callback(error, downloadURL)
onSuccess: React.PropTypes.func, // on upload success callback(downloadURL)
setFilename: React.PropTypes.func //set upload filename callback(fileEvent)
},
getDefaultProps() {
return {
width: "300",
height: "300",
imageType: 'image/png',
imageQuality: 1.0,
options: {
minSize: [200, 200],
aspectRatio: 1,
bgFade: true,
bgOpacity: 0.5,
boxWidth: 500,
boxHeight: 0,
setSelect: [ 50, 50, 150, 150 ]
},
onError: function(error, upload) {
console.error('Error uploading', upload.xhr.response)
alert (error)
},
onSuccess: function(downloadURL) {
console.log('Success uploading: ', downloadURL)
},
setFilename: function(ev) {
return Meteor.userId() + '-' + Date.now()
}
}
},
mixins: [ReactMeteorData],
getMeteorData() {
return {
color: stage.get() === stages.ACTIVE || stage.get() === stages.SAVING ? 'blue' : '',
label: labels[stage.get()],
loading: stage.get() === stages.ACTIVE || stage.get() === stages.SAVING ? 'loading' : '',
progress: this.updateProgress(uploader.get())
}
},
handleFileChange(ev) {
uploader.set()
stage.set(stages.DISABLED)
let self = this
let file = React.findDOMNode(this.refs.File).files[0]
let img = React.findDOMNode(this.refs.Photo)
let reader = new FileReader()
$(React.findDOMNode(this.refs.Step2)).slideDown()
reader.onload = function(ev) {
img.src = ev.target.result
img.onload = function() {
if (jcropAPI) jcropAPI.setImage(img.src)
let options = self.props.options
options.onChange = self.updatePreview
options.onSelect = self.updatePreview
$(img).Jcrop(options, function(){
jcropAPI = this // store the jcrop API for reuse
})
}
}
reader.readAsDataURL(file)
},
handleFileChoose(ev) {
$(React.findDOMNode(this.refs.File)).click()
},
handleFileUpload(ev) {
uploader.set()
stage.set(stages.DISABLED)
let self = this
let upload = new Slingshot.Upload(this.props.slingshot)
let canvas = React.findDOMNode(this.refs.Preview)
let blob = null
canvas.toBlob(function(newBlob) {
let ext
switch(self.props.imageType) {
case 'image/jpeg': ext = 'jpg'; break
case 'image/webp': ext = 'webp'; break
default: ext = 'png'
}
blob = newBlob
blob.name = `${self.props.setFilename(ev)}.${ext}`
}, this.props.imageType, this.props.imageQuality)
stage.set(stages.ACTIVE)
upload.send(blob, function (error, downloadURL) {
if (error) {
stage.set(stages.ERROR)
self.props.onError(error, upload)
} else {
stage.set(stages.SUCCESS)
self.props.onSuccess(downloadURL)
}
})
uploader.set(upload)
},
updatePreview(ev) {
if (parseInt(ev.w) > 0) {
let img = React.findDOMNode(this.refs.Photo)
let canvas = React.findDOMNode(this.refs.Preview)
canvas.width = this.props.width
canvas.height = this.props.height
let context = canvas.getContext('2d')
context.drawImage(img, ev.x, ev.y, ev.w, ev.h, 0, 0, canvas.width, canvas.height)
}
},
updateProgress(upload) {
if (upload) {
let percent = Math.round(upload.progress() * 100) || 0
if (percent === 100 && stage.get() !== stages.SUCCESS) stage.set(stages.SAVING)
return percent
}
return 0
},
componentDidMount() {
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
let binStr = atob( this.toDataURL(type, quality).split(',')[1] )
let len = binStr.length
let arr = new Uint8Array(len)
for (var i=0; i<len; i++ ) {
arr[i] = binStr.charCodeAt(i)
}
callback( new Blob( [arr], {type: type || 'image/png'} ) )
}
})
}
},
render() {
let previewStyle = {
width: this.props.width / 2,
height: this.props.height / 2,
overflow: 'hidden'
}
return (
<form encType="multipart/form-data" method="post" action="" onSubmit={ev => ev.preventDefault()}>
<h1 className="header">Edit your Profile Picture</h1>
<input type="file" ref="File" onChange={this.handleFileChange} style={{display: 'none'}}/>
<button className="ui primary button" onClick={this.handleFileChoose}>Choose a photo</button>
<hr className="ui hidden divider" />
<div ref="Step2" style={{display: 'none'}}>
<h3 className="header">Please select an area for your avatar</h3>
<hr className="ui hidden divider" />
<img ref="Photo" />
<hr className="ui hidden divider" />
<canvas ref="Preview" style={previewStyle}></canvas>
<hr className="ui hidden divider" />
<div className={`ui progress ${stage.get()} ${this.data.color}`} data-percent={this.data.progress}>
<div className="bar" style={{transitionDuration: '300ms', width: `${this.data.progress}%`}}>
<div className="progress">{this.data.progress}%</div>
</div>
<div className="label">{this.data.label}</div>
</div>
<button className="ui primary button" ref="Upload" onClick={this.handleFileUpload}>Upload your Photo</button>
</div>
</form>
)
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment