mirror of https://github.com/kern/filepizza
WIP
parent
d8febe77be
commit
8ad9855e47
File diff suppressed because one or more lines are too long
@ -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
|
||||
}
|
||||
@ -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,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')
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
body
|
||||
color red
|
||||
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…
Reference in New Issue