mirror of https://github.com/kern/filepizza
Another massive refactor.
parent
3da3b2d443
commit
02d45ac81a
@ -1,9 +0,0 @@
|
||||
import alt from '../alt';
|
||||
|
||||
export default alt.createActions(class PeerActions {
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'peerConnected'
|
||||
)
|
||||
}
|
||||
})
|
||||
@ -1,2 +1,2 @@
|
||||
var Alt = require('alt');
|
||||
module.exports = new Alt();
|
||||
export default new Alt();
|
||||
|
||||
@ -1,70 +1,23 @@
|
||||
import DownloadActions from '../actions/DownloadActions';
|
||||
import DownloadStore from '../stores/DownloadStore';
|
||||
import DropZone from './DropZone';
|
||||
import FileDescription from './FileDescription';
|
||||
import PeerStore from '../stores/PeerStore';
|
||||
import FrozenHead from 'react-frozenhead';
|
||||
import React from 'react';
|
||||
import Tempalink from './Tempalink';
|
||||
import UploadActions from '../actions/UploadActions';
|
||||
import UploadStore from '../stores/UploadStore';
|
||||
|
||||
function getState() {
|
||||
return {
|
||||
peerID: PeerStore.getPeerID(),
|
||||
readyToUpload: UploadStore.getState().status.isUploading(),
|
||||
uploadFile: UploadStore.getState().file,
|
||||
uploadToken: UploadStore.getState().token,
|
||||
downloadFile: DownloadStore.getState().file,
|
||||
downloadToken: DownloadStore.getState().token,
|
||||
readyToDownload: DownloadStore.getState().status.isReady()
|
||||
};
|
||||
}
|
||||
import { RouteHandler } from 'react-router';
|
||||
|
||||
export default class App extends React.Component {
|
||||
|
||||
constructor() {
|
||||
|
||||
this.state = getState();
|
||||
this._onChange = function() {
|
||||
this.setState(getState());
|
||||
}.bind(this);
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
PeerStore.listen(this._onChange);
|
||||
UploadStore.listen(this._onChange);
|
||||
DownloadStore.listen(this._onChange);
|
||||
}
|
||||
|
||||
componentDidUnmount() {
|
||||
PeerStore.unlisten(this._onChange);
|
||||
UploadStore.unlisten(this._onChange);
|
||||
DownloadStore.unlisten(this._onChange);
|
||||
}
|
||||
|
||||
uploadFile(file) {
|
||||
UploadActions.uploadFile(file);
|
||||
}
|
||||
|
||||
downloadFile() {
|
||||
DownloadActions.requestDownload();
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.readyToUpload) {
|
||||
return <div>
|
||||
<FileDescription file={this.state.uploadFile} />
|
||||
<Tempalink token={this.state.uploadToken} />
|
||||
</div>;
|
||||
} else if (this.state.readyToDownload) {
|
||||
return <div>
|
||||
<FileDescription file={this.state.downloadFile} />
|
||||
<button onClick={this.downloadFile.bind(this)}>Download</button>
|
||||
</div>;
|
||||
} else {
|
||||
return <DropZone onDrop={this.uploadFile.bind(this)} />;
|
||||
}
|
||||
return <html lang="en" data-bootstrap={this.props.data}>
|
||||
<FrozenHead>
|
||||
<meta charSet="utf-8" />
|
||||
<title>WebDrop - Send Files, Easily</title>
|
||||
|
||||
<link rel="stylesheet" href="/index.css" />
|
||||
<script src="/app.js" />
|
||||
</FrozenHead>
|
||||
|
||||
<body>
|
||||
<RouteHandler />
|
||||
</body>
|
||||
</html>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
// 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,64 @@
|
||||
// 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) + "%";
|
||||
}
|
||||
|
||||
export default class DownloadPage extends React.Component {
|
||||
|
||||
constructor() {
|
||||
this.state = DownloadStore.getState();
|
||||
|
||||
this._onChange = () => {
|
||||
this.setState(DownloadStore.getState());
|
||||
};
|
||||
|
||||
this._onConnection = (conn) => {
|
||||
DownloadActions.beginDownload(conn);
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
DownloadStore.listen(this._onChange);
|
||||
peer.on('connection', this._onConnection);
|
||||
}
|
||||
|
||||
componentDidUnmount() {
|
||||
DownloadStore.unlisten(this._onChange);
|
||||
peer.removeListener('connection', this._onConnection);
|
||||
}
|
||||
|
||||
downloadFile() {
|
||||
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>;
|
||||
|
||||
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>;
|
||||
|
||||
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>;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,24 +1,43 @@
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export default class DropZone extends React.Component {
|
||||
|
||||
dragOver(e) {
|
||||
constructor() {
|
||||
this.state = { focus: false };
|
||||
}
|
||||
|
||||
onDragEnter() {
|
||||
this.setState({ focus: true });
|
||||
}
|
||||
|
||||
onDragLeave() {
|
||||
this.setState({ focus: false });
|
||||
}
|
||||
|
||||
onDragOver(e) {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
|
||||
drop(e) {
|
||||
onDrop(e) {
|
||||
e.preventDefault();
|
||||
var file = e.dataTransfer.files[0];
|
||||
this.props.onDrop(file);
|
||||
this.setState({ focus: false });
|
||||
|
||||
let file = e.dataTransfer.files[0];
|
||||
if (this.props.onDrop) this.props.onDrop(file);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className="drop-zone"
|
||||
onDragOver={this.dragOver.bind(this)}
|
||||
onDrop={this.drop.bind(this)}>
|
||||
Drop a file here.
|
||||
</div>;
|
||||
let classes = classnames('drop-zone', {
|
||||
'drop-zone-focus': this.state.focus
|
||||
});
|
||||
|
||||
return <div className={classes}
|
||||
onDragEnter={this.onDragEnter.bind(this)}
|
||||
onDragLeave={this.onDragLeave.bind(this)}
|
||||
onDragOver={this.onDragOver.bind(this)}
|
||||
onDrop={this.onDrop.bind(this)} />;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
export default class NotFoundPage extends React.Component {
|
||||
|
||||
render() {
|
||||
return <h1>Not Found</h1>;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
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';
|
||||
|
||||
export default class UploadPage extends React.Component {
|
||||
|
||||
constructor() {
|
||||
this.state = UploadStore.getState();
|
||||
|
||||
this._onChange = () => {
|
||||
this.setState(UploadStore.getState());
|
||||
};
|
||||
|
||||
this._onDownload = (peerID) => {
|
||||
UploadActions.sendToDownloader(peerID);
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
UploadStore.listen(this._onChange);
|
||||
socket.on('download', this._onDownload);
|
||||
}
|
||||
|
||||
componentDidUnmount() {
|
||||
UploadStore.unlisten(this._onChange);
|
||||
socket.removeListener('download', this._onDownload);
|
||||
}
|
||||
|
||||
uploadFile(file) {
|
||||
UploadActions.uploadFile(file);
|
||||
}
|
||||
|
||||
render() {
|
||||
switch (this.state.status) {
|
||||
case 'ready':
|
||||
return <div className="upload-page">
|
||||
<DropZone onDrop={this.uploadFile.bind(this)} />
|
||||
<Arrow 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>;
|
||||
|
||||
case 'processing':
|
||||
return <div className="upload-page">
|
||||
<Arrow dir="up" animated />
|
||||
<h1>WebDrop</h1>
|
||||
<p>Processing...</p>
|
||||
</div>;
|
||||
|
||||
case 'uploading':
|
||||
return <div className="upload-page">
|
||||
|
||||
<Arrow dir="up" animated {...this.state.file} />
|
||||
|
||||
<Tempalink token={this.state.token} />
|
||||
<p>Send someone this link to download.</p>
|
||||
<p>This link will work as long as this page is open.</p>
|
||||
|
||||
<div className="data">
|
||||
<div className="datum"><strong>In Progress:</strong> {this.state.inProgress}</div>
|
||||
<div className="datum"><strong>Completed:</strong> {this.state.completed}</div>
|
||||
</div>
|
||||
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
import Arrow from './Arrow';
|
||||
import React from 'react';
|
||||
import UploadActions from '../actions/UploadActions';
|
||||
|
||||
export default class UploadPage extends React.Component {
|
||||
|
||||
uploadFile(file) {
|
||||
UploadActions.uploadFile(file);
|
||||
}
|
||||
|
||||
render() {
|
||||
switch (this.props.status) {
|
||||
case 'ready':
|
||||
return <div>
|
||||
<DropZone onDrop={this.uploadFile.bind(this)} />
|
||||
<Arrow dir="up" />
|
||||
</div>;
|
||||
break;
|
||||
|
||||
case 'processing':
|
||||
return <div>
|
||||
<Arrow dir="up" animated />
|
||||
<FileDescription file={this.props.file} />
|
||||
</div>;
|
||||
break;
|
||||
|
||||
case 'uploading':
|
||||
return <div>
|
||||
<Arrow dir="up" animated />
|
||||
<FileDescription file={this.props.file} />
|
||||
<Temaplink token={this.props.token} />
|
||||
</div>;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
import App from './components/App';
|
||||
import React from 'react';
|
||||
import DownloadActions from './actions/DownloadActions';
|
||||
import ReactRouter from 'react-router';
|
||||
import routes from './routes';
|
||||
import alt from './alt';
|
||||
|
||||
if (window.WebDrop) DownloadActions.setDownloadInfo({
|
||||
token: window.WebDrop.token,
|
||||
name: window.WebDrop.metadata.name,
|
||||
size: window.WebDrop.metadata.size,
|
||||
type: window.WebDrop.metadata.type
|
||||
})
|
||||
let bootstrap = document.documentElement.getAttribute('data-bootstrap');
|
||||
alt.bootstrap(bootstrap);
|
||||
|
||||
React.render(<App />, document.getElementById('app'));
|
||||
ReactRouter.run(routes, ReactRouter.HistoryLocation, function (Handler) {
|
||||
React.render(<Handler data={bootstrap} />, document);
|
||||
});
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import uuid from 'node-uuid';
|
||||
|
||||
let id = uuid.v4();
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
var peer = { id: id };
|
||||
} else {
|
||||
let Peer = require('peerjs');
|
||||
var peer = new Peer(id, {
|
||||
host: window.location.hostname,
|
||||
port: window.location.port,
|
||||
path: '/peer'
|
||||
});
|
||||
}
|
||||
|
||||
export default peer;
|
||||
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Route, DefaultRoute, NotFoundRoute, RouteHandler } from 'react-router';
|
||||
|
||||
import App from './components/App';
|
||||
import DownloadPage from './components/DownloadPage';
|
||||
import UploadPage from './components/UploadPage';
|
||||
import NotFoundPage from './components/NotFoundPage';
|
||||
|
||||
export default (
|
||||
<Route handler={App}>
|
||||
<DefaultRoute handler={UploadPage} />
|
||||
<Route name="download" path="d/:token" handler={DownloadPage} />
|
||||
<NotFoundRoute handler={NotFoundPage} />
|
||||
</Route>
|
||||
);
|
||||
@ -1,8 +1,8 @@
|
||||
import io from 'socket.io-client';
|
||||
import UploadActions from './actions/UploadActions';
|
||||
if (typeof window === 'undefined') {
|
||||
var socket = {};
|
||||
} else {
|
||||
let io = require('socket.io-client');
|
||||
var socket = io.connect();
|
||||
}
|
||||
|
||||
var socket = module.exports = io.connect();
|
||||
|
||||
socket.on('download', function (peerID) {
|
||||
UploadActions.sendToDownloader(peerID);
|
||||
});
|
||||
export default socket;
|
||||
|
||||
@ -1,81 +1,64 @@
|
||||
import DownloadActions from '../actions/DownloadActions';
|
||||
import DownloadFile from '../DownloadFile';
|
||||
import PeerActions from '../actions/PeerActions';
|
||||
import PeerStore from './PeerStore';
|
||||
import Status from '../Status';
|
||||
import peer from '../peer';
|
||||
import alt from '../alt';
|
||||
import socket from '../socket';
|
||||
|
||||
class DownloadStatus extends Status {
|
||||
constructor() {
|
||||
super([
|
||||
'offline',
|
||||
'ready',
|
||||
'requesting',
|
||||
'downloading',
|
||||
'cancelled',
|
||||
'done'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createStore(class DownloadStore {
|
||||
|
||||
constructor() {
|
||||
this.bindActions(DownloadActions);
|
||||
this.bindActions(PeerActions);
|
||||
|
||||
this.status = 'offline';
|
||||
this.token = null;
|
||||
this.file = null;
|
||||
this.status = new DownloadStatus();
|
||||
}
|
||||
|
||||
onSetDownloadInfo(info) {
|
||||
if (!this.status.isOffline()) return;
|
||||
this.status.set('ready');
|
||||
this.progress = 0;
|
||||
|
||||
this.token = info.token;
|
||||
this.file = new DownloadFile(info.name, info.size, info.type);
|
||||
this.on('bootstrap', () => {
|
||||
if (this.file && !(this.file instanceof DownloadFile))
|
||||
this.file = new DownloadFile(this.file.name, this.file.size, this.file.type);
|
||||
});
|
||||
}
|
||||
|
||||
onRequestDownload() {
|
||||
if (!this.status.isReady()) return;
|
||||
this.status.set('requesting');
|
||||
if (this.status !== 'ready') return;
|
||||
this.status = 'requesting';
|
||||
|
||||
socket.emit('download', {
|
||||
peerID: PeerStore.getPeerID(),
|
||||
peerID: peer.id,
|
||||
token: this.token
|
||||
});
|
||||
}
|
||||
|
||||
onPeerConnected(conn) {
|
||||
if (!this.status.isRequesting()) return;
|
||||
this.status.set('downloading');
|
||||
onBeginDownload(conn) {
|
||||
if (this.status !== 'requesting') return;
|
||||
this.status = 'downloading';
|
||||
|
||||
let chunkSize = conn.metadata.chunkSize;
|
||||
let i = 0;
|
||||
|
||||
conn.on('data', (data) => {
|
||||
if (!this.status.isDownloading()) return;
|
||||
if (this.status !== 'downloading') return;
|
||||
|
||||
this.file.addPacket(data);
|
||||
i++;
|
||||
|
||||
if (this.file.isComplete()) {
|
||||
this.status.set('done');
|
||||
this.setState({ status: 'done', progress: 1 });
|
||||
this.file.download();
|
||||
conn.close();
|
||||
} else if (i % chunkSize === 0) {
|
||||
conn.send('more');
|
||||
} else {
|
||||
this.setState({ progress: this.file.getProgress() });
|
||||
if (i % chunkSize === 0) conn.send('more');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
conn.on('close', () => {
|
||||
if (!this.status.isDownloading()) return;
|
||||
this.status.set('cancelled');
|
||||
if (this.status !== 'downloading') return;
|
||||
this.setState({ status: 'cancelled', progress: 0 });
|
||||
this.file.clearPackets();
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
}, 'DownloadStore')
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
import Peer from 'peerjs';
|
||||
import PeerActions from '../actions/PeerActions';
|
||||
import alt from '../alt';
|
||||
import uuid from 'node-uuid';
|
||||
|
||||
let id = uuid.v4();
|
||||
let peer = new Peer(id, {
|
||||
host: window.location.hostname,
|
||||
port: window.location.port,
|
||||
path: '/peer'
|
||||
});
|
||||
|
||||
peer.on('connection', (conn) => {
|
||||
PeerActions.peerConnected(conn);
|
||||
});
|
||||
|
||||
export default alt.createStore(class PeerStore {
|
||||
|
||||
static connect(peerID, metadata) {
|
||||
return peer.connect(peerID, {
|
||||
reliable: true,
|
||||
metadata: metadata
|
||||
});
|
||||
}
|
||||
|
||||
static getPeerID() {
|
||||
return id;
|
||||
}
|
||||
|
||||
})
|
||||
@ -1,72 +1,79 @@
|
||||
import PeerStore from './PeerStore';
|
||||
import Status from '../Status';
|
||||
import UploadActions from '../actions/UploadActions';
|
||||
import UploadFile from '../UploadFile';
|
||||
import alt from '../alt';
|
||||
import peer from '../peer';
|
||||
import socket from '../socket';
|
||||
|
||||
const chunkSize = 32;
|
||||
|
||||
class UploadStatus extends Status {
|
||||
constructor() {
|
||||
super([
|
||||
'ready',
|
||||
'processing',
|
||||
'uploading'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createStore(class UploadStore {
|
||||
|
||||
constructor() {
|
||||
this.bindActions(UploadActions);
|
||||
|
||||
this.status = new UploadStatus();
|
||||
this.status = 'ready';
|
||||
this.token = null;
|
||||
this.file = null;
|
||||
this.downloaders = [];
|
||||
|
||||
this.inProgress = 0;
|
||||
this.completed = 0;
|
||||
}
|
||||
|
||||
onUploadFile(file) {
|
||||
if (!this.status.isReady()) return;
|
||||
this.status.set('processing');
|
||||
|
||||
if (this.status !== 'ready') return;
|
||||
this.status = 'processing';
|
||||
this.file = new UploadFile(file);
|
||||
|
||||
socket.emit('upload', {
|
||||
name: this.file.name,
|
||||
size: this.file.size,
|
||||
type: this.file.type
|
||||
}, (token) => {
|
||||
this.status.set('uploading');
|
||||
this.token = token;
|
||||
this.emitChange();
|
||||
this.setState({
|
||||
status: 'uploading',
|
||||
token: token
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onSendToDownloader(peerID) {
|
||||
if (!this.status.isUploading()) return;
|
||||
this.downloaders.push(peerID); // TODO
|
||||
if (this.status !== 'uploading') return;
|
||||
|
||||
let conn = PeerStore.connect(peerID, {
|
||||
chunkSize: chunkSize
|
||||
let conn = peer.connect(peerID, {
|
||||
reliable: true,
|
||||
metadata: { chunkSize: chunkSize }
|
||||
});
|
||||
|
||||
let complete = false;
|
||||
let totalPackets = this.file.countPackets();
|
||||
let i = 0;
|
||||
|
||||
let sendNextChunk = () => {
|
||||
if (complete) return;
|
||||
|
||||
for (let j = 0; i < totalPackets && j < chunkSize; i++, j++) {
|
||||
let packet = this.file.getPacket(i);
|
||||
conn.send(packet);
|
||||
}
|
||||
|
||||
if (i === totalPackets) complete = true;
|
||||
}
|
||||
|
||||
conn.on('open', () => { sendNextChunk(); });
|
||||
conn.on('open', () => {
|
||||
this.setState({ inProgress: this.inProgress + 1 });
|
||||
sendNextChunk();
|
||||
});
|
||||
|
||||
conn.on('data', (data) => {
|
||||
if (data === 'more') sendNextChunk();
|
||||
});
|
||||
|
||||
conn.on('close', () => {
|
||||
this.setState({
|
||||
inProgress: this.inProgress - 1,
|
||||
completed: this.completed + (complete ? 1 : 0)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
}, 'UploadStore')
|
||||
|
||||
@ -1,44 +1,58 @@
|
||||
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 router = module.exports = new express.Router();
|
||||
var routes = module.exports = new express.Router();
|
||||
|
||||
router.get('/', function (req, res) {
|
||||
res.render('index');
|
||||
});
|
||||
routes.use(express.static(__dirname + '/../static'));
|
||||
|
||||
router.get('/d/:token', function (req, res, next) {
|
||||
routes.get('/d/:token', function (req, res, next) {
|
||||
|
||||
var uploader = Upload.find(req.params.token);
|
||||
if (uploader) {
|
||||
res.render('download', {
|
||||
res.locals.data = {
|
||||
DownloadStore: {
|
||||
status: 'ready',
|
||||
token: uploader.token,
|
||||
meta: uploader.metadata
|
||||
});
|
||||
} else {
|
||||
var err = new Error('Unknown token');
|
||||
err.status = 404;
|
||||
next(err);
|
||||
file: uploader.metadata
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
});
|
||||
next();
|
||||
|
||||
router.use(express.static(__dirname + '/../static'));
|
||||
});
|
||||
|
||||
router.use(function (req, res, next) {
|
||||
routes.use(function (req, res, next) {
|
||||
var err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
next(err);
|
||||
});
|
||||
|
||||
router.use(function (err, req, res, next) {
|
||||
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 || '';
|
||||
|
||||
res.status(status).render('error', {
|
||||
status: status,
|
||||
message: message
|
||||
res.status(status);
|
||||
next();
|
||||
|
||||
});
|
||||
|
||||
routes.use(function (req, res) {
|
||||
|
||||
alt.bootstrap(JSON.stringify(res.locals.data || {}));
|
||||
|
||||
ReactRouter.run(clientRoutes, req.url, function (Handler) {
|
||||
var html = React.renderToString(<Handler data={alt.takeSnapshot()} />);
|
||||
alt.recycle();
|
||||
res.write('<!DOCTYPE html>');
|
||||
res.end(html);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,10 +1,142 @@
|
||||
#drop_zone {
|
||||
border: 1.5px solid #bbb;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
font: 20pt 'Helvetica-Light';
|
||||
color: #bbb;
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
.upload-page {
|
||||
margin: 100px auto 0;
|
||||
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);
|
||||
}
|
||||
|
||||
.drop-zone-focus {
|
||||
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 2s infinite linear;
|
||||
-moz-animation: rotate 2s infinite linear;
|
||||
animation: rotate 2s 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;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
font: bold 32px/40px sans-serif;
|
||||
text-align: center;
|
||||
margin: 60px 0 10px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #777;
|
||||
font: 14px/20px sans-serif;
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.data {
|
||||
color: #777;
|
||||
font: 14px/20px sans-serif;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.data > .datum {
|
||||
float: left;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotate {
|
||||
from { -webkit-transform: rotate(0deg); }
|
||||
to { -webkit-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@-moz-keyframes rotate {
|
||||
from { -moz-transform: rotate(0deg); }
|
||||
to { -moz-transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue