Merge branch 'my-files-page'

This commit is contained in:
Alex Liebowitz 2016-05-29 04:02:33 -04:00
commit 1609cc0e3f
16 changed files with 390 additions and 78 deletions

2
.gitignore vendored
View file

@ -1,5 +1,7 @@
dist/css/*
dist/js/*
!dist/js/flowplayer/
!dist/js/flowplayer/
node_modules
.sass-cache
.idea

2
dist/index.html vendored
View file

@ -21,6 +21,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.6/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.7.4/polyfill.js"></script>
<script src="./js/flowplayer/flowplayer-3.2.13.min.js"></script>
<script src="./js/lbry.js"></script>
<script src="./js/component/common.js"></script>
<script src="./js/component/splash.js"></script>
@ -29,6 +30,7 @@
<script src="./js/page/help.js"></script>
<script src="./js/page/watch.js"></script>
<script src="./js/page/report.js"></script>
<script src="./js/page/my_files.js"></script>
<script src="./js/page/start.js"></script>
<script src="./js/app.js"></script>
<script src="./js/main.js"></script>

File diff suppressed because one or more lines are too long

BIN
dist/js/flowplayer/flowplayer-3.2.18.swf vendored Normal file

Binary file not shown.

Binary file not shown.

View file

@ -1,15 +1,10 @@
var appStyles = {
width: '800px',
marginLeft: 'auto',
marginRight: 'auto',
};
var App = React.createClass({
getInitialState: function() {
// For now, routes are in format ?page or ?page=args
var match, param, val;
[match, param, val] = window.location.search.match(/\??([^=]*)(?:=(.*))?/);
if (['settings', 'help', 'start', 'watch', 'report'].indexOf(param) != -1) {
if (['settings', 'help', 'start', 'watch', 'report', 'files'].indexOf(param) != -1) {
var viewingPage = param;
} else {
var viewingPage = 'home';
@ -44,31 +39,21 @@ var App = React.createClass({
}
});
},
componentDidMount: function() {
lbry.getStartNotice(function(notice) {
if (notice) {
alert(notice);
}
});
},
render: function() {
if (this.state.viewingPage == 'home') {
var content = <HomePage />;
return <HomePage />;
} else if (this.state.viewingPage == 'settings') {
var content = <SettingsPage />;
return <SettingsPage />;
} else if (this.state.viewingPage == 'help') {
var content = <HelpPage />;
return <HelpPage />;
} else if (this.state.viewingPage == 'watch') {
var content = <WatchPage name={this.state.pageArgs}/>;
return <WatchPage name={this.state.pageArgs}/>;
} else if (this.state.viewingPage == 'report') {
var content = <ReportPage />;
return <ReportPage />;
} else if (this.state.viewingPage == 'files') {
return <MyFilesPage />;
} else if (this.state.viewingPage == 'start') {
var content = <StartPage />;
return <StartPage />;
}
return (
<div style={appStyles}>
{content}
</div>
);
}
});

View file

@ -3,9 +3,10 @@
var Icon = React.createClass({
propTypes: {
style: React.PropTypes.object,
fixed: React.PropTypes.boolean,
},
render: function() {
var className = 'icon ' + this.props.icon;
var className = 'icon ' + ('fixed' in this.props ? 'icon-fixed-width ' : '') + this.props.icon;
return <span className={className} style={this.props.style}></span>
}
});
@ -18,7 +19,8 @@ var Link = React.createClass({
className = (this.props.button ? 'button-block button-' + this.props.button : 'button-text') +
(this.props.hidden ? ' hidden' : '') + (this.props.disabled ? ' disabled' : '');
return (
<a className={className} href={href} style={this.props.style ? this.props.style : {}} onClick={this.props.onClick}>
<a className={className} href={href} style={this.props.style ? this.props.style : {}}
title={this.props.title} onClick={this.props.onClick}>
{this.props.icon ? icon : '' }
{this.props.label}
</a>
@ -26,6 +28,79 @@ var Link = React.createClass({
}
});
// Generic menu styles
var menuStyle = {
border: '1px solid #aaa',
padding: '4px',
whiteSpace: 'nowrap',
};
var Menu = React.createClass({
handleWindowClick: function(e) {
if (this.props.toggleButton && ReactDOM.findDOMNode(this.props.toggleButton).contains(e.target)) {
// Toggle button was clicked
this.setState({
open: !this.state.open
});
} else if (this.state.open && !this.refs.div.contains(e.target)) {
// Menu is open and user clicked outside of it
this.setState({
open: false
});
}
},
propTypes: {
openButton: React.PropTypes.element,
},
getInitialState: function() {
return {
open: false,
};
},
componentDidMount: function() {
window.addEventListener('click', this.handleWindowClick, false);
},
componentWillUnmount: function() {
window.removeEventListener('click', this.handleWindowClick, false);
},
render: function() {
return (
<div ref='div' style={menuStyle} className={this.state.open ? '' : 'hidden'}>
{this.props.children}
</div>
);
}
});
var menuItemStyle = {
display: 'block',
};
var MenuItem = React.createClass({
propTypes: {
href: React.PropTypes.string,
label: React.PropTypes.string,
icon: React.PropTypes.string,
onClick: React.PropTypes.function,
},
getDefaultProps: function() {
return {
iconPosition: 'left',
}
},
render: function() {
var icon = (this.props.icon ? <Icon icon={this.props.icon} fixed /> : null);
return (
<a style={menuItemStyle} href={this.props.href} label={this.props.label} title={this.props.label}
className="button-text no-underline">
{this.props.iconPosition == 'left' ? icon : null}
{this.props.label}
{this.props.iconPosition == 'left' ? null : icon}
</a>
);
}
});
var creditAmountStyle = {
color: '#216C2A',
fontWeight: 'bold',
@ -48,4 +123,16 @@ var CreditAmount = React.createClass({
</span>
);
}
});
var subPageLogoStyle = {
maxWidth: '150px',
display: 'block',
marginTop: '36px',
};
var SubPageLogo = React.createClass({
render: function() {
return <img src="img/lbry-dark-1600x528.png" style={subPageLogoStyle} />;
}
});

View file

@ -103,6 +103,22 @@ lbry.getFileStatus = function(name, callback) {
lbry.call('get_lbry_file', { 'name': name }, callback);
}
lbry.getFilesInfo = function(callback) {
lbry.call('get_lbry_files', {}, callback);
}
lbry.startFile = function(name, callback) {
lbry.call('start_lbry_file', { name: name }, callback);
}
lbry.stopFile = function(name, callback) {
lbry.call('stop_lbry_file', { name: name }, callback);
}
lbry.deleteFile = function(name, callback) {
lbry.call('delete_lbry_file', { name: name }, callback)
}
lbry.getVersionInfo = function(callback) {
lbry.call('version', {}, callback);
};
@ -154,6 +170,26 @@ lbry.imagePath = function(file)
return lbry.rootPath + '/img/' + file;
}
lbry.getMediaType = function(filename) {
var dotIndex = filename.lastIndexOf('.');
if (dotIndex == -1) {
return 'unknown';
}
var ext = filename.substr(dotIndex + 1);
if (/^mp4|mov|m4v|flv|f4v$/i.test(ext)) {
return 'video';
} else if (/^mp3|m4a|aac|wav|flac|ogg$/i.test(ext)) {
return 'audio';
} else if (/^html|htm|pdf|odf|doc|docx|md|markdown|txt$/i.test(ext)) {
return 'document';
} else {
return 'unknown';
}
}
lbry.stop = function(callback) {
lbry.call('stop', {}, callback);
};

View file

@ -3,7 +3,8 @@
var HelpPage = React.createClass({
render: function() {
return (
<main>
<main className="page">
<SubPageLogo />
<h1>Troubleshooting</h1>
<p>Here are the most commonly encountered problems and what to try doing about them.</p>

View file

@ -228,23 +228,41 @@ var Header = React.createClass({
});
var topBarStyle = {
'float': 'right'
'float': 'right',
'position': 'relative',
'height': '26px',
},
balanceStyle = {
'marginRight': '5px'
},
closeIconStyle = {
'color': '#ff5155'
};
var mainMenuStyle = {
position: 'absolute',
top: '26px',
right: '0px',
};
var MainMenu = React.createClass({
render: function() {
var isLinux = /linux/i.test(navigator.userAgent); // @TODO: find a way to use getVersionInfo() here without messy state management
return (
<div style={mainMenuStyle}>
<Menu {...this.props}>
<MenuItem href='/?files' label="My Files" icon='icon-cloud-download' />
<MenuItem href='/?settings' label="Settings" icon='icon-gear' />
<MenuItem href='/?help' label="Help" icon='icon-question-circle' />
{isLinux ? <MenuItem href="/?start" label="Exit LBRY" icon="icon-close" />
: null}
</Menu>
</div>
);
}
});
var TopBar = React.createClass({
onClose: function() {
window.location.href = "?start";
},
getInitialState: function() {
return {
balance: 0,
showClose: /linux/i.test(navigator.userAgent) // @TODO: find a way to use getVersionInfo() here without messy state management
};
},
componentDidMount: function() {
@ -254,27 +272,34 @@ var TopBar = React.createClass({
});
}.bind(this));
},
onClose: function() {
window.location.href = "?start";
},
render: function() {
return (
<span className='top-bar' style={topBarStyle}>
<span style={balanceStyle}>
<CreditAmount amount={this.state.balance}/>
</span>
<Link href='/?settings' icon='icon-gear' />
{ ' ' }
<Link href='/?help' icon='icon-question-circle' />
{ ' ' }
<Link href="/?start" onClick={this.onClose} icon="icon-close" style={closeIconStyle} hidden={!this.state.showClose} />
<Link ref="menuButton" title="LBRY Menu" icon="icon-bars" />
<MainMenu toggleButton={this.refs.menuButton} />
</span>
);
}
});
var HomePage = React.createClass({
componentDidMount: function() {
lbry.getStartNotice(function(notice) {
if (notice) {
alert(notice);
}
});
},
render: function() {
return (
<div>
<div className="page">
<Header />
<Discover />
</div>

138
js/page/my_files.js Normal file
View file

@ -0,0 +1,138 @@
var removeIconColumnStyle = {
fontSize: '1.3em',
height: '120px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
progressBarStyle = {
height: '15px',
width: '230px',
backgroundColor: '#444',
border: '2px solid #eee',
display: 'inline-block',
},
myFilesRowImgStyle = {
maxHeight: '100px',
display: 'block',
marginLeft: 'auto',
marginRight: 'auto',
float: 'left'
};
var MyFilesRow = React.createClass({
onRemoveClicked: function() {
var alertText = 'Are you sure you\'d like to remove "' + this.props.title + '?" This will ' +
(this.completed ? ' stop the download and ' : '') +
'permanently remove the file from your system.';
if (confirm(alertText)) {
lbry.deleteFile(this.props.lbryUri);
}
},
onPauseResumeClicked: function() {
if (this.props.stopped) {
lbry.startFile(this.props.lbryUri);
} else {
lbry.stopFile(this.props.lbryUri);
}
},
render: function() {
if (this.props.completed) {
var pauseLink = null;
var curProgressBarStyle = {display: 'none'};
} else {
var pauseLink = <Link icon={this.props.stopped ? 'icon-play' : 'icon-pause'}
label={this.props.stopped ? 'Resume download' : 'Pause download'}
onClick={() => { this.onPauseResumeClicked() }} />;
var curProgressBarStyle = Object.assign({}, progressBarStyle);
curProgressBarStyle.width = this.props.ratioLoaded * 230;
curProgressBarStyle.borderRightWidth = 230 - (this.props.ratioLoaded * 230) + 2;
}
if (this.props.showWatchButton) {
// No support for lbry:// URLs in Windows or on Chrome yet
if (/windows|win32/i.test(navigator.userAgent) || (window.chrome && window.navigator.vendor == "Google Inc.")) {
var watchUri = "/?watch=" + this.props.lbryUri;
} else {
var watchUri = 'lbry://' + this.props.lbryUri;
}
var watchLink = <Link href={watchUri} label="Watch" icon="icon-play" button="primary" />;
} else {
var watchLink = null;
}
return (
<div className="row-fluid">
<div className="span3">
<img src={this.props.imgUrl} alt={'Photo for ' + this.props.title} style={myFilesRowImgStyle} />
</div>
<div className="span6">
<h2>{this.props.title}</h2>
<div className={this.props.completed ? 'hidden' : ''} style={curProgressBarStyle}></div>
{ ' ' }
{this.props.completed ? 'Download complete' : (parseInt(this.props.ratioLoaded * 100) + '%')}
<div>{ pauseLink }</div>
<div>{ watchLink }</div>
</div>
<div className="span1" style={removeIconColumnStyle}>
<Link icon="icon-close" title="Remove file" onClick={() => { this.onRemoveClicked() } } /><br />
</div>
</div>
);
}
});
var MyFilesPage = React.createClass({
getInitialState: function() {
return {
filesInfo: null,
};
},
componentWillMount: function() {
this.updateFilesInfo();
},
updateFilesInfo: function() {
lbry.getFilesInfo((filesInfo) => {
this.setState({
filesInfo: (filesInfo ? filesInfo : []),
});
setTimeout(() => { this.updateFilesInfo() }, 1000);
});
},
render: function() {
if (this.state.filesInfo === null) {
return null;
}
if (!this.state.filesInfo.length) {
var content = <span>You haven't downloaded anything from LBRY yet. Go <Link href="/" label="search for your first download" />!</span>;
} else {
var content = [];
for (let fileInfo of this.state.filesInfo) {
let {completed, written_bytes, total_bytes, lbry_uri, file_name, stopped, metadata} = fileInfo;
let {name, stream_name, thumbnail} = metadata;
var title = (name || stream_name || ('lbry://' + lbry_uri));
var ratioLoaded = written_bytes / total_bytes;
var showWatchButton = (lbry.getMediaType(file_name) == 'video');
content.push(<MyFilesRow lbryUri={lbry_uri} title={title} completed={completed} stopped={stopped}
ratioLoaded={ratioLoaded} imgUrl={thumbnail}
showWatchButton={showWatchButton}/>);
}
}
return (
<main className="page">
<SubPageLogo />
<h1>My files</h1>
{content}
<section>
<Link href="/" label="<< Return" />
</section>
</main>
);
}
});

View file

@ -20,7 +20,8 @@ var ReportPage = React.createClass({
},
render: function() {
return (
<main>
<main className="page">
<SubPageLogo />
<h1>Report a bug</h1>
<section>
<p>Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!</p>

View file

@ -73,7 +73,8 @@ var SettingsPage = React.createClass({
}
return (
<main>
<main className="page">
<SubPageLogo />
<h1>Settings</h1>
<section>
<h4>Run on startup</h4>

View file

@ -4,7 +4,8 @@ var StartPage = React.createClass({
},
render: function() {
return (
<main>
<main className="page">
<SubPageLogo />
<h1>LBRY has closed</h1>
<Link href="lbry://lbry" label="Click here to start LBRY" />
</main>

View file

@ -1,6 +1,6 @@
var videoStyle = {
width: '100%',
height: '100%',
// height: '100%',
backgroundColor: '#000'
};
@ -19,19 +19,6 @@ var WatchPage = React.createClass({
lbry.getStream(this.props.name);
this.updateLoadStatus();
},
reloadIfNeeded: function() {
// Fallback option for loading problems: every 15 seconds, if the video hasn't reported being
// playable yet, ask it to reload.
if (!this.state.readyToPlay) {
this._video.load()
setTimeout(() => { this.reloadIfNeeded() }, 15000);
}
},
onCanPlay: function() {
this.setState({
readyToPlay: true
});
},
updateLoadStatus: function() {
lbry.getFileStatus(this.props.name, (status) => {
if (!status || status.code != 'running' || status.written_bytes == 0) {
@ -44,31 +31,20 @@ var WatchPage = React.createClass({
setTimeout(() => { this.updateLoadStatus() }, 250);
} else {
this.setState({
loadStatusMessage: "Buffering",
downloadStarted: true,
});
setTimeout(() => { this.reloadIfNeeded() }, 15000);
readyToPlay: true
})
flowplayer('player', 'js/flowplayer/flowplayer-3.2.18.swf');
}
});
},
render: function() {
if (!this.state.downloadStarted) {
var video = null;
} else {
// If the download has started, render the <video> behind the scenes so it can start loading.
// When the video is actually ready to play, the loading text is hidden and the video shown.
var video = <video src={"/view?name=" + this.props.name} style={videoStyle}
className={this.state.readyToPlay ? '' : 'hidden'} controls
onCanPlay={this.onCanPlay} ref={(video) => {this._video = video}}/>;
}
return (
<main>
<main className="page full-width">
<div className={this.state.readyToPlay ? 'hidden' : ''}>
<h3>Loading lbry://{this.props.name}</h3>
{this.state.loadStatusMessage}...
</div>
{video}
<a id="player" href={"/view?name=" + this.props.name} style={videoStyle} />
</main>
);
}

View file

@ -13,6 +13,22 @@ body
position: relative;
}
.page {
margin-left: auto;
margin-right: auto;
width: 800px;
&.full-width {
width: 100%;
}
}
.icon-fixed-width {
/* This borrowed is from a component of Font Awesome we're not using, maybe add it? */
width: (18em / 14);
text-align: center;
}
section
{
margin-bottom: $spacing-vertical;
@ -22,7 +38,7 @@ section
}
}
h1 { font-size: 2.0em; margin-bottom: $spacing-vertical / 2; margin-top: $spacing-vertical * 1.5; }
h1 { font-size: 2.0em; margin-bottom: $spacing-vertical / 2; margin-top: $spacing-vertical; }
h2 { font-size: 1.75em; }
h3 { font-size: 1.4em; }
h4 { font-size: 1.2em; }
@ -127,15 +143,34 @@ input[type="search"]
.button-text
{
color: $color-primary;
text-decoration: underline;
.icon
{
&:first-child {
padding-right: 5px;
}
&:last-child:not(:only-child) {
padding-left: 5px;
}
}
&:not(.no-underline) {
text-decoration: underline;
.icon {
text-decoration: none;
}
}
&:hover
{
opacity: 0.70;
transition: opacity .225s ease;
text-decoration: underline;
.icon {
text-decoration: none;
}
}
}
.icon {
.icon:only-child {
position: relative;
top: 0.16em;
}