Merge pull request #109 from lbryio/development

Merge development into master
This commit is contained in:
alexliebowitz 2016-12-29 16:59:40 -05:00 committed by GitHub
commit cadb901209
33 changed files with 970 additions and 267 deletions

6
.babelrc Normal file
View file

@ -0,0 +1,6 @@
{
"presets": [
"es2015",
"react"
],
}

239
.eslintrc.js Normal file
View file

@ -0,0 +1,239 @@
module.exports = {
"extends": "airbnb",
"plugins": [
"react",
"jsx-a11y",
"import"
],
"env": {
"browser": true,
},
// Grabbed from https://gist.github.com/cletusw/e01a85e399ab563b1236
"rules": {
////////// Possible Errors //////////
"no-comma-dangle": 0, // disallow trailing commas in object literals
"no-cond-assign": 0, // disallow assignment in conditional expressions
"no-console": 0, // disallow use of console (off by default in the node environment)
"no-constant-condition": 0, // disallow use of constant expressions in conditions
"no-control-regex": 0, // disallow control characters in regular expressions
"no-debugger": 0, // disallow use of debugger
"no-dupe-keys": 0, // disallow duplicate keys when creating object literals
"no-empty": 0, // disallow empty statements
"no-empty-class": 0, // disallow the use of empty character classes in regular expressions
"no-ex-assign": 0, // disallow assigning to the exception in a catch block
"no-extra-boolean-cast": 0, // disallow double-negation boolean casts in a boolean context
"no-extra-parens": 0, // disallow unnecessary parentheses (off by default)
"no-extra-semi": 0, // disallow unnecessary semicolons
"no-func-assign": 0, // disallow overwriting functions written as function declarations
"no-inner-declarations": 0, // disallow function or variable declarations in nested blocks
"no-invalid-regexp": 0, // disallow invalid regular expression strings in the RegExp constructor
"no-irregular-whitespace": 0, // disallow irregular whitespace outside of strings and comments
"no-negated-in-lhs": 0, // disallow negation of the left operand of an in expression
"no-obj-calls": 0, // disallow the use of object properties of the global object (Math and JSON) as functions
"no-regex-spaces": 0, // disallow multiple spaces in a regular expression literal
"no-reserved-keys": 0, // disallow reserved words being used as object literal keys (off by default)
"no-sparse-arrays": 0, // disallow sparse arrays
"no-unreachable": 0, // disallow unreachable statements after a return, throw, continue, or break statement
"use-isnan": 0, // disallow comparisons with the value NaN
"valid-jsdoc": 0, // Ensure JSDoc comments are valid (off by default)
"valid-typeof": 0, // Ensure that the results of typeof are compared against a valid string
////////// Best Practices //////////
"block-scoped-var": 0, // treat var statements as if they were block scoped (off by default)
"complexity": 0, // specify the maximum cyclomatic complexity allowed in a program (off by default)
"consistent-return": 0, // require return statements to either always or never specify values
"curly": 0, // specify curly brace conventions for all control statements
"default-case": 0, // require default case in switch statements (off by default)
"dot-notation": 0, // encourages use of dot notation whenever possible
"eqeqeq": 0, // require the use of === and !==
"guard-for-in": 0, // make sure for-in loops have an if statement (off by default)
"no-alert": 0, // disallow the use of alert, confirm, and prompt
"no-caller": 0, // disallow use of arguments.caller or arguments.callee
"no-div-regex": 0, // disallow division operators explicitly at beginning of regular expression (off by default)
"no-else-return": 0, // disallow else after a return in an if (off by default)
"no-empty-label": 0, // disallow use of labels for anything other then loops and switches
"no-eq-null": 0, // disallow comparisons to null without a type-checking operator (off by default)
"no-eval": 0, // disallow use of eval()
"no-extend-native": 0, // disallow adding to native types
"no-extra-bind": 0, // disallow unnecessary function binding
"no-fallthrough": 0, // disallow fallthrough of case statements
"no-floating-decimal": 0, // disallow the use of leading or trailing decimal points in numeric literals (off by default)
"no-implied-eval": 0, // disallow use of eval()-like methods
"no-iterator": 0, // disallow usage of __iterator__ property
"no-labels": 0, // disallow use of labeled statements
"no-lone-blocks": 0, // disallow unnecessary nested blocks
"no-loop-func": 0, // disallow creation of functions within loops
"no-multi-spaces": 0, // disallow use of multiple spaces
"no-multi-str": 0, // disallow use of multiline strings
"no-native-reassign": 0, // disallow reassignments of native objects
"no-new": 0, // disallow use of new operator when not part of the assignment or comparison
"no-new-func": 0, // disallow use of new operator for Function object
"no-new-wrappers": 0, // disallows creating new instances of String, Number, and Boolean
"no-octal": 0, // disallow use of octal literals
"no-octal-escape": 0, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251";
"no-process-env": 0, // disallow use of process.env (off by default)
"no-proto": 0, // disallow usage of __proto__ property
"no-redeclare": 0, // disallow declaring the same variable more then once
"no-return-assign": 0, // disallow use of assignment in return statement
"no-script-url": 0, // disallow use of javascript: urls.
"no-self-compare": 0, // disallow comparisons where both sides are exactly the same (off by default)
"no-sequences": 0, // disallow use of comma operator
"no-unused-expressions": 0, // disallow usage of expressions in statement position
"no-void": 0, // disallow use of void operator (off by default)
"no-warning-comments": 0, // disallow usage of configurable warning terms in comments, e.g. TODO or FIXME (off by default)
"no-with": 0, // disallow use of the with statement
"radix": 0, // require use of the second argument for parseInt() (off by default)
"vars-on-top": 0, // requires to declare all vars on top of their containing scope (off by default)
"wrap-iife": 0, // require immediate function invocation to be wrapped in parentheses (off by default)
"yoda": 0, // require or disallow Yoda conditions
////////// Strict Mode //////////
"global-strict": 0, // (deprecated) require or disallow the "use strict" pragma in the global scope (off by default in the node environment)
"no-extra-strict": 0, // (deprecated) disallow unnecessary use of "use strict"; when already in strict mode
"strict": 0, // controls location of Use Strict Directives
////////// Variables //////////
"no-catch-shadow": 0, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment)
"no-delete-var": 0, // disallow deletion of variables
"no-label-var": 0, // disallow labels that share a name with a variable
"no-shadow": 0, // disallow declaration of variables already declared in the outer scope
"no-shadow-restricted-names": 0, // disallow shadowing of names such as arguments
"no-undef": 1, // disallow use of undeclared variables unless mentioned in a /*global */ block
"no-undef-init": 0, // disallow use of undefined when initializing variables
"no-undefined": 0, // disallow use of undefined variable (off by default)
"no-unused-vars": 0, // disallow declaration of variables that are not used in the code
"no-use-before-define": 0, // disallow use of variables before they are defined
////////// Node.js //////////
"handle-callback-err": 0, // enforces error handling in callbacks (off by default) (on by default in the node environment)
"no-mixed-requires": 0, // disallow mixing regular variable and require declarations (off by default) (on by default in the node environment)
"no-new-require": 0, // disallow use of new operator with the require function (off by default) (on by default in the node environment)
"no-path-concat": 0, // disallow string concatenation with __dirname and __filename (off by default) (on by default in the node environment)
"no-process-exit": 0, // disallow process.exit() (on by default in the node environment)
"no-restricted-modules": 0, // restrict usage of specified node modules (off by default)
"no-sync": 0, // disallow use of synchronous methods (off by default)
////////// Stylistic Issues //////////
"brace-style": 0, // enforce one true brace style (off by default)
"camelcase": 0, // require camel case names
"comma-spacing": 0, // enforce spacing before and after comma
"comma-style": 0, // enforce one true comma style (off by default)
"consistent-this": 0, // enforces consistent naming when capturing the current execution context (off by default)
"eol-last": 0, // enforce newline at the end of file, with no multiple empty lines
"func-names": 0, // require function expressions to have a name (off by default)
"func-style": 0, // enforces use of function declarations or expressions (off by default)
"key-spacing": 0, // enforces spacing between keys and values in object literal properties
"max-nested-callbacks": 0, // specify the maximum depth callbacks can be nested (off by default)
"new-cap": 0, // require a capital letter for constructors
"new-parens": 0, // disallow the omission of parentheses when invoking a constructor with no arguments
"no-array-constructor": 0, // disallow use of the Array constructor
"no-inline-comments": 0, // disallow comments inline after code (off by default)
"no-lonely-if": 0, // disallow if as the only statement in an else block (off by default)
"no-mixed-spaces-and-tabs": 0, // disallow mixed spaces and tabs for indentation
"no-multiple-empty-lines": 0, // disallow multiple empty lines (off by default)
"no-nested-ternary": 0, // disallow nested ternary expressions (off by default)
"no-new-object": 0, // disallow use of the Object constructor
"no-space-before-semi": 0, // disallow space before semicolon
"no-spaced-func": 0, // disallow space between function identifier and application
"no-ternary": 0, // disallow the use of ternary operators (off by default)
"no-trailing-spaces": 0, // disallow trailing whitespace at the end of lines
"no-underscore-dangle": 0, // disallow dangling underscores in identifiers
"no-wrap-func": 0, // disallow wrapping of non-IIFE statements in parens
"one-var": 0, // allow just one var statement per function (off by default)
"operator-assignment": 0, // require assignment operator shorthand where possible or prohibit it entirely (off by default)
"padded-blocks": 0, // enforce padding within blocks (off by default)
"quote-props": 0, // require quotes around object literal property names (off by default)
"quotes": 0, // specify whether double or single quotes should be used
"semi": 0, // require or disallow use of semicolons instead of ASI
"sort-vars": 0, // sort variables within the same declaration block (off by default)
"space-after-function-name": 0, // require a space after function names (off by default)
"space-after-keywords": 0, // require a space after certain keywords (off by default)
"space-before-blocks": 0, // require or disallow space before blocks (off by default)
"space-in-brackets": 0, // require or disallow spaces inside brackets (off by default)
"space-in-parens": 0, // require or disallow spaces inside parentheses (off by default)
"space-infix-ops": 0, // require spaces around operators
"space-return-throw-case": 0, // require a space after return, throw, and case
"space-unary-ops": 0, // Require or disallow spaces before/after unary operators (words on by default, nonwords off by default)
"spaced-line-comment": 0, // require or disallow a space immediately following the // in a line comment (off by default)
"wrap-regex": 0, // require regex literals to be wrapped in parentheses (off by default)
////////// ECMAScript 6 //////////
"no-var": 0, // require let or const instead of var (off by default)
"generator-star": 0, // enforce the position of the * in generator functions (off by default)
////////// Legacy //////////
"max-depth": 0, // specify the maximum depth that blocks can be nested (off by default)
"max-len": 0, // specify the maximum length of a line in your program (off by default)
"max-params": 0, // limits the number of parameters that can be used in the function declaration. (off by default)
"max-statements": 0, // specify the maximum number of statement allowed in a function (off by default)
"no-bitwise": 0, // disallow use of bitwise operators (off by default)
"no-plusplus": 0, // disallow use of unary operators, ++ and -- (off by default)
// There were other errors that were being thrown
"import/newline-after-import": 0,
"object-curly-spacing": 0,
"space-before-function-paren": 0,
"no-restricted-syntax": 0,
"no-unneeded-ternary": 0,
"react/prefer-es6-class": 0,
"arrow-parens": 0,
"jsx-quotes": 0,
"react/sort-comp": 0,
"react/no-unescaped-entities": 0,
"object-shorthand": 0,
"jsx-a11y/label-has-for": 0,
"no-mixed-operators": 0,
"react/jsx-no-bind": 0,
"no-duplicate-case": 0,
"react/jsx-filename-extension": 0,
"jsx-a11y/img-has-alt": 0,
"react/self-closing-comp": 0,
"prefer-const": 0,
"one-var-declaration-per-line": 0,
"react/jsx-indent": 0,
"react/jsx-curly-spacing": 0,
"react/prefer-stateless-function": 0,
"react/jsx-indent-props": 0,
"react/no-find-dom-node": 0,
"react/no-unused-prop-types": 0,
"react/jsx-no-undef": 0,
"react/no-string-refs": 0,
"react/jsx-first-prop-new-line": 0,
"comma-dangle": 0,
"react/no-multi-comp": 0,
"spaced-comment": 0,
"jsx-a11y/anchor-has-content": 0,
"semi-spacing": 0,
"no-param-reassign": 0,
"react/jsx-no-target-blank": 0,
"prefer-arrow-callback": 0,
"react/jsx-space-before-closing": 0,
"react/forbid-prop-types": 0,
"indent": 0,
"import/no-unresolved": 0,
"react/jsx-boolean-value": 0,
"prefer-template": 0,
"react/jsx-wrap-multilines": 0,
"keyword-spacing": 0,
"react/jsx-closing-bracket-location": 0,
"react/react-in-jsx-scope": 0,
"no-useless-escape": 0,
"no-continue": 0,
"react/prop-types": 0
}
};

View file

@ -7,8 +7,7 @@ install:
script:
- mkdir -p dist/css dist/js
- node_modules/.bin/node-sass scss/all.scss dist/css/all.css
- node_modules/.bin/babel -V
- node_modules/.bin/babel --presets es2015,react --out-dir dist/js js
- node_modules/.bin/webpack
- mkdir upload
- cd dist; zip -r ../upload/dist.zip *; cd -
- .travis/echo_sha.sh > upload/data.json

BIN
dist.zip

Binary file not shown.

34
dist/index.html vendored
View file

@ -20,38 +20,6 @@
</head>
<body>
<div id="canvas"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-modal/1.5.2/react-modal.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.7.4/polyfill.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Clamp.js/0.5.1/clamp.min.js"></script>
<script src="./js/mediaelement/jquery.js"></script>
<script src="./js/mediaelement/mediaelement-and-player.min.js"></script>
<script src="./js/lbry.js?i=0"></script>
<script src="./js/lighthouse.js?i=0"></script>
<script src="./js/component/common.js?i=0"></script>
<script src="./js/component/form.js?i=0"></script>
<script src="./js/component/link.js?i=0"></script>
<script src="./js/component/menu.js?i=0"></script>
<script src="./js/component/modal.js?i=0"></script>
<script src="./js/component/header.js?i=0"></script>
<script src="./js/component/drawer.js?i=0"></script>
<script src="./js/component/splash.js?i=0"></script>
<script src="./js/component/load_screen.js?i=0"></script>
<script src="./js/page/discover.js?i=0"></script>
<script src="./js/page/settings.js?i=0"></script>
<script src="./js/page/help.js?i=0"></script>
<script src="./js/page/watch.js?i=0"></script>
<script src="./js/page/report.js?i=0"></script>
<script src="./js/page/my_files.js?i=0"></script>
<script src="./js/page/publish.js?i=0"></script>
<script src="./js/page/start.js?i=0"></script>
<script src="./js/page/claim_code.js?i=0"></script>
<script src="./js/page/referral.js?i=0"></script>
<script src="./js/page/wallet.js?i=0"></script>
<script src="./js/page/show.js?i=0"></script>
<script src="./js/page/wallet.js?i=0"></script>
<script src="./js/app.js?i=0"></script>
<script src="./js/main.js?i=0"></script>
<script src="./js/bundle.js"></script>
</body>
</html>

View file

@ -1,3 +1,23 @@
import React from 'react';
import lbry from './lbry.js';
import SettingsPage from './page/settings.js';
import HelpPage from './page/help.js';
import WatchPage from './page/watch.js';
import ReportPage from './page/report.js';
import MyFilesPage from './page/my_files.js';
import StartPage from './page/start.js';
import ClaimCodePage from './page/claim_code.js';
import ReferralPage from './page/referral.js';
import WalletPage from './page/wallet.js';
import DetailPage from './page/show.js';
import PublishPage from './page/publish.js';
import DiscoverPage from './page/discover.js';
import SplashScreen from './component/splash.js';
import Drawer from './component/drawer.js';
import Header from './component/header.js';
import Modal from './component/modal.js';
import {Link} from './component/link.js';
var App = React.createClass({
_error_key_labels: {
connectionString: 'API connection string',
@ -167,10 +187,6 @@ var App = React.createClass({
case 'send':
case 'receive':
return <WalletPage viewingPage={this.state.viewingPage} />;
case 'send':
return <SendPage />;
case 'receive':
return <ReceivePage />;
case 'show':
return <DetailPage name={this.state.pageArgs} />;
case 'publish':
@ -220,3 +236,6 @@ var App = React.createClass({
);
}
});
export default App;

View file

@ -1,17 +1,22 @@
//component/icon.js
import React from 'react';
import lbry from '../lbry.js';
import $clamp from 'clamp';
var Icon = React.createClass({
//component/icon.js
export let Icon = React.createClass({
propTypes: {
style: React.PropTypes.object,
fixed: React.PropTypes.bool,
className: React.PropTypes.string,
},
render: function() {
var className = 'icon ' + ('fixed' in this.props ? 'icon-fixed-width ' : '') + this.props.icon;
var className = ('icon ' + ('fixed' in this.props ? 'icon-fixed-width ' : '') + this.props.icon + ' ' +
(this.props.className || ''));
return <span className={className} style={this.props.style}></span>
}
});
var TruncatedText = React.createClass({
export let TruncatedText = React.createClass({
propTypes: {
lines: React.PropTypes.number,
height: React.PropTypes.string,
@ -35,7 +40,7 @@ var TruncatedText = React.createClass({
}
});
var BusyMessage = React.createClass({
export let BusyMessage = React.createClass({
propTypes: {
message: React.PropTypes.string
},
@ -55,7 +60,7 @@ var toolTipStyle = {
backgroundColor: '#fff',
fontSize: '14px',
};
var ToolTip = React.createClass({
export let ToolTip = React.createClass({
propTypes: {
open: React.PropTypes.bool.isRequired,
onMouseOut: React.PropTypes.func
@ -78,11 +83,11 @@ var creditAmountStyle = {
color: '#aaa',
};
var CurrencySymbol = React.createClass({
export let CurrencySymbol = React.createClass({
render: function() { return <span>LBC</span>; }
});
var CreditAmount = React.createClass({
export let CreditAmount = React.createClass({
propTypes: {
amount: React.PropTypes.number,
precision: React.PropTypes.number
@ -101,7 +106,7 @@ var CreditAmount = React.createClass({
var addressStyle = {
fontFamily: '"Consolas", "Lucida Console", "Adobe Source Code Pro", monospace',
};
var Address = React.createClass({
export let Address = React.createClass({
propTypes: {
address: React.PropTypes.string,
},
@ -112,7 +117,7 @@ var Address = React.createClass({
}
});
var Thumbnail = React.createClass({
export let Thumbnail = React.createClass({
_defaultImageUri: '/img/default-thumb.svg',
_maxLoadTime: 10000,

View file

@ -1,3 +1,7 @@
import lbry from '../lbry.js';
import React from 'react';
import {Link} from './link.js';
var DrawerItem = React.createClass({
getDefaultProps: function() {
return {
@ -46,4 +50,7 @@ var Drawer = React.createClass({
</nav>
);
}
});
});
export default Drawer;

View file

@ -1,9 +1,12 @@
import React from 'react';
var requiredFieldWarningStyle = {
color: '#cc0000',
transition: 'opacity 400ms ease-in',
};
var FormField = React.createClass({
_fieldRequiredText: 'This field is required',
_type: null,
_element: null,
@ -13,7 +16,8 @@ var FormField = React.createClass({
},
getInitialState: function() {
return {
warningState: 'hidden',
adviceState: 'hidden',
adviceText: null,
}
},
componentWillMount: function() {
@ -25,22 +29,26 @@ var FormField = React.createClass({
this._element = this.props.type;
}
},
warnRequired: function() {
showAdvice: function(text) {
this.setState({
warningState: 'shown',
adviceState: 'shown',
adviceText: text,
});
setTimeout(() => {
this.setState({
warningState: 'fading',
adviceState: 'fading',
});
setTimeout(() => {
this.setState({
warningState: 'hidden',
adviceState: 'hidden',
});
}, 450);
}, 5000);
},
warnRequired: function() {
this.showAdvice(this._fieldRequiredText);
},
focus: function() {
this.refs.field.focus();
},
@ -55,24 +63,45 @@ var FormField = React.createClass({
return this.refs.field.options[this.refs.field.selectedIndex];
},
render: function() {
var warningStyle = Object.assign({}, requiredFieldWarningStyle);
if (this.state.warningState == 'fading') {
warningStyle.opacity = '0';
}
// Pass all unhandled props to the field element
var otherProps = Object.assign({}, this.props);
delete otherProps.type;
delete otherProps.hidden;
return (
<span className={this.props.hidden ? 'hidden' : ''}>
<this._element type={this._type} name={this.props.name} ref="field" placeholder={this.props.placeholder}
{...otherProps}>
{this.props.children}
</this._element>
<span className={this.state.warningState == 'hidden' ? 'hidden' : ''} style={warningStyle}> This field is required</span>
</span>
!this.props.hidden
? <div className="form-field-container">
<this._element type={this._type} className="form-field" name={this.props.name} ref="field" placeholder={this.props.placeholder}
{...otherProps}>
{this.props.children}
</this._element>
<FormFieldAdvice field={this.refs.field} state={this.state.adviceState}>{this.state.adviceText}</FormFieldAdvice>
</div>
: null
);
}
});
var FormFieldAdvice = React.createClass({
propTypes: {
state: React.PropTypes.string.isRequired,
},
render: function() {
return (
this.props.state != 'hidden'
? <div className="form-field-advice-container">
<div className={'form-field-advice' + (this.props.state == 'fading' ? ' form-field-advice--fading' : '')}>
<Icon icon="icon-caret-up" className="form-field-advice__arrow" />
<div className="form-field-advice__content-container">
<span className="form-field-advice__content">
{this.props.children}
</span>
</div>
</div>
</div>
: null
);
}
});
export default FormField;

View file

@ -1,3 +1,6 @@
import React from 'react';
import {Link} from './link.js';
var Header = React.createClass({
getInitialState: function() {
return {
@ -81,4 +84,6 @@ var SubHeader = React.createClass({
</nav>
);
}
});
});
export default Header;

View file

@ -1,4 +1,10 @@
var Link = React.createClass({
import React from 'react';
import lbry from '../lbry.js';
import Modal from './modal.js';
import {Icon, ToolTip} from './common.js';
export let Link = React.createClass({
handleClick: function() {
if (this.props.onClick) {
this.props.onClick();
@ -27,7 +33,7 @@ var linkContainerStyle = {
position: 'relative',
};
var ToolTipLink = React.createClass({
export let ToolTipLink = React.createClass({
getInitialState: function() {
return {
showTooltip: false,
@ -73,7 +79,7 @@ var ToolTipLink = React.createClass({
}
});
var DownloadLink = React.createClass({
export let DownloadLink = React.createClass({
propTypes: {
type: React.PropTypes.string,
streamName: React.PropTypes.string,
@ -107,16 +113,16 @@ var DownloadLink = React.createClass({
downloading: true
});
lbry.getCostEstimate(this.props.streamName, (amount) => {
lbry.getCostInfoForName(this.props.streamName, ({cost}) => {
lbry.getBalance((balance) => {
if (amount > balance) {
if (cost > balance) {
this.setState({
modal: 'notEnoughCredits',
downloading: false
});
} else {
lbry.getStream(this.props.streamName, (streamInfo) => {
if (typeof streamInfo !== 'object') {
if (streamInfo === null || typeof streamInfo !== 'object') {
this.setState({
modal: 'timedOut',
downloading: false,
@ -138,8 +144,9 @@ var DownloadLink = React.createClass({
<span className="button-container">
<Link button={this.props.button} hidden={this.props.hidden} style={this.props.style}
disabled={this.state.downloading} label={label} icon={this.props.icon} onClick={this.handleClick} />
<Modal isOpen={this.state.modal == 'downloadStarted'} onConfirmed={this.closeModal}>
Downloading to {this.state.filePath}
<Modal className="download-started-modal" isOpen={this.state.modal == 'downloadStarted'} onConfirmed={this.closeModal}>
<p>Downloading to:</p>
<div className="download-started-modal__file-path">{this.state.filePath}</div>
</Modal>
<Modal isOpen={this.state.modal == 'notEnoughCredits'} onConfirmed={this.closeModal}>
You don't have enough LBRY credits to pay for this stream.
@ -152,7 +159,7 @@ var DownloadLink = React.createClass({
}
});
var WatchLink = React.createClass({
export let WatchLink = React.createClass({
propTypes: {
type: React.PropTypes.string,
streamName: React.PropTypes.string,
@ -165,9 +172,9 @@ var WatchLink = React.createClass({
this.setState({
loading: true,
})
lbry.getCostEstimate(this.props.streamName, (amount) => {
lbry.getCostInfoForName(this.props.streamName, ({cost}) => {
lbry.getBalance((balance) => {
if (amount > balance) {
if (cost > balance) {
this.setState({
modal: 'notEnoughCredits',
loading: false,
@ -207,4 +214,4 @@ var WatchLink = React.createClass({
</span>
);
}
});
});

View file

@ -1,3 +1,7 @@
import React from 'react';
import lbry from '../lbry.js';
import {BusyMessage, Icon} from './common.js';
var loadScreenStyle = {
color: 'white',
backgroundImage: 'url(' + lbry.imagePath('lbry-bg.png') + ')',
@ -46,4 +50,7 @@ var LoadScreen = React.createClass({
</div>
);
}
});
});
export default LoadScreen;

View file

@ -1,9 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {Icon} from './common.js';
// Generic menu styles
var menuStyle = {
export let menuStyle = {
whiteSpace: 'nowrap'
};
var Menu = React.createClass({
export let Menu = React.createClass({
handleWindowClick: function(e) {
if (this.props.toggleButton && ReactDOM.findDOMNode(this.props.toggleButton).contains(e.target)) {
// Toggle button was clicked
@ -40,10 +44,10 @@ var Menu = React.createClass({
}
});
var menuItemStyle = {
export let menuItemStyle = {
display: 'block',
};
var MenuItem = React.createClass({
export let MenuItem = React.createClass({
propTypes: {
href: React.PropTypes.string,
label: React.PropTypes.string,
@ -67,4 +71,4 @@ var MenuItem = React.createClass({
</a>
);
}
});
});

View file

@ -1,3 +1,8 @@
import React from 'react';
import ReactModal from 'react-modal';
import {Link} from './link.js';
var Modal = React.createClass({
propTypes: {
type: React.PropTypes.oneOf(['alert', 'confirm', 'custom']),
@ -57,3 +62,5 @@ var Modal = React.createClass({
);
}
});
export default Modal;

View file

@ -1,3 +1,7 @@
import React from 'react';
import lbry from '../lbry.js';
import LoadScreen from './load_screen.js';
var SplashScreen = React.createClass({
propTypes: {
message: React.PropTypes.string,
@ -32,4 +36,6 @@ var SplashScreen = React.createClass({
render: function() {
return <LoadScreen message={this.props.message} details={this.state.details} isWarning={this.state.isLagging} />;
}
});
});
export default SplashScreen;

View file

@ -1,7 +1,10 @@
import lighthouse from './lighthouse.js';
var lbry = {
isConnected: false,
rootPath: '.',
daemonConnectionString: 'http://localhost:5279/lbryapi',
webUiUri: 'http://localhost:5279',
colors: {
primary: '#155B4A'
},
@ -178,10 +181,59 @@ lbry.getMyClaim = function(name, callback) {
lbry.call('get_my_claim', { name: name }, callback);
}
lbry.getCostEstimate = function(name, callback) {
lbry.getKeyFee = function(name, callback) {
lbry.call('get_est_cost', { name: name }, callback);
}
lbry.getTotalCost = function(name, size, callback) {
lbry.call('get_est_cost', {
name: name,
size: size,
}, callback);
}
lbry.getPeersForBlobHash = function(blobHash, callback) {
lbry.call('get_peers_for_hash', { blob_hash: blobHash }, callback)
}
lbry.getCostInfoForName = function(name, callback) {
/**
* Takes a LBRY name; will first try and calculate a total cost using
* Lighthouse. If Lighthouse can't be reached, it just retrives the
* key fee.
*
* Returns an object with members:
* - cost: Number; the calculated cost of the name
* - includes_data: Boolean; indicates whether or not the data fee info
* from Lighthouse is included.
*/
function getCostWithData(name, size, callback) {
lbry.getTotalCost(name, size, (cost) => {
callback({
cost: cost,
includesData: true,
});
});
}
function getCostNoData(name, callback) {
lbry.getKeyFee(name, (cost) => {
callback({
cost: cost,
includesData: false,
});
});
}
lighthouse.getSizeForName(name, (size) => {
getCostWithData(name, size, callback);
}, () => {
getCostNoData(name, callback);
}, () => {
getCostNoData(name, callback);
});
}
lbry.getFileStatus = function(name, callback) {
lbry.call('get_lbry_file', { 'name': name }, callback);
}
@ -190,6 +242,22 @@ lbry.getFilesInfo = function(callback) {
lbry.call('get_lbry_files', {}, callback);
}
lbry.getFileInfoByName = function(name, callback) {
lbry.call('get_lbry_file', {name: name}, callback);
}
lbry.getFileInfoBySdHash = function(sdHash, callback) {
lbry.call('get_lbry_file', {sd_hash: sdHash}, callback);
}
lbry.getFileInfoByFilename = function(filename, callback) {
lbry.call('get_lbry_file', {file_name: filename}, callback);
}
lbry.getMyClaims = function(callback) {
lbry.call('get_name_claims', {}, callback);
}
lbry.startFile = function(name, callback) {
lbry.call('start_lbry_file', { name: name }, callback);
}
@ -310,6 +378,9 @@ lbry.setClientSetting = function(setting, value) {
return localStorage.setItem('setting_' + setting, JSON.stringify(value));
}
lbry.getSessionInfo = function(callback) {
lbry.call('get_lbry_session_info', {}, callback);
}
lbry.reportBug = function(message, callback) {
lbry.call('upload_log', {
@ -327,8 +398,15 @@ lbry.formatCredits = function(amount, precision)
}
lbry.formatName = function(name) {
// Converts LBRY name to standard format (all lower case, no special characters)
return name.toLowerCase().replace(/[^a-z0-9\-]/g, '');
// Converts LBRY name to standard format (all lower case, no special characters, spaces replaced by dashes)
name = name.replace('/\s+/g', '-');
name = name.toLowerCase().replace(/[^a-z0-9\-]/g, '');
return name;
}
lbry.nameIsValid = function(name, checkCase=true) {
const regexp = new RegExp('^[a-z0-9-]+$', checkCase ? '' : 'i');
return regexp.test(name);
}
lbry.loadJs = function(src, type, onload)
@ -341,7 +419,7 @@ lbry.loadJs = function(src, type, onload)
newScriptTag.type = type;
if (onload)
{
newScript.onload = onload;
newScriptTag.onload = onload;
}
lbryScriptTag.parentNode.insertBefore(newScriptTag, lbryScriptTag);
}
@ -380,3 +458,4 @@ lbry.stop = function(callback) {
};
export default lbry;

View file

@ -1,11 +1,13 @@
lbry.lighthouse = {
import lbry from './lbry.js';
var lighthouse = {
_search_timeout: 5000,
_max_search_tries: 5,
servers: [
'http://lighthouse1.lbry.io:50005',
'http://lighthouse2.lbry.io:50005',
'http://lighthouse3.lbry.io:50005',
'http://lighthouse4.lbry.io:50005',
'http://lighthouse5.lbry.io:50005',
'http://lighthouse6.lbry.io:50005',
],
path: '/',
@ -13,24 +15,34 @@ lbry.lighthouse = {
lbry.jsonrpc_call(this.server + this.path, method, params, callback, errorCallback, connectFailedCallback, timeout);
},
search: function(query, callback) {
search: function(query, callback, errorCallback, connectFailedCallback, timeout) {
let handleSearchFailed = function(tryNum=0) {
if (tryNum > lbry.lighthouse._max_search_tries) {
throw new Error(`Could not connect to Lighthouse server. Last server attempted: ${lbry.lighthouse.server}`);
if (tryNum > lighthouse._max_search_tries) {
if (connectFailedCallback) {
connectFailedCallback();
} else {
throw new Error(`Could not connect to Lighthouse server. Last server attempted: ${lighthouse.server}`);
}
} else {
// Randomly choose one of the other search servers to switch to
let otherServers = lbry.lighthouse.servers.slice();
otherServers.splice(otherServers.indexOf(lbry.lighthouse.server), 1);
lbry.lighthouse.server = otherServers[Math.round(Math.random() * (otherServers.length - 1))];
let otherServers = lighthouse.servers.slice();
otherServers.splice(otherServers.indexOf(lighthouse.server), 1);
lighthouse.server = otherServers[Math.round(Math.random() * (otherServers.length - 1))];
lbry.lighthouse.call('search', [query], callback, undefined, function() {
lighthouse.call('search', [query], callback, errorCallback, function() {
handleSearchFailed(tryNum + 1);
}, lbry.lighthouse._search_timeout);
}, lighthouse._search_timeout);
}
}
lbry.lighthouse.call('search', [query], callback, undefined, function() { handleSearchFailed() }, lbry.lighthouse._search_timeout);
lighthouse.call('search', [query], callback, errorCallback, function() { handleSearchFailed() }, lighthouse._search_timeout);
},
getSizeForName: function(name, callback, errorCallback, connectFailedCallback, timeout) {
return lighthouse.call('get_size_for_name', [name], callback, errorCallback, connectFailedCallback, timeout);
}
};
lbry.lighthouse.server = lbry.lighthouse.servers[Math.round(Math.random() * (lbry.lighthouse.servers.length - 1))];
lighthouse.server = lighthouse.servers[Math.round(Math.random() * (lighthouse.servers.length - 1))];
export default lighthouse;

View file

@ -1,4 +1,10 @@
//main.js
import React from 'react';
import ReactDOM from 'react-dom';
import lbry from './lbry.js';
import App from './app.js';
import SplashScreen from './component/splash.js';
var init = function() {
var canvas = document.getElementById('canvas');

View file

@ -1,3 +1,8 @@
import React from 'react';
import lbry from '../lbry.js';
import Modal from '../component/modal.js';
import {Link} from '../component/link.js';
var claimCodeContentStyle = {
display: 'inline-block',
textAlign: 'left',
@ -126,10 +131,10 @@ var ClaimCodePage = React.createClass({
<Modal isOpen={this.state.modal == 'codeRedeemed'} onConfirmed={this.handleFinished}>
Your invite code has been redeemed.
{this.state.referralCredits > 0
? `You have also earned {referralCredits} credits from referrals. A total of {activationCredits + referralCredits}
? `You have also earned ${referralCredits} credits from referrals. A total of ${activationCredits + referralCredits}
will be added to your balance shortly.`
: (this.state.activationCredits > 0
? `{this.state.activationCredits} credits will be added to your balance shortly.`
? `${this.state.activationCredits} credits will be added to your balance shortly.`
: 'The credits will be added to your balance shortly.')}
</Modal>
<Modal isOpen={this.state.modal == 'skipped'} onConfirmed={this.handleFinished}>
@ -143,3 +148,5 @@ var ClaimCodePage = React.createClass({
);
}
});
export default ClaimCodePage;

View file

@ -1,3 +1,9 @@
import React from 'react';
import lbry from '../lbry.js';
import lighthouse from '../lighthouse.js';
import {Link, ToolTipLink, DownloadLink, WatchLink} from '../component/link.js';
import {Thumbnail, CreditAmount, TruncatedText} from '../component/common.js';
var fetchResultsStyle = {
color: '#888',
textAlign: 'center',
@ -40,8 +46,7 @@ var SearchResults = React.createClass({
var mediaType = lbry.getMediaType(result.value.content_type);
rows.push(
<SearchResultRow key={result.name} name={result.name} title={result.value.title} imgUrl={result.value.thumbnail}
description={result.value.description} cost={result.cost} nsfw={result.value.nsfw}
mediaType={mediaType} />
description={result.value.description} nsfw={result.value.nsfw} mediaType={mediaType} />
);
});
return (
@ -87,6 +92,8 @@ var SearchResultRow = React.createClass({
return {
downloading: false,
isHovered: false,
cost: null,
costIncludesData: null,
}
},
handleMouseOver: function() {
@ -99,6 +106,21 @@ var SearchResultRow = React.createClass({
isHovered: false,
});
},
componentWillMount: function() {
if ('cost' in this.props) {
this.setState({
cost: this.props.cost,
costIncludesData: this.props.costIncludesData,
});
} else {
lbry.getCostInfoForName(this.props.name, ({cost, includesData}) => {
this.setState({
cost: cost,
costIncludesData: includesData,
});
});
}
},
render: function() {
var obscureNsfw = !lbry.getClientSetting('showNsfw') && this.props.nsfw;
if (!this.props.compact) {
@ -116,9 +138,11 @@ var SearchResultRow = React.createClass({
<a href={'/?show=' + this.props.name}><Thumbnail src={this.props.imgUrl} alt={'Photo for ' + (this.props.title || this.props.name)} style={searchRowImgStyle} /></a>
</div>
<div className="span9">
<span style={searchRowCostStyle}>
<CreditAmount amount={this.props.cost} isEstimate={!this.props.available}/>
</span>
{this.state.cost !== null
? <span style={searchRowCostStyle}>
<CreditAmount amount={this.state.cost} isEstimate={!this.state.costIncludesData}/>
</span>
: null}
<div className="meta"><a href={'/?show=' + this.props.name}>lbry://{this.props.name}</a></div>
<h3 style={titleStyle}>
<a href={'/?show=' + this.props.name}>
@ -167,7 +191,7 @@ var FeaturedContentItem = React.createClass({
return {
metadata: null,
title: null,
amount: 0.0,
cost: null,
overlayShowing: false,
};
},
@ -177,21 +201,18 @@ var FeaturedContentItem = React.createClass({
},
componentDidMount: function() {
this.resolveSearch = true;
this._isMounted = true;
lbry.lighthouse.search(this.props.name, function(results) {
var result = results[0];
var metadata = result.value;
if (this.resolveSearch)
{
this.setState({
metadata: metadata,
amount: result.cost,
available: result.available,
title: metadata && metadata.title ? metadata.title : ('lbry://' + this.props.name),
});
lbry.resolveName(this.props.name, (metadata) => {
if (!this._isMounted) {
return;
}
}.bind(this));
this.setState({
metadata: metadata,
title: metadata && metadata.title ? metadata.title : ('lbry://' + this.props.name),
});
});
},
render: function() {
@ -203,7 +224,7 @@ var FeaturedContentItem = React.createClass({
return (<div style={featuredContentItemContainerStyle}>
<SearchResultRow name={this.props.name} title={this.state.title} imgUrl={this.state.metadata.thumbnail}
description={this.state.metadata.description} mediaType={lbry.getMediaType(this.state.metadata.content_type)}
cost={this.state.amount} nsfw={this.state.metadata.nsfw} available={this.state.available} compact />
nsfw={this.state.metadata.nsfw} compact />
</div>);
}
});
@ -257,7 +278,7 @@ var DiscoverPage = React.createClass({
query: this.props.query,
});
lbry.lighthouse.search(this.props.query, this.searchCallback);
lighthouse.search(this.props.query, this.searchCallback);
},
componentDidMount: function() {
@ -297,3 +318,5 @@ var DiscoverPage = React.createClass({
);
}
});
export default DiscoverPage;

View file

@ -1,9 +1,14 @@
//@TODO: Customize advice based on OS
//@TODO: Customize advice based on OS
import React from 'react';
import lbry from '../lbry.js';
import {Link} from '../component/link.js';
var HelpPage = React.createClass({
getInitialState: function() {
return {
versionInfo: null,
lbryId: null,
};
},
componentWillMount: function() {
@ -12,26 +17,34 @@ var HelpPage = React.createClass({
versionInfo: info,
});
});
lbry.getSessionInfo((info) => {
this.setState({
lbryId: info.lbry_id,
});
});
},
componentDidMount: function() {
document.title = "Help";
},
render: function() {
var ver = this.state.versionInfo;
let ver, osName, platform, newVerLink;
if (this.state.versionInfo) {
ver = this.state.versionInfo;
if (ver) {
if (ver.os_system == 'Darwin') {
var osName = (parseInt(ver.os_release.match(/^\d+/)) < 16 ? 'Mac OS X' : 'Mac OS');
osName = (parseInt(ver.os_release.match(/^\d+/)) < 16 ? 'Mac OS X' : 'Mac OS');
var platform = osName + ' ' + ver.os_release;
var newVerLink = 'https://lbry.io/get/lbry.dmg';
platform = `${osName} ${ver.os_release}`
newVerLink = 'https://lbry.io/get/lbry.dmg';
} else if (ver.os_system == 'Linux') {
var platform = 'Linux (' + ver.platform + ')';
var newVerLink = 'https://lbry.io/get/lbry.deb';
platform = `Linux (${ver.platform})`;
newVerLink = 'https://lbry.io/get/lbry.deb';
} else {
var platform = 'Windows (' + ver.platform + ')';
var newVerLink = 'https://lbry.io/get/lbry.msi';
platform = `Windows (${ver.platform})`;
newVerLink = 'https://lbry.io/get/lbry.msi';
}
} else {
ver = null;
}
return (
@ -60,7 +73,7 @@ var HelpPage = React.createClass({
<section className="card">
<h3>About</h3>
{ver.lbrynet_update_available || ver.lbryum_update_available ?
<p>A newer version of LBRY is available. <Link href={newVerLink} label={"Download LBRY " + ver.remote_lbrynet + " now!"} /></p>
<p>A newer version of LBRY is available. <Link href={newVerLink} label={`Download LBRY ${ver.remote_lbrynet} now!`} /></p>
: <p>Your copy of LBRY is up to date.</p>
}
<table className="table-standard">
@ -77,6 +90,10 @@ var HelpPage = React.createClass({
<th>Platform</th>
<td>{platform}</td>
</tr>
<tr>
<th>Installation ID</th>
<td>{this.state.lbryId}</td>
</tr>
</tbody>
</table>
</section>
@ -85,3 +102,5 @@ var HelpPage = React.createClass({
);
}
});
export default HelpPage;

View file

@ -1,3 +1,11 @@
import React from 'react';
import lbry from '../lbry.js';
import {Link, WatchLink} from '../component/link.js';
import {Menu, MenuItem} from '../component/menu.js';
import FormField from '../component/form.js';
import Modal from '../component/modal.js';
import {BusyMessage, Thumbnail} from '../component/common.js';
var moreMenuStyle = {
position: 'absolute',
display: 'block',
@ -24,7 +32,12 @@ var MyFilesRowMoreMenu = React.createClass({
},
handleDeleteConfirmed: function() {
lbry.deleteFile(this.props.lbryUri);
lbry.setState({
this.setState({
modal: null,
});
},
closeModal: function() {
this.setState({
modal: null,
});
},
@ -43,7 +56,8 @@ var MyFilesRowMoreMenu = React.createClass({
<MenuItem onClick={this.handleDeleteClicked} label="Remove and delete file" />
</section>
</Menu>
<Modal isOpen={this.state.modal == 'confirmDelete'} type="confirm" confirmButtonLabel="Delete File" onConfirmed={this.handleDeleteConfirmed}>
<Modal isOpen={this.state.modal == 'confirmDelete'} type="confirm" confirmButtonLabel="Delete File"
onConfirmed={this.handleDeleteConfirmed} onAborted={this.closeModal}>
Are you sure you'd like to delete <cite>{this.props.title}</cite>? This will {this.props.completed ? ' stop the download and ' : ''}
permanently remove the file from your system.
</Modal>
@ -153,14 +167,33 @@ var MyFilesRow = React.createClass({
var MyFilesPage = React.createClass({
_fileTimeout: null,
_fileInfoCheckRate: 300,
_fileInfoCheckNum: 0,
_filesOwnership: {},
_sortFunctions: {
date: function(filesInfo) {
return filesInfo.reverse();
},
title: function(filesInfo) {
return filesInfo.sort(function(a, b) {
console.log('in title sort. a is', a, '; b is', b)
return ((a.metadata ? a.metadata.title.toLowerCase() : a.name) >
(b.metadata ? b.metadata.title.toLowerCase() : b.name));
});
},
filename: function(filesInfo) {
return filesInfo.sort(function(a, b) {
return (a.file_name.toLowerCase() >
b.file_name.toLowerCase());
});
},
},
getInitialState: function() {
return {
filesInfo: null,
filesOwnershipLoaded: false,
publishedFilesSdHashes: null,
filesAvailable: {},
sortBy: 'date',
};
},
getDefaultProps: function() {
@ -172,8 +205,30 @@ var MyFilesPage = React.createClass({
document.title = "My Files";
},
componentWillMount: function() {
this.getFilesOwnership();
this.updateFilesInfo();
if (this.props.show == 'downloaded') {
this.getPublishedFilesSdHashes(() => {
this.updateFilesInfo();
});
} else {
this.updateFilesInfo();
}
},
getPublishedFilesSdHashes: function(callback) {
// Determines which files were published by the user and saves their SD hashes in
// this.state.publishedFilesSdHashes. Used on the Downloads page to filter out claims published
// by the user.
var publishedFilesSdHashes = [];
lbry.getMyClaims((claimsInfo) => {
for (let claimInfo of claimsInfo) {
let metadata = JSON.parse(claimInfo.value);
publishedFilesSdHashes.push(metadata.sources.lbry_sd_hash);
}
this.setState({
publishedFilesSdHashes: publishedFilesSdHashes,
});
callback();
});
},
componentWillUnmount: function() {
if (this._fileTimeout)
@ -181,102 +236,95 @@ var MyFilesPage = React.createClass({
clearTimeout(this._fileTimeout);
}
},
getFilesOwnership: function() {
lbry.getFilesInfo((filesInfo) => {
if (!filesInfo) {
this.setState({
filesOwnershipLoaded: true,
});
return;
}
var ownershipLoadedCount = 0;
for (let i = 0; i < filesInfo.length; i++) {
let fileInfo = filesInfo[i];
lbry.call('get_my_claim', {name: fileInfo.lbry_uri}, (claim) => {
this._filesOwnership[fileInfo.lbry_uri] = !!claim;
ownershipLoadedCount++;
if (ownershipLoadedCount >= filesInfo.length) {
this.setState({
filesOwnershipLoaded: true,
});
}
}, (claim) => {
this._filesOwnership[fileInfo.lbry_uri] = true;
ownershipLoadedCount++;
if (ownershipLoadedCount >= filesInfo.length) {
this.setState({
filesOwnershipLoaded: true,
});
}
});
}
setFilesInfo: function(filesInfo) {
this.setState({
filesInfo: this._sortFunctions[this.state.sortBy](filesInfo),
});
},
handleSortChanged: function(event) {
this.setState({
sortBy: event.target.value,
filesInfo: this._sortFunctions[event.target.value](this.state.filesInfo),
});
},
updateFilesInfo: function() {
lbry.getFilesInfo((filesInfo) => {
if (!filesInfo) {
filesInfo = [];
}
this._fileInfoCheckNum += 1;
if (!(this._fileInfoCheckNum % 5)) {
// Time to update file availability status
if (this.props.show == 'published') {
// We're in the Published tab, so populate this.state.filesInfo with data from the user's claims
lbry.getMyClaims((claimsInfo) => {
let newFilesInfo = [];
let claimInfoProcessedCount = 0;
for (let claimInfo of claimsInfo) {
let metadata = JSON.parse(claimInfo.value);
lbry.getFileInfoBySdHash(metadata.sources.lbry_sd_hash, (fileInfo) => {
claimInfoProcessedCount++;
if (fileInfo !== false) {
newFilesInfo.push(fileInfo);
}
if (claimInfoProcessedCount >= claimsInfo.length) {
this.setFilesInfo(newFilesInfo);
for (let fileInfo of filesInfo) {
let name = fileInfo.lbry_uri;
if (name === null) {
continue;
}
lbry.lighthouse.search(name, (results) => {
var result = results[0];
var available = result.name == name && result.available;
if (typeof this.state.filesAvailable[name] === 'undefined' || available != this.state.filesAvailable[name]) {
var newFilesAvailable = Object.assign({}, this.state.filesAvailable);
newFilesAvailable[name] = available;
this.setState({
filesAvailable: newFilesAvailable,
});
this._fileTimeout = setTimeout(() => { this.updateFilesInfo() }, 1000);
}
});
}
}
this._fileInfoCheckNum += 1;
this.setState({
filesInfo: filesInfo,
});
} else {
// We're in the Downloaded tab, so populate this.state.filesInfo with files the user has in
// lbrynet, with published files filtered out.
lbry.getFilesInfo((filesInfo) => {
this.setFilesInfo(filesInfo.filter(({sd_hash}) => {
return this.state.publishedFilesSdHashes.indexOf(sd_hash) == -1;
}));
this._fileTimeout = setTimeout(() => { this.updateFilesInfo() }, 1000);
});
let newFilesAvailable;
if (!(this._fileInfoCheckNum % this._fileInfoCheckRate)) {
// Time to update file availability status
newFilesAvailable = {};
let filePeersCheckCount = 0;
for (let {sd_hash} of filesInfo) {
lbry.getPeersForBlobHash(sd_hash, (peers) => {
filePeersCheckCount++;
newFilesAvailable[sd_hash] = peers.length >= 0;
if (filePeersCheckCount >= filesInfo.length) {
this.setState({
filesAvailable: newFilesAvailable,
});
}
});
}
}
this._fileTimeout = setTimeout(() => { this.updateFilesInfo() }, 1000);
})
}
},
render: function() {
if (this.state.filesInfo === null || !this.state.filesOwnershipLoaded) {
if (this.state.filesInfo === null || (this.props.show == 'downloaded' && this.state.publishedFileSdHashes === null)) {
return (
<main className="page">
<BusyMessage message="Loading" />
</main>
);
}
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 if (!this.state.filesInfo.length) {
return (
<main className="page">
{this.props.show == 'downloaded'
? <span>You haven't downloaded anything from LBRY yet. Go <Link href="/" label="search for your first download" />!</span>
: <span>You haven't published anything to LBRY yet.</span>}
</main>
);
} else {
var content = [],
seenUris = {};
for (let fileInfo of this.state.filesInfo) {
let {completed, written_bytes, total_bytes, lbry_uri, file_name, download_path,
stopped, metadata} = fileInfo;
stopped, metadata, sd_hash} = fileInfo;
var isMine = this._filesOwnership[lbry_uri];
if (!metadata || seenUris[lbry_uri] || (this.props.show == 'downloaded' && isMine) ||
(this.props.show == 'published' && !isMine)) {
if (!metadata || seenUris[lbry_uri]) {
continue;
}
@ -301,13 +349,24 @@ var MyFilesPage = React.createClass({
content.push(<MyFilesRow key={lbry_uri} lbryUri={lbry_uri} title={title || ('lbry://' + lbry_uri)} completed={completed} stopped={stopped}
ratioLoaded={ratioLoaded} imgUrl={thumbnail} path={download_path}
showWatchButton={showWatchButton} pending={pending}
available={this.state.filesAvailable[lbry_uri]} isMine={isMine} />);
available={this.state.filesAvailable[sd_hash]} isMine={this.props.show == 'published'} />);
}
}
return (
<main className="page">
<span className='sort-section'>
Sort by { ' ' }
<FormField type="select" onChange={this.handleSortChanged}>
<option value="date">Date</option>
<option value="title">Title</option>
<option value="filename">File name</option>
</FormField>
</span>
{content}
</main>
);
}
});
export default MyFilesPage;

View file

@ -1,3 +1,10 @@
import React from 'react';
import lbry from '../lbry.js';
import FormField from '../component/form.js';
import {Link} from '../component/link.js';
import Modal from '../component/modal.js';
var publishNumberStyle = {
width: '50px',
}, publishFieldLabelStyle = {
@ -38,7 +45,14 @@ var PublishPage = React.createClass({
}
}
if (missingFieldFound) {
let fileProcessing = false;
if (this.state.fileInfo && !this.state.tempFileReady) {
this.refs.file.showAdvice('Your file is still processing.');
this.refs.file.focus();
fileProcessing = true;
}
if (missingFieldFound || fileProcessing) {
this.setState({
submitting: false,
});
@ -104,6 +118,7 @@ var PublishPage = React.createClass({
this._tempFilePath = null;
return {
rawName: '',
name: '',
bid: '',
feeAmount: '',
@ -145,6 +160,7 @@ var PublishPage = React.createClass({
if (!rawName) {
this.setState({
rawName: '',
name: '',
nameResolved: false,
});
@ -152,10 +168,19 @@ var PublishPage = React.createClass({
return;
}
var name = lbry.formatName(rawName);
if (!lbry.nameIsValid(rawName, false)) {
this.refs.name.showAdvice('LBRY names must contain only letters, numbers and dashes.');
return;
}
this.setState({
rawName: rawName,
});
var name = rawName.toLowerCase();
lbry.resolveName(name, (info) => {
if (name != lbry.formatName(this.refs.name.getValue())) {
if (name != this.refs.name.getValue().toLowerCase()) {
// A new name has been typed already, so bail
return;
}
@ -164,6 +189,7 @@ var PublishPage = React.createClass({
this.setState({
name: name,
nameResolved: false,
myClaimExists: false,
});
} else {
lbry.getMyClaim(name, (myClaimInfo) => {
@ -258,7 +284,7 @@ var PublishPage = React.createClass({
var formData = new FormData(fileInput.form);
formData.append('file', fileInput.files[0]);
xhr.open('POST', '/upload', true);
xhr.open('POST', lbry.webUiUri + '/upload', true);
xhr.send(formData);
}
},
@ -325,6 +351,7 @@ var PublishPage = React.createClass({
}
}
},
// Also getting a type warning here too
render: function() {
return (
<main ref="page">
@ -332,7 +359,7 @@ var PublishPage = React.createClass({
<section className="card">
<h4>LBRY Name</h4>
<div className="form-row">
lbry://<FormField type="text" ref="name" onChange={this.handleNameChange} />
lbry://<FormField type="text" ref="name" value={this.state.rawName} onChange={this.handleNameChange} />
{
(!this.state.name ? '' :
(! this.state.nameResolved ? <em> The name <strong>{this.state.name}</strong> is available.</em>
@ -477,3 +504,5 @@ var PublishPage = React.createClass({
);
}
});
export default PublishPage;

View file

@ -1,3 +1,8 @@
import React from 'react';
import lbry from '../lbry.js';
import {Link} from '../component/link.js';
import Modal from '../component/modal.js';
var referralCodeContentStyle = {
display: 'inline-block',
textAlign: 'left',
@ -105,7 +110,7 @@ var ReferralPage = React.createClass({
</form>
<Modal isOpen={this.state.modal == 'referralInfo'} onConfirmed={this.handleFinished}>
{this.state.referralCredits > 0
? `You have earned {response.referralCredits} credits from referrals. We will credit your account shortly. Thanks!`
? `You have earned ${response.referralCredits} credits from referrals. We will credit your account shortly. Thanks!`
: 'You have not earned any new referral credits since the last time you checked. Please check back in a week or two.'}
</Modal>
<Modal isOpen={this.state.modal == 'lookupFailed'} onConfirmed={this.closeModal}>
@ -118,3 +123,5 @@ var ReferralPage = React.createClass({
);
}
});
export default ReferralPage;

View file

@ -1,3 +1,6 @@
import React from 'react';
import lbry from '../lbry.js';
var ReportPage = React.createClass({
submitMessage: function() {
if (this._messageArea.value) {
@ -50,4 +53,6 @@ var ReportPage = React.createClass({
</main>
);
}
});
});
export default ReportPage;

View file

@ -1,3 +1,6 @@
import React from 'react';
import lbry from '../lbry.js';
var settingsRadioOptionStyles = {
display: 'block',
marginLeft: '13px'
@ -129,3 +132,6 @@ var SettingsPage = React.createClass({
);
}
});
export default SettingsPage;

View file

@ -1,3 +1,9 @@
import React from 'react';
import lbry from '../lbry.js';
import lighthouse from '../lighthouse.js';
import {CreditAmount, Thumbnail} from '../component/common.js';
import {Link, DownloadLink, WatchLink} from '../component/link.js';
var formatItemImgStyle = {
maxWidth: '100%',
maxHeight: '100%',
@ -10,9 +16,9 @@ var formatItemImgStyle = {
var FormatItem = React.createClass({
propTypes: {
claimInfo: React.PropTypes.object,
amount: React.PropTypes.number,
cost: React.PropTypes.number,
name: React.PropTypes.string,
available: React.PropTypes.bool,
costIncludesData: React.PropTypes.bool,
},
render: function() {
@ -25,8 +31,8 @@ var FormatItem = React.createClass({
var license = claimInfo.license;
var fileContentType = (claimInfo.content_type || claimInfo['content-type']);
var mediaType = lbry.getMediaType(fileContentType);
var available = this.props.available;
var amount = this.props.amount || 0.0;
var costIncludesData = this.props.costIncludesData;
var cost = this.props.cost || 0.0;
return (
<div className="row-fluid">
@ -42,7 +48,7 @@ var FormatItem = React.createClass({
<td>Content-Type</td><td>{fileContentType}</td>
</tr>
<tr>
<td>Cost</td><td><CreditAmount amount={amount} isEstimate={!available}/></td>
<td>Cost</td><td><CreditAmount amount={cost} isEstimate={!costIncludesData}/></td>
</tr>
<tr>
<td>Author</td><td>{author}</td>
@ -72,9 +78,9 @@ var FormatItem = React.createClass({
var FormatsSection = React.createClass({
propTypes: {
claimInfo: React.PropTypes.object,
amount: React.PropTypes.number,
cost: React.PropTypes.number,
name: React.PropTypes.string,
available: React.PropTypes.bool,
costIncludesData: React.PropTypes.bool,
},
render: function() {
var name = this.props.name;
@ -96,7 +102,7 @@ var FormatsSection = React.createClass({
{/* In future, anticipate multiple formats, just a guess at what it could look like
// var formats = this.props.claimInfo.formats
// return (<tbody>{formats.map(function(format,i){ */}
<FormatItem claimInfo={format} amount={this.props.amount} name={this.props.name} available={this.props.available} />
<FormatItem claimInfo={format} cost={this.props.cost} name={this.props.name} costIncludesData={this.props.costIncludesData} />
{/* })}</tbody>); */}
</div>);
}
@ -108,50 +114,44 @@ var DetailPage = React.createClass({
},
getInitialState: function() {
return {
claimInfo: null,
amount: null,
searching: true,
matchFound: null,
metadata: null,
cost: null,
costIncludesData: null,
nameLookupComplete: null,
};
},
componentWillMount: function() {
document.title = 'lbry://' + this.props.name;
lbry.lighthouse.search(this.props.name, (results) => {
var result = results[0];
lbry.resolveName(this.props.name, (metadata) => {
this.setState({
metadata: metadata,
nameLookupComplete: true,
});
});
if (result.name != this.props.name) {
this.setState({
searching: false,
matchFound: false,
});
} else {
this.setState({
amount: result.cost,
available: result.available,
claimInfo: result.value,
searching: false,
matchFound: true,
});
}
lbry.getCostInfoForName(this.props.name, ({cost, includesData}) => {
this.setState({
cost: cost,
costIncludesData: includesData,
});
});
},
render: function() {
if (this.state.claimInfo == null && this.state.searching) {
// Still waiting for metadata
if (this.state.metadata == null) {
return null;
}
var name = this.props.name;
var available = this.state.available;
var claimInfo = this.state.claimInfo;
var amount = this.state.amount;
const name = this.props.name;
const costIncludesData = this.state.costIncludesData;
const metadata = this.state.metadata;
const cost = this.state.cost;
return (
<main>
<section className="card">
{this.state.matchFound ? (
<FormatsSection name={name} claimInfo={claimInfo} amount={amount} available={available} />
{this.state.nameLookupComplete ? (
<FormatsSection name={name} claimInfo={metadata} cost={cost} costIncludesData={costIncludesData} />
) : (
<div>
<h2>No content</h2>
@ -161,4 +161,6 @@ var DetailPage = React.createClass({
</section>
</main>);
}
});
});
export default DetailPage;

View file

@ -1,3 +1,6 @@
import React from 'react';
import lbry from '../lbry.js';
var StartPage = React.createClass({
componentWillMount: function() {
lbry.stop();
@ -13,4 +16,6 @@ var StartPage = React.createClass({
</main>
);
}
});
});
export default StartPage;

View file

@ -1,3 +1,10 @@
import React from 'react';
import lbry from '../lbry.js';
import {Link} from '../component/link.js';
import Modal from '../component/modal.js';
import {Address, BusyMessage, CreditAmount} from '../component/common.js';
var addressRefreshButtonStyle = {
fontSize: '11pt',
};
@ -8,7 +15,7 @@ var AddressSection = React.createClass({
}
lbry.getNewAddress((address) => {
localStorage.setItem('wallet_address', address);
window.localStorage.setItem('wallet_address', address);
this.setState({
address: address,
});
@ -21,7 +28,7 @@ var AddressSection = React.createClass({
}
},
componentWillMount: function() {
var address = localStorage.getItem('wallet_address');
var address = window.localStorage.getItem('wallet_address');
if (address === null) {
this._refreshAddress();
} else {
@ -272,3 +279,5 @@ var WalletPage = React.createClass({
);
}
});
export default WalletPage;

View file

@ -1,3 +1,7 @@
import React from 'react';
import lbry from '../lbry.js';
import MediaElementPlayer from 'mediaelement';
var WatchPage = React.createClass({
propTypes: {
name: React.PropTypes.string,
@ -43,9 +47,11 @@ var WatchPage = React.createClass({
? <LoadScreen message={'Loading video...'} details={this.state.loadStatusMessage} />
: <main className="full-screen">
<video ref="player" width="100%" height="100%">
<source type={(this.state.mimeType == 'audio/m4a' || this.state.mimeType == 'audio/mp4a-latm') ? 'video/mp4' : this.state.mimeType} src={'/view?name=' + this.props.name} />
<source type={(this.state.mimeType == 'audio/m4a' || this.state.mimeType == 'audio/mp4a-latm') ? 'video/mp4' : this.state.mimeType} src={lbry.webUiUri + '/view?name=' + this.props.name} />
</video>
</main>
);
}
});
export default WatchPage;

View file

@ -22,6 +22,27 @@
"babel-cli": "^6.11.4",
"babel-preset-es2015": "^6.13.2",
"babel-preset-react": "^6.11.1",
"node-sass": "^3.8.0"
"clamp": "^1.0.1",
"mediaelement": "^2.23.4",
"node-sass": "^3.8.0",
"react": "^15.4.0",
"react-dom": "^15.4.0",
"react-modal": "^1.5.2"
},
"devDependencies": {
"babel": "^6.5.2",
"babel-core": "^6.18.2",
"babel-loader": "^6.2.8",
"babel-plugin-react-require": "^3.0.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"eslint": "^3.10.2",
"eslint-config-airbnb": "^13.0.0",
"eslint-loader": "^1.6.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-jsx-a11y": "^2.2.3",
"eslint-plugin-react": "^6.7.1",
"node-sass": "^3.13.0",
"webpack": "^1.13.3"
}
}

View file

@ -234,6 +234,65 @@ input[type="text"], input[type="search"]
}
}
.form-field-container {
display: inline-block;
}
.form-field-advice-container {
position: relative;
}
.form-field-advice {
position: absolute;
top: 0px;
left: 0px;
display: flex;
flex-direction: column;
white-space: nowrap;
transition: opacity 400ms ease-in;
}
.form-field-advice--fading {
opacity: 0;
}
.form-field-advice__arrow {
text-align: left;
padding-left: 18px;
font-size: 22px;
line-height: 0.3;
color: darken($color-primary, 5%);
}
.form-field-advice__content-container {
display: inline-block;
}
.form-field-advice__content {
display: inline-block;
padding: 5px;
border-radius: 2px;
background-color: darken($color-primary, 5%);
color: #fff;
}
.sort-section {
display: block;
margin-bottom: 5px;
text-align: right;
font-size: 0.85em;
color: $color-help;
}
.modal-overlay {
position: fixed;
@ -302,4 +361,8 @@ input[type="text"], input[type="search"]
.error-modal__warning-symbol {
margin-top: 6px;
margin-right: 7px;
}
.download-started-modal__file-path {
word-break: break-all;
}

37
webpack.config.js Normal file
View file

@ -0,0 +1,37 @@
const path = require('path');
const PATHS = {
app: path.join(__dirname, 'app'),
dist: path.join(__dirname, 'dist')
};
module.exports = {
entry: "./js/main.js",
output: {
path: path.join(PATHS.dist, 'js'),
publicPath: '/js/',
filename: "bundle.js"
},
devtool: 'source-map',
module: {
preLoaders: [
{
test: /\.jsx?$/,
loaders: ['eslint'],
// define an include so we check just the files we need
include: PATHS.app
}
],
loaders: [
{ test: /\.css$/, loader: "style!css" },
{
test: /\.jsx?$/,
// Enable caching for improved performance during development
// It uses default OS directory by default. If you need
// something more custom, pass a path to it.
// I.e., babel?cacheDirectory=<path>
loader: 'babel?cacheDirectory'
}
]
}
};