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

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

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

@ -1,6 +1,6 @@
import FrozenHead from 'react-frozenhead'; import FrozenHead from 'react-frozenhead'
import React from 'react'; import React from 'react'
import { RouteHandler } from 'react-router'; import { RouteHandler } from 'react-router'
export default class App extends React.Component { export default class App extends React.Component {
@ -9,15 +9,17 @@ export default class App extends React.Component {
<FrozenHead> <FrozenHead>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
<title>WebDrop - Send Files, Easily</title> <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" /> <link rel="stylesheet" href="/index.css" />
<script src="/app.js" />
</FrozenHead> </FrozenHead>
<body> <body>
<RouteHandler /> <div className="container">
<RouteHandler />
<script src="/app.js" />
</div>
</body> </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 DownloadActions from '../actions/DownloadActions'
import DownloadButton from './DownloadButton'
import Arrow from './Arrow'; import ProgressBar from './ProgressBar'
import DownloadActions from '../actions/DownloadActions'; import DownloadStore from '../stores/DownloadStore'
import DownloadStore from '../stores/DownloadStore'; import React from 'react'
import React from 'react'; import Spinner from './Spinner'
import peer from '../peer'; import peer from '../peer'
function formatProgress(dec) {
return (dec * 100).toPrecision(3) + "%";
}
export default class DownloadPage extends React.Component { export default class DownloadPage extends React.Component {
constructor() { constructor() {
this.state = DownloadStore.getState(); this.state = DownloadStore.getState()
this._onChange = () => { this._onChange = () => {
this.setState(DownloadStore.getState()); this.setState(DownloadStore.getState())
}; }
this._onConnection = (conn) => { this._onConnection = (conn) => {
DownloadActions.beginDownload(conn); DownloadActions.beginDownload(conn)
}; }
} }
componentDidMount() { componentDidMount() {
DownloadStore.listen(this._onChange); DownloadStore.listen(this._onChange)
peer.on('connection', this._onConnection); peer.on('connection', this._onConnection)
} }
componentDidUnmount() { componentDidUnmount() {
DownloadStore.unlisten(this._onChange); DownloadStore.unlisten(this._onChange)
peer.removeListener('connection', this._onConnection); peer.removeListener('connection', this._onConnection)
} }
downloadFile() { downloadFile() {
DownloadActions.requestDownload(); DownloadActions.requestDownload()
} }
render() { render() {
switch (this.state.status) { switch (this.state.status) {
case 'ready': case 'ready':
return <div className="download-page"> return <div className="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>;
<Spinner dir="down"
name={this.state.file.name}
size={this.state.file.size} />
<DownloadButton onClick={this.downloadFile.bind(this)} />
</div>
case 'requesting':
case 'downloading': case 'downloading':
return <div className="download-page"> return <div className="page">
<Arrow dir="down" name={this.state.name} size={this.state.size} animated />
<span>Progress: {formatProgress(this.state.progress)}</span> <Spinner dir="down" animated
</div>; 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': case 'done':
return <div className="download-page"> return <div className="page">
<Arrow dir="down" name={this.state.name} size={this.state.size} />
<span>Progress: {formatProgress(this.state.progress)}</span> <Spinner dir="down"
</div>; 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 }); this.setState({ focus: false });
let file = e.dataTransfer.files[0]; let file = e.dataTransfer.files[0];
if (this.props.onDrop) this.props.onDrop(file); if (this.props.onDrop && file) this.props.onDrop(file);
} }
render() { 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 { export default class Tempalink extends React.Component {
onClick() { onClick() {
this.refs.input.getDOMNode().setSelectionRange(0, 9999); this.refs.input.getDOMNode().setSelectionRange(0, 9999)
} }
render() { render() {
var url = window.location.origin + '/d/' + this.props.token; var url = window.location.origin + '/d/' + this.props.token
return <input return <input
className="tempalink" className="tempalink"
onClick={this.onClick.bind(this)} onClick={this.onClick.bind(this)}
readOnly readOnly
ref="input" ref="input"
type="text" type="text"
value={url} />; value={url} />
} }
} }

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

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react'
import ReactRouter from 'react-router'; import ReactRouter from 'react-router'
import routes from './routes'; import routes from './routes'
import alt from './alt'; import alt from './alt'
let bootstrap = document.documentElement.getAttribute('data-bootstrap'); let bootstrap = document.documentElement.getAttribute('data-bootstrap')
alt.bootstrap(bootstrap); alt.bootstrap(bootstrap)
ReactRouter.run(routes, ReactRouter.HistoryLocation, function (Handler) { 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') { if (typeof window === 'undefined') {
var peer = { id: id }; var peer = { id: id }
} else { } else {
let Peer = require('peerjs'); let Peer = require('peerjs')
var peer = new Peer(id, { var peer = new Peer(id, {
host: window.location.hostname, host: window.location.hostname,
port: window.location.port, port: window.location.port,
path: '/peer' path: '/peer'
}); })
} }
export default peer; export default peer

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

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

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

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

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

@ -1,18 +1,26 @@
var DownloadFile = require('../client/DownloadFile'); var DownloadFile = require('../client/DownloadFile')
var React = require('react'); var React = require('react')
var ReactRouter = require('react-router'); var ReactRouter = require('react-router')
var Upload = require('./Upload'); var Upload = require('./Upload')
var alt = require('../client/alt'); var alt = require('../client/alt')
var clientRoutes = require('../client/routes'); var clientRoutes = require('../client/routes')
var express = require('express'); 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) { routes.get('/d/:token', function (req, res, next) {
var uploader = Upload.find(req.params.token); var uploader = Upload.find(req.params.token)
if (uploader) { if (uploader) {
res.locals.data = { res.locals.data = {
DownloadStore: { DownloadStore: {
@ -20,39 +28,39 @@ routes.get('/d/:token', function (req, res, next) {
token: uploader.token, token: uploader.token,
file: uploader.metadata file: uploader.metadata
} }
}; }
} }
next(); next()
}); })
routes.use(function (req, res, next) { routes.use(function (req, res, next) {
var err = new Error('Not Found'); var err = new Error('Not Found')
err.status = 404; err.status = 404
next(err); next(err)
}); })
routes.use(function (err, req, res, next) { routes.use(function (err, req, res, next) {
// TODO: Get these error pages working with isomorphic react. // TODO: Get these error pages working with isomorphic react.
var status = err.status || 500; var status = err.status || 500
var message = err.message || ''; var message = err.message || ''
res.status(status); res.status(status)
next(); next()
}); })
routes.use(function (req, res) { 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) { ReactRouter.run(clientRoutes, req.url, function (Handler) {
var html = React.renderToString(<Handler data={alt.takeSnapshot()} />); var html = React.renderToString(<Handler data={alt.takeSnapshot()} />)
alt.flush(); alt.flush()
res.write('<!DOCTYPE html>'); res.write('<!DOCTYPE html>')
res.end(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; } * { box-sizing: border-box; }
.upload-page { html {
margin: 100px auto 0; height: 100%;
width: 300px; }
body {
margin: 0;
height: 100%;
}
.container {
display: table;
overflow: visible;
}
.page {
width: 300px;
} }
.drop-zone { .drop-zone {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
right: 0; width: 100%;
bottom: 0; height: 100%;
display: table; display: table;
width: 100%; background: rgba(0, 0, 0, 0);
height: 100%;
background: rgba(0, 0, 0, 0);
} }
.drop-zone-focus { .drop-zone-focus {
background: rgba(0, 0, 0, 0.5); z-index: 1;
background: rgba(0, 0, 0, 0.5);
} }
.drop-zone-focus:after { .drop-zone-focus:after {
color: white; color: white;
content: 'DROP TO UPLOAD'; content: 'DROP TO UPLOAD';
display: table-cell; display: table-cell;
font: 24px/40px sans-serif; font: 24px/40px "Quicksand", sans-serif;
text-align: center; text-align: center;
text-shadow: 0 1px #333; text-shadow: 0 1px #333;
vertical-align: middle; vertical-align: middle;
} }
.arrow { .spinner {
position: relative; position: relative;
z-index: -1; z-index: -1;
width: 300px; width: 300px;
height: 300px; height: 300px;
} margin-bottom: 40px;
.arrow .arrow-border { display: flex;
position: absolute; align-items: center;
width: 100%; justify-content: center;
height: 100%; }
border: 20px dotted #CCC;
border-radius: 100%; .spinner .spinner-border {
position: absolute;
-webkit-transition: -webkit-transform 1s; top: 0;
-moz-transition: -moz-transform 1s; left: 0;
transition: transform 1s; width: 100%;
} height: 100%;
border: 20px dotted #40C0CB;
.arrow.arrow-animated .arrow-border { border-radius: 100%;
-webkit-animation: rotate 5s infinite linear;
-moz-animation: rotate 5s infinite linear; -webkit-transition: -webkit-transform 1s;
animation: rotate 5s infinite linear; -moz-transition: -moz-transform 1s;
} transition: transform 1s;
}
.arrow .arrow-image {
position: absolute; .spinner.spinner-animated .spinner-border {
top: 60px; -webkit-animation: rotate 5s infinite linear;
left: 80px; -moz-animation: rotate 5s infinite linear;
right: 80px; animation: rotate 5s infinite linear;
bottom: 100px; }
background: red;
} .spinner .spinner-image {
display: block;
.arrow .arrow-name { width: 150px;
bottom: 60px; }
font: bold 18px/20px sans-serif;
left: 60px; .spinner .spinner-name {
overflow: hidden; font: bold 18px/20px "Quicksand", sans-serif;
position: absolute; text-align: center;
right: 60px; color: #333;
text-align: center; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
color: #333; margin-top: 10px;
} }
.arrow .arrow-size { .spinner .spinner-size {
bottom: 40px; font: italic 12px/20px "Quicksand", sans-serif;
font: italic 12px/20px sans-serif; text-align: center;
left: 60px; color: #777;
position: absolute; overflow: hidden;
right: 60px; text-overflow: ellipsis;
text-align: center; white-space: nowrap;
color: #777;
} }
.tempalink { .tempalink {
border-radius: 3px; border-radius: 3px;
border: 1px solid #EEE; box-shadow: inset 0 1px 1px #AEE239, 0 1px 1px #EEE;
color: #333; background: #8FBE00;
font: 18px/1 sans-serif; color: white;
height: 40px; border: 0;
margin: 60px 0 0; margin: 20px 0;
padding: 10px; font: 14px/1 monospace;
text-align: center; height: 60px;
width: 100%; padding: 20px;
text-align: center;
width: 100%;
} }
h1 { h1 {
color: #333; color: #8FBE00;
font: bold 32px/40px sans-serif; font: bold 32px/40px "Quicksand", sans-serif;
text-align: center; text-align: center;
margin: 60px 0 10px; margin: 0 0 10px;
} }
p { p {
color: #777; color: #777;
font: 14px/20px sans-serif; font: 14px/20px "Quicksand", sans-serif;
text-align: center; text-align: center;
margin: 10px 0; margin: 0 0;
} }
.data { .data {
color: #777; color: #777;
font: 14px/20px sans-serif; font: 14px/20px "Quicksand", sans-serif;
text-align: center; text-align: center;
overflow: hidden; overflow: hidden;
} }
.data > .datum { .data > .datum {
float: left; float: left;
width: 50%; 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 { @-webkit-keyframes rotate {
from { -webkit-transform: rotate(0deg); } from { -webkit-transform: rotate(0deg); }
to { -webkit-transform: rotate(360deg); } to { -webkit-transform: rotate(360deg); }
} }
@-moz-keyframes rotate { @-moz-keyframes rotate {
from { -moz-transform: rotate(0deg); } from { -moz-transform: rotate(0deg); }
to { -moz-transform: rotate(360deg); } to { -moz-transform: rotate(360deg); }
} }
@keyframes rotate { @keyframes rotate {
from { transform: rotate(0deg); } from { transform: rotate(0deg); }
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }

Loading…
Cancel
Save