Seed Support #56

Closed
ocnios wants to merge 173 commits from master into build
39 changed files with 1120 additions and 1166 deletions
Showing only changes of commit ecf54f400b - Show all commits

View file

@ -2,22 +2,18 @@ import React from 'react';
import {Line} from 'rc-progress';
import lbry from './lbry.js';
import lbryio from './lbryio.js';
import EmailPage from './page/email.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 StartPage from './page/start.js';
import ClaimCodePage from './page/claim_code.js';
import ReferralPage from './page/referral.js';
import RewardsPage from './page/rewards.js';
import RewardPage from './page/reward.js';
import WalletPage from './page/wallet.js';
import ShowPage from './page/show.js';
import PublishPage from './page/publish.js';
import DiscoverPage from './page/discover.js';
import SplashScreen from './component/splash.js';
import DeveloperPage from './page/developer.js';
import {FileListDownloaded, FileListPublished} from './page/file-list.js';
import Drawer from './component/drawer.js';
@ -229,15 +225,11 @@ var App = React.createClass({
case 'wallet':
case 'send':
case 'receive':
case 'claim':
case 'referral':
case 'rewards':
return {
'?wallet': 'Overview',
'?send': 'Send',
'?receive': 'Receive',
'?claim': 'Claim Beta Code',
'?referral': 'Check Referral Credit',
'?rewards': 'Rewards',
};
case 'downloaded':
@ -268,14 +260,8 @@ var App = React.createClass({
return <FileListPublished />;
case 'start':
return <StartPage />;
case 'claim':
return <ClaimCodePage />;
case 'referral':
return <ReferralPage />;
case 'rewards':
return <RewardsPage />;
case 'reward':
return <RewardPage name={this.state.pageArgs} />;
case 'wallet':
case 'send':
case 'receive':

View file

@ -1,10 +1,11 @@
import React from 'react';
import lbryio from '../lbryio.js';
import Modal from './modal.js';
import ModalPage from './modal-page.js';
import {Link} from '../component/link.js';
import FormField from '../component/form.js';
import Notice from '../component/notice.js'
import {Link, RewardLink} from '../component/link.js';
import {FormField, FormRow} from '../component/form.js';
import rewards from '../rewards.js';
const SubmitEmailStage = React.createClass({
@ -29,8 +30,8 @@ const SubmitEmailStage = React.createClass({
lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => {
this.props.onEmailSaved();
}, (error) => {
if (this._emailField) {
this._emailField.showError(error.message)
if (this._emailRow) {
this._emailRow.showError(error.message)
}
this.setState({ submitting: false });
});
@ -39,10 +40,10 @@ const SubmitEmailStage = React.createClass({
return (
<section>
<form onSubmit={this.handleSubmit}>
<FormField row={true} ref={(field) => { this._emailField = field }} type="text" label="Email" placeholder="webmaster@toplbryfan.com"
<FormRow ref={(ref) => { this._emailRow = ref }} type="text" label="Email" placeholder="webmaster@toplbryfan.com"
name="email" value={this.state.email}
onChange={this.handleEmailChanged} />
<div className="form-row">
<div className="form-row-submit">
<Link button="primary" label="Next" disabled={this.state.submitting} onClick={this.handleSubmit} />
</div>
</form>
@ -71,27 +72,29 @@ const ConfirmEmailStage = React.createClass({
submitting: true,
});
lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then(() => {
rewards.claimReward('confirm_email').then(() => {
this.props.onDone();
}, (err) => {l
this.props.onDone();
});
}, (error) => {
if (this._codeField) {
this._codeField.showError(error.message)
this.setState({ submitting: false })
const onSubmitError = function(error) {
if (this._codeRow) {
this._codeRow.showError(error.message)
}
});
this.setState({ submitting: false });
}.bind(this)
lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then((userEmail) => {
if (userEmail.IsVerified) {
this.props.onEmailConfirmed();
} else {
onSubmitError(new Error("Your email is still not verified.")) //shouldn't happen?
}
}, onSubmitError);
},
render: function() {
return (
<section>
<form onSubmit={this.handleSubmit}>
<FormField row={true} label="Verification Code" ref={(field) => { this._codeField = field }} type="text"
<FormRow label="Verification Code" ref={(ref) => { this._codeRow = ref }} type="text"
name="code" placeholder="a94bXXXXXXXXXXXXXX" value={this.state.code} onChange={this.handleCodeChanged}
helper="A verification code is required to access this version."/>
<div className="form-row">
<div className="form-row-submit">
<Link button="primary" label="Verify" disabled={this.state.submitting} onClick={this.handleSubmit} />
</div>
</form>
@ -100,9 +103,29 @@ const ConfirmEmailStage = React.createClass({
}
});
const WelcomeStage = React.createClass({
onRewardClaim: function() {
console.log('omg');
},
render: function() {
return (
<section>
<h3 className="modal__header">Welcome to LBRY.</h3>
<p>LBRY is the first community controlled content marketplace.</p>
<p>Since you're new here, we'll toss you some credits.</p>
<div style={{textAlign: "center", marginBottom: "12px"}}>
<RewardLink type="new_user" onRewardClaim={this.onRewardClaim} />
</div>
<p>LBC is blah blah blah.</p>
<p>And remember, LBRY is a beta and be safe!</p>
</section>
);
}
});
const ErrorStage = React.createClass({
render: function() {
// <section><Link button="primary" label="OK" onClick={this.props.onDone} /></section>
return (
<section>
<p>An error was encountered that we cannot continue from.</p>
@ -129,68 +152,66 @@ export const AuthOverlay = React.createClass({
error: ErrorStage,
email: SubmitEmailStage,
confirm: ConfirmEmailStage,
welcome: WelcomeStage,
},
propTypes: {
// onDone: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
stage: "pending",
stage: "welcome",
stageProps: {}
};
},
componentWillMount: function() {
lbryio.authenticate().then(function(user) {
console.log(user);
if (!user.HasVerifiedEmail) {
this.setState({
stage: "email",
stageProps: {
onEmailSaved: function() {
this.setState({
stage: "confirm"
})
}.bind(this)
}
})
} else {
this.setState({ stage: null })
}
}.bind(this)).catch((err) => {
this.setState({
stage: "error",
stageProps: { errorText: err.message }
})
document.dispatchEvent(new CustomEvent('unhandledError', {
detail: {
message: err.message,
data: err.stack
}
}));
})
endAuth: function() {
this.setState({
stage: null
});
},
componentWillMount: function() {
// lbryio.authenticate().then(function(user) {
// if (!user.HasVerifiedEmail) { //oops I fucked this up
// this.setState({
// stage: "email",
// stageProps: {
// onEmailSaved: function() {
// this.setState({
// stage: "confirm",
// stageProps: {
// onEmailConfirmed: function() { this.setState({ stage: "welcome"}) }.bind(this)
// }
// })
// }.bind(this)
// }
// })
// } else {
// this.endAuth()
// }
// }.bind(this)).catch((err) => {
// this.setState({
// stage: "error",
// stageProps: { errorText: err.message }
// })
// document.dispatchEvent(new CustomEvent('unhandledError', {
// detail: {
// message: err.message,
// data: err.stack
// }
// }));
// })
},
// handleStageDone: function() {
// if (this.state.stageNum >= this._stages.length - 1) {
// this.props.onDone();
// } else {
// this.setState({
// stageNum: this.state.stageNum + 1,
// });
// }
// },
// <Content onDone={this.handleStageDone} />
render: function() {
console.log(lbryio.user);
if (!this.state.stage || lbryio.user && lbryio.user.HasVerifiedEmail) {
return null;
}
const StageContent = this._stages[this.state.stage];
return (
<ModalPage isOpen={true} contentLabel="Authentication" {...this.props}>
<h1>LBRY Early Access</h1>
<StageContent {...this.state.stageProps} />
</ModalPage>
this.state.stage != "welcome" ?
<ModalPage className="modal-page--full"isOpen={true} contentLabel="Authentication" {...this.props}>
<h1>LBRY Early Access</h1>
<StageContent {...this.state.stageProps} />
</ModalPage> :
<Modal isOpen={true} contentLabel="Welcome to LBRY" {...this.props} onConfirmed={this.endAuth}>
<StageContent {...this.state.stageProps} />
</Modal>
);
}
});

View file

@ -55,7 +55,7 @@ var Drawer = React.createClass({
<DrawerItem href='?discover' viewingPage={this.props.viewingPage} label="Discover" icon="icon-search" />
<DrawerItem href='?publish' viewingPage={this.props.viewingPage} label="Publish" icon="icon-upload" />
<DrawerItem href='?downloaded' subPages={['published']} viewingPage={this.props.viewingPage} label="My Files" icon='icon-cloud-download' />
<DrawerItem href="?wallet" subPages={['send', 'receive', 'claim', 'referral']} viewingPage={this.props.viewingPage} label="My Wallet" badge={lbry.formatCredits(this.state.balance) } icon="icon-bank" />
<DrawerItem href="?wallet" subPages={['send', 'receive', 'rewards']} viewingPage={this.props.viewingPage} label="My Wallet" badge={lbry.formatCredits(this.state.balance) } icon="icon-bank" />
<DrawerItem href='?settings' viewingPage={this.props.viewingPage} label="Settings" icon='icon-gear' />
<DrawerItem href='?help' viewingPage={this.props.viewingPage} label="Help" icon='icon-question-circle' />
</nav>

View file

@ -3,7 +3,7 @@ import lbry from '../lbry.js';
import {Link} from '../component/link.js';
import {Icon} from '../component/common.js';
import Modal from './modal.js';
import FormField from './form.js';
import {FormField} from './form.js';
import {ToolTip} from '../component/tooltip.js';
import {DropDownMenu, DropDownMenuItem} from './menu.js';
@ -53,7 +53,7 @@ let WatchLink = React.createClass({
render: function() {
return (
<div className="button-set-item">
<Link button="primary" disabled={this.state.loading} label="Watch" icon="icon-play" onClick={this.handleClick} />
<Link button={ this.props.button ? this.props.button : 'alt' } disabled={this.state.loading} label="Watch" icon="icon-play" onClick={this.handleClick} />
<Modal contentLabel="Not enough credits" isOpen={this.state.modal == 'notEnoughCredits'} onConfirmed={this.closeModal}>
You don't have enough LBRY credits to pay for this stream.
</Modal>

View file

@ -47,7 +47,7 @@ let FilePrice = React.createClass({
}
return (
<span className="file-tile__cost">
<span className="file-price">
<CreditAmount amount={this.state.cost} isEstimate={!this.state.costIncludesData}/>
</span>
);
@ -131,8 +131,8 @@ export let FileTileStream = React.createClass({
const title = isConfirmed ? metadata.title : lbryUri;
const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw;
return (
<section className={ 'file-tile card ' + (obscureNsfw ? 'card-obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
<div className={"row-fluid card-content file-tile__row"}>
<section className={ 'file-tile card ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
<div className={"row-fluid card__inner file-tile__row"}>
<div className="span3">
<a href={'?show=' + lbryUri}><Thumbnail className="file-tile__thumbnail" src={metadata.thumbnail} alt={'Photo for ' + (title || this.props.uri)} /></a>
</div>
@ -140,24 +140,139 @@ export let FileTileStream = React.createClass({
{ !this.props.hidePrice
? <FilePrice uri={this.props.uri} />
: null}
<div className="meta"><a href={'?show=' + this.props.uri}>{lbryUri}</a></div>
<h3 className="file-tile__title">
<div className="card__title-primary">
<div className="meta"><a href={'?show=' + this.props.uri}>{lbryUri}</a></div>
<h3>
<a href={'?show=' + this.props.uri}>
<TruncatedText lines={1}>
{title}
</TruncatedText>
</a>
</h3>
</div>
<div className="card__actions">
<FileActions uri={this.props.uri} outpoint={this.props.outpoint} metadata={metadata} contentType={this.props.contentType} />
</div>
<div className="card__content">
<p className="file-tile__description">
<TruncatedText lines={3}>
{isConfirmed
? metadata.description
: <span className="empty">This file is pending confirmation.</span>}
</TruncatedText>
</p>
</div>
</div>
</div>
{this.state.showNsfwHelp
? <div className='card-overlay'>
<p>
This content is Not Safe For Work.
To view adult content, please change your <Link className="button-text" href="?settings" label="Settings" />.
</p>
</div>
: null}
</section>
);
}
});
export let FileCardStream = React.createClass({
_fileInfoSubscribeId: null,
_isMounted: null,
propTypes: {
metadata: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]),
outpoint: React.PropTypes.string,
hideOnRemove: React.PropTypes.bool,
hidePrice: React.PropTypes.bool,
obscureNsfw: React.PropTypes.bool
},
getInitialState: function() {
return {
showNsfwHelp: false,
isHidden: false,
available: null,
}
},
getDefaultProps: function() {
return {
obscureNsfw: !lbry.getClientSetting('showNsfw'),
hidePrice: false
}
},
componentDidMount: function() {
this._isMounted = true;
if (this.props.hideOnRemove) {
this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
}
},
componentWillUnmount: function() {
if (this._fileInfoSubscribeId) {
lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId);
}
},
onFileInfoUpdate: function(fileInfo) {
if (!fileInfo && this._isMounted && this.props.hideOnRemove) {
this.setState({
isHidden: true
});
}
},
handleMouseOver: function() {
if (this.props.obscureNsfw && this.props.metadata && this.props.metadata.nsfw) {
this.setState({
showNsfwHelp: true,
});
}
},
handleMouseOut: function() {
if (this.state.showNsfwHelp) {
this.setState({
showNsfwHelp: false,
});
}
},
render: function() {
if (this.state.isHidden) {
return null;
}
const lbryUri = uri.normalizeLbryUri(this.props.uri);
const metadata = this.props.metadata;
const isConfirmed = typeof metadata == 'object';
const title = isConfirmed ? metadata.title : lbryUri;
const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw;
return (
<section className={ 'card card--small ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
<div className="card__inner">
<div className="card__title-identity">
<h4>
<a href={'?show=' + this.props.uri}>
<TruncatedText lines={1}>
{title}
</TruncatedText>
</a>
</h3>
</h4>
<div className="card__subtitle"><a href={'?show=' + lbryUri}>{lbryUri}</a></div></div>
<ChannelIndicator uri={lbryUri} metadata={metadata} contentType={this.props.contentType}
hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} />
<FileActions uri={this.props.uri} outpoint={this.props.outpoint} metadata={metadata} contentType={this.props.contentType} />
<p className="file-tile__description">
<TruncatedText lines={3}>
hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} />
<div className="card__media">
<a href={'?show=' + this.props.uri}><Thumbnail src={metadata.thumbnail} alt={'Photo for ' + (title || this.props.uri)} /></a>
</div>
{ !this.props.hidePrice
? <FilePrice uri={this.props.uri} />
: null}
<div className="card__content card__subtext card__subtext--two-lines">
<TruncatedText lines={2}>
{isConfirmed
? metadata.description
: <span className="empty">This file is pending confirmation.</span>}
? metadata.description
: <span className="empty">This file is pending confirmation.</span>}
</TruncatedText>
</p>
</div>
<div className="card__actions card__actions--bottom">
<FileActions uri={this.props.uri} outpoint={this.props.outpoint} metadata={metadata} contentType={this.props.contentType} />
</div>
</div>
{this.state.showNsfwHelp

View file

@ -1,31 +1,30 @@
import React from 'react';
import {Icon} from './common.js';
var requiredFieldWarningStyle = {
color: '#cc0000',
transition: 'opacity 400ms ease-in',
};
var formFieldCounter = 0,
formFieldNestedLabelTypes = ['radio', 'checkbox'];
var formFieldCounter = 0;
function formFieldId() {
return "form-field-" + (++formFieldCounter);
}
var FormField = React.createClass({
export let FormField = React.createClass({
_fieldRequiredText: 'This field is required',
_type: null,
_element: null,
propTypes: {
type: React.PropTypes.string.isRequired,
row: React.PropTypes.bool,
hidden: React.PropTypes.bool,
hasError: React.PropTypes.bool
},
getInitialState: function() {
return {
errorState: 'hidden',
adviceText: null,
isError: null,
errorMessage: null,
}
},
componentWillMount: function() {
if (['text', 'radio', 'checkbox', 'file'].includes(this.props.type)) {
if (['text', 'number', 'radio', 'checkbox', 'file'].includes(this.props.type)) {
this._element = 'input';
this._type = this.props.type;
} else if (this.props.type == 'text-number') {
@ -38,22 +37,11 @@ var FormField = React.createClass({
},
showError: function(text) {
this.setState({
errorState: 'shown',
adviceText: text,
isError: true,
errorMessage: text,
});
// setTimeout(() => {
// this.setState({
// errorState: 'fading',
// });
// setTimeout(() => {
// this.setState({
// errorState: 'hidden',
// });
// }, 450);
// }, 5000);
},
warnRequired: function() {
showRequiredError: function() {
this.showError(this._fieldRequiredText);
},
focus: function() {
@ -74,33 +62,27 @@ var FormField = React.createClass({
render: function() {
// Pass all unhandled props to the field element
const otherProps = Object.assign({}, this.props),
hasError = this.state.errorState != 'hidden';
isError = this.state.isError !== null ? this.state.isError : this.props.hasError,
elementId = this.props.id ? this.props.id : formFieldId(),
renderElementInsideLabel = this.props.label && formFieldNestedLabelTypes.includes(this.props.type);
delete otherProps.type;
delete otherProps.hidden;
delete otherProps.label;
delete otherProps.row;
delete otherProps.helper;
delete otherProps.hasError;
++formFieldCounter;
const elementId = "form-field-" + formFieldCounter
const element = <this._element id={elementId} type={this._type} name={this.props.name} ref="field" placeholder={this.props.placeholder}
className={'form-field__input form-field__input-' + this.props.type + ' ' + (this.props.className || '') + (isError ? 'form-field__input--error' : '')}
{...otherProps}>
{this.props.children}
</this._element>;
if (this.props.hidden) {
return null;
}
const field = <div className="form-field">
{ this.props.label ?
<div className={"form-field__label " + (hasError ? 'form-field__label--error' : '')}>
<label htmlFor={elementId}>{this.props.label}</label>
</div> : ''
}
<this._element id={elementId} type={this._type} name={this.props.name} ref="field" placeholder={this.props.placeholder}
className={'form-field__input form-field__input-' + this.props.type + ' ' + (this.props.className || '') + (hasError ? 'form-field__input--error' : '')}
{...otherProps}>
{this.props.children}
</this._element>
{ !hasError && this.props.helper ? <div className="form-field__helper">{this.props.helper}</div> : '' }
{ hasError ? <div className="form-field__error">{this.state.adviceText}</div> : '' }
return <div className="form-field">
{ renderElementInsideLabel ?
<label htmlFor={elementId} className={"form-field__label " + (isError ? 'form-field__label--error' : '')}>
{element}
{this.props.label}
</label> : element }
{ isError ? <div className="form-field__error">{this.state.errorMessage}</div> : '' }
</div>
return (
this.props.row ?
@ -108,6 +90,57 @@ var FormField = React.createClass({
field
);
}
});
})
export default FormField;
export let FormRow = React.createClass({
propTypes: {
label: React.PropTypes.string,
// helper: React.PropTypes.html,
},
getValue: function() {
if (this.props.type == 'checkbox') {
return this.refs.field.checked;
} else if (this.props.type == 'file') {
return this.refs.field.files[0].path;
} else {
return this.refs.field.value;
}
},
getInitialState: function() {
return {
isError: false,
errorMessage: null,
}
},
showError: function(text) {
this.setState({
isError: true,
errorMessage: text,
});
},
getValue: function() {
return this.refs.field.getValue();
},
render: function() {
const fieldProps = Object.assign({}, this.props),
elementId = formFieldId(),
renderLabelInFormField = formFieldNestedLabelTypes.includes(this.props.type);
if (!renderLabelInFormField) {
delete fieldProps.label;
}
delete fieldProps.helper;
return <div className="form-row">
{ this.props.label && !renderLabelInFormField ?
<div className={"form-row__label-row " + (this.props.labelPrefix ? "form-row__label-row--prefix" : "") }>
<label htmlFor={elementId} className={"form-field__label " + (this.state.isError ? 'form-field__label--error' : '')}>
{this.props.label}
</label>
</div> : '' }
<FormField ref="field" hasError={this.state.isError} {...fieldProps} />
{ !this.state.isError && this.props.helper ? <div className="form-field__helper">{this.props.helper}</div> : '' }
{ this.state.isError ? <div className="form-field__error">{this.state.errorMessage}</div> : '' }
</div>
}
})

View file

@ -1,6 +1,6 @@
import React from 'react';
import {Link} from './link.js';
import NotificationBar from './notification-bar.js';
import {Icon} from './common.js';
var Header = React.createClass({
getInitialState: function() {
@ -53,6 +53,7 @@ var Header = React.createClass({
<Link onClick={this.props.onOpenDrawer} icon="icon-bars" className="open-drawer-link" />
<h1>{ this.state.title }</h1>
<div className="header-search">
<Icon icon="icon-search" />
<input type="search" onChange={this.onQueryChange} defaultValue={this.props.initialQuery}
placeholder="Find movies, music, games, and more"/>
</div>
@ -62,7 +63,6 @@ var Header = React.createClass({
<SubHeader links={this.props.links} viewingPage={this.props.viewingPage} /> :
''
}
<NotificationBar />
</header>
);
}

View file

@ -1,5 +1,7 @@
import React from 'react';
import {Icon} from './common.js';
import Modal from '../component/modal.js';
import rewards from '../rewards.js';
export let Link = React.createClass({
propTypes: {
@ -53,3 +55,75 @@ export let Link = React.createClass({
);
}
});
export let RewardLink = React.createClass({
propTypes: {
type: React.PropTypes.string.isRequired,
claimed: React.PropTypes.bool,
onRewardClaim: React.PropTypes.func
},
refreshClaimable: function() {
switch(this.props.type) {
case 'new_user':
this.setState({ claimable: true });
return;
case 'first_publish':
lbry.claim_list_mine().then(function(list) {
this.setState({
claimable: list.length > 0
})
}.bind(this));
return;
}
},
componentWillMount: function() {
this.refreshClaimable();
},
getInitialState: function() {
return {
claimable: true,
pending: false,
errorMessage: null
}
},
claimReward: function() {
this.setState({
pending: true
})
rewards.claimReward(this.props.type).then(function(reward) {
console.log(reward);
this.setState({
pending: false,
errorMessage: null
})
if (this.props.onRewardClaim) {
this.props.onRewardClaim();
}
}.bind(this)).catch(function(error) {
this.setState({
errorMessage: error.message,
pending: false
})
}.bind(this))
},
clearError: function() {
this.setState({
errorMessage: null
})
},
render: function() {
return (
<div className="reward-link">
{this.props.claimed
? <span><Icon icon="icon-check" /> Reward claimed.</span>
: <Link button="alt" disabled={this.state.pending || !this.state.claimable } label="Claim Reward" onClick={this.claimReward} />}
{this.state.errorMessage ?
<Modal isOpen={true} contentLabel="Reward Claim Error" className="error-modal" onConfirmed={this.clearError}>
{this.state.errorMessage}
</Modal>
: ''}
</div>
);
}
});

View file

@ -1,53 +0,0 @@
import React from 'react';
import lbry from '../lbry.js';
import Notice from '../component/notice.js';
export const NotificationBar = React.createClass({
_displayTime: 8, // in seconds
_hideTimeout: null,
getInitialState: function() {
return {
message: null,
isError: null,
}
},
handleNoticeReceived: function(event) {
if (this._hideTimeout) {
clearTimeout(this._hideTimeout);
}
const {detail: {message, isError}} = event;
this.setState({
message: message,
isError: isError,
});
this._hideTimeout = setTimeout(() => {
this.setState({
message: null,
isError: null,
});
}, this._displayTime * 1000);
},
componentWillMount: function() {
document.addEventListener('globalNotice', this.handleNoticeReceived);
},
componentWillUnmount: function() {
document.removeEventListener('globalNotice', this.handleNoticeReceived);
},
render: function() {
if (!this.state.message) {
return null;
}
return (
<Notice isError={this.state.isError} className="notification-bar">
{this.state.message}
</Notice>
);
},
});
export default NotificationBar;

View file

@ -0,0 +1,58 @@
import React from 'react';
import lbry from '../lbry.js';
export const SnackBar = React.createClass({
_displayTime: 5, // in seconds
_hideTimeout: null,
getInitialState: function() {
return {
snacks: []
}
},
handleSnackReceived: function(event) {
// console.log(event);
// if (this._hideTimeout) {
// clearTimeout(this._hideTimeout);
// }
let snacks = this.state.snacks;
snacks.push(event.detail);
this.setState({ snacks: snacks});
},
componentWillMount: function() {
document.addEventListener('globalNotice', this.handleSnackReceived);
},
componentWillUnmount: function() {
document.removeEventListener('globalNotice', this.handleSnackReceived);
},
render: function() {
if (!this.state.snacks.length) {
this._hideTimeout = null; //should be unmounting anyway, but be safe?
return null;
}
let snack = this.state.snacks[0];
if (this._hideTimeout === null) {
this._hideTimeout = setTimeout(function() {
this._hideTimeout = null;
let snacks = this.state.snacks;
snacks.shift();
this.setState({ snacks: snacks });
}.bind(this), this._displayTime * 1000);
}
return (
<div className="snack-bar">
{snack.message}
{snack.linkText && snack.linkTarget ?
<a className="snack-bar__action" href={snack.linkTarget}>{snack.linkText}</a> : ''}
</div>
);
},
});
export default SnackBar;

View file

@ -1,156 +0,0 @@
import React from 'react';
import lbryio from '../lbryio.js';
import ModalPage from './modal-page.js';
import {Link} from '../component/link.js';
import FormField from '../component/form.js';
import Notice from '../component/notice.js'
const SubmitEmailStage = React.createClass({
getInitialState: function() {
return {
rewardType: null,
email: '',
submitting: false,
errorMessage: null,
};
},
handleEmailChanged: function(event) {
this.setState({
email: event.target.value,
});
},
handleSubmit: function(event) {
event.preventDefault();
this.setState({
submitting: true,
});
lbryio.call('user_email', 'new', {email: this.state.email}, 'post').then(() => {
this.props.onDone();
}, (error) => {
this.setState({
submitting: false,
errorMessage: error.message,
});
});
},
render: function() {
return (
<section>
<h1>Welcome to LBRY</h1>
{this.state.errorMessage
? <Notice isError>
{this.state.errorMessage}
</Notice>
: null}
<p>Copy here explaining what we do with your email, and the reward.</p>
<form onSubmit={this.handleSubmit}>
<section>Email <label><FormField ref={(field) => { this._emailField = field }} type="text" name="email" value={this.state.email} onChange={this.handleEmailChanged} /></label></section>
<section><Link button="primary" label="Submit and Continue" disabled={this.state.submitting} onClick={this.handleSubmit} /></section>
</form>
</section>
);
}
});
const ConfirmEmailStage = React.createClass({
getInitialState: function() {
return {
rewardType: null,
code: '',
submitting: false,
errorMessage: null,
};
},
handleCodeChanged: function(event) {
this.setState({
code: event.target.value,
});
},
handleSubmit: function(event) {
event.preventDefault();
this.setState({
submitting: true,
});
lbryio.call('user_email', 'confirm', {verification_token: this.state.code}, 'post').then(() => {
rewards.claimReward('confirm_email').then(() => {
console.log('succeeded');
this.props.onDone();
}, (err) => {
console.log('failed');
this.props.onDone();
});
}, (error) => {
this.setState({
submitting: false,
errorMessage: error.message,
});
});
},
render: function() {
return (
<section>
<h1>Confirm Your Email Address</h1>
{this.state.errorMessage
? <Notice isError>
{this.state.errorMessage}
</Notice>
: null}
<p>Please enter your verification code to confirm your email address.</p>
<form onSubmit={this.handleSubmit}>
<section><label>Verification Code: <FormField ref={(field) => { this._codeField = field }} type="text" name="code" value={this.state.code} onChange={this.handleCodeChanged} /></label></section>
<section><Link button="primary" label="Verify" disabled={this.state.submitting} onClick={this.handleSubmit} /></section>
</form>
</section>
);
}
});
const FinalMessageStage = React.createClass({
render: function() {
return (
<section>
<h1>Email verified</h1>
<p>Text here about what happens next</p>
<section><Link button="primary" label="OK" onClick={this.props.onDone} /></section>
</section>
);
}
});
export const Welcome = React.createClass({
_stages: [
SubmitEmailStage,
ConfirmEmailStage,
FinalMessageStage,
],
propTypes: {
onDone: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
stageNum: 0,
};
},
handleStageDone: function() {
if (this.state.stageNum >= this._stages.length - 1) {
this.props.onDone();
} else {
this.setState({
stageNum: this.state.stageNum + 1,
});
}
},
render: function() {
const Content = this._stages[this.state.stageNum];
return (
<ModalPage contentLabel="Welcome to LBRY" {...this.props}>
<Content onDone={this.handleStageDone} />
</ModalPage>
);
}
});

View file

@ -282,29 +282,6 @@ lbry.getCostInfo = function(lbryUri, callback, errorCallback) {
});
}
lbry.getFeaturedDiscoverNames = function(callback) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', 'https://api.lbry.io/discover/list', true);
xhr.onload = () => {
if (xhr.status === 200) {
var responseData = JSON.parse(xhr.responseText);
if (responseData.data) //new signature, once api.lbry.io is updated
{
resolve(responseData.data);
}
else
{
resolve(responseData);
}
} else {
reject(Error('Failed to fetch featured names.'));
}
};
xhr.send();
});
}
lbry.getMyClaims = function(callback) {
lbry.call('get_name_claims', {}, callback);
}

View file

@ -1,4 +1,4 @@
import {getLocal, setLocal} from './utils.js';
import {getLocal, getSession, setSession, setLocal} from './utils.js';
import lbry from './lbry.js';
const querystring = require('querystring');
@ -20,18 +20,7 @@ const mocks = {
value: 50,
claimed: false,
};
},
'reward_type.list': () => {
return [
{
name: 'link_github',
title: 'Link your GitHub account',
description: 'Link LBRY to your GitHub account',
value: 50,
claimed: false,
},
];
},
}
};
lbryio.call = function(resource, action, params={}, method='get') {
@ -103,7 +92,7 @@ lbryio.setAccessToken = (token) => {
lbryio._accessToken = token
}
lbryio.authenticate = () => {
lbryio.authenticate = function() {
if (lbryio._authenticationPromise === null) {
lbryio._authenticationPromise = new Promise((resolve, reject) => {
lbry.status().then(({installation_id}) => {
@ -117,7 +106,12 @@ lbryio.authenticate = () => {
resolve(data)
}).catch(function(err) {
lbryio.setAccessToken(null);
reject(err);
if (!getSession('reloadedOnFailedAuth')) {
setSession('reloadedOnFailedAuth', true)
window.location.reload();
} else {
reject(err);
}
})
}

View file

@ -5,8 +5,8 @@ import lbryio from './lbryio.js';
import lighthouse from './lighthouse.js';
import App from './app.js';
import SplashScreen from './component/splash.js';
import SnackBar from './component/snack-bar.js';
import {AuthOverlay} from './component/auth.js';
import {Welcome} from './component/welcome.js';
const {remote} = require('electron');
const contextMenu = remote.require('./menu/context-menu');
@ -28,12 +28,12 @@ let init = function() {
lbryio.authenticate() //start auth process as soon as soon as we can get an install ID
})
async function onDaemonReady() {
function onDaemonReady() {
window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again
ReactDOM.render(<div><AuthOverlay /><App /></div>, canvas)
ReactDOM.render(<div><AuthOverlay /><App /><SnackBar /></div>, canvas)
}
if (window.sessionStorage.getItem('loaded') == 'y' && false) {
if (window.sessionStorage.getItem('loaded') == 'y') {
onDaemonReady();
} else {
ReactDOM.render(<SplashScreen message="Connecting" onLoadDone={onDaemonReady} />, canvas);

View file

@ -1,158 +0,0 @@
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',
width: '600px',
}, claimCodeLabelStyle = {
display: 'inline-block',
cursor: 'default',
width: '130px',
textAlign: 'right',
marginRight: '6px',
};
var ClaimCodePage = React.createClass({
getInitialState: function() {
return {
submitting: false,
modal: null,
referralCredits: null,
activationCredits: null,
failureReason: null,
}
},
handleSubmit: function(event) {
if (typeof event !== 'undefined') {
event.preventDefault();
}
if (!this.refs.code.value) {
this.setState({
modal: 'missingCode',
});
return;
} else if (!this.refs.email.value) {
this.setState({
modal: 'missingEmail',
});
return;
}
this.setState({
submitting: true,
});
lbry.getUnusedAddress((address) => {
var code = this.refs.code.value;
var email = this.refs.email.value;
var xhr = new XMLHttpRequest;
xhr.addEventListener('load', () => {
var response = JSON.parse(xhr.responseText);
if (response.success) {
this.setState({
modal: 'codeRedeemed',
referralCredits: response.referralCredits,
activationCredits: response.activationCredits,
});
} else {
this.setState({
submitting: false,
modal: 'codeRedeemFailed',
failureReason: response.reason,
});
}
});
xhr.addEventListener('error', () => {
this.setState({
submitting: false,
modal: 'couldNotConnect',
});
});
xhr.open('POST', 'https://invites.lbry.io', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send('code=' + encodeURIComponent(code) + '&address=' + encodeURIComponent(address) +
'&email=' + encodeURIComponent(email));
});
},
handleSkip: function() {
this.setState({
modal: 'skipped',
});
},
handleFinished: function() {
localStorage.setItem('claimCodeDone', true);
window.location = '?home';
},
closeModal: function() {
this.setState({
modal: null,
});
},
render: function() {
return (
<main>
<form onSubmit={this.handleSubmit}>
<div className="card">
<h2>Claim your beta invitation code</h2>
<section style={claimCodeContentStyle}>
<p>Thanks for beta testing LBRY! Enter your invitation code and email address below to receive your initial
LBRY credits.</p>
<p>You will be added to our mailing list (if you're not already on it) and will be eligible for future rewards for beta testers.</p>
</section>
<section>
<section><label style={claimCodeLabelStyle} htmlFor="code">Invitation code</label><input name="code" ref="code" /></section>
<section><label style={claimCodeLabelStyle} htmlFor="email">Email</label><input name="email" ref="email" /></section>
</section>
<section>
<Link button="primary" label={this.state.submitting ? "Submitting..." : "Submit"}
disabled={this.state.submitting} onClick={this.handleSubmit} />
<Link button="alt" label="Skip" disabled={this.state.submitting} onClick={this.handleSkip} />
<input type='submit' className='hidden' />
</section>
</div>
</form>
<Modal isOpen={this.state.modal == 'missingCode'} contentLabel="Invitation code required"
onConfirmed={this.closeModal}>
Please enter an invitation code or choose "Skip."
</Modal>
<Modal isOpen={this.state.modal == 'missingEmail'} contentLabel="Email required"
onConfirmed={this.closeModal}>
Please enter an email address or choose "Skip."
</Modal>
<Modal isOpen={this.state.modal == 'codeRedeemFailed'} contentLabel="Failed to redeem code"
onConfirmed={this.closeModal}>
{this.state.failureReason}
</Modal>
<Modal isOpen={this.state.modal == 'codeRedeemed'} contentLabel="Code redeemed"
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}
will be added to your balance shortly.`
: (this.state.activationCredits > 0
? `${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'} contentLabel="Welcome to LBRY"
onConfirmed={this.handleFinished}>
Welcome to LBRY! You can visit the Wallet page to redeem an invite code at any time.
</Modal>
<Modal isOpen={this.state.modal == 'couldNotConnect'} contentLabel="Could not connect"
onConfirmed={this.closeModal}>
<p>LBRY couldn't connect to our servers to confirm your invitation code. Please check your internet connection.</p>
If you continue to have problems, you can still browse LBRY and visit the Settings page to redeem your code later.
</Modal>
</main>
);
}
});
export default ClaimCodePage;

View file

@ -1,6 +1,6 @@
import lbry from '../lbry.js';
import React from 'react';
import FormField from '../component/form.js';
import {FormField} from '../component/form.js';
import {Link} from '../component/link.js';
const fs = require('fs');

View file

@ -1,5 +1,6 @@
import React from 'react';
import lbry from '../lbry.js';
import lbryio from '../lbryio.js';
import lighthouse from '../lighthouse.js';
import {FileTile} from '../component/file-tile.js';
import {Link} from '../component/link.js';
@ -58,45 +59,44 @@ var SearchResults = React.createClass({
}
});
var featuredContentLegendStyle = {
fontSize: '12px',
color: '#aaa',
verticalAlign: '15%',
};
const communityCategoryToolTipText = ('Community Content is a public space where anyone can share content with the ' +
'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' +
'"five" to put your content here!');
var FeaturedCategory = React.createClass({
render: function() {
return (<div className="card-row">
{ this.props.category ?
<h3 className="card-row__header">{this.props.category}
{ this.props.category == "community" ?
<ToolTip label="What's this?" body={communityCategoryToolTipText} className="tooltip--header"/>
: '' }</h3>
: '' }
{ this.props.names.map((name) => { return <FileTile key={name} displayStyle="card" name={name} uri={name} /> }) }
</div>)
}
})
var FeaturedContent = React.createClass({
getInitialState: function() {
return {
featuredNames: [],
featuredNames: {},
};
},
componentWillMount: function() {
lbry.getFeaturedDiscoverNames().then((featuredNames) => {
lbryio.call('discover', 'list', { version: "early-access" } ).then((featuredNames) => {
this.setState({ featuredNames: featuredNames });
});
},
render: function() {
const toolTipText = ('Community Content is a public space where anyone can share content with the ' +
'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' +
'"five" to put your content here!');
console.log(this.state.featuredNames);
return (
<div className="row-fluid">
<div className="span6">
<h3>Featured Content</h3>
{ this.state.featuredNames.map(name => <FileTile key={name} uri={name} />) }
</div>
<div className="span6">
<h3>
Community Content
<ToolTip label="What's this?" body={toolTipText} className="tooltip--header"/>
</h3>
<FileTile uri="one" />
<FileTile uri="two" />
<FileTile uri="three" />
<FileTile uri="four" />
<FileTile uri="five" />
</div>
<div>
{
Object.keys(this.state.featuredNames).map(function(category) {
return <FeaturedCategory key={category} category={category} names={this.state.featuredNames[category]} />
}.bind(this))
}
</div>
);
}
@ -173,12 +173,12 @@ var DiscoverPage = React.createClass({
},
render: function() {
//{ !this.props.query && !this.state.searching ? <FeaturedContent /> : null }
return (
<main>
{ this.state.searching ? <SearchActive /> : null }
{ !this.state.searching && this.props.query && this.state.results.length ? <SearchResults results={this.state.results} /> : null }
{ !this.state.searching && this.props.query && !this.state.results.length ? <SearchNoResults query={this.props.query} /> : null }
{ !this.props.query && !this.state.searching ? <FeaturedContent /> : null }
</main>
);
}

View file

@ -1,7 +1,7 @@
import React from 'react';
import lbryio from '../lbryio.js';
import {getLocal, setLocal} from '../utils.js';
import FormField from '../component/form.js'
import {FormField} from '../component/form.js'
import {Link} from '../component/link.js'
import rewards from '../rewards.js';
@ -12,7 +12,7 @@ const EmailPage = React.createClass({
}
if (!this.state.email) {
this._emailField.warnRequired();
this._emailField.showRequiredError();
}
},
componentWillMount: function() {

View file

@ -2,7 +2,7 @@ import React from 'react';
import lbry from '../lbry.js';
import uri from '../uri.js';
import {Link} from '../component/link.js';
import FormField from '../component/form.js';
import {FormField} from '../component/form.js';
import {FileTileStream} from '../component/file-tile.js';
import {BusyMessage, Thumbnail} from '../component/common.js';

View file

@ -1,7 +1,7 @@
import React from 'react';
import lbry from '../lbry.js';
import uri from '../uri.js';
import FormField from '../component/form.js';
import {FormField, FormRow} from '../component/form.js';
import {Link} from '../component/link.js';
import Modal from '../component/modal.js';
@ -36,7 +36,7 @@ var PublishPage = React.createClass({
for (let fieldName of checkFields) {
var field = this.refs[fieldName];
if (field.getValue() === '') {
field.warnRequired();
field.showRequiredError();
if (!missingFieldFound) {
field.focus();
missingFieldFound = true;
@ -344,9 +344,12 @@ var PublishPage = React.createClass({
<main ref="page">
<form onSubmit={this.handleSubmit}>
<section className="card">
<h4>LBRY Name</h4>
<div className="form-row">
<FormField type="text" ref="name" value={this.state.rawName} onChange={this.handleNameChange} />
<div className="card__title-primary">
<h4>LBRY Name</h4>
</div>
<div className="card__content">
<FormRow label="lbry://" type="text" ref="name" placeholder="lbry://myname" value={this.state.rawName} onChange={this.handleNameChange}
helper={(<div>What LBRY name would you like to claim for this file? <Link label="Read more" href="https://lbry.io/faq/naming" />.</div>)} />
{
(!this.state.name
? null
@ -356,7 +359,6 @@ var PublishPage = React.createClass({
? <em> You already have a claim on the name <strong>{this.state.name}</strong>. You can use this page to update your claim.</em>
: <em> The name <strong>{this.state.name}</strong> is currently claimed for <strong>{this.state.topClaimValue}</strong> {this.state.topClaimValue == 1 ? 'credit' : 'credits'}.</em>)))
}
<div className="help">What LBRY name would you like to claim for this file?</div>
</div>
</section>
@ -381,9 +383,11 @@ var PublishPage = React.createClass({
</section>
<section className="card">
<h4>Choose File</h4>
<FormField name="file" ref="file" type="file" />
{ this.state.myClaimExists ? <div className="help">If you don't choose a file, the file from your existing claim will be used.</div> : null }
<div className="card__title-primary"><h4>Choose File</h4></div>
<div className="card__content">
<FormField name="file" ref="file" type="file" />
{ this.state.myClaimExists ? <div className="help">If you don't choose a file, the file from your existing claim will be used.</div> : null }
</div>
</section>
<section className="card">

View file

@ -1,130 +0,0 @@
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',
width: '600px',
}, referralCodeLabelStyle = {
display: 'inline-block',
cursor: 'default',
width: '130px',
textAlign: 'right',
marginRight: '6px',
};
var ReferralPage = React.createClass({
getInitialState: function() {
return {
submitting: false,
modal: null,
referralCredits: null,
failureReason: null,
}
},
handleSubmit: function(event) {
if (typeof event !== 'undefined') {
event.preventDefault();
}
if (!this.refs.code.value) {
this.setState({
modal: 'missingCode',
});
} else if (!this.refs.email.value) {
this.setState({
modal: 'missingEmail',
});
}
this.setState({
submitting: true,
});
lbry.getUnusedAddress((address) => {
var code = this.refs.code.value;
var email = this.refs.email.value;
var xhr = new XMLHttpRequest;
xhr.addEventListener('load', () => {
var response = JSON.parse(xhr.responseText);
if (response.success) {
this.setState({
modal: 'referralInfo',
referralCredits: response.referralCredits,
});
} else {
this.setState({
submitting: false,
modal: 'lookupFailed',
failureReason: response.reason,
});
}
});
xhr.addEventListener('error', () => {
this.setState({
submitting: false,
modal: 'couldNotConnect',
});
});
xhr.open('POST', 'https://invites.lbry.io/check', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send('code=' + encodeURIComponent(code) + '&address=' + encodeURIComponent(address) +
'&email=' + encodeURIComponent(email));
});
},
closeModal: function() {
this.setState({
modal: null,
});
},
handleFinished: function() {
localStorage.setItem('claimCodeDone', true);
window.location = '?home';
},
render: function() {
return (
<main>
<form onSubmit={this.handleSubmit}>
<div className="card">
<h2>Check your referral credits</h2>
<section style={referralCodeContentStyle}>
<p>Have you referred others to LBRY? Enter your referral code and email address below to check how many credits you've earned!</p>
<p>As a reminder, your referral code is the same as your LBRY invitation code.</p>
</section>
<section>
<section><label style={referralCodeLabelStyle} htmlFor="code">Referral code</label><input name="code" ref="code" /></section>
<section><label style={referralCodeLabelStyle} htmlFor="email">Email</label><input name="email" ref="email" /></section>
</section>
<section>
<Link button="primary" label={this.state.submitting ? "Submitting..." : "Submit"}
disabled={this.state.submitting} onClick={this.handleSubmit} />
<input type='submit' className='hidden' />
</section>
</div>
</form>
<Modal isOpen={this.state.modal == 'referralInfo'} contentLabel="Credit earnings"
onConfirmed={this.handleFinished}>
{this.state.referralCredits > 0
? `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'} contentLabel={this.state.failureReason}
onConfirmed={this.closeModal}>
{this.state.failureReason}
</Modal>
<Modal isOpen={this.state.modal == 'couldNotConnect'} contentLabel="Couldn't confirm referral code"
onConfirmed={this.closeModal}>
LBRY couldn't connect to our servers to confirm your referral code. Please check your internet connection.
</Modal>
</main>
);
}
});
export default ReferralPage;

View file

@ -3,124 +3,124 @@ import lbryio from '../lbryio.js';
import {Link} from '../component/link.js';
import Notice from '../component/notice.js';
import {CreditAmount} from '../component/common.js';
const {shell} = require('electron');
const querystring = require('querystring');
const GITHUB_CLIENT_ID = '6baf581d32bad60519';
const LinkGithubReward = React.createClass({
propTypes: {
onClaimed: React.PropTypes.func,
},
_launchLinkPage: function() {
/* const githubAuthParams = {
client_id: GITHUB_CLIENT_ID,
redirect_uri: 'https://lbry.io/',
scope: 'user:email,public_repo',
allow_signup: false,
}
shell.openExternal('https://github.com/login/oauth/authorize?' + querystring.stringify(githubAuthParams)); */
shell.openExternal('https://lbry.io');
},
handleConfirmClicked: function() {
this.setState({
confirming: true,
});
lbry.get_new_address().then((address) => {
lbryio.call('reward', 'new', {
reward_type: 'new_developer',
access_token: '**access token here**',
wallet_address: address,
}, 'post').then((response) => {
console.log('response:', response);
this.props.onClaimed(); // This will trigger another API call to show that we succeeded
this.setState({
confirming: false,
error: null,
});
}, (error) => {
console.log('failed with error:', error);
this.setState({
confirming: false,
error: error,
});
});
});
},
getInitialState: function() {
return {
confirming: false,
error: null,
};
},
render: function() {
return (
<section>
<p><Link button="alt" label="Link with GitHub" onClick={this._launchLinkPage} /></p>
<section className="reward-page__details">
<p>This will open a browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.</p>
<p>Once you're finished, you may confirm you've linked the account to receive your reward.</p>
</section>
{this.state.error
? <Notice isError>
{this.state.error.message}
</Notice>
: null}
<Link button="primary" label={this.state.confirming ? 'Confirming...' : 'Confirm'}
onClick={this.handleConfirmClicked} />
</section>
);
}
});
const RewardPage = React.createClass({
propTypes: {
name: React.PropTypes.string.isRequired,
},
_getRewardType: function() {
lbryio.call('reward_type', 'get', this.props.name).then((rewardType) => {
this.setState({
rewardType: rewardType,
});
});
},
getInitialState: function() {
return {
rewardType: null,
};
},
componentWillMount: function() {
this._getRewardType();
},
render: function() {
if (!this.state.rewardType) {
return null;
}
let Reward;
if (this.props.name == 'link_github') {
Reward = LinkGithubReward;
}
const {title, description, value} = this.state.rewardType;
return (
<main>
<section className="card">
<h2>{title}</h2>
<CreditAmount amount={value} />
<p>{this.state.rewardType.claimed
? <span class="empty">This reward has been claimed.</span>
: description}</p>
<Reward onClaimed={this._getRewardType} />
</section>
</main>
);
}
});
export default RewardPage;
//
// const {shell} = require('electron');
// const querystring = require('querystring');
//
// const GITHUB_CLIENT_ID = '6baf581d32bad60519';
//
// const LinkGithubReward = React.createClass({
// propTypes: {
// onClaimed: React.PropTypes.func,
// },
// _launchLinkPage: function() {
// /* const githubAuthParams = {
// client_id: GITHUB_CLIENT_ID,
// redirect_uri: 'https://lbry.io/',
// scope: 'user:email,public_repo',
// allow_signup: false,
// }
// shell.openExternal('https://github.com/login/oauth/authorize?' + querystring.stringify(githubAuthParams)); */
// shell.openExternal('https://lbry.io');
// },
// handleConfirmClicked: function() {
// this.setState({
// confirming: true,
// });
//
// lbry.get_new_address().then((address) => {
// lbryio.call('reward', 'new', {
// reward_type: 'new_developer',
// access_token: '**access token here**',
// wallet_address: address,
// }, 'post').then((response) => {
// console.log('response:', response);
//
// this.props.onClaimed(); // This will trigger another API call to show that we succeeded
//
// this.setState({
// confirming: false,
// error: null,
// });
// }, (error) => {
// console.log('failed with error:', error);
// this.setState({
// confirming: false,
// error: error,
// });
// });
// });
// },
// getInitialState: function() {
// return {
// confirming: false,
// error: null,
// };
// },
// render: function() {
// return (
// <section>
// <p><Link button="alt" label="Link with GitHub" onClick={this._launchLinkPage} /></p>
// <section className="reward-page__details">
// <p>This will open a browser window where you can authorize GitHub to link your account to LBRY. This will record your email (no spam) and star the LBRY repo.</p>
// <p>Once you're finished, you may confirm you've linked the account to receive your reward.</p>
// </section>
// {this.state.error
// ? <Notice isError>
// {this.state.error.message}
// </Notice>
// : null}
//
// <Link button="primary" label={this.state.confirming ? 'Confirming...' : 'Confirm'}
// onClick={this.handleConfirmClicked} />
// </section>
// );
// }
// });
//
// const RewardPage = React.createClass({
// propTypes: {
// name: React.PropTypes.string.isRequired,
// },
// _getRewardType: function() {
// lbryio.call('reward_type', 'get', this.props.name).then((rewardType) => {
// this.setState({
// rewardType: rewardType,
// });
// });
// },
// getInitialState: function() {
// return {
// rewardType: null,
// };
// },
// componentWillMount: function() {
// this._getRewardType();
// },
// render: function() {
// if (!this.state.rewardType) {
// return null;
// }
//
// let Reward;
// if (this.props.name == 'link_github') {
// Reward = LinkGithubReward;
// }
//
// const {title, description, value} = this.state.rewardType;
// return (
// <main>
// <section className="card">
// <h2>{title}</h2>
// <CreditAmount amount={value} />
// <p>{this.state.rewardType.claimed
// ? <span class="empty">This reward has been claimed.</span>
// : description}</p>
// <Reward onClaimed={this._getRewardType} />
// </section>
// </main>
// );
// }
// });
//
// export default RewardPage;

View file

@ -1,28 +1,34 @@
import React from 'react';
import lbry from '../lbry.js';
import lbryio from '../lbryio.js';
import {CreditAmount} from '../component/common.js';
import {CreditAmount, Icon} from '../component/common.js';
import rewards from '../rewards.js';
import Modal from '../component/modal.js';
import {Link} from '../component/link.js';
import {RewardLink} from '../component/link.js';
const RewardTile = React.createClass({
propTypes: {
name: React.PropTypes.string.isRequired,
type: React.PropTypes.string.isRequired,
title: React.PropTypes.string.isRequired,
description: React.PropTypes.string.isRequired,
claimed: React.PropTypes.bool.isRequired,
value: React.PropTypes.number.isRequired,
onRewardClaim: React.PropTypes.func
},
render: function() {
return (
<section className="card">
<div className={"row-fluid card-content"}>
<h3><Link label={this.props.title} href={'?reward=' + this.props.name} /></h3>
<CreditAmount amount={this.props.value} />
<section>{this.props.description}</section>
{this.props.claimed
? <span className="empty">This reward has been claimed.</span>
: <Link button="primary" label="Start reward" href={'?reward=' + this.props.name} />}
<div className="card__inner">
<div className="card__title-primary">
<CreditAmount amount={this.props.value} />
<h3>{this.props.title}</h3>
</div>
<div className="card__actions">
{this.props.claimed
? <span><Icon icon="icon-check" /> Reward claimed.</span>
: <RewardLink {...this.props} />}
</div>
<div className="card__content">{this.props.description}</div>
</div>
</section>
);
@ -31,29 +37,29 @@ const RewardTile = React.createClass({
var RewardsPage = React.createClass({
componentWillMount: function() {
lbryio.call('reward_type', 'list', {}).then((rewardTypes) => {
this.setState({
rewardTypes: rewardTypes,
});
});
this.loadRewards()
},
getInitialState: function() {
return {
rewardTypes: null,
userRewards: null,
};
},
loadRewards: function() {
lbryio.call('reward', 'list', {}).then((userRewards) => {
this.setState({
userRewards: userRewards,
});
});
},
render: function() {
return (
<main>
<form onSubmit={this.handleSubmit}>
<div className="card">
<h2>Rewards</h2>
{!this.state.rewardTypes
? null
: this.state.rewardTypes.map(({name, title, description, claimed, value}) => {
return <RewardTile key={name} name={name} title={title} description={description} claimed={claimed} value={value} />;
})}
</div>
{!this.state.userRewards
? null
: this.state.userRewards.map(({RewardType, RewardTitle, RewardDescription, TransactionID, RewardAmount}) => {
return <RewardTile key={RewardType} onRewardClaim={this.loadRewards} type={RewardType} title={RewardTitle} description={RewardDescription} claimed={!!TransactionID} value={RewardAmount} />;
})}
</form>
</main>
);

View file

@ -1,21 +1,7 @@
import React from 'react';
import {FormField, FormRow} from '../component/form.js';
import lbry from '../lbry.js';
var settingsRadioOptionStyles = {
display: 'block',
marginLeft: '13px'
}, settingsCheckBoxOptionStyles = {
display: 'block',
marginLeft: '13px'
}, settingsNumberFieldStyles = {
width: '40px'
}, downloadDirectoryLabelStyles = {
fontSize: '.9em',
marginLeft: '13px'
}, downloadDirectoryFieldStyles= {
width: '300px'
};
var SettingsPage = React.createClass({
onRunOnStartChange: function (event) {
lbry.setDaemonSetting('run_on_startup', event.target.checked);
@ -81,29 +67,54 @@ var SettingsPage = React.createClass({
return (
<main>
<section className="card">
<h3>Run on Startup</h3>
<label style={settingsCheckBoxOptionStyles}>
<input type="checkbox" onChange={this.onRunOnStartChange} defaultChecked={this.state.daemonSettings.run_on_startup} /> Run LBRY automatically when I start my computer
</label>
</section>
<section className="card">
<h3>Download Directory</h3>
<div className="help">Where would you like the files you download from LBRY to be saved?</div>
<input style={downloadDirectoryFieldStyles} type="text" name="download_directory" defaultValue={this.state.daemonSettings.download_directory} onChange={this.onDownloadDirChange}/>
</section>
<section className="card">
<h3>Bandwidth Limits</h3>
<div className="form-row">
<h4>Max Upload</h4>
<label style={settingsRadioOptionStyles}>
<input type="radio" name="max_upload_pref" onChange={this.onMaxUploadPrefChange.bind(this, false)} defaultChecked={!this.state.isMaxUpload}/> Unlimited
</label>
<label style={settingsRadioOptionStyles}>
<input type="radio" name="max_upload_pref" onChange={this.onMaxUploadPrefChange.bind(this, true)} defaultChecked={this.state.isMaxUpload}/> { this.state.isMaxUpload ? 'Up to' : 'Choose limit...' }
<span className={ this.state.isMaxUpload ? '' : 'hidden'}> <input type="number" min="0" step=".5" defaultValue={this.state.daemonSettings.max_upload} style={settingsNumberFieldStyles} onChange={this.onMaxUploadFieldChange}/> MB/s</span>
</label>
<div className="card__content">
<h3>Run on Startup</h3>
</div>
<div className="form-row">
<div className="card__content">
<FormRow type="checkbox"
onChange={this.onRunOnStartChange}
defaultChecked={this.state.daemonSettings.run_on_startup}
label="Run LBRY automatically when I start my computer" />
</div>
</section>
<section className="card">
<div className="card__content">
<h3>Download Directory</h3>
</div>
<div className="card__content">
<FormRow type="text"
name="download_directory"
defaultValue={this.state.daemonSettings.download_directory}
helper="LBRY downloads will be saved here."
onChange={this.onDownloadDirChange} />
</div>
</section>
<section className="card">
<div className="card__content">
<h3>Bandwidth Limits</h3>
</div>
<div className="card__content">
<h4>Max Upload</h4>
<FormField type="radio"
name="max_upload_pref"
onChange={this.onMaxUploadPrefChange.bind(this, false)}
defaultChecked={!this.state.isMaxUpload}
label="Unlimited" />
<FormField type="radio"
name="max_upload_pref"
onChange={this.onMaxUploadPrefChange.bind(this, true)}
defaultChecked={this.state.isMaxUpload}
label={ this.state.isMaxUpload ? 'Up to' : 'Choose limit...' } />
{ this.state.isMaxUpload ?
<FormField type="number"
min="0"
step=".5"
label="MB/s"
onChange={this.onMaxUploadFieldChange}
/> : ''
}
</div>
<div className="card__content">
<h4>Max Download</h4>
<label style={settingsRadioOptionStyles}>
<input type="radio" name="max_download_pref" onChange={this.onMaxDownloadPrefChange.bind(this, false)} defaultChecked={!this.state.isMaxDownload}/> Unlimited
@ -115,40 +126,56 @@ var SettingsPage = React.createClass({
</div>
</section>
<section className="card">
<h3>Content</h3>
<div className="form-row">
<label style={settingsCheckBoxOptionStyles}>
<input type="checkbox" onChange={this.onShowNsfwChange} defaultChecked={this.state.showNsfw} /> Show NSFW content
</label>
<div className="help">
NSFW content may include nudity, intense sexuality, profanity, or other adult content.
By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction.
</div>
<div className="card__content">
<h3>Content</h3>
</div>
<div class="card__content">
<FormRow type="checkbox"
onChange={this.onShowUnavailableChange}
defaultChecked={this.state.showUnavailable}
label="Show unavailable content in search results" />
</div>
<div className="card__content">
<FormRow label="Show NSFW content" type="checkbox"
onChange={this.onShowNsfwChange} defaultChecked={this.state.showNsfw}
helper="NSFW content may include nudity, intense sexuality, profanity, or other adult content. By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. " />
</div>
</section>
<section className="card">
<h3>Search</h3>
<div className="form-row">
<div className="help">
Would you like search results to include items that are not currently available for download?
<div className="card__content">
<h3>Share Diagnostic Data</h3>
</div>
<label style={settingsCheckBoxOptionStyles}>
<input type="checkbox" onChange={this.onShowUnavailableChange} defaultChecked={this.state.showUnavailable} />
Show unavailable content in search results
</label>
<div class="card__content">
<FormRow type="checkbox"
onChange={this.onShareDataChange}
defaultChecked={this.state.daemonSettings.share_debug_info}
label="Help make LBRY better by contributing diagnostic data about my usage" />
</div>
</section>
<section className="card">
<h3>Share Diagnostic Data</h3>
<label style={settingsCheckBoxOptionStyles}>
<input type="checkbox" onChange={this.onShareDataChange} defaultChecked={this.state.daemonSettings.share_debug_info} />
Help make LBRY better by contributing diagnostic data about my usage
</label>
</section>
</main>
);
}
});
/*
<section className="card">
<h3>Search</h3>
<div className="form-row">
<div className="help">
Would you like search results to include items that are not currently available for download?
</div>
<label style={settingsCheckBoxOptionStyles}>
<input type="checkbox" onChange={this.onShowUnavailableChange} defaultChecked={this.state.showUnavailable} /> Show unavailable content in search results
</label>
</div>
</section>
<section className="card">
<h3>Share Diagnostic Data</h3>
<label style={settingsCheckBoxOptionStyles}>
<input type="checkbox" onChange={this.onShareDataChange} defaultChecked={this.state.daemonSettings.upload_log} /> Help make LBRY better by contributing diagnostic data about my usage
</label>
</section>
*/
export default SettingsPage;

View file

@ -2,12 +2,9 @@ import React from 'react';
import lbry from '../lbry.js';
import {Link} from '../component/link.js';
import Modal from '../component/modal.js';
import {FormField, FormRow} from '../component/form.js';
import {Address, BusyMessage, CreditAmount} from '../component/common.js';
var addressRefreshButtonStyle = {
fontSize: '11pt',
};
var AddressSection = React.createClass({
_refreshAddress: function(event) {
if (typeof event !== 'undefined') {
@ -27,12 +24,12 @@ var AddressSection = React.createClass({
event.preventDefault();
}
lbry.getNewAddress((address) => {
lbry.wallet_new_address().then((address) => {
window.localStorage.setItem('wallet_address', address);
this.setState({
address: address,
});
});
}.bind(this));
},
getInitialState: function() {
@ -60,12 +57,20 @@ var AddressSection = React.createClass({
render: function() {
return (
<section className="card">
<h3>Wallet Address</h3>
<Address address={this.state.address} /> <Link text="Get new address" icon='icon-refresh' onClick={this._getNewAddress} style={addressRefreshButtonStyle} />
<input type='submit' className='hidden' />
<div className="help">
<p>Other LBRY users may send credits to you by entering this address on the "Send" page.</p>
You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.
<div className="card__title-primary">
<h3>Wallet Address</h3>
</div>
<div className="card__content">
<Address address={this.state.address} />
</div>
<div className="card__actions">
<Link label="Get New Address" button="primary" icon='icon-refresh' onClick={this._getNewAddress} />
</div>
<div className="card__content">
<div className="help">
<p>Other LBRY users may send credits to you by entering this address on the "Send" page.</p>
<p>You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.</p>
</div>
</div>
</section>
);
@ -143,27 +148,26 @@ var SendToAddressSection = React.createClass({
return (
<section className="card">
<form onSubmit={this.handleSubmit}>
<h3>Send Credits</h3>
<div className="form-row">
<label htmlFor="amount">Amount</label>
<input id="amount" type="text" size="10" onChange={this.setAmount}></input>
<div className="card__title-primary">
<h3>Send Credits</h3>
</div>
<div className="form-row">
<label htmlFor="address">Recipient address</label>
<input id="address" type="text" size="60" onChange={this.setAddress}></input>
<div className="card__content">
<FormRow label="Amount" type="number" placeholder="1.23" size="10" onChange={this.setAmount} />
</div>
<div className="form-row form-row-submit">
<div className="card__content">
<FormRow label="Recipient Address" placeholder="bbFxRyXXXXXXXXXXXZD8nE7XTLUxYnddTs" type="text" size="60" onChange={this.setAddress} />
</div>
<div className="card__actions card__actions--form-submit">
<Link button="primary" label="Send" onClick={this.handleSubmit} disabled={!(parseFloat(this.state.amount) > 0.0) || this.state.address == ""} />
<input type='submit' className='hidden' />
</div>
{
this.state.results ?
<div className="form-row">
<h4>Results</h4>
{this.state.results}
</div>
: ''
}
{
this.state.results ?
<div className="card__content">
<h4>Results</h4>
{this.state.results}
</div> : ''
}
</form>
<Modal isOpen={this.state.modal === 'insufficientBalance'} contentLabel="Insufficient balance"
onConfirmed={this.closeModal}>
@ -231,25 +235,29 @@ var TransactionList = React.createClass({
}
return (
<section className="card">
<h3>Transaction History</h3>
{ this.state.transactionItems === null ? <BusyMessage message="Loading transactions" /> : '' }
{ this.state.transactionItems && rows.length === 0 ? <div className="empty">You have no transactions.</div> : '' }
{ this.state.transactionItems && rows.length > 0 ?
<table className="table-standard table-stretch">
<thead>
<tr>
<th>Amount</th>
<th>Date</th>
<th>Time</th>
<th>Transaction</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
<div className="card__title-primary">
<h3>Transaction History</h3>
</div>
<div className="card__content">
{ this.state.transactionItems === null ? <BusyMessage message="Loading transactions" /> : '' }
{ this.state.transactionItems && rows.length === 0 ? <div className="empty">You have no transactions.</div> : '' }
{ this.state.transactionItems && rows.length > 0 ?
<table className="table-standard table-stretch">
<thead>
<tr>
<th>Amount</th>
<th>Date</th>
<th>Time</th>
<th>Transaction</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
: ''
}
}
</div>
</section>
);
}
@ -290,9 +298,13 @@ var WalletPage = React.createClass({
return (
<main className="page">
<section className="card">
<h3>Balance</h3>
{ this.state.balance === null ? <BusyMessage message="Checking balance" /> : ''}
{ this.state.balance !== null ? <CreditAmount amount={this.state.balance} precision={8} /> : '' }
<div className="card__title-primary">
<h3>Balance</h3>
</div>
<div className="card__content">
{ this.state.balance === null ? <BusyMessage message="Checking balance" /> : ''}
{ this.state.balance !== null ? <CreditAmount amount={this.state.balance} precision={8} /> : '' }
</div>
</section>
{ this.props.viewingPage === 'wallet' ? <TransactionList /> : '' }
{ this.props.viewingPage === 'send' ? <SendToAddressSection /> : '' }

View file

@ -1,11 +1,14 @@
import lbry from './lbry.js';
import lbryio from './lbryio.js';
const MESSAGES = {
new_developer: "Your reward has been confirmed for registering as a new developer.",
confirm_email: "Your reward has been confirmed for verifying your email address.",
first_publish: "Your reward has been confirmed for making your first publication.",
};
function rewardMessage(type, amount) {
return {
new_developer: "Your reward has been confirmed for registering as a new developer.",
new_user: `You received ${amount} LBC new user reward.`,
confirm_email: "Your reward has been confirmed for verifying your email address.",
first_publish: "Your reward has been confirmed for making your first publication.",
}[type];
}
const rewards = {};
@ -13,22 +16,25 @@ rewards.claimReward = function(type) {
return new Promise((resolve, reject) => {
console.log('top of promise body')
lbry.get_new_address().then((address) => {
console.log('top of get_new_address')
const params = {
reward_type: type,
wallet_address: address,
};
lbryio.call('reward', 'new', params, 'post').then(({RewardAmount}) => {
const result = {
type: type,
amount: RewardAmount,
message: MESSAGES[type],
};
const
message = rewardMessage(type, RewardAmount),
result = {
type: type,
amount: RewardAmount,
message: message
};
// Display global notice
document.dispatchEvent(new CustomEvent('globalNotice', {
detail: {
message: MESSAGES[type],
message: message,
linkText: "Show All",
linkTarget: "?rewards",
isError: false,
},
}));
@ -36,16 +42,7 @@ rewards.claimReward = function(type) {
// Add more events here to display other places
resolve(result);
}, (error) => {
document.dispatchEvent(new CustomEvent('globalNotice', {
detail: {
message: `Failed to claim reward: ${error.message}`,
isError: true,
},
}));
document.dispatchEvent(new CustomEvent('rewardFailed', error));
reject(error);
});
}, reject);
});
});
}

View file

@ -13,3 +13,19 @@ export function getLocal(key) {
export function setLocal(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
/**
* Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value
* is not set yet.
*/
export function getSession(key) {
const itemRaw = sessionStorage.getItem(key);
return itemRaw === null ? undefined : JSON.parse(itemRaw);
}
/**
* Thin wrapper around localStorage.setItem(). Converts value to JSON.
*/
export function setSession(key, value) {
sessionStorage.setItem(key, JSON.stringify(value));
}

View file

@ -60,6 +60,11 @@ $drawer-width: 240px;
text-align: center;
}
#window
{
position: relative; /*window has it's own z-index inside of it*/
z-index: 1;
}
#window.drawer-closed
{
#drawer { display: none }
@ -100,12 +105,28 @@ $drawer-width: 240px;
.header-search
{
margin-left: 60px;
$padding-adjust: 36px;
text-align: center;
.icon {
position: absolute;
top: $spacing-vertical * 1.5 / 2 + 2px; //hacked
margin-left: -$padding-adjust + 14px; //hacked
}
input[type="search"] {
position: relative;
left: -$padding-adjust;
background: rgba(255, 255, 255, 0.3);
color: white;
width: 400px;
height: $spacing-vertical * 1.5;
line-height: $spacing-vertical * 1.5;
padding-left: $padding-adjust + 3;
padding-right: 3px;
@include border-radius(2px);
@include placeholder-color(#e8e8e8);
&:focus {
box-shadow: $focus-box-shadow;
}
}
}
@ -159,26 +180,6 @@ nav.sub-header
{
padding: $spacing-vertical;
}
h2
{
margin-bottom: $spacing-vertical;
}
h3, h4
{
margin-bottom: $spacing-vertical / 2;
margin-top: $spacing-vertical;
&:first-child
{
margin-top: 0;
}
}
.meta
{
+ h2, + h3, + h4
{
margin-top: 0;
}
}
}
$header-icon-size: 1.5em;
@ -197,48 +198,6 @@ $header-icon-size: 1.5em;
padding: 0 6px 0 18px;
}
.card {
margin-left: auto;
margin-right: auto;
max-width: 800px;
padding: $spacing-vertical;
background: $color-bg;
box-shadow: $default-box-shadow;
border-radius: 2px;
}
.card-obscured
{
position: relative;
}
.card-obscured .card-content {
-webkit-filter: blur($blur-intensity);
-moz-filter: blur($blur-intensity);
-o-filter: blur($blur-intensity);
-ms-filter: blur($blur-intensity);
filter: blur($blur-intensity);
}
.card-overlay {
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
padding: 20px;
background-color: rgba(128, 128, 128, 0.8);
color: #fff;
display: flex;
align-items: center;
font-weight: 600;
}
.card-series-submit
{
margin-left: auto;
margin-right: auto;
max-width: 800px;
padding: $spacing-vertical / 2;
}
.full-screen
{
width: 100%;

View file

@ -1,66 +0,0 @@
@import "global";
/* this probably shouldn't exist but it does so here we are - Jeremy */
textarea,
select,
input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
input[type="search"],
input[type="date"] {
@include placeholder {
color: lighten($color-text-dark, 60%);
}
transition: all $transition-standard;
cursor: pointer;
padding-left: 1px;
padding-right: 1px;
box-sizing: border-box;
-webkit-appearance: none;
&[readonly] {
background-color: #bbb;
}
}
input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
input[type="search"],
input[type="date"] {
border-bottom: 2px solid $color-form-border;
line-height: $spacing-vertical - 4;
height: $spacing-vertical * 1.5;
&.form-field__input--error {
border-color: $color-error;
}
}
textarea:focus,
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="date"]:focus {
border-color: $color-primary;
}
textarea {
border: 2px solid $color-form-border;
}
.form-row
{
+ .form-row
{
margin-top: $spacing-vertical;
}
+ .form-row-submit
{
margin-top: $spacing-vertical;
}
}

View file

@ -6,8 +6,10 @@ $padding-button: 12px;
$padding-text-link: 4px;
$color-primary: #155B4A;
$color-primary-light: saturate(lighten($color-primary, 50%), 20%);
$color-light-alt: hsl(hue($color-primary), 15, 85);
$color-text-dark: #000;
$color-black-transparent: rgba(32,32,32,0.9);
$color-help: rgba(0,0,0,.6);
$color-notice: #8a6d3b;
$color-error: #a94442;
@ -30,7 +32,7 @@ $height-header: $spacing-vertical * 2.5;
$height-button: $spacing-vertical * 1.5;
$default-box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);
//$focus-box-shadow: 2px 4px 4px 0 rgba(0,0,0,.14),2px 5px 3px -2px rgba(0,0,0,.2),2px 3px 7px 0 rgba(0,0,0,.12);
$focus-box-shadow: 2px 4px 4px 0 rgba(0,0,0,.14),2px 5px 3px -2px rgba(0,0,0,.2),2px 3px 7px 0 rgba(0,0,0,.12);
$transition-standard: .225s ease;

View file

@ -38,6 +38,7 @@
text-align: center;
}
/*
section
{
margin-bottom: $spacing-vertical;
@ -46,10 +47,10 @@ section
margin-bottom: 0;
}
&:only-child {
/* If it's an only child, assume it's part of a React layout that will handle the last child condition on its own */
margin-bottom: $spacing-vertical;
}
}
*/
main h1 {
font-size: 2.0em;
@ -178,7 +179,8 @@ p
background: rgb(255, 255, 255);
overflow: auto;
border-radius: 4px;
padding: 36px;
padding: $spacing-vertical;
box-shadow: $default-box-shadow;
max-width: 250px;
}

View file

@ -7,6 +7,10 @@ body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fiel
{
outline: 0;
}
input::-webkit-search-cancel-button {
/* Remove default */
-webkit-appearance: none;
}
table
{
border-collapse: collapse;

View file

@ -1,12 +1,12 @@
@import "_reset";
@import "_grid";
@import "_icons";
@import "_form";
@import "_mediaelement";
@import "_canvas";
@import "_gui";
@import "component/_table";
@import "component/_button.scss";
@import "component/_card.scss";
@import "component/_file-actions.scss";
@import "component/_file-tile.scss";
@import "component/_form-field.scss";
@ -16,7 +16,7 @@
@import "component/_channel-indicator.scss";
@import "component/_notice.scss";
@import "component/_modal-page.scss";
@import "component/_notification-bar.scss";
@import "component/_snack-bar.scss";
@import "page/_developer.scss";
@import "page/_watch.scss";
@import "page/_reward.scss";

View file

@ -0,0 +1,122 @@
@import "../global";
$padding-card-horizontal: $spacing-vertical * 2/3;
.card {
margin-left: auto;
margin-right: auto;
max-width: 800px;
background: $color-bg;
box-shadow: $default-box-shadow;
border-radius: 2px;
margin-bottom: $spacing-vertical * 2/3;
overflow: auto;
}
.card--obscured
{
position: relative;
}
.card--obscured .card__inner {
-webkit-filter: blur($blur-intensity);
-moz-filter: blur($blur-intensity);
-o-filter: blur($blur-intensity);
-ms-filter: blur($blur-intensity);
filter: blur($blur-intensity);
}
.card__title-primary {
padding: 0 $padding-card-horizontal;
margin-top: $spacing-vertical;
}
.card__title-identity {
padding: 0 $padding-card-horizontal;
margin-top: $spacing-vertical * 1/3;
margin-bottom: $spacing-vertical * 1/3;
}
.card__actions {
padding: 0 $padding-card-horizontal;
}
.card__actions {
margin-top: $spacing-vertical * 2/3;
}
.card__actions--bottom {
margin-top: $spacing-vertical * 1/3;
margin-bottom: $spacing-vertical * 1/3;
}
.card__actions--form-submit {
margin-top: $spacing-vertical;
margin-bottom: $spacing-vertical * 2/3;
}
.card__content {
margin-top: $spacing-vertical * 2/3;
margin-bottom: $spacing-vertical * 2/3;
padding: 0 $padding-card-horizontal;
}
.card__subtext {
color: #444;
margin-top: 12px;
font-size: 0.9em;
margin-top: $spacing-vertical * 2/3;
margin-bottom: $spacing-vertical * 2/3;
padding: 0 $padding-card-horizontal;
}
.card__subtext--two-lines {
height: $font-size * 0.9 * $font-line-height * 2;
}
.card-overlay {
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
padding: 20px;
background-color: rgba(128, 128, 128, 0.8);
color: #fff;
display: flex;
align-items: center;
font-weight: 600;
}
.card__media img {
max-width: 100%;;
display: block;
margin-left: auto;
margin-right: auto;
}
$width-card-small: $spacing-vertical * 12;
.card--small {
width: $width-card-small;
}
.card--small .card__media {
max-height: $width-card-small * 9 / 16;
img {
max-height: $width-card-small * 9 / 16;
}
}
.card__subtitle {
color: $color-meta-light;
font-size: 0.85em;
}
.card-series-submit
{
margin-left: auto;
margin-right: auto;
max-width: 800px;
padding: $spacing-vertical / 2;
}
.card-row {
> .card {
vertical-align: top;
display: inline-block;
margin-right: $spacing-vertical / 3;
}
+ .card-row {
margin-top: $spacing-vertical * 1/3;
}
}
.card-row__header {
margin-bottom: $spacing-vertical / 3;
}

View file

@ -2,10 +2,10 @@
.file-tile__row {
height: $spacing-vertical * 7;
}
.file-tile__row--unavailable {
opacity: 0.5;
.file-price {
float: right;
}
}
.file-tile__thumbnail {
@ -20,10 +20,6 @@
font-weight: bold;
}
.file-tile__cost {
float: right;
}
.file-tile__description {
color: #444;
margin-top: 12px;

View file

@ -1,14 +1,88 @@
@import "../global";
.form-field {
display: inline-block;
.form-row-submit
{
margin-top: $spacing-vertical;
}
.form-field__label {
$height-input: $spacing-vertical * 1.5;
.form-row__label-row {
margin-top: $spacing-vertical * 2/3;
margin-bottom: $spacing-vertical * 1/3;
line-height: 1;
}
.form-row__label-row--prefix {
float: left;
margin-right: 5px;
line-height: $height-input;
}
.form-field {
display: inline-block;
input[type="checkbox"],
input[type="radio"] {
cursor: pointer;
}
textarea,
select,
input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
input[type="search"],
input[type="date"] {
@include placeholder {
color: lighten($color-text-dark, 60%);
}
transition: all $transition-standard;
cursor: pointer;
padding-left: 1px;
padding-right: 1px;
box-sizing: border-box;
-webkit-appearance: none;
&[readonly] {
background-color: #bbb;
}
}
input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
input[type="search"],
input[type="date"] {
border-bottom: 2px solid $color-form-border;
line-height: $spacing-vertical - 4;
height: $height-input;
&.form-field__input--error {
border-color: $color-error;
}
}
textarea:focus,
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="date"]:focus {
border-color: $color-primary;
}
textarea {
border: 2px solid $color-form-border;
}
}
.form-field__label {
&[for] { cursor: pointer; }
> input[type="checkbox"], input[type="radio"] {
margin-right: 6px;
}
}
.form-field__label--error {
color: $color-error;
@ -18,8 +92,8 @@
width: 330px;
}
.form-field__input-text-number {
width: 50px;
.form-field__input-number {
width: 100px;
}
.form-field__error, .form-field__helper {

View file

@ -19,14 +19,16 @@
justify-content: center;
align-items: center;
border: 1px solid rgb(204, 204, 204);
background: rgb(255, 255, 255);
overflow: auto;
}
.modal-page--full {
left: 0;
right: 0;
top: 0;
bottom: 0;
border: 1px solid rgb(204, 204, 204);
background: rgb(255, 255, 255);
overflow: auto;
}
/*

View file

@ -1,6 +0,0 @@
@import "../global";
.notification-bar {
margin-top: 5px;
margin-right: 10px;
}

View file

@ -0,0 +1,42 @@
@import "../global";
$padding-snack-horizontal: $spacing-vertical;
.snack-bar {
$height-snack: $spacing-vertical * 2;
$padding-snack-vertical: $spacing-vertical / 4;
line-height: $height-snack - $padding-snack-vertical * 2;
padding: $padding-snack-vertical $padding-snack-horizontal;
position: fixed;
top: $spacing-vertical;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
min-width: 300px;
max-width: 500px;
background: $color-black-transparent;
color: #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
border-radius: 2px;
transition: all $transition-standard;
z-index: 10000; /*hack to get it over react modal */
}
.snack-bar__action {
display: inline-block;
text-transform: uppercase;
color: $color-primary-light;
margin: 0px 0px 0px $padding-snack-horizontal;
min-width: min-content;
&:hover {
text-decoration: underline;
}
}