Created
May 18, 2017 09:10
-
-
Save krhoyt/3b6d76af63194052b98e07d970c53d5f to your computer and use it in GitHub Desktop.
Animated File Upload Button
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var express = require( 'express' ); | |
var fs = require( 'fs' ); | |
var multer = require( 'multer' ); | |
var path = require( 'path' ); | |
var randomstring = require( 'randomstring' ); | |
// Router | |
var router = express.Router(); | |
// Upload storage options | |
// Unique name with extension | |
var storage = multer.diskStorage( { | |
destination: 'uploads', | |
filename: function( req, file, cb ) { | |
cb( null, randomstring.generate() + '.jpg' ); | |
} | |
} ); | |
// Upload handler | |
var upload = multer( { | |
storage: storage | |
} ); | |
// Get specific image | |
router.get( '/upload/:path', function( req, res ) { | |
res.sendFile( path.resolve( __dirname + '/../uploads/' + req.params.path ) ); | |
} ); | |
// Image upload | |
router.post( '/upload', upload.single( 'attachment' ), function( req, res ) { | |
// Get name from path | |
var parts = req.file.path.split( '/' ); | |
var file = parts[1].split( '.' )[0]; | |
// Respond with name | |
res.json( { | |
name: file | |
} ); | |
} ); | |
// Test | |
router.get( '/test', function( req, res ) { | |
res.json( {facial: 'Image management.'} ); | |
} ); | |
// Export | |
module.exports = router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>Progress</title> | |
<link href="favicon.ico" rel="icon"> | |
<!-- Google Fonts --> | |
<!-- Roboto --> | |
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700" rel="stylesheet"> | |
<!-- Styles --> | |
<link href="style/progress.css" rel="stylesheet" type="text/css"> | |
</head> | |
<body> | |
<!-- File upload --> | |
<input id="upload" type="file" accept=".jpg,.jpeg"> | |
<label for="upload"> | |
<!-- SVG content in label --> | |
<svg width="56" height="56"> | |
<!-- Background --> | |
<circle cx="28" cy="28" r="28" fill="red"/> | |
<!-- Icon --> | |
<!-- From Material Design --> | |
<path | |
class="icon" | |
d="M9 16h6v-6h4l-7-7-7 7h4zm-4 2h14v2H5z" | |
fill="white" | |
transform="translate( 15, 15 )"/> | |
<!-- Animated completion indicator --> | |
<!-- Pie slice --> | |
<path | |
class="pie" | |
d="M 28 28 L 28 0 A 28 28 1 0 1 28 0 z" | |
fill="red" | |
opacity="0"/> | |
<!-- Numeric completion indicator --> | |
<text | |
x="28" | |
y="28" | |
text-anchor="middle" | |
fill="white" | |
font-size="14" | |
font-weight="700" | |
dominant-baseline="central" | |
opacity="0">0%</text> | |
</svg> | |
</label> | |
<!-- Full size image --> | |
<img id="full" style="visibility: hidden; position: absolute;"> | |
<!-- Progress --> | |
<script src="script/progress.js"></script> | |
<!-- Application --> | |
<script> | |
let button = new ProgressButton( 'label' ); | |
button.root.addEventListener( ProgressButton.COMPLETE, evt => { | |
console.log( evt ); | |
let image = document.querySelector( '#full' ); | |
image.src = '/api/image/upload/' + evt.detail.name + '.jpg'; | |
} ); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ProgressButton { | |
constructor( path ) { | |
this.root = document.querySelector( path ); | |
this.root.addEventListener( 'click', evt => this.doClick( evt ) ); | |
this.svg = this.root.querySelector( 'svg' ); | |
// Hidden file form field | |
this.file = document.querySelector( '#' + this.root.getAttribute( 'for' ) ); | |
this.file.addEventListener( 'change', evt => this.doFile( evt ) ); | |
// Reference parts | |
this.circle = this.svg.querySelector( 'circle' ); | |
this.complete = this.svg.querySelector( 'text' ); | |
this.icon = this.svg.querySelector( '.icon' ); | |
this.pie = this.svg.querySelector( '.pie' ); | |
// Complete (0 - 100) | |
this.percent = 0; | |
} | |
// Animate to current completion | |
progress() { | |
// Completed | |
// Reset to ready | |
if( this.percent == 100 ) { | |
this.circle.setAttributeNS( null, 'opacity', 1 ); | |
this.pie.setAttributeNS( null, 'opacity', 0 ); | |
this.complete.setAttributeNS( null, 'opacity', 0 ); | |
this.icon.setAttributeNS( null, 'opacity', 1 ); | |
this.percent = 0; | |
// Allow selection of same file | |
// Set value to blank | |
// Same file will look like a change | |
this.file.value = ''; | |
} | |
// Completion arc calculator | |
// http://stackoverflow.com/questions/5736398/how-to-calculate-the-svg-path-for-an-arc-of-a-circle | |
let angle = ( this.percent / 100 ) * 360; | |
let radians = ( angle - 90 ) * Math.PI / 180; | |
let arc = angle <= 180 ? '0' : '1'; | |
let slice = { | |
x: 28 + ( 28 * Math.cos( radians ) ), | |
y: 28 + ( 28 * Math.sin( radians ) ) | |
}; | |
let d = [ | |
'M', 28, 28, | |
'L', 28, 0, | |
'A', 28, 28, 1, arc, 1, slice.x, slice.y, | |
'z' | |
].join( ' ' ); | |
// Update completion pie | |
// Update numeric indicator | |
this.pie.setAttributeNS( null, 'd', d ); | |
this.complete.innerHTML = Math.round( this.percent ) + '%'; | |
} | |
doClick( evt ) { | |
// Hold up on file selection | |
evt.preventDefault(); | |
// Not currently uploading | |
// Proceed | |
if( this.icon.getAttributeNS( null, 'opacity' ) != '0' ) { | |
this.file.click(); | |
} | |
} | |
// File selected | |
doFile( evt ) { | |
// No file selected | |
// Abort | |
if( evt.target.files.length == 0 ) { | |
return; | |
} | |
// Set state for upload reporting | |
this.icon.setAttributeNS( null, 'opacity', 0 ); | |
this.circle.setAttributeNS( null, 'opacity', 0.50 ); | |
this.pie.setAttributeNS( null, 'opacity', 1 ); | |
this.complete.innerHTML = '0%'; | |
this.complete.setAttributeNS( null, 'opacity', 1 ); | |
// Instantiate | |
// Hook events if needed | |
if( this.xhr == null ) { | |
this.xhr = new XMLHttpRequest(); | |
this.xhr.addEventListener( 'load', evt => this.doLoad( evt ) ); | |
this.xhr.upload.addEventListener( 'progress', evt => this.doProgress( evt ) ); | |
} | |
// File to upload | |
let data = new FormData(); | |
data.append( 'attachment', evt.target.files[0] ); | |
// Send to API | |
this.xhr.open( 'POST', '/api/image/upload', true ); | |
this.xhr.send( data ); | |
} | |
// Upload completed | |
// Response recieved | |
doLoad( evt ) { | |
let data = JSON.parse( this.xhr.responseText ); | |
console.log( data ); | |
let event = new CustomEvent( ProgressButton.COMPLETE, {detail: data} ); | |
this.root.dispatchEvent( event ); | |
} | |
// Upload in progress | |
// Reflect values in visualization | |
doProgress( evt ) { | |
this.percent = ( evt.loaded / evt.total ) * 100; | |
this.progress(); | |
} | |
} | |
ProgressButton.COMPLETE = 'progress_complete'; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment