Merge pull request #27 from lbryio/ux_gonna_give_it_to_ya
Wicked awesome UX overhaul
This commit is contained in:
commit
f78c6a65bb
27 changed files with 1308 additions and 1087 deletions
7
dist/index.html
vendored
7
dist/index.html
vendored
|
@ -27,8 +27,13 @@
|
|||
<script src="./js/mediaelement/mediaelement-and-player.min.js"></script>
|
||||
<script src="./js/lbry.js"></script>
|
||||
<script src="./js/component/common.js"></script>
|
||||
<script src="./js/component/form.js"></script>
|
||||
<script src="./js/component/link.js"></script>
|
||||
<script src="./js/component/menu.js"></script>
|
||||
<script src="./js/component/header.js"></script>
|
||||
<script src="./js/component/drawer.js"></script>
|
||||
<script src="./js/component/splash.js"></script>
|
||||
<script src="./js/page/home.js"></script>
|
||||
<script src="./js/page/discover.js"></script>
|
||||
<script src="./js/page/settings.js"></script>
|
||||
<script src="./js/page/help.js"></script>
|
||||
<script src="./js/page/watch.js"></script>
|
||||
|
|
88
js/app.js
88
js/app.js
|
@ -1,21 +1,32 @@
|
|||
var App = React.createClass({
|
||||
getInitialState: function() {
|
||||
// For now, routes are in format ?page or ?page=args
|
||||
var match, param, val, viewingPage;
|
||||
var match, param, val, viewingPage,
|
||||
drawerOpenRaw = sessionStorage.getItem('drawerOpen');
|
||||
|
||||
[match, param, val] = window.location.search.match(/\??([^=]*)(?:=(.*))?/);
|
||||
|
||||
if (param && ['settings', 'help', 'start', 'watch', 'report', 'files', 'claim', 'show', 'wallet', 'publish'].indexOf(param) != -1) {
|
||||
if (param && ['settings', 'discover', 'help', 'start', 'watch', 'report', 'files', 'claim', 'show', 'wallet', 'publish'].indexOf(param) != -1) {
|
||||
viewingPage = param;
|
||||
}
|
||||
|
||||
return {
|
||||
viewingPage: viewingPage ? viewingPage : 'home',
|
||||
viewingPage: viewingPage ? viewingPage : 'discover',
|
||||
drawerOpen: drawerOpenRaw !== null ? JSON.parse(drawerOpenRaw) : true,
|
||||
pageArgs: val,
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
lbry.getStartNotice(function(notice) {
|
||||
if (notice) {
|
||||
alert(notice);
|
||||
}
|
||||
});
|
||||
},
|
||||
componentWillMount: function() {
|
||||
lbry.checkNewVersionAvailable(function(isAvailable) {
|
||||
if (!isAvailable) {
|
||||
|
||||
if (!isAvailable || sessionStorage.getItem('upgradeSkipped')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -37,38 +48,71 @@ var App = React.createClass({
|
|||
var updateUrl = 'https://lbry.io/get/lbry.deb';
|
||||
}
|
||||
|
||||
if (window.confirm(message)) {
|
||||
if (window.confirm(message))
|
||||
{
|
||||
lbry.stop();
|
||||
window.location = updateUrl;
|
||||
} else {
|
||||
sessionStorage.setItem('upgradeSkipped', true);
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
if (this.state.viewingPage == 'home') {
|
||||
return <HomePage />;
|
||||
} else if (this.state.viewingPage == 'settings') {
|
||||
openDrawer: function() {
|
||||
sessionStorage.setItem('drawerOpen', true);
|
||||
this.setState({ drawerOpen: true });
|
||||
},
|
||||
closeDrawer: function() {
|
||||
sessionStorage.setItem('drawerOpen', false);
|
||||
this.setState({ drawerOpen: false });
|
||||
},
|
||||
onSearch: function(term) {
|
||||
this.setState({
|
||||
viewingPage: 'discover',
|
||||
pageArgs: term
|
||||
});
|
||||
},
|
||||
getMainContent: function()
|
||||
{
|
||||
switch(this.state.viewingPage)
|
||||
{
|
||||
case 'discover':
|
||||
return <DiscoverPage query={this.state.pageArgs} />;
|
||||
case 'settings':
|
||||
return <SettingsPage />;
|
||||
} else if (this.state.viewingPage == 'help') {
|
||||
case 'help':
|
||||
return <HelpPage />;
|
||||
} else if (this.state.viewingPage == 'watch') {
|
||||
return <WatchPage name={this.state.pageArgs}/>;
|
||||
} else if (this.state.viewingPage == 'report') {
|
||||
case 'watch':
|
||||
return <WatchPage name={this.state.pageArgs} />;
|
||||
case 'report':
|
||||
return <ReportPage />;
|
||||
} else if (this.state.viewingPage == 'files') {
|
||||
case 'files':
|
||||
return <MyFilesPage />;
|
||||
} else if (this.state.viewingPage == 'start') {
|
||||
case 'start':
|
||||
return <StartPage />;
|
||||
} else if (this.state.viewingPage == 'claim') {
|
||||
case 'claim':
|
||||
return <ClaimCodePage />;
|
||||
} else if (this.state.viewingPage == 'wallet') {
|
||||
case 'wallet':
|
||||
return <WalletPage />;
|
||||
} else if (this.state.viewingPage == 'show') {
|
||||
return <DetailPage name={this.state.pageArgs}/>;
|
||||
} else if (this.state.viewingPage == 'wallet') {
|
||||
return <WalletPage />;
|
||||
} else if (this.state.viewingPage == 'publish') {
|
||||
case 'show':
|
||||
return <DetailPage name={this.state.pageArgs} />;
|
||||
case 'publish':
|
||||
return <PublishPage />;
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var mainContent = this.getMainContent();
|
||||
|
||||
return (
|
||||
this.state.viewingPage == 'watch' ?
|
||||
mainContent :
|
||||
<div id="window" className={ this.state.drawerOpen ? 'drawer-open' : 'drawer-closed' }>
|
||||
<Drawer onCloseDrawer={this.closeDrawer} viewingPage={this.state.viewingPage} />
|
||||
<div id="main-content">
|
||||
<Header onOpenDrawer={this.openDrawer} onSearch={this.onSearch} />
|
||||
{mainContent}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -52,287 +52,6 @@ var ToolTip = React.createClass({
|
|||
}
|
||||
});
|
||||
|
||||
var linkContainerStyle = {
|
||||
position: 'relative',
|
||||
};
|
||||
var Link = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showTooltip: false,
|
||||
};
|
||||
},
|
||||
handleClick: function() {
|
||||
if (this.props.tooltip) {
|
||||
this.setState({
|
||||
showTooltip: !this.state.showTooltip,
|
||||
});
|
||||
}
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
}
|
||||
},
|
||||
handleTooltipMouseOut: function() {
|
||||
this.setState({
|
||||
showTooltip: false,
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var href = this.props.href ? this.props.href : 'javascript:;',
|
||||
icon = this.props.icon ? <Icon icon={this.props.icon} /> : '',
|
||||
className = (this.props.button ? 'button-block button-' + this.props.button : 'button-text') +
|
||||
(this.props.hidden ? ' hidden' : '') + (this.props.disabled ? ' disabled' : '');
|
||||
|
||||
|
||||
return (
|
||||
<span style={linkContainerStyle}>
|
||||
<a className={className} href={href} style={this.props.style ? this.props.style : {}}
|
||||
title={this.props.title} onClick={this.handleClick}>
|
||||
{this.props.icon ? icon : '' }
|
||||
{this.props.label}
|
||||
</a>
|
||||
{(!this.props.tooltip ? null :
|
||||
<ToolTip open={this.state.showTooltip} onMouseOut={this.handleTooltipMouseOut}>
|
||||
{this.props.tooltip}
|
||||
</ToolTip>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var DownloadLink = React.createClass({
|
||||
propTypes: {
|
||||
type: React.PropTypes.string,
|
||||
streamName: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
downloadingLabel: React.PropTypes.string,
|
||||
button: React.PropTypes.string,
|
||||
style: React.PropTypes.object,
|
||||
hidden: React.PropTypes.bool,
|
||||
},
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
icon: 'icon-download',
|
||||
label: 'Download',
|
||||
downloadingLabel: 'Downloading...',
|
||||
}
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
downloading: false,
|
||||
}
|
||||
},
|
||||
handleClick: function() {
|
||||
lbry.getCostEstimate(this.props.streamName, (amount) => {
|
||||
lbry.getBalance((balance) => {
|
||||
if (amount > balance) {
|
||||
alert("You don't have enough LBRY credits to pay for this stream.");
|
||||
} else {
|
||||
this.startDownload();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
startDownload: function() {
|
||||
if (!this.state.downloading) { //@TODO: Continually update this.state.downloading based on actual status of file
|
||||
this.setState({
|
||||
downloading: true
|
||||
});
|
||||
|
||||
lbry.getStream(this.props.streamName, (streamInfo) => {
|
||||
alert('Downloading to ' + streamInfo.path);
|
||||
console.log(streamInfo);
|
||||
});
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var label = (!this.state.downloading ? this.props.label : this.props.downloadingLabel);
|
||||
return <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} />;
|
||||
}
|
||||
});
|
||||
|
||||
var WatchLink = React.createClass({
|
||||
propTypes: {
|
||||
type: React.PropTypes.string,
|
||||
streamName: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
button: React.PropTypes.string,
|
||||
style: React.PropTypes.object,
|
||||
hidden: React.PropTypes.bool,
|
||||
},
|
||||
handleClick: function() {
|
||||
lbry.getCostEstimate(this.props.streamName, (amount) => {
|
||||
lbry.getBalance((balance) => {
|
||||
if (amount > balance) {
|
||||
alert("You don't have enough LBRY credits to pay for this stream.");
|
||||
} else {
|
||||
window.location = '?watch=' + this.props.streamName;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
icon: 'icon-play',
|
||||
label: 'Watch',
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Link button={this.props.button} hidden={this.props.hidden} style={this.props.style}
|
||||
label={this.props.label} icon={this.props.icon} onClick={this.handleClick} />;
|
||||
}
|
||||
});
|
||||
|
||||
var requiredFieldWarningStyle = {
|
||||
color: '#cc0000',
|
||||
transition: 'opacity 400ms ease-in',
|
||||
};
|
||||
var FormField = React.createClass({
|
||||
_type: null,
|
||||
_element: null,
|
||||
|
||||
propTypes: {
|
||||
type: React.PropTypes.string.isRequired,
|
||||
hidden: React.PropTypes.bool,
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
warningState: 'hidden',
|
||||
}
|
||||
},
|
||||
componentWillMount: function() {
|
||||
if (['text', 'radio', 'checkbox', 'file'].indexOf(this.props.type) != -1) {
|
||||
this._element = 'input';
|
||||
this._type = this.props.type;
|
||||
} else {
|
||||
// Non <input> field, e.g. <select>, <textarea>
|
||||
this._element = this.props.type;
|
||||
}
|
||||
},
|
||||
warnRequired: function() {
|
||||
this.setState({
|
||||
warningState: 'shown',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
warningState: 'fading',
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
warningState: 'hidden',
|
||||
});
|
||||
}, 450);
|
||||
}, 5000);
|
||||
},
|
||||
focus: function() {
|
||||
this.refs.field.focus();
|
||||
},
|
||||
getValue: function() {
|
||||
if (this.props.type == 'checkbox') {
|
||||
return this.refs.field.checked;
|
||||
} else {
|
||||
return this.refs.field.value;
|
||||
}
|
||||
},
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Generic menu styles
|
||||
var menuStyle = {
|
||||
border: '1px solid #aaa',
|
||||
padding: '4px',
|
||||
whiteSpace: 'nowrap',
|
||||
};
|
||||
|
||||
var Menu = React.createClass({
|
||||
handleWindowClick: function(e) {
|
||||
if (this.props.toggleButton && ReactDOM.findDOMNode(this.props.toggleButton).contains(e.target)) {
|
||||
// Toggle button was clicked
|
||||
this.setState({
|
||||
open: !this.state.open
|
||||
});
|
||||
} else if (this.state.open && !this.refs.div.contains(e.target)) {
|
||||
// Menu is open and user clicked outside of it
|
||||
this.setState({
|
||||
open: false
|
||||
});
|
||||
}
|
||||
},
|
||||
propTypes: {
|
||||
openButton: React.PropTypes.element,
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
open: false,
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
window.addEventListener('click', this.handleWindowClick, false);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener('click', this.handleWindowClick, false);
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div ref='div' style={menuStyle} className={this.state.open ? '' : 'hidden'}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var menuItemStyle = {
|
||||
display: 'block',
|
||||
};
|
||||
var MenuItem = React.createClass({
|
||||
propTypes: {
|
||||
href: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
icon: React.PropTypes.string,
|
||||
onClick: React.PropTypes.func,
|
||||
},
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
iconPosition: 'left',
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var icon = (this.props.icon ? <Icon icon={this.props.icon} fixed /> : null);
|
||||
|
||||
return (
|
||||
<a style={menuItemStyle} className="button-text no-underline" onClick={this.props.onClick}
|
||||
href={this.props.href || 'javascript:'} label={this.props.label}>
|
||||
{this.props.iconPosition == 'left' ? icon : null}
|
||||
{this.props.label}
|
||||
{this.props.iconPosition == 'left' ? null : icon}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var creditAmountStyle = {
|
||||
color: '#216C2A',
|
||||
fontWeight: 'bold',
|
||||
|
@ -342,6 +61,10 @@ var creditAmountStyle = {
|
|||
color: '#aaa',
|
||||
};
|
||||
|
||||
var CurrencySymbol = React.createClass({
|
||||
render: function() { return <span>LBC</span>; }
|
||||
});
|
||||
|
||||
var CreditAmount = React.createClass({
|
||||
propTypes: {
|
||||
amount: React.PropTypes.number,
|
||||
|
@ -356,15 +79,3 @@ var CreditAmount = React.createClass({
|
|||
);
|
||||
}
|
||||
});
|
||||
|
||||
var subPageLogoStyle = {
|
||||
maxWidth: '150px',
|
||||
display: 'block',
|
||||
marginTop: '36px',
|
||||
};
|
||||
|
||||
var SubPageLogo = React.createClass({
|
||||
render: function() {
|
||||
return <img src="img/lbry-dark-1600x528.png" style={subPageLogoStyle} />;
|
||||
}
|
||||
});
|
43
js/component/drawer.js
Normal file
43
js/component/drawer.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
var DrawerItem = React.createClass({
|
||||
render: function() {
|
||||
var isSelected = this.props.viewingPage == this.props.href.substr(2);
|
||||
return <Link {...this.props} className={ 'drawer-item ' + (isSelected ? 'drawer-item-selected' : '') } />
|
||||
}
|
||||
});
|
||||
|
||||
var drawerImageStyle = { //@TODO: remove this, img should be properly scaled once size is settled
|
||||
height: '36px'
|
||||
};
|
||||
|
||||
var Drawer = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
balance: 0,
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
lbry.getBalance(function(balance) {
|
||||
this.setState({
|
||||
balance: balance
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
render: function() {
|
||||
var isLinux = false && /linux/i.test(navigator.userAgent); // @TODO: find a way to use getVersionInfo() here without messy state management
|
||||
return (
|
||||
<nav id="drawer">
|
||||
<div id="drawer-handle">
|
||||
<Link title="Close" onClick={this.props.onCloseDrawer} icon="icon-bars" className="close-drawer-link"/>
|
||||
<a href="/"><img src="./img/lbry-dark-1600x528.png" style={drawerImageStyle}/></a>
|
||||
</div>
|
||||
<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='/?files' viewingPage={this.props.viewingPage} label="My Files" icon='icon-cloud-download' />
|
||||
<DrawerItem href="/?wallet" 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' />
|
||||
{isLinux ? <Link href="/?start" icon="icon-close" className="close-lbry-link" /> : null}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
});
|
75
js/component/form.js
Normal file
75
js/component/form.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
var requiredFieldWarningStyle = {
|
||||
color: '#cc0000',
|
||||
transition: 'opacity 400ms ease-in',
|
||||
};
|
||||
|
||||
var FormField = React.createClass({
|
||||
_type: null,
|
||||
_element: null,
|
||||
|
||||
propTypes: {
|
||||
type: React.PropTypes.string.isRequired,
|
||||
hidden: React.PropTypes.bool,
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
warningState: 'hidden',
|
||||
}
|
||||
},
|
||||
componentWillMount: function() {
|
||||
if (['text', 'radio', 'checkbox', 'file'].indexOf(this.props.type) != -1) {
|
||||
this._element = 'input';
|
||||
this._type = this.props.type;
|
||||
} else {
|
||||
// Non <input> field, e.g. <select>, <textarea>
|
||||
this._element = this.props.type;
|
||||
}
|
||||
},
|
||||
warnRequired: function() {
|
||||
this.setState({
|
||||
warningState: 'shown',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
warningState: 'fading',
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.setState({
|
||||
warningState: 'hidden',
|
||||
});
|
||||
}, 450);
|
||||
}, 5000);
|
||||
},
|
||||
focus: function() {
|
||||
this.refs.field.focus();
|
||||
},
|
||||
getValue: function() {
|
||||
if (this.props.type == 'checkbox') {
|
||||
return this.refs.field.checked;
|
||||
} else {
|
||||
return this.refs.field.value;
|
||||
}
|
||||
},
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
57
js/component/header.js
Normal file
57
js/component/header.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
var Header = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
title: "LBRY",
|
||||
isScrolled: false
|
||||
};
|
||||
},
|
||||
componentWillMount: function() {
|
||||
new MutationObserver(function(mutations) {
|
||||
this.setState({ title: mutations[0].target.innerHTML });
|
||||
}.bind(this)).observe(
|
||||
document.querySelector('title'),
|
||||
{ subtree: true, characterData: true, childList: true }
|
||||
);
|
||||
},
|
||||
componentDidMount: function() {
|
||||
document.addEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
document.removeEventListener('scroll', this.handleScroll);
|
||||
if (this.userTypingTimer)
|
||||
{
|
||||
clearTimeout(this.userTypingTimer);
|
||||
}
|
||||
},
|
||||
handleScroll: function() {
|
||||
this.setState({
|
||||
isScrolled: event.srcElement.body.scrollTop > 0
|
||||
});
|
||||
},
|
||||
onQueryChange: function(event) {
|
||||
|
||||
if (this.userTypingTimer)
|
||||
{
|
||||
clearTimeout(this.userTypingTimer);
|
||||
}
|
||||
|
||||
//@TODO: Switch to React.js timing
|
||||
var searchTerm = event.target.value;
|
||||
this.userTypingTimer = setTimeout(() => {
|
||||
this.props.onSearch(searchTerm);
|
||||
}, 800); // 800ms delay, tweak for faster/slower
|
||||
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<header id="header" className={this.state.isScrolled ? 'header-scrolled' : 'header-unscrolled'}>
|
||||
<Link onClick={this.props.onOpenDrawer} icon="icon-bars" className="open-drawer-link" />
|
||||
<h1>{ this.state.title }</h1>
|
||||
<div className="header-search">
|
||||
<input type="search" onChange={this.onQueryChange}
|
||||
placeholder="Find movies, music, games, and more"/>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
});
|
159
js/component/link.js
Normal file
159
js/component/link.js
Normal file
|
@ -0,0 +1,159 @@
|
|||
var Link = React.createClass({
|
||||
handleClick: function() {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var href = this.props.href ? this.props.href : 'javascript:;',
|
||||
icon = this.props.icon ? <Icon icon={this.props.icon} fixed={true} /> : '',
|
||||
className = (this.props.className ? this.props.className : '') +
|
||||
(this.props.button ? ' button-block button-' + this.props.button : '') +
|
||||
(this.props.hidden ? ' hidden' : '') +
|
||||
(this.props.disabled ? ' disabled' : '');
|
||||
|
||||
return (
|
||||
<a className={className ? className : 'button-text'} href={href} style={this.props.style ? this.props.style : {}}
|
||||
title={this.props.title} onClick={this.handleClick}>
|
||||
{this.props.icon ? icon : '' }
|
||||
<span className="link-label">{this.props.label}</span>
|
||||
{this.props.badge ? <span className="badge">{this.props.badge}</span> : '' }
|
||||
</a>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var linkContainerStyle = {
|
||||
position: 'relative',
|
||||
};
|
||||
|
||||
var ToolTipLink = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
showTooltip: false,
|
||||
};
|
||||
},
|
||||
handleClick: function() {
|
||||
if (this.props.tooltip) {
|
||||
this.setState({
|
||||
showTooltip: !this.state.showTooltip,
|
||||
});
|
||||
}
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
}
|
||||
},
|
||||
handleTooltipMouseOut: function() {
|
||||
this.setState({
|
||||
showTooltip: false,
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var href = this.props.href ? this.props.href : 'javascript:;',
|
||||
icon = this.props.icon ? <Icon icon={this.props.icon} /> : '',
|
||||
className = this.props.className +
|
||||
(this.props.button ? ' button-block button-' + this.props.button : '') +
|
||||
(this.props.hidden ? ' hidden' : '') +
|
||||
(this.props.disabled ? ' disabled' : '');
|
||||
|
||||
return (
|
||||
<span style={linkContainerStyle}>
|
||||
<a className={className ? className : 'button-text'} href={href} style={this.props.style ? this.props.style : {}}
|
||||
title={this.props.title} onClick={this.handleClick}>
|
||||
{this.props.icon ? icon : '' }
|
||||
{this.props.label}
|
||||
</a>
|
||||
{(!this.props.tooltip ? null :
|
||||
<ToolTip open={this.state.showTooltip} onMouseOut={this.handleTooltipMouseOut}>
|
||||
{this.props.tooltip}
|
||||
</ToolTip>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var DownloadLink = React.createClass({
|
||||
propTypes: {
|
||||
type: React.PropTypes.string,
|
||||
streamName: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
downloadingLabel: React.PropTypes.string,
|
||||
button: React.PropTypes.string,
|
||||
style: React.PropTypes.object,
|
||||
hidden: React.PropTypes.bool,
|
||||
},
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
icon: 'icon-download',
|
||||
label: 'Download',
|
||||
downloadingLabel: 'Downloading...',
|
||||
}
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
downloading: false,
|
||||
}
|
||||
},
|
||||
handleClick: function() {
|
||||
lbry.getCostEstimate(this.props.streamName, (amount) => {
|
||||
lbry.getBalance((balance) => {
|
||||
if (amount > balance) {
|
||||
alert("You don't have enough LBRY credits to pay for this stream.");
|
||||
} else {
|
||||
this.startDownload();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
startDownload: function() {
|
||||
if (!this.state.downloading) { //@TODO: Continually update this.state.downloading based on actual status of file
|
||||
this.setState({
|
||||
downloading: true
|
||||
});
|
||||
|
||||
lbry.getStream(this.props.streamName, (streamInfo) => {
|
||||
alert('Downloading to ' + streamInfo.path);
|
||||
console.log(streamInfo);
|
||||
});
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var label = (!this.state.downloading ? this.props.label : this.props.downloadingLabel);
|
||||
return <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} />;
|
||||
}
|
||||
});
|
||||
|
||||
var WatchLink = React.createClass({
|
||||
propTypes: {
|
||||
type: React.PropTypes.string,
|
||||
streamName: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
button: React.PropTypes.string,
|
||||
style: React.PropTypes.object,
|
||||
hidden: React.PropTypes.bool,
|
||||
},
|
||||
handleClick: function() {
|
||||
lbry.getCostEstimate(this.props.streamName, (amount) => {
|
||||
lbry.getBalance((balance) => {
|
||||
if (amount > balance) {
|
||||
alert("You don't have enough LBRY credits to pay for this stream.");
|
||||
} else {
|
||||
window.location = '?watch=' + this.props.streamName;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
icon: 'icon-play',
|
||||
label: 'Watch',
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Link button={this.props.button} hidden={this.props.hidden} style={this.props.style}
|
||||
label={this.props.label} icon={this.props.icon} onClick={this.handleClick} />;
|
||||
}
|
||||
});
|
70
js/component/menu.js
Normal file
70
js/component/menu.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
// Generic menu styles
|
||||
var menuStyle = {
|
||||
whiteSpace: 'nowrap'
|
||||
};
|
||||
|
||||
var Menu = React.createClass({
|
||||
handleWindowClick: function(e) {
|
||||
if (this.props.toggleButton && ReactDOM.findDOMNode(this.props.toggleButton).contains(e.target)) {
|
||||
// Toggle button was clicked
|
||||
this.setState({
|
||||
open: !this.state.open
|
||||
});
|
||||
} else if (this.state.open && !this.refs.div.contains(e.target)) {
|
||||
// Menu is open and user clicked outside of it
|
||||
this.setState({
|
||||
open: false
|
||||
});
|
||||
}
|
||||
},
|
||||
propTypes: {
|
||||
openButton: React.PropTypes.element,
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
open: false,
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
window.addEventListener('click', this.handleWindowClick, false);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
window.removeEventListener('click', this.handleWindowClick, false);
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div ref='div' style={menuStyle} className={this.state.open ? '' : 'hidden'}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var menuItemStyle = {
|
||||
display: 'block',
|
||||
};
|
||||
var MenuItem = React.createClass({
|
||||
propTypes: {
|
||||
href: React.PropTypes.string,
|
||||
label: React.PropTypes.string,
|
||||
icon: React.PropTypes.string,
|
||||
onClick: React.PropTypes.func,
|
||||
},
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
iconPosition: 'left',
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
var icon = (this.props.icon ? <Icon icon={this.props.icon} fixed /> : null);
|
||||
|
||||
return (
|
||||
<a style={menuItemStyle} className="button-text no-underline" onClick={this.props.onClick}
|
||||
href={this.props.href || 'javascript:'} label={this.props.label}>
|
||||
{this.props.iconPosition == 'left' ? icon : null}
|
||||
{this.props.label}
|
||||
{this.props.iconPosition == 'left' ? null : icon}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -6,7 +6,7 @@ var init = function() {
|
|||
<SplashScreen message="Connecting" onLoadDone={function() {
|
||||
// On home page, if the balance is 0, display claim code page instead of home page.
|
||||
// Find somewhere better for this logic
|
||||
if (window.location.search == '' || window.location.search == '?' || window.location.search == 'home') {
|
||||
if (window.location.search == '' || window.location.search == '?' || window.location.search == 'discover') {
|
||||
lbry.getBalance((balance) => {
|
||||
if (balance <= 0) {
|
||||
window.location.href = '?claim';
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
var claimCodePageStyle = {
|
||||
textAlign: 'center',
|
||||
}, claimCodeContentStyle = {
|
||||
var claimCodeContentStyle = {
|
||||
display: 'inline-block',
|
||||
textAlign: 'left',
|
||||
width: '600px',
|
||||
|
@ -72,8 +70,9 @@ var ClaimCodePage = React.createClass({
|
|||
},
|
||||
render: function() {
|
||||
return (
|
||||
<main className="page" style={claimCodePageStyle}>
|
||||
<h1>Claim your beta invitation code</h1>
|
||||
<main>
|
||||
<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>
|
||||
|
@ -90,6 +89,7 @@ var ClaimCodePage = React.createClass({
|
|||
disabled={this.state.submitting} onClick={this.handleSubmit} />
|
||||
<Link button="alt" label="Skip" disabled={this.state.submitting} onClick={this.handleSkip} />
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
228
js/page/discover.js
Normal file
228
js/page/discover.js
Normal file
|
@ -0,0 +1,228 @@
|
|||
var fetchResultsStyle = {
|
||||
color: '#888',
|
||||
textAlign: 'center',
|
||||
fontSize: '1.2em'
|
||||
};
|
||||
|
||||
var SearchActive = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<div style={fetchResultsStyle}>
|
||||
Looking up the Dewey Decimals
|
||||
<span className="busy-indicator"></span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var searchNoResultsStyle = {
|
||||
textAlign: 'center'
|
||||
}, searchNoResultsMessageStyle = {
|
||||
fontStyle: 'italic',
|
||||
marginRight: '5px'
|
||||
};
|
||||
|
||||
var SearchNoResults = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<section style={searchNoResultsStyle}>
|
||||
<span style={searchNoResultsMessageStyle}>No one has checked anything in for {this.props.query} yet.</span>
|
||||
<Link label="Be the first" href="?publish" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var SearchResults = React.createClass({
|
||||
render: function() {
|
||||
var rows = [];
|
||||
this.props.results.forEach(function(result) {
|
||||
rows.push(
|
||||
<SearchResultRow key={result.name} name={result.name} title={result.title} imgUrl={result.thumbnail}
|
||||
description={result.description} cost_est={result.cost_est} />
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div>{rows}</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var
|
||||
searchRowStyle = {
|
||||
height: (24 * 7) + 'px',
|
||||
overflowY: 'hidden'
|
||||
},
|
||||
searchRowImgStyle = {
|
||||
maxWidth: '100%',
|
||||
maxHeight: (24 * 7) + 'px',
|
||||
display: 'block',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto'
|
||||
},
|
||||
searchRowTitleStyle = {
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
searchRowCostStyle = {
|
||||
float: 'right',
|
||||
},
|
||||
searchRowDescriptionStyle = {
|
||||
color : '#444',
|
||||
marginTop: '12px',
|
||||
fontSize: '0.9em'
|
||||
};
|
||||
|
||||
|
||||
var SearchResultRow = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
downloading: false
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
|
||||
<section className="card">
|
||||
<div className="row-fluid" style={searchRowStyle}>
|
||||
<div className="span3">
|
||||
<img src={this.props.imgUrl} alt={'Photo for ' + (this.props.title || this.props.name)} style={searchRowImgStyle} />
|
||||
</div>
|
||||
<div className="span9">
|
||||
<span style={searchRowCostStyle}>
|
||||
<CreditAmount amount={this.props.cost_est} isEstimate={true}/>
|
||||
</span>
|
||||
<div className="meta">lbry://{this.props.name}</div>
|
||||
<h3 style={searchRowTitleStyle}><a href={'/?show=' + this.props.name}>{this.props.title}</a></h3>
|
||||
<div>
|
||||
<WatchLink streamName={this.props.name} button="primary" />
|
||||
<DownloadLink streamName={this.props.name} button="text" />
|
||||
</div>
|
||||
<p style={searchRowDescriptionStyle}>{this.props.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
var FeaturedContentItem = React.createClass({
|
||||
propTypes: {
|
||||
name: React.PropTypes.string,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
metadata: null,
|
||||
title: null,
|
||||
amount: 0.0,
|
||||
};
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
lbry.resolveName(this.props.name, (metadata) => {
|
||||
this.setState({
|
||||
metadata: metadata,
|
||||
title: metadata && metadata.title ? metadata.title : ('lbry://' + this.props.name),
|
||||
})
|
||||
});
|
||||
lbry.getCostEstimate(this.props.name, (amount) => {
|
||||
this.setState({
|
||||
amount: amount,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if (this.state.metadata == null) {
|
||||
// Still waiting for metadata
|
||||
return null;
|
||||
}
|
||||
return <SearchResultRow name={this.props.name} title={this.state.title} imgUrl={this.state.metadata.thumbnail}
|
||||
description={this.state.metadata.description} cost_est={this.state.amount} />;
|
||||
}
|
||||
});
|
||||
|
||||
var featuredContentLegendStyle = {
|
||||
fontSize: '12px',
|
||||
color: '#aaa',
|
||||
verticalAlign: '15%',
|
||||
};
|
||||
|
||||
var FeaturedContent = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<div className="row-fluid">
|
||||
<div className="span6">
|
||||
<h3>Featured Content</h3>
|
||||
<FeaturedContentItem name="what" />
|
||||
<FeaturedContentItem name="itsadisaster" />
|
||||
<FeaturedContentItem name="keynesvhayek" />
|
||||
<FeaturedContentItem name="meetlbry1" />
|
||||
</div>
|
||||
<div className="span6">
|
||||
<h3>Community Content <ToolTipLink style={featuredContentLegendStyle} label="What's this?"
|
||||
tooltip='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" and "four" to put your content here!' /></h3>
|
||||
<FeaturedContentItem name="one" />
|
||||
<FeaturedContentItem name="two" />
|
||||
<FeaturedContentItem name="three" />
|
||||
<FeaturedContentItem name="four" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var DiscoverPage = React.createClass({
|
||||
userTypingTimer: null,
|
||||
|
||||
componentDidUpdate: function() {
|
||||
if (this.props.query != this.state.query)
|
||||
{
|
||||
this.setState({
|
||||
searching: true,
|
||||
query: this.props.query,
|
||||
});
|
||||
|
||||
lbry.search(this.props.query, this.searchCallback);
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
document.title = "Discover";
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
results: [],
|
||||
query: this.props.query,
|
||||
searching: this.props.query && this.props.query.length > 0
|
||||
};
|
||||
},
|
||||
|
||||
searchCallback: function(results) {
|
||||
console.log('results:', results)
|
||||
console.log('search callback');
|
||||
console.log(this.state);
|
||||
console.log(this.props);
|
||||
if (this.state.searching) //could have canceled while results were pending, in which case nothing to do
|
||||
{
|
||||
this.setState({
|
||||
results: results,
|
||||
searching: false //multiple searches can be out, we're only done if we receive one we actually care about
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -1,32 +1,46 @@
|
|||
//@TODO: Customize advice based on OS
|
||||
|
||||
var HelpPage = React.createClass({
|
||||
componentDidMount: function() {
|
||||
document.title = "Help";
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<main className="page">
|
||||
<SubPageLogo />
|
||||
<h1>Troubleshooting</h1>
|
||||
<p>Here are the most commonly encountered problems and what to try doing about them.</p>
|
||||
|
||||
<h3>Nothing seems to start downloading.</h3>
|
||||
<section className="card">
|
||||
<h3>Read the FAQ</h3>
|
||||
<p>Our FAQ answers many common questions.</p>
|
||||
<p><Link href="https://lbry.io/faq" label="Read the FAQ" icon="icon-question" button="alt"/></p>
|
||||
</section>
|
||||
<section className="card">
|
||||
<h3>Get Live Help</h3>
|
||||
<p>
|
||||
Live help is available most hours in the #help channel of our Slack chat room.
|
||||
</p>
|
||||
<p>
|
||||
<Link button="alt" label="Join Our Slack" icon="icon-slack" href="https://slack.lbry.io" />
|
||||
</p>
|
||||
</section>
|
||||
<section className="card">
|
||||
<h3>Common Issues</h3>
|
||||
<h4>Nothing seems to start downloading.</h4>
|
||||
<p>If you can't download anything, including 'wonderfullife', try forwarding ports 4444 and 3333 on your firewall or router. If you can access 'wonderfullife' but not other content, it's possible the content is not longer hosted on the network.</p>
|
||||
|
||||
<h3>Videos have trouble playing.</h3>
|
||||
<h4>Videos have trouble playing.</h4>
|
||||
<p>Sometimes your video player will start the file while it's still empty. Try reloading the page after a few seconds and it may work. You should also see the file appear in your downloads folder (configured in <a href="/?settings">settings</a>).</p>
|
||||
|
||||
<p>A real fix for this is underway!</p>
|
||||
|
||||
<h3>How do I turn LBRY off?</h3>
|
||||
<h4>How do I turn LBRY off?</h4>
|
||||
<p>If you're on OS X you can find the app running in the notification area at the top right of your screen. Click the LBRY icon and choose <code>Quit</code>.</p>
|
||||
|
||||
<p>On Linux, you'll find a close button in the menu at the top right of LBRY.</p>
|
||||
|
||||
<p>If you're running LBRY from the command line, you may also close the app with the command <code>stop-lbrynet-daemon</code></p>
|
||||
|
||||
</section>
|
||||
<section className="card">
|
||||
<h3>None of this applies to me, or it didn't work.</h3>
|
||||
<p>Please <Link href="/?report" label="send us a bug report" />. Thanks!</p>
|
||||
<section>
|
||||
<Link href="/" label="<< Return" />
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
|
|
439
js/page/home.js
439
js/page/home.js
|
@ -1,439 +0,0 @@
|
|||
var searchInputStyle = {
|
||||
width: '400px',
|
||||
display: 'block',
|
||||
marginBottom: '48px',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto'
|
||||
},
|
||||
fetchResultsStyle = {
|
||||
color: '#888',
|
||||
textAlign: 'center',
|
||||
fontSize: '1.2em'
|
||||
};
|
||||
|
||||
var SearchActive = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<section style={fetchResultsStyle}>
|
||||
Looking up the Dewey Decimals
|
||||
<span className="busy-indicator"></span>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var searchNoResultsStyle = {
|
||||
textAlign: 'center'
|
||||
}, searchNoResultsMessageStyle = {
|
||||
fontStyle: 'italic',
|
||||
marginRight: '5px'
|
||||
};
|
||||
|
||||
var SearchNoResults = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<section style={searchNoResultsStyle}>
|
||||
<span style={searchNoResultsMessageStyle}>No one has checked anything in for {this.props.query} yet.</span>
|
||||
<Link label="Be the first" href="?publish" />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var SearchResults = React.createClass({
|
||||
render: function() {
|
||||
var rows = [];
|
||||
this.props.results.forEach(function(result) {
|
||||
rows.push(
|
||||
<SearchResultRow key={result.name} name={result.name} title={result.title} imgUrl={result.thumbnail}
|
||||
description={result.description} cost_est={result.cost_est} />
|
||||
);
|
||||
});
|
||||
return (
|
||||
<section>{rows}</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var
|
||||
searchRowImgStyle = {
|
||||
maxWidth: '100%',
|
||||
maxHeight: '250px',
|
||||
display: 'block',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
float: 'left'
|
||||
},
|
||||
searchRowTitleStyle = {
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
searchRowCostStyle = {
|
||||
float: 'right',
|
||||
marginLeft: '20px',
|
||||
marginTop: '5px',
|
||||
display: 'inline-block'
|
||||
},
|
||||
searchRowNameStyle = {
|
||||
fontSize: '0.9em',
|
||||
color: '#666',
|
||||
marginBottom: '24px',
|
||||
clear: 'both'
|
||||
},
|
||||
searchRowDescriptionStyle = {
|
||||
color : '#444',
|
||||
marginBottom: '24px',
|
||||
fontSize: '0.9em'
|
||||
};
|
||||
|
||||
|
||||
var SearchResultRow = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
downloading: false
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<section className="row-fluid">
|
||||
<div className="span3">
|
||||
<img src={this.props.imgUrl} alt={'Photo for ' + (this.props.title || this.props.name)} style={searchRowImgStyle} />
|
||||
</div>
|
||||
<div className="span9">
|
||||
<span style={searchRowCostStyle}>
|
||||
<CreditAmount amount={this.props.cost_est} isEstimate={true}/>
|
||||
</span>
|
||||
<h2 style={searchRowTitleStyle}><a href={'/?show=' + this.props.name}>{this.props.title}</a></h2>
|
||||
<div style={searchRowNameStyle}>lbry://{this.props.name}</div>
|
||||
<p style={searchRowDescriptionStyle}><TruncatedText>{this.props.description}</TruncatedText></p>
|
||||
<div>
|
||||
<WatchLink streamName={this.props.name} button="primary" />
|
||||
{ ' ' }
|
||||
<DownloadLink streamName={this.props.name} button="alt" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var featuredContentItemStyle = {
|
||||
fontSize: '0.95em',
|
||||
marginTop: '10px',
|
||||
maxHeight: '220px'
|
||||
}, featuredContentItemImgStyle = {
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
display: 'block',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginTop: '5px',
|
||||
}, featuredContentHeaderStyle = {
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '5px'
|
||||
}, featuredContentSubheaderStyle = {
|
||||
marginBottom: '10px',
|
||||
fontSize: '0.9em'
|
||||
}, featuredContentItemDescriptionStyle = {
|
||||
color: '#444',
|
||||
marginBottom: '5px',
|
||||
fontSize: '0.9em',
|
||||
}, featuredContentItemCostStyle = {
|
||||
float: 'right'
|
||||
};
|
||||
|
||||
var FeaturedContentItem = React.createClass({
|
||||
_maxDescriptionLength: 250,
|
||||
|
||||
propTypes: {
|
||||
name: React.PropTypes.string,
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
metadata: null,
|
||||
title: null,
|
||||
amount: 0.0,
|
||||
};
|
||||
},
|
||||
componentWillMount: function() {
|
||||
lbry.resolveName(this.props.name, (metadata) => {
|
||||
this.setState({
|
||||
metadata: metadata,
|
||||
title: metadata && metadata.title ? metadata.title : ('lbry://' + this.props.name),
|
||||
})
|
||||
});
|
||||
lbry.getCostEstimate(this.props.name, (amount) => {
|
||||
this.setState({
|
||||
amount: amount,
|
||||
});
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
if (this.state.metadata == null) {
|
||||
// Still waiting for metadata
|
||||
return null;
|
||||
}
|
||||
|
||||
var metadata = this.state.metadata;
|
||||
|
||||
if ('narrow' in this.props) {
|
||||
// Workaround -- narrow thumbnails look a bit funky without some extra left margin.
|
||||
// Find a way to do this in CSS.
|
||||
|
||||
var thumbStyle = Object.assign({}, featuredContentItemImgStyle, {
|
||||
position: 'relative',
|
||||
maxHeight: '102px',
|
||||
left: '13px',
|
||||
});
|
||||
} else {
|
||||
var thumbStyle = featuredContentItemImgStyle;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row-fluid" style={featuredContentItemStyle}>
|
||||
<div className="span4">
|
||||
<img src={metadata.thumbnail} alt={'Photo for ' + this.state.title} style={thumbStyle} />
|
||||
</div>
|
||||
<div className="span8">
|
||||
<h4 style={featuredContentHeaderStyle}><a href={'/?show=' + this.props.name}>{this.state.title}</a></h4>
|
||||
<div style={featuredContentSubheaderStyle}>
|
||||
<div style={featuredContentItemCostStyle}><CreditAmount amount={this.state.amount} isEstimate={true}/></div>
|
||||
<WatchLink streamName={this.props.name} />
|
||||
|
||||
<DownloadLink streamName={this.props.name} />
|
||||
</div>
|
||||
<p style={featuredContentItemDescriptionStyle}><TruncatedText>{metadata.description}</TruncatedText></p>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
});
|
||||
|
||||
var featuredContentStyle = {
|
||||
width: '100%',
|
||||
marginTop: '-8px',
|
||||
}, featuredContentLegendStyle = {
|
||||
fontSize: '12px',
|
||||
color: '#aaa',
|
||||
verticalAlign: '15%',
|
||||
};
|
||||
|
||||
var FeaturedContent = React.createClass({
|
||||
render: function() {
|
||||
return (<section style={featuredContentStyle}>
|
||||
<div className="row-fluid">
|
||||
<div className="span6">
|
||||
<h3>Featured Content</h3>
|
||||
</div>
|
||||
<div className="span6">
|
||||
<h3>Community Content <Link style={featuredContentLegendStyle} label="What's this?" tooltip='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" and "four" to put your content here!' /></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row-fluid">
|
||||
<div className="span6">
|
||||
<FeaturedContentItem name="what" />
|
||||
</div>
|
||||
<div className="span6">
|
||||
<FeaturedContentItem name="one" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="row-fluid">
|
||||
<div className="span6">
|
||||
<FeaturedContentItem name="itsadisaster" narrow />
|
||||
</div>
|
||||
<div className="span6">
|
||||
<FeaturedContentItem name="two" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="row-fluid">
|
||||
<div className="span6">
|
||||
<FeaturedContentItem name="keynesvhayek" />
|
||||
</div>
|
||||
<div className="span6">
|
||||
<FeaturedContentItem name="three" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="row-fluid">
|
||||
<div className="span6">
|
||||
<FeaturedContentItem name="meetlbry1" />
|
||||
</div>
|
||||
<div className="span6">
|
||||
<FeaturedContentItem name="four" />
|
||||
</div>
|
||||
</div>
|
||||
</section>);
|
||||
}
|
||||
});
|
||||
|
||||
var discoverMainStyle = {
|
||||
color: '#333'
|
||||
};
|
||||
|
||||
var Discover = React.createClass({
|
||||
userTypingTimer: null,
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
results: [],
|
||||
searching: false,
|
||||
query: ''
|
||||
};
|
||||
},
|
||||
|
||||
search: function() {
|
||||
if (this.state.query)
|
||||
{
|
||||
lbry.search(this.state.query, this.searchCallback.bind(this, this.state.query));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.setState({
|
||||
searching: false,
|
||||
results: []
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
searchCallback: function(originalQuery, results) {
|
||||
if (this.state.searching) //could have canceled while results were pending, in which case nothing to do
|
||||
{
|
||||
this.setState({
|
||||
results: results,
|
||||
searching: this.state.query != originalQuery, //multiple searches can be out, we're only done if we receive one we actually care about
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onQueryChange: function(event) {
|
||||
if (this.userTypingTimer)
|
||||
{
|
||||
clearTimeout(this.userTypingTimer);
|
||||
}
|
||||
|
||||
//@TODO: Switch to React.js timing
|
||||
this.userTypingTimer = setTimeout(this.search, 800); // 800ms delay, tweak for faster/slower
|
||||
|
||||
this.setState({
|
||||
searching: event.target.value.length > 0,
|
||||
query: event.target.value
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<main style={discoverMainStyle}>
|
||||
<section><input type="search" style={searchInputStyle} onChange={this.onQueryChange}
|
||||
placeholder="Find movies, music, games, and more"/></section>
|
||||
{ this.state.searching ? <SearchActive /> : null }
|
||||
{ !this.state.searching && this.state.query && this.state.results.length ? <SearchResults results={this.state.results} /> : null }
|
||||
{ !this.state.searching && this.state.query && !this.state.results.length ? <SearchNoResults query={this.state.query} /> : null }
|
||||
{ !this.state.query && !this.state.searching ? <FeaturedContent /> : null }
|
||||
</main>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var logoStyle = {
|
||||
padding: '48px 12px',
|
||||
textAlign: 'center',
|
||||
maxHeight: '80px',
|
||||
},
|
||||
imgStyle = { //@TODO: remove this, img should be properly scaled once size is settled
|
||||
height: '80px'
|
||||
};
|
||||
|
||||
var Header = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<header>
|
||||
<TopBar />
|
||||
<div style={logoStyle}>
|
||||
<img src="./img/lbry-dark-1600x528.png" style={imgStyle}/>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var topBarStyle = {
|
||||
'float': 'right',
|
||||
'position': 'relative',
|
||||
'height': '26px',
|
||||
},
|
||||
balanceStyle = {
|
||||
'marginRight': '5px',
|
||||
'position': 'relative',
|
||||
'top': '1px',
|
||||
};
|
||||
|
||||
var mainMenuStyle = {
|
||||
position: 'absolute',
|
||||
top: '26px',
|
||||
right: '0px',
|
||||
};
|
||||
|
||||
var MainMenu = React.createClass({
|
||||
render: function() {
|
||||
var isLinux = /linux/i.test(navigator.userAgent); // @TODO: find a way to use getVersionInfo() here without messy state management
|
||||
|
||||
return (
|
||||
<div style={mainMenuStyle}>
|
||||
<Menu {...this.props}>
|
||||
<MenuItem href='/?files' label="My Files" icon='icon-cloud-download' />
|
||||
<MenuItem href='/?wallet' label="My Wallet" icon='icon-bank' />
|
||||
<MenuItem href='/?publish' label="Publish" icon='icon-upload' />
|
||||
<MenuItem href='/?settings' label="Settings" icon='icon-gear' />
|
||||
<MenuItem href='/?help' label="Help" icon='icon-question-circle' />
|
||||
{isLinux ? <MenuItem href="/?start" label="Exit LBRY" icon="icon-close" />
|
||||
: null}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var TopBar = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
balance: 0,
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
lbry.getBalance(function(balance) {
|
||||
this.setState({
|
||||
balance: balance
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
onClose: function() {
|
||||
window.location.href = "?start";
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<span className='top-bar' style={topBarStyle}>
|
||||
<span style={balanceStyle}>
|
||||
<CreditAmount amount={this.state.balance}/>
|
||||
</span>
|
||||
<Link ref="menuButton" title="LBRY Menu" icon="icon-bars" />
|
||||
<MainMenu toggleButton={this.refs.menuButton} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var HomePage = React.createClass({
|
||||
componentDidMount: function() {
|
||||
lbry.getStartNotice(function(notice) {
|
||||
if (notice) {
|
||||
alert(notice);
|
||||
}
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div className="page">
|
||||
<Header />
|
||||
<Discover />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
|
@ -24,9 +24,11 @@ var MyFilesRowMoreMenu = React.createClass({
|
|||
return (
|
||||
<div style={moreMenuStyle}>
|
||||
<Menu {...this.props}>
|
||||
<section className="card">
|
||||
<MenuItem onClick={this.onRevealClicked} label="Reveal file" /> {/* @TODO: Switch to OS specific wording */}
|
||||
<MenuItem onClick={this.onRemoveClicked} label="Remove from LBRY" />
|
||||
<MenuItem onClick={this.onDeleteClicked} label="Remove and delete file" />
|
||||
</section>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
@ -92,12 +94,13 @@ var MyFilesRow = React.createClass({
|
|||
}
|
||||
|
||||
return (
|
||||
<section className="card">
|
||||
<div className="row-fluid">
|
||||
<div className="span3">
|
||||
{this.props.imgUrl ? <img src={this.props.imgUrl} alt={'Photo for ' + this.props.title} style={artStyle} /> : null}
|
||||
</div>
|
||||
<div className="span6">
|
||||
<h2>{this.props.pending ? this.props.title : <a href={'/?show=' + this.props.lbryUri}>{this.props.title}</a>}</h2>
|
||||
<div className="span8">
|
||||
<h3>{this.props.pending ? this.props.title : <a href={'/?show=' + this.props.lbryUri}>{this.props.title}</a>}</h3>
|
||||
{this.props.pending ? <em>This file is pending confirmation</em>
|
||||
: (
|
||||
<div>
|
||||
|
@ -121,25 +124,36 @@ var MyFilesRow = React.createClass({
|
|||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var MyFilesPage = React.createClass({
|
||||
fileTimeout: null,
|
||||
getInitialState: function() {
|
||||
return {
|
||||
filesInfo: null,
|
||||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
document.title = "My Files";
|
||||
},
|
||||
componentWillMount: function() {
|
||||
this.updateFilesInfo();
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
if (this.fileTimeout)
|
||||
{
|
||||
clearTimeout(this.fileTimeout);
|
||||
}
|
||||
},
|
||||
updateFilesInfo: function() {
|
||||
lbry.getFilesInfo((filesInfo) => {
|
||||
this.setState({
|
||||
filesInfo: (filesInfo ? filesInfo : []),
|
||||
});
|
||||
setTimeout(() => { this.updateFilesInfo() }, 1000);
|
||||
this.fileTimeout = setTimeout(() => { this.updateFilesInfo() }, 1000);
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
|
@ -151,16 +165,19 @@ var MyFilesPage = React.createClass({
|
|||
var content = <span>You haven't downloaded anything from LBRY yet. Go <Link href="/" label="search for your first download" />!</span>;
|
||||
} else {
|
||||
var content = [],
|
||||
keyIndex = 0;
|
||||
seenUris = {};
|
||||
|
||||
for (let fileInfo of this.state.filesInfo) {
|
||||
let {completed, written_bytes, total_bytes, lbry_uri, file_name, download_path,
|
||||
stopped, metadata} = fileInfo;
|
||||
|
||||
if (!metadata)
|
||||
if (!metadata || seenUris[lbry_uri])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
seenUris[lbry_uri] = true;
|
||||
|
||||
let {title, thumbnail} = metadata;
|
||||
|
||||
if (!fileInfo.pending && typeof metadata == 'object') {
|
||||
|
@ -175,21 +192,14 @@ var MyFilesPage = React.createClass({
|
|||
var ratioLoaded = written_bytes / total_bytes;
|
||||
var showWatchButton = (lbry.getMediaType(file_name) == 'video' || lbry.getMediaType(file_name) == 'audio');
|
||||
|
||||
content.push(<MyFilesRow key={lbry_uri + (++keyIndex)} lbryUri={lbry_uri} title={title || ('lbry://' + lbry_uri)} completed={completed} stopped={stopped}
|
||||
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} />);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<main className="page">
|
||||
<SubPageLogo />
|
||||
<h1>My Files</h1>
|
||||
<section>
|
||||
{content}
|
||||
</section>
|
||||
<section>
|
||||
<Link href="/" label="<< Return" />
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -241,6 +241,9 @@ var PublishPage = React.createClass({
|
|||
isFee: feeEnabled
|
||||
});
|
||||
},
|
||||
componentDidMount: function() {
|
||||
document.title = "Publish";
|
||||
},
|
||||
componentDidUpdate: function() {
|
||||
if (this.state.fileInfo && !this.state.tempFileReady) {
|
||||
// A file was chosen but the daemon hasn't finished processing it yet, i.e. it's loading, so
|
||||
|
@ -260,11 +263,10 @@ var PublishPage = React.createClass({
|
|||
},
|
||||
render: function() {
|
||||
return (
|
||||
<main className="page" ref="page">
|
||||
<SubPageLogo />
|
||||
<h1>Publish Content</h1>
|
||||
<section className="section-block">
|
||||
<main ref="page">
|
||||
<section className="card">
|
||||
<h4>LBRY Name</h4>
|
||||
<div className="form-row">
|
||||
lbry://<FormField type="text" ref="name" onChange={this.handleNameChange} />
|
||||
{
|
||||
(!this.state.name ? '' :
|
||||
|
@ -273,9 +275,10 @@ var PublishPage = React.createClass({
|
|||
: <em> This name is currently claimed for <strong>{lbry.formatCredits(this.state.claimValue)}</strong> credits.</em>)))
|
||||
}
|
||||
<div className="help">What LBRY name would you like to claim for this file?</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section-block">
|
||||
<section className="card">
|
||||
<h4>Choose File</h4>
|
||||
<form>
|
||||
<FormField name="file" ref="file" type="file" onChange={this.handleFileChange} />
|
||||
|
@ -289,19 +292,21 @@ var PublishPage = React.createClass({
|
|||
{ this.state.nameIsMine ? <div className="help">If you don't choose a file, the file from your existing claim will be used.</div> : null }
|
||||
</section>
|
||||
|
||||
<section className="section-block">
|
||||
<section className="card">
|
||||
<h4>Bid Amount</h4>
|
||||
<div className="form-row">
|
||||
Credits <FormField ref="bid" style={publishNumberStyle} type="text" onChange={this.handleBidChange} value={this.state.bid} placeholder={this.state.nameResolved ? lbry.formatCredits(this.state.claimValue + 10) : 100} />
|
||||
<div className="help">How much would you like to bid for this name?
|
||||
{ !this.state.nameResolved ? <span> Since this name is not currently resolved, you may bid as low as you want, but higher bids help prevent others from claiming your name.</span>
|
||||
: (this.state.nameIsMine ? <span> Your current bid is <strong>{lbry.formatCredits(this.state.claimValue)}</strong> credits.</span>
|
||||
: <span> You must bid over <strong>{lbry.formatCredits(this.state.claimValue)}</strong> credits to claim this name.</span>) }
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section-block">
|
||||
<section className="card">
|
||||
<h4>Fee</h4>
|
||||
<div className="spacer-bottom--sm">
|
||||
<div className="form-row">
|
||||
<label>
|
||||
<FormField type="radio" onChange={ () => { this.handleFeePrefChange(false) } } checked={!this.state.isFee} /> No fee
|
||||
</label>
|
||||
|
@ -314,20 +319,27 @@ var PublishPage = React.createClass({
|
|||
</FormField>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="help">
|
||||
<p>How much would you like to charge for this file?</p>
|
||||
If you choose to price this content in dollars, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section className="section-block">
|
||||
<section className="card">
|
||||
<h4>Your Content</h4>
|
||||
|
||||
<div className="form-row">
|
||||
<label htmlFor="title">Title</label><FormField type="text" ref="meta_title" name="title" placeholder="My Show, Episode 1" style={publishFieldStyle} />
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="author">Author</label><FormField type="text" ref="meta_author" name="author" placeholder="My Company, Inc." style={publishFieldStyle} />
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="license">License info</label><FormField type="text" ref="meta_license" name="license" defaultValue="Creative Commons Attribution 3.0 United States" style={publishFieldStyle} />
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="language">Language</label> <FormField type="select" defaultValue="en" ref="meta_language" name="language">
|
||||
<option value="en">English</option>
|
||||
<option value="zh">Chinese</option>
|
||||
|
@ -337,24 +349,30 @@ var PublishPage = React.createClass({
|
|||
<option value="ru">Russian</option>
|
||||
<option value="es">Spanish</option>
|
||||
</FormField>
|
||||
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="description">Description</label> <FormField type="textarea" ref="meta_description" name="description" placeholder="Description of your content" style={publishFieldStyle} />
|
||||
|
||||
<div><label><FormField type="checkbox" ref="meta_nsfw" name="nsfw" placeholder="Description of your content" /> Not Safe For Work</label></div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label><FormField type="checkbox" ref="meta_nsfw" name="nsfw" placeholder="Description of your content" /> Not Safe For Work</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section className="section-block">
|
||||
<section className="card">
|
||||
<h4>Additional Content Information (Optional)</h4>
|
||||
<div className="form-row">
|
||||
<label htmlFor="meta_thumbnail">Thumbnail URL</label> <FormField type="text" ref="meta_thumbnail" name="thumbnail" placeholder="http://mycompany.com/images/ep_1.jpg" style={publishFieldStyle} />
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="meta_license_url">License URL</label> <FormField type="text" ref="meta_license_url" name="license_url" defaultValue="https://creativecommons.org/licenses/by/3.0/us/legalcode" style={publishFieldStyle} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="footer-buttons">
|
||||
<Link button="alt" href="/" label="Cancel"/>
|
||||
{ ' ' }
|
||||
<div className="card-series-submit">
|
||||
<Link button="primary" label={!this.state.submitting ? 'Publish' : 'Publishing...'} onClick={this.handleSubmit} disabled={this.state.submitting} />
|
||||
<Link button="cancel" href="/" label="Cancel"/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
|
|
@ -13,6 +13,9 @@ var ReportPage = React.createClass({
|
|||
this._messageArea.value = '';
|
||||
}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
document.title = "Report an Issue";
|
||||
},
|
||||
getInitialState: function() {
|
||||
return {
|
||||
submitting: false,
|
||||
|
@ -21,18 +24,15 @@ var ReportPage = React.createClass({
|
|||
render: function() {
|
||||
return (
|
||||
<main className="page">
|
||||
<SubPageLogo />
|
||||
<h1>Report a bug</h1>
|
||||
<section>
|
||||
<section className="card">
|
||||
<h3>Report an Issue</h3>
|
||||
<p>Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!</p>
|
||||
<textarea ref={(t) => this._messageArea = t} cols="50" rows="10" name="message" type="text"/>
|
||||
<div><button onClick={this.submitMessage} className={this.state.submitting ? 'disabled' : ''}>{this.state.submitting ? 'Submitting...' : 'Submit bug report'}</button></div>
|
||||
<div><button onClick={this.submitMessage} className={'button-block button-primary ' + (this.state.submitting ? 'disabled' : '')}>{this.state.submitting ? 'Submitting...' : 'Submit Report'}</button></div>
|
||||
</section>
|
||||
<section>
|
||||
Developers, feel free to instead <Link href="https://github.com/lbryio/lbry/issues" label="submit an issue on GitHub"/>.
|
||||
</section>
|
||||
<section>
|
||||
<Link href="/?help" label="<< Return to Help"/>
|
||||
<section className="card">
|
||||
<h3>Developer?</h3>
|
||||
You can also <Link href="https://github.com/lbryio/lbry/issues" label="submit an issue on GitHub"/>.
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
|
|
|
@ -58,6 +58,9 @@ var SettingsPage = React.createClass({
|
|||
settings: null
|
||||
}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
document.title = "Settings";
|
||||
},
|
||||
componentWillMount: function() {
|
||||
lbry.getSettings(function(settings) {
|
||||
this.setState({
|
||||
|
@ -73,21 +76,21 @@ var SettingsPage = React.createClass({
|
|||
}
|
||||
|
||||
return (
|
||||
<main className="page">
|
||||
<SubPageLogo />
|
||||
<h1>Settings</h1>
|
||||
<section>
|
||||
<h4>Run on startup</h4>
|
||||
<main>
|
||||
<section className="card">
|
||||
<h3>Run on Startup</h3>
|
||||
<label style={settingsCheckBoxOptionStyles}>
|
||||
<input type="checkbox" onChange={this.onRunOnStartChange} defaultChecked={this.state.settings.run_on_startup} /> Run LBRY automatically when I start my computer
|
||||
</label>
|
||||
</section>
|
||||
<section>
|
||||
<h4>Download directory</h4>
|
||||
<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.settings.download_directory} onChange={this.onDownloadDirChange}/>
|
||||
</section>
|
||||
<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
|
||||
|
@ -96,8 +99,8 @@ var SettingsPage = React.createClass({
|
|||
<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.settings.max_upload} style={settingsNumberFieldStyles} onChange={this.onMaxUploadFieldChange}/> MB/s</span>
|
||||
</label>
|
||||
</section>
|
||||
<section>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<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
|
||||
|
@ -106,21 +109,14 @@ var SettingsPage = React.createClass({
|
|||
<input type="radio" name="max_download_pref" onChange={this.onMaxDownloadPrefChange.bind(this, true)} defaultChecked={this.state.isMaxDownload}/> { this.state.isMaxDownload ? 'Up to' : 'Choose limit...' }
|
||||
<span className={ this.state.isMaxDownload ? '' : 'hidden'}> <input type="number" min="0" step=".5" defaultValue={this.state.settings.max_download} style={settingsNumberFieldStyles} onChange={this.onMaxDownloadFieldChange}/> MB/s</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<h4>Share diagnostic data</h4>
|
||||
<section className="card">
|
||||
<h3>Share Diagnostic Data</h3>
|
||||
<label style={settingsCheckBoxOptionStyles}>
|
||||
<input type="checkbox" onChange={this.onShareDataChange} defaultChecked={this.state.settings.upload_log} /> Help make LBRY better by contributing diagnostic data about my usage
|
||||
</label>
|
||||
</section>
|
||||
<section>
|
||||
<h4>Claim invite code</h4>
|
||||
<Link href="?claim" label="Claim a LBRY beta invite code"/>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<Link href="/" label="<< Return"/>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,30 +1,10 @@
|
|||
var formatItemStyle = {
|
||||
fontSize: '0.95em',
|
||||
marginTop: '10px',
|
||||
maxHeight: '220px'
|
||||
}, formatItemImgStyle = {
|
||||
var formatItemImgStyle = {
|
||||
maxWidth: '100%',
|
||||
maxHeight: '100%',
|
||||
display: 'block',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginTop: '5px',
|
||||
}, formatHeaderStyle = {
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '5px'
|
||||
}, formatSubheaderStyle = {
|
||||
marginBottom: '10px',
|
||||
fontSize: '0.9em'
|
||||
}, formatItemDescriptionStyle = {
|
||||
color: '#444',
|
||||
marginBottom: '5px',
|
||||
fontSize: '1.2em',
|
||||
}, formatItemMetadataStyle = {
|
||||
color: '#444',
|
||||
marginBottom: '5px',
|
||||
fontSize: '0.9em',
|
||||
}, formatItemCostStyle = {
|
||||
float: 'right'
|
||||
};
|
||||
|
||||
var FormatItem = React.createClass({
|
||||
|
@ -34,7 +14,6 @@ var FormatItem = React.createClass({
|
|||
name: React.PropTypes.string,
|
||||
},
|
||||
render: function() {
|
||||
var name = this.props.name;
|
||||
|
||||
var claimInfo = this.props.claimInfo;
|
||||
var thumbnail = claimInfo.thumbnail;
|
||||
|
@ -48,25 +27,33 @@ var FormatItem = React.createClass({
|
|||
var amount = this.props.amount || 0.0;
|
||||
|
||||
return (
|
||||
<div className="row-fluid" style={formatItemStyle}>
|
||||
<div className="row-fluid">
|
||||
<div className="span4">
|
||||
<img src={thumbnail} alt={'Photo for ' + title} style={formatItemImgStyle} />
|
||||
</div>
|
||||
<div className="span8">
|
||||
<h4 style={formatItemMetadataStyle}><b>Address:</b> {name}</h4>
|
||||
<h4 style={formatItemMetadataStyle}><b>Content-Type:</b> {fileContentType}</h4>
|
||||
<div style={formatSubheaderStyle}>
|
||||
<div style={formatItemCostStyle}><CreditAmount amount={amount} isEstimate={true}/></div>
|
||||
<WatchLink streamName={name} />
|
||||
|
||||
<DownloadLink streamName={name} />
|
||||
</div>
|
||||
<p style={formatItemDescriptionStyle}>{description}</p>
|
||||
<div>
|
||||
<span style={formatItemMetadataStyle}><b>Author:</b> {author}</span><br />
|
||||
<span style={formatItemMetadataStyle}><b>Language:</b> {language}</span><br />
|
||||
<span style={formatItemMetadataStyle}><b>License:</b> {license}</span><br />
|
||||
</div>
|
||||
<p>{description}</p>
|
||||
<table className="table-standard">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Content-Type</td><td>{fileContentType}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cost</td><td><CreditAmount amount={amount} isEstimate={true}/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Author</td><td>{author}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Language</td><td>{language}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>License</td><td>{license}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<WatchLink streamName={this.props.name} button="primary" />
|
||||
<DownloadLink streamName={this.props.name} button="alt" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -88,17 +75,18 @@ var FormatsSection = React.createClass({
|
|||
{
|
||||
return (
|
||||
<div>
|
||||
<h1 style={formatHeaderStyle}>Sorry, no results found for "{name}".</h1>
|
||||
<h2>Sorry, no results found for "{name}".</h2>
|
||||
</div>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 style={formatHeaderStyle}>{title}</h1>
|
||||
<div className="meta">lbry://{name}</div>
|
||||
<h2>{title}</h2>
|
||||
{/* 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 name={name} claimInfo={format} amount={this.props.amount} />
|
||||
<FormatItem claimInfo={format} amount={this.props.amount} />
|
||||
{/* })}</tbody>); */}
|
||||
</div>);
|
||||
}
|
||||
|
@ -116,6 +104,8 @@ var DetailPage = React.createClass({
|
|||
};
|
||||
},
|
||||
componentWillMount: function() {
|
||||
document.title = 'lbry://' + this.props.name;
|
||||
|
||||
lbry.getClaimInfo(this.props.name, (claimInfo) => {
|
||||
this.setState({
|
||||
claimInfo: claimInfo.value,
|
||||
|
@ -140,11 +130,9 @@ var DetailPage = React.createClass({
|
|||
var amount = this.state.amount;
|
||||
|
||||
return (
|
||||
<main className="page">
|
||||
<SubPageLogo />
|
||||
<main>
|
||||
<section className="card">
|
||||
<FormatsSection name={name} claimInfo={claimInfo} amount={amount} />
|
||||
<section>
|
||||
<Link href="/" label="<< Return" />
|
||||
</section>
|
||||
</main>);
|
||||
}
|
||||
|
|
|
@ -2,11 +2,13 @@ var StartPage = React.createClass({
|
|||
componentWillMount: function() {
|
||||
lbry.stop();
|
||||
},
|
||||
componentDidMount: function() {
|
||||
document.title = "LBRY is Closed";
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<main className="page">
|
||||
<SubPageLogo />
|
||||
<h1>LBRY has closed</h1>
|
||||
<h3>LBRY is Closed</h3>
|
||||
<Link href="lbry://lbry" label="Click here to start LBRY" />
|
||||
</main>
|
||||
);
|
||||
|
|
|
@ -13,10 +13,10 @@ var NewAddressSection = React.createClass({
|
|||
},
|
||||
render: function() {
|
||||
return (
|
||||
<section>
|
||||
<h1>Generate New Address</h1>
|
||||
<section><input type="text" size="60" value={this.state.address}></input></section>
|
||||
<Link button="primary" label="Generate" onClick={this.generateAddress} />
|
||||
<section className="card">
|
||||
<h3>Generate New Address</h3>
|
||||
<div className="form-row"><input type="text" size="60" value={this.state.address}></input></div>
|
||||
<div className="form-row form-row-submit"><Link button="primary" label="Generate" onClick={this.generateAddress} /></div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
@ -80,32 +80,64 @@ var SendToAddressSection = React.createClass({
|
|||
},
|
||||
render: function() {
|
||||
return (
|
||||
<section>
|
||||
<h1>Send Credits</h1>
|
||||
<section>
|
||||
<section><label for="balance">Balance {this.state.balance}</label></section>
|
||||
<label for="amount">Amount <input id="amount" type="text" size="10" onChange={this.setAmount}></input></label>
|
||||
<label for="address">Recipient address <input id="address" type="text" size="60" onChange={this.setAddress}></input></label>
|
||||
<section className="card">
|
||||
<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>
|
||||
<div className="form-row">
|
||||
<label htmlFor="address">Recipient address</label>
|
||||
<input id="address" type="text" size="60" onChange={this.setAddress}></input>
|
||||
</div>
|
||||
<div className="form-row form-row-submit">
|
||||
<Link button="primary" label="Send" onClick={this.sendToAddress} disabled={!(parseFloat(this.state.amount) > 0.0) || this.state.address == ""} />
|
||||
</section>
|
||||
<section className={!this.state.results ? 'hidden' : ''}>
|
||||
<h4>Results:</h4>
|
||||
</div>
|
||||
{
|
||||
this.state.results ?
|
||||
<div className="form-row">
|
||||
<h4>Results</h4>
|
||||
{this.state.results}
|
||||
</section>
|
||||
</div>
|
||||
: ''
|
||||
}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var WalletPage = React.createClass({
|
||||
componentDidMount: function() {
|
||||
document.title = "My Wallet";
|
||||
},
|
||||
/*
|
||||
Below should be refactored so that balance is shared all of wallet page. Or even broader?
|
||||
What is the proper React pattern for sharing a global state like balance?
|
||||
*/
|
||||
getInitialState: function() {
|
||||
return {
|
||||
balance: "Checking balance...",
|
||||
}
|
||||
},
|
||||
componentWillMount: function() {
|
||||
lbry.getBalance((results) => {
|
||||
this.setState({
|
||||
balance: results,
|
||||
});
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<main className="page">
|
||||
<SubPageLogo />
|
||||
<NewAddressSection />
|
||||
<section className="card">
|
||||
<h3>Balance</h3>
|
||||
{this.state.balance} <CurrencySymbol />
|
||||
</section>
|
||||
<SendToAddressSection />
|
||||
<section>
|
||||
<Link href="/" label="<< Return" />
|
||||
<NewAddressSection />
|
||||
<section className="card">
|
||||
<h3>Claim Invite Code</h3>
|
||||
<Link href="?claim" label="Claim a LBRY beta invite code" button="alt" />
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
|
|
|
@ -38,7 +38,7 @@ var WatchPage = React.createClass({
|
|||
},
|
||||
render: function() {
|
||||
return (
|
||||
<main className="page full-screen">
|
||||
<main className="full-screen">
|
||||
<div className={this.state.readyToPlay ? 'hidden' : ''}>
|
||||
<h3>Loading lbry://{this.props.name}</h3>
|
||||
{this.state.loadStatusMessage}...
|
||||
|
|
172
scss/_canvas.scss
Normal file
172
scss/_canvas.scss
Normal file
|
@ -0,0 +1,172 @@
|
|||
@import "global";
|
||||
|
||||
html
|
||||
{
|
||||
height: 100%;
|
||||
font-size: $font-size;
|
||||
}
|
||||
body
|
||||
{
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
line-height: 1.3333;
|
||||
}
|
||||
|
||||
$drawer-width: 240px;
|
||||
|
||||
#drawer
|
||||
{
|
||||
width: $drawer-width;
|
||||
position: fixed;
|
||||
min-height: 100vh;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: $color-bg;
|
||||
z-index: 1;
|
||||
.drawer-item
|
||||
{
|
||||
display: block;
|
||||
padding: $spacing-vertical / 2;
|
||||
font-size: 1.2em;
|
||||
height: $spacing-vertical * 1.5;
|
||||
.icon
|
||||
{
|
||||
margin-right: 6px;
|
||||
}
|
||||
.link-label
|
||||
{
|
||||
line-height: $spacing-vertical * 1.5;
|
||||
}
|
||||
.badge
|
||||
{
|
||||
float: right;
|
||||
background: $color-money;
|
||||
display: inline-block;
|
||||
padding: 2px;
|
||||
color: white;
|
||||
margin-top: $spacing-vertical * 0.25 - 2;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
.drawer-item-selected
|
||||
{
|
||||
background: $color-canvas;
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
||||
#drawer-handle
|
||||
{
|
||||
padding: $spacing-vertical / 2;
|
||||
max-height: $header-height - $spacing-vertical;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#window.drawer-closed
|
||||
{
|
||||
#drawer { display: none }
|
||||
}
|
||||
#window.drawer-open
|
||||
{
|
||||
#main-content { margin-left: $drawer-width; }
|
||||
.open-drawer-link { visibility: hidden; }
|
||||
#header { padding-left: $drawer-width + $spacing-vertical / 2; }
|
||||
}
|
||||
|
||||
#header
|
||||
{
|
||||
background: $color-primary;
|
||||
color: white;
|
||||
height: $header-height;
|
||||
padding: $spacing-vertical / 2;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
h1 { font-size: 1.8em; line-height: $header-height - $spacing-vertical; display: inline-block; float: left; }
|
||||
&.header-scrolled
|
||||
{
|
||||
box-shadow: $default-box-shadow;
|
||||
}
|
||||
}
|
||||
.header-search
|
||||
{
|
||||
margin-left: 60px;
|
||||
text-align: center;
|
||||
input[type="search"] {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
color: white;
|
||||
width: 400px;
|
||||
@include placeholder-color(#e8e8e8);
|
||||
}
|
||||
}
|
||||
|
||||
#main-content
|
||||
{
|
||||
background: $color-canvas;
|
||||
min-height: calc(100vh - 60px); //should be -$header-height, but I'm dumb I guess? It wouldn't work
|
||||
main
|
||||
{
|
||||
margin-top: $header-height;
|
||||
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;
|
||||
|
||||
.open-drawer-link, .close-drawer-link
|
||||
{
|
||||
display: inline-block;
|
||||
font-size: $header-icon-size;
|
||||
padding: 2px 6px 0 6px;
|
||||
float: left;
|
||||
}
|
||||
.close-lbry-link
|
||||
{
|
||||
font-size: $header-icon-size;
|
||||
float: right;
|
||||
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-series-submit
|
||||
{
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 800px;
|
||||
padding: $spacing-vertical / 2;
|
||||
}
|
||||
|
||||
.full-screen
|
||||
{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
|
@ -6,6 +6,8 @@ $color-primary: #155B4A;
|
|||
$color-light-alt: hsl(hue($color-primary), 15, 85);
|
||||
$color-text-dark: #000;
|
||||
$color-help: rgba(0,0,0,.6);
|
||||
$color-canvas: #f5f5f5;
|
||||
$color-bg: #ffffff;
|
||||
$color-money: #216C2A;
|
||||
$color-meta-light: #505050;
|
||||
|
||||
|
@ -15,6 +17,10 @@ $mobile-width-threshold: 801px;
|
|||
$max-content-width: 1000px;
|
||||
$max-text-width: 660px;
|
||||
|
||||
$header-height: $spacing-vertical * 2.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);
|
||||
|
||||
|
||||
@mixin clearfix()
|
||||
{
|
||||
|
@ -37,6 +43,22 @@ $max-text-width: 660px;
|
|||
border-radius: $radius;
|
||||
}
|
||||
|
||||
@mixin placeholder-color($color) {
|
||||
/*do not group these it breaks because CSS*/
|
||||
&:-moz-placeholder {
|
||||
color: $color;
|
||||
}
|
||||
&::-moz-placeholder {
|
||||
color: $color;
|
||||
}
|
||||
&:-ms-input-placeholder {
|
||||
color: $color;
|
||||
}
|
||||
&::-webkit-input-placeholder {
|
||||
color: $color;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin display-flex()
|
||||
{
|
||||
display: -webkit-box;
|
||||
|
|
114
scss/_gui.scss
114
scss/_gui.scss
|
@ -1,30 +1,5 @@
|
|||
@import "global";
|
||||
|
||||
html
|
||||
{
|
||||
height: 100%;
|
||||
font-size: $font-size;
|
||||
}
|
||||
body
|
||||
{
|
||||
font-family: 'Source Sans Pro', sans-serif;
|
||||
line-height: 1.3333;
|
||||
min-height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.page {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 800px;
|
||||
padding-bottom: $spacing-vertical*3;
|
||||
|
||||
&.full-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-fixed-width {
|
||||
/* This borrowed is from a component of Font Awesome we're not using, maybe add it? */
|
||||
width: (18em / 14);
|
||||
|
@ -44,14 +19,7 @@ section
|
|||
}
|
||||
}
|
||||
|
||||
.section-block {
|
||||
background-color: rgba(0,0,0,.05);
|
||||
padding: $spacing-vertical;
|
||||
border-radius: 2px;
|
||||
margin-bottom: $spacing-vertical;
|
||||
}
|
||||
|
||||
h1 {
|
||||
main h1 {
|
||||
font-size: 2.0em;
|
||||
margin-bottom: $spacing-vertical;
|
||||
margin-top: $spacing-vertical*2;
|
||||
|
@ -65,10 +33,7 @@ h2 {
|
|||
h3 { font-size: 1.4em; }
|
||||
|
||||
h4 {
|
||||
font-weight: 600;
|
||||
font-size: 1.2em;
|
||||
margin: 0;
|
||||
margin-bottom: $spacing-vertical/2;
|
||||
}
|
||||
|
||||
h5 { font-size: 1.1em; }
|
||||
|
@ -84,14 +49,13 @@ label {
|
|||
display: block;
|
||||
}
|
||||
|
||||
header
|
||||
{
|
||||
line-height: $spacing-vertical;
|
||||
}
|
||||
|
||||
p
|
||||
{
|
||||
margin-bottom: 0.8em;
|
||||
&:last-child
|
||||
{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
|
@ -107,17 +71,7 @@ textarea {
|
|||
opacity: 0.7;
|
||||
}
|
||||
|
||||
input[type="search"]
|
||||
{
|
||||
border: 0 none;
|
||||
border: 2px solid rgba(160,160,160,.5);
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
line-height: $spacing-vertical * 1.5;
|
||||
margin-bottom: $spacing-vertical/2;
|
||||
}
|
||||
|
||||
input[type="text"], textarea
|
||||
input[type="text"], input[type="search"], textarea
|
||||
{
|
||||
@include placeholder {
|
||||
color: lighten($color-text-dark, 60%);
|
||||
|
@ -126,12 +80,9 @@ input[type="text"], textarea
|
|||
border: 2px solid rgba(160,160,160,.5);
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
line-height: $spacing-vertical;
|
||||
margin-bottom: $spacing-vertical/2;
|
||||
}
|
||||
|
||||
select {
|
||||
margin-bottom: $spacing-vertical/2;
|
||||
height: $spacing-vertical * 1.5;
|
||||
line-height: $spacing-vertical - 4;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.busy-indicator
|
||||
|
@ -159,16 +110,14 @@ select {
|
|||
display: inline-block;
|
||||
height: $spacing-vertical * 1.5;
|
||||
line-height: $spacing-vertical * 1.5;
|
||||
padding: 0 30px;
|
||||
text-decoration: none;
|
||||
border: 0 none;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
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);
|
||||
text-transform: uppercase;
|
||||
+ .button-block
|
||||
{
|
||||
margin-left: 20px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
.icon
|
||||
{
|
||||
|
@ -188,11 +137,18 @@ select {
|
|||
{
|
||||
color: white;
|
||||
background-color: $color-primary;
|
||||
|
||||
box-shadow: $default-box-shadow;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.button-alt
|
||||
{
|
||||
background-color: rgba(0,0,0,.15);
|
||||
box-shadow: $default-box-shadow;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.button-cancel
|
||||
{
|
||||
padding: 0 12px;
|
||||
}
|
||||
.button-text
|
||||
{
|
||||
|
@ -224,28 +180,34 @@ select {
|
|||
}
|
||||
}
|
||||
|
||||
.footer-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon:only-child {
|
||||
position: relative;
|
||||
top: 0.16em;
|
||||
}
|
||||
|
||||
.fade-in-link {
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.help {
|
||||
font-size: .85em;
|
||||
color: $color-help;
|
||||
}
|
||||
|
||||
.spacer-bottom--sm {
|
||||
margin-bottom: $spacing-vertical/2;
|
||||
.meta
|
||||
{
|
||||
font-size: 0.9em;
|
||||
color: $color-meta-light;
|
||||
}
|
||||
|
||||
.form-row
|
||||
{
|
||||
+ .form-row
|
||||
{
|
||||
margin-top: $spacing-vertical / 2;
|
||||
}
|
||||
.help
|
||||
{
|
||||
margin-top: $spacing-vertical / 2;
|
||||
}
|
||||
+ .form-row-submit
|
||||
{
|
||||
margin-top: $spacing-vertical;
|
||||
}
|
||||
}
|
50
scss/_table.scss
Normal file
50
scss/_table.scss
Normal file
|
@ -0,0 +1,50 @@
|
|||
table.table-standard {
|
||||
margin-bottom: $spacing-vertical;
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
|
||||
th, td {
|
||||
padding: $spacing-vertical/2 8px;
|
||||
}
|
||||
th {
|
||||
font-weight: bold;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
thead th, > tr:first-child th {
|
||||
vertical-align: bottom;
|
||||
font-weight: bold;
|
||||
font-size: 0.9em;
|
||||
padding: $spacing-vertical/4+1 8px $spacing-vertical/4-2;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
img {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
tr.thead:not(:first-child) th {
|
||||
border-top: 1px solid #e2e2e2;
|
||||
}
|
||||
tfoot td {
|
||||
padding: $spacing-vertical / 2 8px;
|
||||
font-size: .85em;
|
||||
}
|
||||
tbody {
|
||||
tr {
|
||||
&:nth-child(even):not(.odd) {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
&:nth-child(odd):not(.even) {
|
||||
background-color: white;
|
||||
}
|
||||
&.thead {
|
||||
background: none;
|
||||
}
|
||||
td {
|
||||
border: 0 none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,4 +2,6 @@
|
|||
@import "_grid";
|
||||
@import "_icons";
|
||||
@import "_mediaelement";
|
||||
@import "_canvas";
|
||||
@import "_table";
|
||||
@import "_gui";
|
Loading…
Reference in a new issue