nb/hide-http
Alex Kern 11 years ago
parent d8febe77be
commit 8ad9855e47

File diff suppressed because one or more lines are too long

@ -1,4 +1,4 @@
const chunkSize = 32;
const rankSize = 16;
function blobLength(b) {
if (typeof b.byteLength !== 'undefined') return b.byteLength;
@ -9,27 +9,27 @@ function blobLength(b) {
export default class ChunkedBlob {
constructor() {
this.count = 0;
this.size = 0;
this.chunks = [];
this.lastChunk = [];
this.ranks = [[]];
}
add(b) {
this.count++;
this.size += blobLength(b);
this.lastChunk.push(b);
this.ranks[0].push(b);
if (this.lastChunk.length === chunkSize) {
let chunk = new Blob(this.lastChunk);
this.chunks.push(chunk);
this.lastChunk = [];
for (let i = 0; i < this.ranks.length; i++) {
let rank = this.ranks[i]
if (rank.length === rankSize) {
this.ranks[i + 1] = this.ranks[i + 1] || []
this.ranks[i + 1].push(new Blob(rank))
this.ranks[i] = []
}
}
}
toBlob() {
let allChunks = this.chunks.concat(this.lastChunk);
return new Blob(allChunks);
let allRanks = [].concat(...this.ranks)
return new Blob(allRanks)
}
}

@ -6,15 +6,15 @@ export default class DownloadFile {
this.name = name
this.size = size
this.type = type
this.packets = new ChunkedBlob()
this.chunks = new ChunkedBlob()
}
addPacket(b) {
this.packets.add(b)
addChunk(b) {
this.chunks.add(b)
}
clearPackets() {
this.packets = new ChunkedBlob()
clearChunks() {
this.chunks = new ChunkedBlob()
}
isComplete() {
@ -22,11 +22,11 @@ export default class DownloadFile {
}
getProgress() {
return this.packets.size / this.size
return this.chunks.size / this.size
}
download() {
let blob = this.packets.toBlob()
let blob = this.chunks.toBlob()
let url = URL.createObjectURL(blob)
let a = document.createElement('a')
a.download = this.name

@ -1,4 +1,4 @@
const packetSize = 16 * 1024
const chunkSize = 256 * 1024
export default class UploadFile {
@ -9,16 +9,16 @@ export default class UploadFile {
this.blob = file
}
countPackets() {
return Math.ceil(this.size / packetSize)
countChunks() {
return Math.ceil(this.size / chunkSize)
}
getPacket(i) {
if (i < 0 || i >= this.countPackets())
throw new Error('Packet out of bounds')
getChunk(i) {
if (i < 0 || i >= this.countChunks())
throw new Error('Chunk out of bounds')
let start = i * packetSize
let end = Math.min(start + packetSize, this.size)
let start = i * chunkSize
let end = Math.min(start + chunkSize, this.size)
return this.blob.slice(start, end)
}

@ -1,6 +1,6 @@
import FrozenHead from 'react-frozenhead';
import React from 'react';
import { RouteHandler } from 'react-router';
import FrozenHead from 'react-frozenhead'
import React from 'react'
import { RouteHandler } from 'react-router'
export default class App extends React.Component {
@ -9,15 +9,17 @@ export default class App extends React.Component {
<FrozenHead>
<meta charSet="utf-8" />
<title>WebDrop - Send Files, Easily</title>
<link rel="stylesheet" href="http://fonts.googleapis.com/css?family=Quicksand:300,400,700" />
<link rel="stylesheet" href="/index.css" />
<script src="/app.js" />
</FrozenHead>
<body>
<RouteHandler />
<div className="container">
<RouteHandler />
<script src="/app.js" />
</div>
</body>
</html>;
</html>
}
}

@ -1,51 +0,0 @@
// TODO: Rename this.
import React from 'react';
import classnames from 'classnames';
// Taken from StackOverflow
// http://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript
function formatSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1000;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
let i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];
}
export default class Arrow extends React.Component {
render() {
let classes = classnames('arrow', {
'arrow-up': this.props.dir === 'up',
'arrow-down': this.props.dir === 'down',
'arrow-animated': this.props.animated
});
return <div className={classes}>
<div className="arrow-border" />
<div className="arrow-image">{this.props.dir === 'up' ? '^' : 'v'}</div>
{this.props.name === null ? null
: <div className="arrow-name">{this.props.name}</div>}
{this.props.size === null ? null
: <div className="arrow-size">{formatSize(this.props.size)}</div>}
</div>;
}
}
Arrow.propTypes = {
dir: React.PropTypes.oneOf(['up', 'down']).isRequired,
name: React.PropTypes.string,
size: React.PropTypes.number,
animated: React.PropTypes.bool
};
Arrow.defaultProps = {
name: null,
size: null,
animated: false
};

@ -0,0 +1,21 @@
import React from 'react'
export default class DownloadButton extends React.Component {
onClick(e) {
this.props.onClick(e)
}
render() {
return <button
className="download-button"
onClick={this.onClick.bind(this)}>
Download
</button>
}
}
DownloadButton.propTypes = {
onClick: React.PropTypes.func.isRequired
}

@ -1,63 +1,85 @@
// TODO: Flesh out this page further.
import Arrow from './Arrow';
import DownloadActions from '../actions/DownloadActions';
import DownloadStore from '../stores/DownloadStore';
import React from 'react';
import peer from '../peer';
function formatProgress(dec) {
return (dec * 100).toPrecision(3) + "%";
}
import DownloadActions from '../actions/DownloadActions'
import DownloadButton from './DownloadButton'
import ProgressBar from './ProgressBar'
import DownloadStore from '../stores/DownloadStore'
import React from 'react'
import Spinner from './Spinner'
import peer from '../peer'
export default class DownloadPage extends React.Component {
constructor() {
this.state = DownloadStore.getState();
this.state = DownloadStore.getState()
this._onChange = () => {
this.setState(DownloadStore.getState());
};
this.setState(DownloadStore.getState())
}
this._onConnection = (conn) => {
DownloadActions.beginDownload(conn);
};
DownloadActions.beginDownload(conn)
}
}
componentDidMount() {
DownloadStore.listen(this._onChange);
peer.on('connection', this._onConnection);
DownloadStore.listen(this._onChange)
peer.on('connection', this._onConnection)
}
componentDidUnmount() {
DownloadStore.unlisten(this._onChange);
peer.removeListener('connection', this._onConnection);
DownloadStore.unlisten(this._onChange)
peer.removeListener('connection', this._onConnection)
}
downloadFile() {
DownloadActions.requestDownload();
DownloadActions.requestDownload()
}
render() {
switch (this.state.status) {
case 'ready':
return <div className="download-page">
<Arrow dir="down" name={this.state.name} size={this.state.size} />
<button onClick={this.downloadFile.bind(this)}>Download</button>
<span>Progress: {formatProgress(this.state.progress)}</span>
</div>;
return <div className="page">
<Spinner dir="down"
name={this.state.file.name}
size={this.state.file.size} />
<DownloadButton onClick={this.downloadFile.bind(this)} />
</div>
case 'requesting':
case 'downloading':
return <div className="download-page">
<Arrow dir="down" name={this.state.name} size={this.state.size} animated />
<span>Progress: {formatProgress(this.state.progress)}</span>
</div>;
return <div className="page">
<Spinner dir="down" animated
name={this.state.file.name}
size={this.state.file.size} />
<ProgressBar value={this.state.progress} />
</div>
case 'cancelled':
return <div className="page">
<Spinner dir="down"
name={this.state.file.name}
size={this.state.file.size} />
<ProgressBar value={-1} />
</div>
case 'done':
return <div className="download-page">
<Arrow dir="down" name={this.state.name} size={this.state.size} />
<span>Progress: {formatProgress(this.state.progress)}</span>
</div>;
return <div className="page">
<Spinner dir="down"
name={this.state.file.name}
size={this.state.file.size} />
<ProgressBar value={1} />
</div>
}
}

@ -25,7 +25,7 @@ export default class DropZone extends React.Component {
this.setState({ focus: false });
let file = e.dataTransfer.files[0];
if (this.props.onDrop) this.props.onDrop(file);
if (this.props.onDrop && file) this.props.onDrop(file);
}
render() {

@ -0,0 +1,34 @@
import React from 'react'
import classnames from 'classnames'
function formatProgress(dec) {
return (dec * 100).toPrecision(3) + "%"
}
export default class ProgressBar extends React.Component {
render() {
const failed = this.props.value < 0;
const classes = classnames('progress-bar', {
'progress-bar-failed': failed
})
const formatted = formatProgress(this.props.value)
return <div className={classes}>
{failed
? <div className="progress-bar-text">Failed</div>
: <div
className="progress-bar-inner"
style={{width: formatted}}>
<div className="progress-bar-text">
{formatted}
</div>
</div>}
</div>
}
}
ProgressBar.propTypes = {
value: React.PropTypes.number.isRequired
}

@ -0,0 +1,55 @@
import React from 'react'
import classnames from 'classnames'
// Taken from StackOverflow
// http://stackoverflow.com/questions/15900485/correct-way-to-convert-size-in-bytes-to-kb-mb-gb-in-javascript
function formatSize(bytes) {
if (bytes === 0) return '0 Bytes'
const k = 1000
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]
}
export default class Spinner extends React.Component {
render() {
const classes = classnames('spinner', {
'spinner-animated': this.props.animated
})
return <div className={classes}>
<div className="spinner-border" />
<div className="spinner-content">
<img
alt={this.props.name || this.props.dir}
src={`/images/${this.props.dir}.png`}
className="spinner-image" />
{this.props.name === null ? null
: <div className="spinner-name">{this.props.name}</div>}
{this.props.size === null ? null
: <div className="spinner-size">{formatSize(this.props.size)}</div>}
</div>
</div>
}
}
Spinner.propTypes = {
dir: React.PropTypes.oneOf(['up', 'down']).isRequired,
name: React.PropTypes.string,
size: React.PropTypes.number,
animated: React.PropTypes.bool
}
Spinner.defaultProps = {
name: null,
size: null,
animated: false
}

@ -1,20 +1,20 @@
import React from 'react';
import React from 'react'
export default class Tempalink extends React.Component {
onClick() {
this.refs.input.getDOMNode().setSelectionRange(0, 9999);
this.refs.input.getDOMNode().setSelectionRange(0, 9999)
}
render() {
var url = window.location.origin + '/d/' + this.props.token;
var url = window.location.origin + '/d/' + this.props.token
return <input
className="tempalink"
onClick={this.onClick.bind(this)}
readOnly
ref="input"
type="text"
value={url} />;
value={url} />
}
}

@ -1,61 +1,67 @@
import Arrow from './Arrow';
import DropZone from './DropZone';
import React from 'react';
import Tempalink from './Tempalink';
import UploadActions from '../actions/UploadActions';
import UploadStore from '../stores/UploadStore';
import socket from '../socket';
import Spinner from './Spinner'
import DropZone from './DropZone'
import React from 'react'
import Tempalink from './Tempalink'
import UploadActions from '../actions/UploadActions'
import UploadStore from '../stores/UploadStore'
import socket from '../socket'
export default class UploadPage extends React.Component {
constructor() {
this.state = UploadStore.getState();
this.state = UploadStore.getState()
this._onChange = () => {
this.setState(UploadStore.getState());
};
this.setState(UploadStore.getState())
}
this._onDownload = (peerID) => {
UploadActions.sendToDownloader(peerID);
};
UploadActions.sendToDownloader(peerID)
}
}
componentDidMount() {
UploadStore.listen(this._onChange);
socket.on('download', this._onDownload);
UploadStore.listen(this._onChange)
socket.on('download', this._onDownload)
}
componentDidUnmount() {
UploadStore.unlisten(this._onChange);
socket.removeListener('download', this._onDownload);
UploadStore.unlisten(this._onChange)
socket.removeListener('download', this._onDownload)
}
uploadFile(file) {
UploadActions.uploadFile(file);
UploadActions.uploadFile(file)
}
render() {
switch (this.state.status) {
case 'ready':
return <div className="upload-page">
return <div className="page">
<DropZone onDrop={this.uploadFile.bind(this)} />
<Arrow dir="up" />
<Spinner dir="up" />
<h1>WebDrop</h1>
<p>The easiest way to send someone a file.</p>
<p>Drag the file into this window to get started.</p>
</div>;
</div>
case 'processing':
return <div className="upload-page">
<Arrow dir="up" animated />
return <div className="page">
<Spinner dir="up" animated />
<h1>WebDrop</h1>
<p>Processing...</p>
</div>;
</div>
case 'uploading':
return <div className="upload-page">
return <div className="page">
<Arrow dir="up" animated {...this.state.file} />
<Spinner dir="up" animated {...this.state.file} />
<Tempalink token={this.state.token} />
<p>Send someone this link to download.</p>
@ -66,7 +72,7 @@ export default class UploadPage extends React.Component {
<div className="datum"><strong>Completed:</strong> {this.state.completed}</div>
</div>
</div>;
</div>
}
}

@ -1,11 +1,11 @@
import React from 'react';
import ReactRouter from 'react-router';
import routes from './routes';
import alt from './alt';
import React from 'react'
import ReactRouter from 'react-router'
import routes from './routes'
import alt from './alt'
let bootstrap = document.documentElement.getAttribute('data-bootstrap');
alt.bootstrap(bootstrap);
let bootstrap = document.documentElement.getAttribute('data-bootstrap')
alt.bootstrap(bootstrap)
ReactRouter.run(routes, ReactRouter.HistoryLocation, function (Handler) {
React.render(<Handler data={bootstrap} />, document);
});
React.render(<Handler data={bootstrap} />, document)
})

@ -1,16 +1,16 @@
import uuid from 'node-uuid';
import uuid from 'node-uuid'
let id = uuid.v4();
const id = uuid.v4()
if (typeof window === 'undefined') {
var peer = { id: id };
var peer = { id: id }
} else {
let Peer = require('peerjs');
let Peer = require('peerjs')
var peer = new Peer(id, {
host: window.location.hostname,
port: window.location.port,
path: '/peer'
});
})
}
export default peer;
export default peer

@ -1,68 +1,63 @@
import DownloadActions from '../actions/DownloadActions';
import DownloadFile from '../DownloadFile';
import peer from '../peer';
import alt from '../alt';
import socket from '../socket';
import DownloadActions from '../actions/DownloadActions'
import DownloadFile from '../DownloadFile'
import peer from '../peer'
import alt from '../alt'
import socket from '../socket'
export default alt.createStore(class DownloadStore {
constructor() {
this.bindActions(DownloadActions);
this.bindActions(DownloadActions)
this.status = 'offline';
this.token = null;
this.file = null;
this.progress = 0;
this.status = 'ready'
this.token = null
this.file = null
this.progress = 0
this.on('bootstrap', () => {
if (this.file && !(this.file instanceof DownloadFile)) {
this.file = new DownloadFile(this.file.name,
this.file.size,
this.file.type);
this.file.type)
}
})
}
onRequestDownload() {
if (this.status !== 'ready') return;
this.status = 'requesting';
if (this.status !== 'ready') return
this.status = 'requesting'
socket.emit('download', {
peerID: peer.id,
token: this.token
});
})
}
onBeginDownload(conn) {
if (this.status !== 'requesting') return;
this.status = 'downloading';
let chunkSize = conn.metadata.chunkSize;
let i = 0;
if (this.status !== 'requesting') return
this.status = 'downloading'
conn.on('data', (data) => {
if (this.status !== 'downloading') return;
conn.on('data', (chunk) => {
if (this.status !== 'downloading') return
console.log(data.byteLength);
this.file.addPacket(data);
i++;
this.file.addChunk(chunk)
if (this.file.isComplete()) {
this.setState({ status: 'done', progress: 1 });
this.file.download();
conn.close();
this.setState({ status: 'done', progress: 1 })
this.file.download()
conn.close()
} else {
this.setState({ progress: this.file.getProgress() });
if (i % chunkSize === 0) conn.send('more');
this.setState({ progress: this.file.getProgress() })
conn.send('more')
}
});
})
conn.on('close', () => {
if (this.status !== 'downloading') return;
this.setState({ status: 'cancelled', progress: 0 });
this.file.clearPackets();
});
if (this.status !== 'downloading') return
this.setState({ status: 'cancelled', progress: 0 })
this.file.clearChunks()
})
}
}, 'DownloadStore')

@ -1,28 +1,26 @@
import UploadActions from '../actions/UploadActions';
import UploadFile from '../UploadFile';
import alt from '../alt';
import peer from '../peer';
import socket from '../socket';
const chunkSize = 32;
import UploadActions from '../actions/UploadActions'
import UploadFile from '../UploadFile'
import alt from '../alt'
import peer from '../peer'
import socket from '../socket'
export default alt.createStore(class UploadStore {
constructor() {
this.bindActions(UploadActions);
this.bindActions(UploadActions)
this.status = 'ready';
this.token = null;
this.file = null;
this.status = 'ready'
this.token = null
this.file = null
this.inProgress = 0;
this.completed = 0;
this.inProgress = 0
this.completed = 0
}
onUploadFile(file) {
if (this.status !== 'ready') return;
this.status = 'processing';
this.file = new UploadFile(file);
if (this.status !== 'ready') return
this.status = 'processing'
this.file = new UploadFile(file)
socket.emit('upload', {
name: this.file.name,
@ -32,49 +30,42 @@ export default alt.createStore(class UploadStore {
this.setState({
status: 'uploading',
token: token
});
});
})
})
}
onSendToDownloader(peerID) {
if (this.status !== 'uploading') return;
if (this.status !== 'uploading') return
let conn = peer.connect(peerID, {
reliable: true,
metadata: { chunkSize: chunkSize }
});
reliable: true
})
let complete = false;
let totalPackets = this.file.countPackets();
let i = 0;
let totalChunks = this.file.countChunks()
let i = 0
let sendNextChunk = () => {
if (complete) return;
for (let j = 0; i < totalPackets && j < chunkSize; i++, j++) {
let packet = this.file.getPacket(i);
console.log(packet.size);
conn.send(packet);
}
if (i === totalPackets) complete = true;
if (i === totalChunks) return
let packet = this.file.getChunk(i)
conn.send(packet)
i++
}
conn.on('open', () => {
this.setState({ inProgress: this.inProgress + 1 });
sendNextChunk();
});
this.setState({ inProgress: this.inProgress + 1 })
sendNextChunk()
})
conn.on('data', (data) => {
if (data === 'more') sendNextChunk();
});
if (data === 'more') sendNextChunk()
})
conn.on('close', () => {
this.setState({
inProgress: this.inProgress - 1,
completed: this.completed + (complete ? 1 : 0)
});
});
completed: this.completed + (i === totalChunks ? 1 : 0)
})
})
}
}, 'UploadStore')

@ -0,0 +1,2 @@
body
color red

@ -19,20 +19,23 @@
},
"homepage": "https://github.com/kern/webdrop",
"dependencies": {
"alt": "^0.14.4",
"babel": "^4.7.16",
"bases": "^0.2.1",
"classnames": "^1.2.0",
"ejs": "^2.3.1",
"express": "^4.12.0",
"morgan": "^1.5.2",
"nib": "^1.1.0",
"node-uuid": "^1.4.3",
"peer": "^0.2.8",
"peerjs": "^0.3.14",
"react": "^0.13.0",
"react-frozenhead": "^0.3.0",
"react-router": "^0.13.1",
"socket.io": "^1.3.5",
"node-uuid": "^1.4.3",
"peerjs": "^0.3.14",
"socket.io-client": "^1.3.5",
"alt": "^0.14.4"
"stylus": "^0.50.0"
},
"devDependencies": {
"babelify": "^5.0.4",

@ -5,6 +5,7 @@ var path = require('path');
var peer = require('peer');
var routes = require('./routes');
var socketIO = require('socket.io');
var morgan = require('morgan');
var app = express();
var server = http.Server(app);
@ -19,8 +20,9 @@ server.listen(process.env.PORT || 3000, function () {
app.set('view engine', 'ejs');
app.set('views', path.resolve(__dirname, '../views'));
app.use(routes);
app.use('/peer', peer.ExpressPeerServer(server));
app.use(morgan('combined'));
app.use(routes);
io.on('connection', function (socket) {

@ -1,18 +1,26 @@
var DownloadFile = require('../client/DownloadFile');
var React = require('react');
var ReactRouter = require('react-router');
var Upload = require('./Upload');
var alt = require('../client/alt');
var clientRoutes = require('../client/routes');
var express = require('express');
var DownloadFile = require('../client/DownloadFile')
var React = require('react')
var ReactRouter = require('react-router')
var Upload = require('./Upload')
var alt = require('../client/alt')
var clientRoutes = require('../client/routes')
var express = require('express')
var nib = require('nib')
var path = require('path')
var stylus = require('stylus')
var routes = module.exports = new express.Router();
var routes = module.exports = new express.Router()
routes.use(express.static(__dirname + '/../static'));
routes.get('/css', function (req, res) {
// req.path = '/index.css'
res.send('foo')
})
routes.use(express.static(__dirname + '/../static'))
routes.get('/d/:token', function (req, res, next) {
var uploader = Upload.find(req.params.token);
var uploader = Upload.find(req.params.token)
if (uploader) {
res.locals.data = {
DownloadStore: {
@ -20,39 +28,39 @@ routes.get('/d/:token', function (req, res, next) {
token: uploader.token,
file: uploader.metadata
}
};
}
}
next();
next()
});
})
routes.use(function (req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
var err = new Error('Not Found')
err.status = 404
next(err)
})
routes.use(function (err, req, res, next) {
// TODO: Get these error pages working with isomorphic react.
var status = err.status || 500;
var message = err.message || '';
var status = err.status || 500
var message = err.message || ''
res.status(status);
next();
res.status(status)
next()
});
})
routes.use(function (req, res) {
alt.bootstrap(JSON.stringify(res.locals.data || {}));
alt.bootstrap(JSON.stringify(res.locals.data || {}))
ReactRouter.run(clientRoutes, req.url, function (Handler) {
var html = React.renderToString(<Handler data={alt.takeSnapshot()} />);
alt.flush();
res.write('<!DOCTYPE html>');
res.end(html);
});
var html = React.renderToString(<Handler data={alt.takeSnapshot()} />)
alt.flush()
res.write('<!DOCTYPE html>')
res.end(html)
})
});
})

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -1,142 +1,214 @@
* { box-sizing: border-box; }
.upload-page {
margin: 100px auto 0;
width: 300px;
html {
height: 100%;
}
body {
margin: 0;
height: 100%;
}
.container {
display: table;
overflow: visible;
}
.page {
width: 300px;
}
.drop-zone {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: table;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: table;
background: rgba(0, 0, 0, 0);
}
.drop-zone-focus {
background: rgba(0, 0, 0, 0.5);
z-index: 1;
background: rgba(0, 0, 0, 0.5);
}
.drop-zone-focus:after {
color: white;
content: 'DROP TO UPLOAD';
display: table-cell;
font: 24px/40px sans-serif;
text-align: center;
text-shadow: 0 1px #333;
vertical-align: middle;
}
.arrow {
position: relative;
z-index: -1;
width: 300px;
height: 300px;
}
.arrow .arrow-border {
position: absolute;
width: 100%;
height: 100%;
border: 20px dotted #CCC;
border-radius: 100%;
-webkit-transition: -webkit-transform 1s;
-moz-transition: -moz-transform 1s;
transition: transform 1s;
}
.arrow.arrow-animated .arrow-border {
-webkit-animation: rotate 5s infinite linear;
-moz-animation: rotate 5s infinite linear;
animation: rotate 5s infinite linear;
}
.arrow .arrow-image {
position: absolute;
top: 60px;
left: 80px;
right: 80px;
bottom: 100px;
background: red;
}
.arrow .arrow-name {
bottom: 60px;
font: bold 18px/20px sans-serif;
left: 60px;
overflow: hidden;
position: absolute;
right: 60px;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
color: #333;
}
.arrow .arrow-size {
bottom: 40px;
font: italic 12px/20px sans-serif;
left: 60px;
position: absolute;
right: 60px;
text-align: center;
color: #777;
color: white;
content: 'DROP TO UPLOAD';
display: table-cell;
font: 24px/40px "Quicksand", sans-serif;
text-align: center;
text-shadow: 0 1px #333;
vertical-align: middle;
}
.spinner {
position: relative;
z-index: -1;
width: 300px;
height: 300px;
margin-bottom: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.spinner .spinner-border {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 20px dotted #40C0CB;
border-radius: 100%;
-webkit-transition: -webkit-transform 1s;
-moz-transition: -moz-transform 1s;
transition: transform 1s;
}
.spinner.spinner-animated .spinner-border {
-webkit-animation: rotate 5s infinite linear;
-moz-animation: rotate 5s infinite linear;
animation: rotate 5s infinite linear;
}
.spinner .spinner-image {
display: block;
width: 150px;
}
.spinner .spinner-name {
font: bold 18px/20px "Quicksand", sans-serif;
text-align: center;
color: #333;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 10px;
}
.spinner .spinner-size {
font: italic 12px/20px "Quicksand", sans-serif;
text-align: center;
color: #777;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tempalink {
border-radius: 3px;
border: 1px solid #EEE;
color: #333;
font: 18px/1 sans-serif;
height: 40px;
margin: 60px 0 0;
padding: 10px;
text-align: center;
width: 100%;
border-radius: 3px;
box-shadow: inset 0 1px 1px #AEE239, 0 1px 1px #EEE;
background: #8FBE00;
color: white;
border: 0;
margin: 20px 0;
font: 14px/1 monospace;
height: 60px;
padding: 20px;
text-align: center;
width: 100%;
}
h1 {
color: #333;
font: bold 32px/40px sans-serif;
text-align: center;
margin: 60px 0 10px;
color: #8FBE00;
font: bold 32px/40px "Quicksand", sans-serif;
text-align: center;
margin: 0 0 10px;
}
p {
color: #777;
font: 14px/20px sans-serif;
text-align: center;
margin: 10px 0;
color: #777;
font: 14px/20px "Quicksand", sans-serif;
text-align: center;
margin: 0 0;
}
.data {
color: #777;
font: 14px/20px sans-serif;
text-align: center;
overflow: hidden;
color: #777;
font: 14px/20px "Quicksand", sans-serif;
text-align: center;
overflow: hidden;
}
.data > .datum {
float: left;
width: 50%;
float: left;
width: 50%;
}
.download-button {
display: block;
border-radius: 5px;
box-shadow: inset 0 1px 1px #AEE239, 0 1px 1px #EEE;
padding: 20px;
background: #8FBE00;
border: none;
color: white;
width: 100%;
font: bold 18px/20px "Quicksand", sans-serif;
transition: 0.25s;
cursor: pointer;
text-transform: uppercase;
}
.download-button:hover, .download-button:focus {
box-shadow: inset 0 1px 1px #8FBE00;
background: #AEE239;
}
.download-button:active {
background: #8FBE00;
}
.progress-bar {
height: 60px;
overflow: hidden;
border-radius: 4px;
background: #F9F2E7;
box-shadow: 0 1px 1px #EEE;
}
.progress-bar .progress-bar-inner {
float: left;
height: 100%;
background: #8FBE00;
box-shadow: inset 0 1px 1px #AEE239;
overflow: hidden;
}
.progress-bar .progress-bar-text {
float: right;
font: 14px/60px "Quicksand", sans-serif;
color: white;
margin-right: 5px;
text-transform: uppercase;
}
.progress-bar.progress-bar-failed {
background: #C40D08;
box-shadow: inset 0 1px 1px #E23430, 0 1px 1px #EEE;
}
.progress-bar.progress-bar-failed .progress-bar-text {
float: none;
text-align: center;
margin-right: 0;
}
@-webkit-keyframes rotate {
from { -webkit-transform: rotate(0deg); }
to { -webkit-transform: rotate(360deg); }
from { -webkit-transform: rotate(0deg); }
to { -webkit-transform: rotate(360deg); }
}
@-moz-keyframes rotate {
from { -moz-transform: rotate(0deg); }
to { -moz-transform: rotate(360deg); }
from { -moz-transform: rotate(0deg); }
to { -moz-transform: rotate(360deg); }
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

Loading…
Cancel
Save