Seed Support #56
36 changed files with 962 additions and 924 deletions
|
@ -8,6 +8,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
|
* The UI has been overhauled to use an omnibar and drop the sidebar.
|
||||||
* The app is much more responsive switching pages. It no longer reloads the entire page and all assets on each page change.
|
* The app is much more responsive switching pages. It no longer reloads the entire page and all assets on each page change.
|
||||||
* lbry.js now offers a subscription model for wallet balance similar to file info.
|
* lbry.js now offers a subscription model for wallet balance similar to file info.
|
||||||
* Fixed file info subscribes not being unsubscribed in unmount.
|
* Fixed file info subscribes not being unsubscribed in unmount.
|
||||||
|
|
130
ui/js/app.js
130
ui/js/app.js
|
@ -12,10 +12,11 @@ import RewardPage from './page/reward.js';
|
||||||
import WalletPage from './page/wallet.js';
|
import WalletPage from './page/wallet.js';
|
||||||
import ShowPage from './page/show.js';
|
import ShowPage from './page/show.js';
|
||||||
import PublishPage from './page/publish.js';
|
import PublishPage from './page/publish.js';
|
||||||
|
import SearchPage from './page/search.js';
|
||||||
import DiscoverPage from './page/discover.js';
|
import DiscoverPage from './page/discover.js';
|
||||||
import DeveloperPage from './page/developer.js';
|
import DeveloperPage from './page/developer.js';
|
||||||
|
import lbryuri from './lbryuri.js';
|
||||||
import {FileListDownloaded, FileListPublished} from './page/file-list.js';
|
import {FileListDownloaded, FileListPublished} from './page/file-list.js';
|
||||||
import Drawer from './component/drawer.js';
|
|
||||||
import Header from './component/header.js';
|
import Header from './component/header.js';
|
||||||
import {Modal, ExpandableModal} from './component/modal.js';
|
import {Modal, ExpandableModal} from './component/modal.js';
|
||||||
import {Link} from './component/link.js';
|
import {Link} from './component/link.js';
|
||||||
|
@ -38,6 +39,7 @@ var App = React.createClass({
|
||||||
data: 'Error data',
|
data: 'Error data',
|
||||||
},
|
},
|
||||||
_fullScreenPages: ['watch'],
|
_fullScreenPages: ['watch'],
|
||||||
|
_storeHistoryOfNextRender: false,
|
||||||
|
|
||||||
_upgradeDownloadItem: null,
|
_upgradeDownloadItem: null,
|
||||||
_isMounted: false,
|
_isMounted: false,
|
||||||
|
@ -73,15 +75,13 @@ var App = React.createClass({
|
||||||
let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/);
|
let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/);
|
||||||
return {
|
return {
|
||||||
viewingPage: viewingPage,
|
viewingPage: viewingPage,
|
||||||
pageArgs: pageArgs === undefined ? null : pageArgs
|
pageArgs: pageArgs === undefined ? null : decodeURIComponent(pageArgs)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
var match, param, val, viewingPage, pageArgs,
|
|
||||||
drawerOpenRaw = sessionStorage.getItem('drawerOpen');
|
|
||||||
|
|
||||||
return Object.assign(this.getViewingPageAndArgs(window.location.search), {
|
return Object.assign(this.getViewingPageAndArgs(window.location.search), {
|
||||||
drawerOpen: drawerOpenRaw !== null ? JSON.parse(drawerOpenRaw) : true,
|
viewingPage: 'discover',
|
||||||
|
appUrl: null,
|
||||||
errorInfo: null,
|
errorInfo: null,
|
||||||
modal: null,
|
modal: null,
|
||||||
downloadProgress: null,
|
downloadProgress: null,
|
||||||
|
@ -89,6 +89,8 @@ var App = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
window.addEventListener("popstate", this.onHistoryPop);
|
||||||
|
|
||||||
document.addEventListener('unhandledError', (event) => {
|
document.addEventListener('unhandledError', (event) => {
|
||||||
this.alertError(event.detail);
|
this.alertError(event.detail);
|
||||||
});
|
});
|
||||||
|
@ -105,9 +107,10 @@ var App = React.createClass({
|
||||||
if (target.matches('a[href^="?"]')) {
|
if (target.matches('a[href^="?"]')) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this._isMounted) {
|
if (this._isMounted) {
|
||||||
history.pushState({}, document.title, target.getAttribute('href'));
|
let appUrl = target.getAttribute('href');
|
||||||
this.registerHistoryPop();
|
this._storeHistoryOfNextRender = true;
|
||||||
this.setState(this.getViewingPageAndArgs(target.getAttribute('href')));
|
this.setState(Object.assign({}, this.getViewingPageAndArgs(appUrl), { appUrl: appUrl }));
|
||||||
|
document.body.scrollTop = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
target = target.parentNode;
|
target = target.parentNode;
|
||||||
|
@ -125,14 +128,6 @@ var App = React.createClass({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openDrawer: function() {
|
|
||||||
sessionStorage.setItem('drawerOpen', true);
|
|
||||||
this.setState({ drawerOpen: true });
|
|
||||||
},
|
|
||||||
closeDrawer: function() {
|
|
||||||
sessionStorage.setItem('drawerOpen', false);
|
|
||||||
this.setState({ drawerOpen: false });
|
|
||||||
},
|
|
||||||
closeModal: function() {
|
closeModal: function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
modal: null,
|
modal: null,
|
||||||
|
@ -143,12 +138,29 @@ var App = React.createClass({
|
||||||
},
|
},
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
this._isMounted = false;
|
this._isMounted = false;
|
||||||
|
window.removeEventListener("popstate", this.onHistoryPop);
|
||||||
},
|
},
|
||||||
registerHistoryPop: function() {
|
onHistoryPop: function() {
|
||||||
window.addEventListener("popstate", () => {
|
this.setState(this.getViewingPageAndArgs(location.search));
|
||||||
this.setState(this.getViewingPageAndArgs(location.pathname));
|
},
|
||||||
|
onSearch: function(term) {
|
||||||
|
this._storeHistoryOfNextRender = true;
|
||||||
|
const isShow = term.startsWith('lbry://');
|
||||||
|
this.setState({
|
||||||
|
viewingPage: isShow ? "show" : "search",
|
||||||
|
appUrl: (isShow ? "?show=" : "?search=") + encodeURIComponent(term),
|
||||||
|
pageArgs: term
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onSubmit: function(uri) {
|
||||||
|
this._storeHistoryOfNextRender = true;
|
||||||
|
this.setState({
|
||||||
|
address: uri,
|
||||||
|
appUrl: "?show=" + encodeURIComponent(uri),
|
||||||
|
viewingPage: "show",
|
||||||
|
pageArgs: uri
|
||||||
|
})
|
||||||
|
},
|
||||||
handleUpgradeClicked: function() {
|
handleUpgradeClicked: function() {
|
||||||
// Make a new directory within temp directory so the filename is guaranteed to be available
|
// Make a new directory within temp directory so the filename is guaranteed to be available
|
||||||
const dir = fs.mkdtempSync(app.getPath('temp') + require('path').sep);
|
const dir = fs.mkdtempSync(app.getPath('temp') + require('path').sep);
|
||||||
|
@ -201,12 +213,6 @@ var App = React.createClass({
|
||||||
modal: null,
|
modal: null,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSearch: function(term) {
|
|
||||||
this.setState({
|
|
||||||
viewingPage: 'discover',
|
|
||||||
pageArgs: term
|
|
||||||
});
|
|
||||||
},
|
|
||||||
alertError: function(error) {
|
alertError: function(error) {
|
||||||
var errorInfoList = [];
|
var errorInfoList = [];
|
||||||
for (let key of Object.keys(error)) {
|
for (let key of Object.keys(error)) {
|
||||||
|
@ -220,75 +226,57 @@ var App = React.createClass({
|
||||||
errorInfo: <ul className="error-modal__error-list">{errorInfoList}</ul>,
|
errorInfo: <ul className="error-modal__error-list">{errorInfoList}</ul>,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getHeaderLinks: function()
|
getContentAndAddress: function()
|
||||||
{
|
|
||||||
switch(this.state.viewingPage)
|
|
||||||
{
|
|
||||||
case 'wallet':
|
|
||||||
case 'send':
|
|
||||||
case 'receive':
|
|
||||||
case 'rewards':
|
|
||||||
return {
|
|
||||||
'?wallet': 'Overview',
|
|
||||||
'?send': 'Send',
|
|
||||||
'?receive': 'Receive',
|
|
||||||
'?rewards': 'Rewards',
|
|
||||||
};
|
|
||||||
case 'downloaded':
|
|
||||||
case 'published':
|
|
||||||
return {
|
|
||||||
'?downloaded': 'Downloaded',
|
|
||||||
'?published': 'Published',
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getMainContent: function()
|
|
||||||
{
|
{
|
||||||
switch(this.state.viewingPage)
|
switch(this.state.viewingPage)
|
||||||
{
|
{
|
||||||
|
case 'search':
|
||||||
|
return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', <SearchPage query={this.state.pageArgs} />];
|
||||||
case 'settings':
|
case 'settings':
|
||||||
return <SettingsPage />;
|
return ["Settings", "icon-gear", <SettingsPage />];
|
||||||
case 'help':
|
case 'help':
|
||||||
return <HelpPage />;
|
return ["Help", "icon-question", <HelpPage />];
|
||||||
case 'report':
|
case 'report':
|
||||||
return <ReportPage />;
|
return ['Report an Issue', 'icon-file', <ReportPage />];
|
||||||
case 'downloaded':
|
case 'downloaded':
|
||||||
return <FileListDownloaded />;
|
return ["Downloads & Purchases", "icon-folder", <FileListDownloaded />];
|
||||||
case 'published':
|
case 'published':
|
||||||
return <FileListPublished />;
|
return ["Publishes", "icon-folder", <FileListPublished />];
|
||||||
case 'start':
|
case 'start':
|
||||||
return <StartPage />;
|
return ["Start", "icon-file", <StartPage />];
|
||||||
case 'rewards':
|
case 'rewards':
|
||||||
return <RewardsPage />;
|
return ["Rewards", "icon-bank", <RewardsPage />];
|
||||||
case 'wallet':
|
case 'wallet':
|
||||||
case 'send':
|
case 'send':
|
||||||
case 'receive':
|
case 'receive':
|
||||||
return <WalletPage viewingPage={this.state.viewingPage} />;
|
return [this.state.viewingPage.charAt(0).toUpperCase() + this.state.viewingPage.slice(1), "icon-bank", <WalletPage viewingPage={this.state.viewingPage} />]
|
||||||
case 'show':
|
case 'show':
|
||||||
return <ShowPage uri={this.state.pageArgs} />;
|
return [lbryuri.normalize(this.state.pageArgs), "icon-file", <ShowPage uri={this.state.pageArgs} />];
|
||||||
case 'publish':
|
case 'publish':
|
||||||
return <PublishPage />;
|
return ["Publish", "icon-upload", <PublishPage />];
|
||||||
case 'developer':
|
case 'developer':
|
||||||
return <DeveloperPage />;
|
return ["Developer", "icon-file", <DeveloperPage />];
|
||||||
case 'discover':
|
case 'discover':
|
||||||
default:
|
default:
|
||||||
return <DiscoverPage showWelcome={this.state.justRegistered} {... this.state.pageArgs !== null ? {query: this.state.pageArgs} : {} } />;
|
return ["Home", "icon-home", <DiscoverPage />];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
var mainContent = this.getMainContent(),
|
let [address, wunderBarIcon, mainContent] = this.getContentAndAddress();
|
||||||
headerLinks = this.getHeaderLinks(),
|
|
||||||
searchQuery = this.state.viewingPage == 'discover' && this.state.pageArgs ? this.state.pageArgs : '';
|
lbry.setTitle(address);
|
||||||
|
|
||||||
|
if (this._storeHistoryOfNextRender) {
|
||||||
|
this._storeHistoryOfNextRender = false;
|
||||||
|
history.pushState({}, document.title, this.state.appUrl);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this._fullScreenPages.includes(this.state.viewingPage) ?
|
this._fullScreenPages.includes(this.state.viewingPage) ?
|
||||||
mainContent :
|
mainContent :
|
||||||
<div id="window" className={ this.state.drawerOpen ? 'drawer-open' : 'drawer-closed' }>
|
<div id="window">
|
||||||
<Drawer onCloseDrawer={this.closeDrawer} viewingPage={this.state.viewingPage} />
|
<Header onSearch={this.onSearch} onSubmit={this.onSubmit} address={address} wunderBarIcon={wunderBarIcon} viewingPage={this.state.viewingPage} />
|
||||||
<div id="main-content" className={ headerLinks ? 'with-sub-nav' : 'no-sub-nav' }>
|
<div id="main-content">
|
||||||
<Header onOpenDrawer={this.openDrawer} initialQuery={searchQuery} onSearch={this.onSearch} links={headerLinks} viewingPage={this.state.viewingPage} />
|
|
||||||
{mainContent}
|
{mainContent}
|
||||||
</div>
|
</div>
|
||||||
<Modal isOpen={this.state.modal == 'upgrade'} contentLabel="Update available"
|
<Modal isOpen={this.state.modal == 'upgrade'} contentLabel="Update available"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import $clamp from 'clamp-js-main';
|
|
||||||
|
|
||||||
//component/icon.js
|
//component/icon.js
|
||||||
export let Icon = React.createClass({
|
export let Icon = React.createClass({
|
||||||
|
@ -19,29 +18,15 @@ export let Icon = React.createClass({
|
||||||
|
|
||||||
export let TruncatedText = React.createClass({
|
export let TruncatedText = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
lines: React.PropTypes.number,
|
lines: React.PropTypes.number
|
||||||
height: React.PropTypes.string,
|
|
||||||
auto: React.PropTypes.bool,
|
|
||||||
},
|
},
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
lines: null,
|
lines: null,
|
||||||
height: null,
|
|
||||||
auto: true,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
|
||||||
// Manually round up the line height, because clamp.js doesn't like fractional-pixel line heights.
|
|
||||||
|
|
||||||
// Need to work directly on the style object because setting the style prop doesn't update internal styles right away.
|
|
||||||
this.refs.span.style.lineHeight = Math.ceil(parseFloat(getComputedStyle(this.refs.span).lineHeight)) + 'px';
|
|
||||||
|
|
||||||
$clamp(this.refs.span, {
|
|
||||||
clamp: this.props.lines || this.props.height || 'auto',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return <span ref="span" className="truncated-text">{this.props.children}</span>;
|
return <span className="truncated-text" style={{ WebkitLineClamp: this.props.lines }}>{this.props.children}</span>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
import lbry from '../lbry.js';
|
|
||||||
import React from 'react';
|
|
||||||
import {Link} from './link.js';
|
|
||||||
|
|
||||||
var DrawerItem = React.createClass({
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
subPages: [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
var isSelected = (this.props.viewingPage == this.props.href.substr(1) ||
|
|
||||||
this.props.subPages.indexOf(this.props.viewingPage) != -1);
|
|
||||||
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({
|
|
||||||
_balanceSubscribeId: null,
|
|
||||||
|
|
||||||
handleLogoClicked: function(event) {
|
|
||||||
if ((event.ctrlKey || event.metaKey) && event.shiftKey) {
|
|
||||||
window.location.href = '?developer'
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
balance: 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentDidMount: function() {
|
|
||||||
this._balanceSubscribeId = lbry.balanceSubscribe((balance) => {
|
|
||||||
this.setState({
|
|
||||||
balance: balance
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
componentWillUnmount: function() {
|
|
||||||
if (this._balanceSubscribeId) {
|
|
||||||
lbry.balanceUnsubscribe(this._balanceSubscribeId)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<nav id="drawer">
|
|
||||||
<div id="drawer-handle">
|
|
||||||
<Link title="Close" onClick={this.props.onCloseDrawer} icon="icon-bars" className="close-drawer-link"/>
|
|
||||||
<a href="?discover" onMouseUp={this.handleLogoClicked}><img src={lbry.imagePath("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='?downloaded' subPages={['published']} viewingPage={this.props.viewingPage} label="My Files" icon='icon-cloud-download' />
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
export default Drawer;
|
|
|
@ -3,7 +3,7 @@ import lbry from '../lbry.js';
|
||||||
import lbryuri from '../lbryuri.js';
|
import lbryuri from '../lbryuri.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link.js';
|
||||||
import {FileActions} from '../component/file-actions.js';
|
import {FileActions} from '../component/file-actions.js';
|
||||||
import {Thumbnail, TruncatedText, FilePrice} from '../component/common.js';
|
import {BusyMessage, TruncatedText, FilePrice} from '../component/common.js';
|
||||||
import UriIndicator from '../component/channel-indicator.js';
|
import UriIndicator from '../component/channel-indicator.js';
|
||||||
|
|
||||||
/*should be merged into FileTile once FileTile is refactored to take a single id*/
|
/*should be merged into FileTile once FileTile is refactored to take a single id*/
|
||||||
|
@ -77,40 +77,32 @@ export let FileTileStream = React.createClass({
|
||||||
const isConfirmed = !!metadata;
|
const isConfirmed = !!metadata;
|
||||||
const title = isConfirmed ? metadata.title : uri;
|
const title = isConfirmed ? metadata.title : uri;
|
||||||
const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw;
|
const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw;
|
||||||
|
const primaryUrl = "?show=" + uri;
|
||||||
return (
|
return (
|
||||||
<section className={ 'file-tile card ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
<section className={ 'file-tile card ' + (obscureNsfw ? 'card--obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||||
<div className={"row-fluid card__inner file-tile__row"}>
|
<a href={primaryUrl} className="card__link">
|
||||||
<div className="span3 file-tile__thumbnail-container">
|
<div className={"card__inner file-tile__row"}>
|
||||||
<a href={'?show=' + uri}><Thumbnail className="file-tile__thumbnail" {... metadata && metadata.thumbnail ? {src: metadata.thumbnail} : {}} alt={'Photo for ' + this.props.uri} /></a>
|
<div className="card__media"
|
||||||
</div>
|
style={{ backgroundImage: "url('" + (metadata && metadata.thumbnail ? metadata.thumbnail : lbry.imagePath('default-thumb.svg')) + "')" }}>
|
||||||
<div className="span9">
|
|
||||||
<div className="card__title-primary">
|
|
||||||
{ !this.props.hidePrice
|
|
||||||
? <FilePrice uri={this.props.uri} />
|
|
||||||
: null}
|
|
||||||
<div className="meta"><a href={'?show=' + this.props.uri}>{uri}</a></div>
|
|
||||||
<h3>
|
|
||||||
<a href={'?show=' + uri} title={title}>
|
|
||||||
<TruncatedText lines={1}>
|
|
||||||
{title}
|
|
||||||
</TruncatedText>
|
|
||||||
</a>
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="card__actions">
|
<div className="file-tile__content">
|
||||||
<FileActions uri={this.props.uri} outpoint={this.props.outpoint} metadata={metadata} contentType={this.props.contentType} />
|
<div className="card__title-primary">
|
||||||
</div>
|
{ !this.props.hidePrice
|
||||||
<div className="card__content">
|
? <FilePrice uri={this.props.uri} />
|
||||||
<p className="file-tile__description">
|
: null}
|
||||||
<TruncatedText lines={2}>
|
<div className="meta">{uri}</div>
|
||||||
|
<h3><TruncatedText lines={1}>{title}</TruncatedText></h3>
|
||||||
|
</div>
|
||||||
|
<div className="card__content card__subtext">
|
||||||
|
<TruncatedText lines={3}>
|
||||||
{isConfirmed
|
{isConfirmed
|
||||||
? metadata.description
|
? metadata.description
|
||||||
: <span className="empty">This file is pending confirmation.</span>}
|
: <span className="empty">This file is pending confirmation.</span>}
|
||||||
</TruncatedText>
|
</TruncatedText>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
{this.state.showNsfwHelp
|
{this.state.showNsfwHelp
|
||||||
? <div className='card-overlay'>
|
? <div className='card-overlay'>
|
||||||
<p>
|
<p>
|
||||||
|
@ -227,6 +219,7 @@ export let FileCardStream = React.createClass({
|
||||||
|
|
||||||
export let FileTile = React.createClass({
|
export let FileTile = React.createClass({
|
||||||
_isMounted: false,
|
_isMounted: false,
|
||||||
|
_isResolvePending: false,
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
uri: React.PropTypes.string.isRequired,
|
uri: React.PropTypes.string.isRequired,
|
||||||
|
@ -238,13 +231,12 @@ export let FileTile = React.createClass({
|
||||||
claimInfo: null
|
claimInfo: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
resolve: function(uri) {
|
||||||
componentDidMount: function() {
|
this._isResolvePending = true;
|
||||||
this._isMounted = true;
|
lbry.resolve({uri: uri}).then((resolutionInfo) => {
|
||||||
|
this._isResolvePending = false;
|
||||||
lbry.resolve({uri: this.props.uri}).then((resolutionInfo) => {
|
|
||||||
if (this._isMounted && resolutionInfo && resolutionInfo.claim && resolutionInfo.claim.value &&
|
if (this._isMounted && resolutionInfo && resolutionInfo.claim && resolutionInfo.claim.value &&
|
||||||
resolutionInfo.claim.value.stream && resolutionInfo.claim.value.stream.metadata) {
|
resolutionInfo.claim.value.stream && resolutionInfo.claim.value.stream.metadata) {
|
||||||
// In case of a failed lookup, metadata will be null, in which case the component will never display
|
// In case of a failed lookup, metadata will be null, in which case the component will never display
|
||||||
this.setState({
|
this.setState({
|
||||||
claimInfo: resolutionInfo.claim,
|
claimInfo: resolutionInfo.claim,
|
||||||
|
@ -252,6 +244,16 @@ export let FileTile = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
if (nextProps.uri != this.props.uri) {
|
||||||
|
this.setState(this.getInitialState());
|
||||||
|
this.resolve(nextProps.uri);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
this._isMounted = true;
|
||||||
|
this.resolve(this.props.uri);
|
||||||
|
},
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
this._isMounted = false;
|
this._isMounted = false;
|
||||||
},
|
},
|
||||||
|
@ -261,6 +263,12 @@ export let FileTile = React.createClass({
|
||||||
return <FileCardStream outpoint={null} metadata={{title: this.props.uri, description: "Loading..."}} contentType={null} hidePrice={true}
|
return <FileCardStream outpoint={null} metadata={{title: this.props.uri, description: "Loading..."}} contentType={null} hidePrice={true}
|
||||||
hasSignature={false} signatureIsValid={false} uri={this.props.uri} />
|
hasSignature={false} signatureIsValid={false} uri={this.props.uri} />
|
||||||
}
|
}
|
||||||
|
if (this.props.showEmpty)
|
||||||
|
{
|
||||||
|
return this._isResolvePending ?
|
||||||
|
<BusyMessage message="Loading magic decentralized data" /> :
|
||||||
|
<div className="empty">{lbryuri.normalize(this.props.uri)} is unclaimed. <Link label="Put something here" href="?publish" /></div>;
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,76 +1,198 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import lbryuri from '../lbryuri.js';
|
||||||
import {Link} from './link.js';
|
import {Link} from './link.js';
|
||||||
import {Icon} from './common.js';
|
import {Icon, CreditAmount} from './common.js';
|
||||||
|
|
||||||
var Header = React.createClass({
|
var Header = React.createClass({
|
||||||
|
_balanceSubscribeId: null,
|
||||||
|
_isMounted: false,
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
onSearch: React.PropTypes.func.isRequired,
|
||||||
|
onSubmit: React.PropTypes.func.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
title: "LBRY",
|
balance: 0
|
||||||
isScrolled: false
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentWillMount: function() {
|
|
||||||
new MutationObserver((mutations) => {
|
|
||||||
this.setState({ title: mutations[0].target.textContent });
|
|
||||||
}).observe(
|
|
||||||
document.querySelector('title'),
|
|
||||||
{ subtree: true, characterData: true, childList: true }
|
|
||||||
);
|
|
||||||
},
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
document.addEventListener('scroll', this.handleScroll);
|
this._isMounted = true;
|
||||||
},
|
this._balanceSubscribeId = lbry.balanceSubscribe((balance) => {
|
||||||
componentWillUnmount: function() {
|
if (this._isMounted) {
|
||||||
document.removeEventListener('scroll', this.handleScroll);
|
this.setState({balance: balance});
|
||||||
if (this.userTypingTimer)
|
}
|
||||||
{
|
|
||||||
clearTimeout(this.userTypingTimer);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleScroll: function() {
|
|
||||||
this.setState({
|
|
||||||
isScrolled: document.body.scrollTop > 0
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onQueryChange: function(event) {
|
componentWillUnmount: function() {
|
||||||
|
this._isMounted = false;
|
||||||
if (this.userTypingTimer)
|
if (this._balanceSubscribeId) {
|
||||||
{
|
lbry.balanceUnsubscribe(this._balanceSubscribeId)
|
||||||
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() {
|
render: function() {
|
||||||
return (
|
return <header id="header">
|
||||||
<header id="header" className={ (this.state.isScrolled ? 'header-scrolled' : 'header-unscrolled') + ' ' + (this.props.links ? 'header-with-subnav' : 'header-no-subnav') }>
|
<div className="header__item">
|
||||||
<div className="header-top-bar">
|
<Link onClick={() => { lbry.back() }} button="alt button--flat" icon="icon-arrow-left" />
|
||||||
<Link onClick={this.props.onOpenDrawer} icon="icon-bars" className="open-drawer-link" />
|
</div>
|
||||||
<h1>{ this.state.title }</h1>
|
<div className="header__item">
|
||||||
<div className="header-search">
|
<Link href="?discover" button="alt button--flat" icon="icon-home" />
|
||||||
<Icon icon="icon-search" />
|
</div>
|
||||||
<input type="search" onChange={this.onQueryChange} defaultValue={this.props.initialQuery}
|
<div className="header__item header__item--wunderbar">
|
||||||
placeholder="Find movies, music, games, and more"/>
|
<WunderBar address={this.props.address} icon={this.props.wunderBarIcon}
|
||||||
</div>
|
onSearch={this.props.onSearch} onSubmit={this.props.onSubmit} viewingPage={this.props.viewingPage} />
|
||||||
|
</div>
|
||||||
|
<div className="header__item">
|
||||||
|
<Link href="?wallet" button="text" icon="icon-bank" label={lbry.formatCredits(this.state.balance, 1)} ></Link>
|
||||||
|
</div>
|
||||||
|
<div className="header__item">
|
||||||
|
<Link button="primary button--flat" href="?publish" icon="icon-upload" label="Publish" />
|
||||||
|
</div>
|
||||||
|
<div className="header__item">
|
||||||
|
<Link button="alt button--flat" href="?downloaded" icon="icon-folder" />
|
||||||
|
</div>
|
||||||
|
<div className="header__item">
|
||||||
|
<Link button="alt button--flat" href="?settings" icon="icon-gear" />
|
||||||
</div>
|
</div>
|
||||||
{
|
|
||||||
this.props.links ?
|
|
||||||
<SubHeader links={this.props.links} viewingPage={this.props.viewingPage} /> :
|
|
||||||
''
|
|
||||||
}
|
|
||||||
</header>
|
</header>
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var SubHeader = React.createClass({
|
class WunderBar extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
onSearch: React.PropTypes.func.isRequired,
|
||||||
|
onSubmit: React.PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this._userTypingTimer = null;
|
||||||
|
this._input = null;
|
||||||
|
this._stateBeforeSearch = null;
|
||||||
|
this._resetOnNextBlur = true;
|
||||||
|
this.onChange = this.onChange.bind(this);
|
||||||
|
this.onFocus = this.onFocus.bind(this);
|
||||||
|
this.onBlur = this.onBlur.bind(this);
|
||||||
|
this.onKeyPress = this.onKeyPress.bind(this);
|
||||||
|
this.onReceiveRef = this.onReceiveRef.bind(this);
|
||||||
|
this.state = {
|
||||||
|
address: this.props.address,
|
||||||
|
icon: this.props.icon
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.userTypingTimer) {
|
||||||
|
clearTimeout(this._userTypingTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange(event) {
|
||||||
|
|
||||||
|
if (this._userTypingTimer)
|
||||||
|
{
|
||||||
|
clearTimeout(this._userTypingTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ address: event.target.value })
|
||||||
|
|
||||||
|
let searchTerm = event.target.value;
|
||||||
|
|
||||||
|
this._userTypingTimer = setTimeout(() => {
|
||||||
|
this._resetOnNextBlur = false;
|
||||||
|
this.props.onSearch(searchTerm);
|
||||||
|
}, 800); // 800ms delay, tweak for faster/slower
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (nextProps.viewingPage !== this.props.viewingPage || nextProps.address != this.props.address) {
|
||||||
|
this.setState({ address: nextProps.address, icon: nextProps.icon });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocus() {
|
||||||
|
this._stateBeforeSearch = this.state;
|
||||||
|
let newState = {
|
||||||
|
icon: "icon-search",
|
||||||
|
isActive: true
|
||||||
|
}
|
||||||
|
|
||||||
|
this._focusPending = true;
|
||||||
|
//below is hacking, improved when we have proper routing
|
||||||
|
if (!this.state.address.startsWith('lbry://') && this.state.icon !== "icon-search") //onFocus, if they are not on an exact URL or a search page, clear the bar
|
||||||
|
{
|
||||||
|
newState.address = '';
|
||||||
|
}
|
||||||
|
this.setState(newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlur() {
|
||||||
|
let commonState = {isActive: false};
|
||||||
|
if (this._resetOnNextBlur) {
|
||||||
|
this.setState(Object.assign({}, this._stateBeforeSearch, commonState));
|
||||||
|
this._input.value = this.state.address;
|
||||||
|
} else {
|
||||||
|
this._resetOnNextBlur = true;
|
||||||
|
this._stateBeforeSearch = this.state;
|
||||||
|
this.setState(commonState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this._input.value = this.state.address;
|
||||||
|
if (this._input && this._focusPending) {
|
||||||
|
this._input.select();
|
||||||
|
this._focusPending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyPress(event) {
|
||||||
|
if (event.charCode == 13 && this._input.value) {
|
||||||
|
|
||||||
|
let uri = null,
|
||||||
|
method = "onSubmit";
|
||||||
|
|
||||||
|
this._resetOnNextBlur = false;
|
||||||
|
clearTimeout(this._userTypingTimer);
|
||||||
|
|
||||||
|
try {
|
||||||
|
uri = lbryuri.normalize(this._input.value);
|
||||||
|
this.setState({ value: uri });
|
||||||
|
} catch (error) { //then it's not a valid URL, so let's search
|
||||||
|
uri = this._input.value;
|
||||||
|
method = "onSearch";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props[method](uri);
|
||||||
|
this._input.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onReceiveRef(ref) {
|
||||||
|
this._input = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={'wunderbar' + (this.state.isActive ? ' wunderbar--active' : '')}>
|
||||||
|
{this.state.icon ? <Icon fixed icon={this.state.icon} /> : '' }
|
||||||
|
<input className="wunderbar__input" type="search" placeholder="Type a LBRY address or search term"
|
||||||
|
ref={this.onReceiveRef}
|
||||||
|
onFocus={this.onFocus}
|
||||||
|
onBlur={this.onBlur}
|
||||||
|
onChange={this.onChange}
|
||||||
|
onKeyPress={this.onKeyPress}
|
||||||
|
value={this.state.address}
|
||||||
|
placeholder="Find movies, music, games, and more" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export let SubHeader = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
var links = [],
|
let links = [],
|
||||||
viewingUrl = '?' + this.props.viewingPage;
|
viewingUrl = '?' + this.props.viewingPage;
|
||||||
|
|
||||||
for (let link of Object.keys(this.props.links)) {
|
for (let link of Object.keys(this.props.links)) {
|
||||||
|
@ -81,7 +203,7 @@ var SubHeader = React.createClass({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<nav className="sub-header">
|
<nav className={'sub-header' + (this.props.modifier ? ' sub-header--' + this.props.modifier : '')}>
|
||||||
{links}
|
{links}
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
|
|
|
@ -41,7 +41,7 @@ export let Link = React.createClass({
|
||||||
content = (
|
content = (
|
||||||
<span {... 'button' in this.props ? {className: 'button__content'} : {}}>
|
<span {... 'button' in this.props ? {className: 'button__content'} : {}}>
|
||||||
{'icon' in this.props ? <Icon icon={this.props.icon} fixed={true} /> : null}
|
{'icon' in this.props ? <Icon icon={this.props.icon} fixed={true} /> : null}
|
||||||
{<span className="link-label">{this.props.label}</span>}
|
{this.props.label ? <span className="link-label">{this.props.label}</span> : null}
|
||||||
{'badge' in this.props ? <span className="badge">{this.props.badge}</span> : null}
|
{'badge' in this.props ? <span className="badge">{this.props.badge}</span> : null}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,9 +9,6 @@ var LoadScreen = React.createClass({
|
||||||
details: React.PropTypes.string,
|
details: React.PropTypes.string,
|
||||||
isWarning: React.PropTypes.bool,
|
isWarning: React.PropTypes.bool,
|
||||||
},
|
},
|
||||||
handleCancelClick: function() {
|
|
||||||
history.back();
|
|
||||||
},
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
isWarning: false,
|
isWarning: false,
|
||||||
|
@ -34,9 +31,6 @@ var LoadScreen = React.createClass({
|
||||||
<BusyMessage message={this.props.message} />
|
<BusyMessage message={this.props.message} />
|
||||||
</h3>
|
</h3>
|
||||||
{this.props.isWarning ? <Icon icon="icon-warning" /> : null} <span className={'load-screen__details ' + (this.props.isWarning ? 'load-screen__details--warning' : '')}>{this.props.details}</span>
|
{this.props.isWarning ? <Icon icon="icon-warning" /> : null} <span className={'load-screen__details ' + (this.props.isWarning ? 'load-screen__details--warning' : '')}>{this.props.details}</span>
|
||||||
{window.history.length > 1
|
|
||||||
? <div><Link label="Cancel" onClick={this.handleCancelClick} className='load-screen__cancel-link button-text' /></div>
|
|
||||||
: null}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -31,7 +31,6 @@ function savePendingPublish({name, channel_name}) {
|
||||||
return newPendingPublish;
|
return newPendingPublish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is a pending publish with the given name or outpoint, remove it.
|
* If there is a pending publish with the given name or outpoint, remove it.
|
||||||
* A channel name may also be provided along with name.
|
* A channel name may also be provided along with name.
|
||||||
|
@ -132,6 +131,21 @@ lbry.connect = function() {
|
||||||
return lbry._connectPromise;
|
return lbry._connectPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//kill this but still better than document.title =, which this replaced
|
||||||
|
lbry.setTitle = function(title) {
|
||||||
|
document.title = title + " - LBRY";
|
||||||
|
}
|
||||||
|
|
||||||
|
//kill this with proper routing
|
||||||
|
lbry.back = function() {
|
||||||
|
if (window.history.length > 1) {
|
||||||
|
window.history.back();
|
||||||
|
} else {
|
||||||
|
window.location.href = "?discover";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lbry.isDaemonAcceptingConnections = function (callback) {
|
lbry.isDaemonAcceptingConnections = function (callback) {
|
||||||
// Returns true/false whether the daemon is at a point it will start returning status
|
// Returns true/false whether the daemon is at a point it will start returning status
|
||||||
lbry.call('status', {}, () => callback(true), null, () => callback(false))
|
lbry.call('status', {}, () => callback(true), null, () => callback(false))
|
||||||
|
@ -633,7 +647,7 @@ lbry.resolve = function(params={}) {
|
||||||
if (!params.uri) {
|
if (!params.uri) {
|
||||||
throw "Resolve has hacked cache on top of it that requires a URI"
|
throw "Resolve has hacked cache on top of it that requires a URI"
|
||||||
}
|
}
|
||||||
if (params.uri && claimCache[params.uri]) {
|
if (params.uri && claimCache[params.uri] !== undefined) {
|
||||||
resolve(claimCache[params.uri]);
|
resolve(claimCache[params.uri]);
|
||||||
} else {
|
} else {
|
||||||
lbry.call('resolve', params, function(data) {
|
lbry.call('resolve', params, function(data) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ const lbryio = {
|
||||||
_accessToken: getLocal('accessToken'),
|
_accessToken: getLocal('accessToken'),
|
||||||
_authenticationPromise: null,
|
_authenticationPromise: null,
|
||||||
_user : null,
|
_user : null,
|
||||||
enabled: true
|
enabled: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONNECTION_STRING = process.env.LBRY_APP_API_URL ? process.env.LBRY_APP_API_URL : 'https://api.lbry.io/';
|
const CONNECTION_STRING = process.env.LBRY_APP_API_URL ? process.env.LBRY_APP_API_URL : 'https://api.lbry.io/';
|
||||||
|
@ -150,20 +150,6 @@ lbryio.authenticate = function() {
|
||||||
} else {
|
} else {
|
||||||
setCurrentUser()
|
setCurrentUser()
|
||||||
}
|
}
|
||||||
// if (!lbryio._
|
|
||||||
//(data) => {
|
|
||||||
// resolve(data)
|
|
||||||
// localStorage.setItem('accessToken', ID);
|
|
||||||
// localStorage.setItem('appId', installation_id);
|
|
||||||
// this.setState({
|
|
||||||
// registrationCheckComplete: true,
|
|
||||||
// justRegistered: true,
|
|
||||||
// });
|
|
||||||
//});
|
|
||||||
// lbryio.call('user_install', 'exists', {app_id: installation_id}).then((userExists) => {
|
|
||||||
// // TODO: deal with case where user exists already with the same app ID, but we have no access token.
|
|
||||||
// // Possibly merge in to the existing user with the same app ID.
|
|
||||||
// })
|
|
||||||
}).catch(reject);
|
}).catch(reject);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,79 +1,18 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
|
||||||
import lbryio from '../lbryio.js';
|
import lbryio from '../lbryio.js';
|
||||||
import lbryuri from '../lbryuri.js';
|
|
||||||
import lighthouse from '../lighthouse.js';
|
|
||||||
import {FileTile, FileTileStream} from '../component/file-tile.js';
|
import {FileTile, FileTileStream} from '../component/file-tile.js';
|
||||||
import {Link} from '../component/link.js';
|
|
||||||
import {ToolTip} from '../component/tooltip.js';
|
import {ToolTip} from '../component/tooltip.js';
|
||||||
import {BusyMessage} from '../component/common.js';
|
|
||||||
|
|
||||||
var fetchResultsStyle = {
|
|
||||||
color: '#888',
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: '1.2em'
|
|
||||||
};
|
|
||||||
|
|
||||||
var SearchActive = React.createClass({
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
<div style={fetchResultsStyle}>
|
|
||||||
<BusyMessage message="Looking up the Dewey Decimals" />
|
|
||||||
</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 = [],
|
|
||||||
seenNames = {}; //fix this when the search API returns claim IDs
|
|
||||||
|
|
||||||
for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of this.props.results) {
|
|
||||||
const uri = lbryuri.build({
|
|
||||||
channelName: channel_name,
|
|
||||||
contentName: name,
|
|
||||||
claimId: channel_id || claim_id,
|
|
||||||
});
|
|
||||||
|
|
||||||
rows.push(
|
|
||||||
<FileTileStream key={name} uri={uri} outpoint={txid + ':' + nout} metadata={claim.stream.metadata} contentType={claim.stream.source.contentType} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div>{rows}</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const communityCategoryToolTipText = ('Community Content is a public space where anyone can share content with the ' +
|
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 ' +
|
'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' +
|
||||||
'"five" to put your content here!');
|
'"five" to put your content here!');
|
||||||
|
|
||||||
var FeaturedCategory = React.createClass({
|
let FeaturedCategory = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
return (<div className="card-row card-row--small">
|
return (<div className="card-row card-row--small">
|
||||||
{ this.props.category ?
|
{ this.props.category ?
|
||||||
<h3 className="card-row__header">{this.props.category}
|
<h3 className="card-row__header">{this.props.category}
|
||||||
{ this.props.category == "community" ?
|
{ this.props.category.match(/^community/i) ?
|
||||||
<ToolTip label="What's this?" body={communityCategoryToolTipText} className="tooltip--header"/>
|
<ToolTip label="What's this?" body={communityCategoryToolTipText} className="tooltip--header"/>
|
||||||
: '' }</h3>
|
: '' }</h3>
|
||||||
: '' }
|
: '' }
|
||||||
|
@ -82,7 +21,7 @@ var FeaturedCategory = React.createClass({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var FeaturedContent = React.createClass({
|
let DiscoverPage = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
featuredUris: {},
|
featuredUris: {},
|
||||||
|
@ -105,101 +44,19 @@ var FeaturedContent = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return <main>{
|
||||||
this.state.failed ?
|
this.state.failed ?
|
||||||
<div className="empty">Failed to load landing content.</div> :
|
<div className="empty">Failed to load landing content.</div> :
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
Object.keys(this.state.featuredUris).map((category) => {
|
Object.keys(this.state.featuredUris).map((category) => {
|
||||||
return this.state.featuredUris[category].length ?
|
return this.state.featuredUris[category].length ?
|
||||||
<FeaturedCategory key={category} category={category} names={this.state.featuredUris[category]} /> :
|
<FeaturedCategory key={category} category={category} names={this.state.featuredUris[category]} /> :
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
}</main>;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var DiscoverPage = React.createClass({
|
|
||||||
userTypingTimer: null,
|
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
showWelcome: React.PropTypes.bool.isRequired,
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidUpdate: function() {
|
|
||||||
if (this.props.query != this.state.query)
|
|
||||||
{
|
|
||||||
this.handleSearchChanged(this.props.query);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
showWelcome: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillReceiveProps: function(nextProps, nextState) {
|
|
||||||
if (nextProps.query != nextState.query)
|
|
||||||
{
|
|
||||||
this.handleSearchChanged(nextProps.query);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSearchChanged: function(query) {
|
|
||||||
this.setState({
|
|
||||||
searching: true,
|
|
||||||
query: query,
|
|
||||||
});
|
|
||||||
|
|
||||||
lighthouse.search(query).then(this.searchCallback);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleWelcomeDone: function() {
|
|
||||||
this.setState({
|
|
||||||
welcomeComplete: true,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount: function() {
|
|
||||||
document.title = "Discover";
|
|
||||||
|
|
||||||
if (this.props.query) {
|
|
||||||
// Rendering with a query already typed
|
|
||||||
this.handleSearchChanged(this.props.query);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
welcomeComplete: false,
|
|
||||||
results: [],
|
|
||||||
query: this.props.query,
|
|
||||||
searching: ('query' in this.props) && (this.props.query.length > 0)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
searchCallback: function(results) {
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,22 @@ import lbry from '../lbry.js';
|
||||||
import lbryuri from '../lbryuri.js';
|
import lbryuri from '../lbryuri.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link.js';
|
||||||
import {FormField} from '../component/form.js';
|
import {FormField} from '../component/form.js';
|
||||||
|
import {SubHeader} from '../component/header.js';
|
||||||
import {FileTileStream} from '../component/file-tile.js';
|
import {FileTileStream} from '../component/file-tile.js';
|
||||||
import rewards from '../rewards.js';
|
import rewards from '../rewards.js';
|
||||||
import lbryio from '../lbryio.js';
|
import lbryio from '../lbryio.js';
|
||||||
import {BusyMessage, Thumbnail} from '../component/common.js';
|
import {BusyMessage, Thumbnail} from '../component/common.js';
|
||||||
|
|
||||||
|
|
||||||
|
export let FileListNav = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return <SubHeader modifier="constrained" viewingPage={this.props.viewingPage} links={{
|
||||||
|
'?downloaded': 'Downloaded',
|
||||||
|
'?published': 'Published',
|
||||||
|
}} />;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export let FileListDownloaded = React.createClass({
|
export let FileListDownloaded = React.createClass({
|
||||||
_isMounted: false,
|
_isMounted: false,
|
||||||
|
|
||||||
|
@ -19,7 +29,6 @@ export let FileListDownloaded = React.createClass({
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
this._isMounted = true;
|
this._isMounted = true;
|
||||||
document.title = "Downloaded Files";
|
|
||||||
|
|
||||||
lbry.claim_list_mine().then((myClaimInfos) => {
|
lbry.claim_list_mine().then((myClaimInfos) => {
|
||||||
if (!this._isMounted) { return; }
|
if (!this._isMounted) { return; }
|
||||||
|
@ -38,25 +47,20 @@ export let FileListDownloaded = React.createClass({
|
||||||
this._isMounted = false;
|
this._isMounted = false;
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
|
let content = "";
|
||||||
if (this.state.fileInfos === null) {
|
if (this.state.fileInfos === null) {
|
||||||
return (
|
content = <BusyMessage message="Loading" />;
|
||||||
<main className="page">
|
|
||||||
<BusyMessage message="Loading" />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
} else if (!this.state.fileInfos.length) {
|
} else if (!this.state.fileInfos.length) {
|
||||||
return (
|
content = <span>You haven't downloaded anything from LBRY yet. Go <Link href="?discover" label="search for your first download" />!</span>;
|
||||||
<main className="page">
|
|
||||||
<span>You haven't downloaded anything from LBRY yet. Go <Link href="?discover" label="search for your first download" />!</span>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return (
|
content = <FileList fileInfos={this.state.fileInfos} hidePrices={true} />;
|
||||||
<main className="page">
|
|
||||||
<FileList fileInfos={this.state.fileInfos} hidePrices={true} />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
|
<main className="main--single-column">
|
||||||
|
<FileListNav viewingPage="downloaded" />
|
||||||
|
{content}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -79,12 +83,11 @@ export let FileListPublished = React.createClass({
|
||||||
else {
|
else {
|
||||||
rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {})
|
rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {})
|
||||||
}
|
}
|
||||||
});
|
}, () => {});
|
||||||
},
|
},
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
this._isMounted = true;
|
this._isMounted = true;
|
||||||
this._requestPublishReward();
|
this._requestPublishReward();
|
||||||
document.title = "Published Files";
|
|
||||||
|
|
||||||
lbry.claim_list_mine().then((claimInfos) => {
|
lbry.claim_list_mine().then((claimInfos) => {
|
||||||
if (!this._isMounted) { return; }
|
if (!this._isMounted) { return; }
|
||||||
|
@ -103,27 +106,22 @@ export let FileListPublished = React.createClass({
|
||||||
this._isMounted = false;
|
this._isMounted = false;
|
||||||
},
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
|
let content = null;
|
||||||
if (this.state.fileInfos === null) {
|
if (this.state.fileInfos === null) {
|
||||||
return (
|
content = <BusyMessage message="Loading" />;
|
||||||
<main className="page">
|
|
||||||
<BusyMessage message="Loading" />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else if (!this.state.fileInfos.length) {
|
else if (!this.state.fileInfos.length) {
|
||||||
return (
|
content = <span>You haven't published anything to LBRY yet. Try <Link href="?publish" label="publishing" />!</span>;
|
||||||
<main className="page">
|
|
||||||
<span>You haven't published anything to LBRY yet.</span> Try <Link href="?publish" label="publishing" />!
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return (
|
content = <FileList fileInfos={this.state.fileInfos} />;
|
||||||
<main className="page">
|
|
||||||
<FileList fileInfos={this.state.fileInfos} />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
|
<main className="main--single-column">
|
||||||
|
<FileListNav viewingPage="published" />
|
||||||
|
{content}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link.js';
|
||||||
|
import {SettingsNav} from './settings.js';
|
||||||
import {version as uiVersion} from 'json!../../package.json';
|
import {version as uiVersion} from 'json!../../package.json';
|
||||||
|
|
||||||
var HelpPage = React.createClass({
|
var HelpPage = React.createClass({
|
||||||
|
@ -24,9 +25,6 @@ var HelpPage = React.createClass({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
|
||||||
document.title = "Help";
|
|
||||||
},
|
|
||||||
render: function() {
|
render: function() {
|
||||||
let ver, osName, platform, newVerLink;
|
let ver, osName, platform, newVerLink;
|
||||||
if (this.state.versionInfo) {
|
if (this.state.versionInfo) {
|
||||||
|
@ -49,7 +47,8 @@ var HelpPage = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="page">
|
<main className="main--single-column">
|
||||||
|
<SettingsNav viewingPage="help" />
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<div className="card__title-primary">
|
<div className="card__title-primary">
|
||||||
<h3>Read the FAQ</h3>
|
<h3>Read the FAQ</h3>
|
||||||
|
|
|
@ -148,7 +148,7 @@ var PublishPage = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handlePublishStartedConfirmed: function() {
|
handlePublishStartedConfirmed: function() {
|
||||||
window.location = "?published";
|
window.location.href = "?published";
|
||||||
},
|
},
|
||||||
handlePublishError: function(error) {
|
handlePublishError: function(error) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -348,9 +348,6 @@ var PublishPage = React.createClass({
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this._updateChannelList();
|
this._updateChannelList();
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
|
||||||
document.title = "Publish";
|
|
||||||
},
|
|
||||||
componentDidUpdate: function() {
|
componentDidUpdate: function() {
|
||||||
},
|
},
|
||||||
onFileChange: function() {
|
onFileChange: function() {
|
||||||
|
@ -387,7 +384,7 @@ var PublishPage = React.createClass({
|
||||||
const lbcInputHelp = "This LBC remains yours and the deposit can be undone at any time."
|
const lbcInputHelp = "This LBC remains yours and the deposit can be undone at any time."
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main ref="page">
|
<main className="main--single-column">
|
||||||
<form onSubmit={this.handleSubmit}>
|
<form onSubmit={this.handleSubmit}>
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<div className="card__title-primary">
|
<div className="card__title-primary">
|
||||||
|
@ -551,7 +548,7 @@ var PublishPage = React.createClass({
|
||||||
|
|
||||||
<div className="card-series-submit">
|
<div className="card-series-submit">
|
||||||
<Link button="primary" label={!this.state.submitting ? 'Publish' : 'Publishing...'} onClick={this.handleSubmit} disabled={this.state.submitting} />
|
<Link button="primary" label={!this.state.submitting ? 'Publish' : 'Publishing...'} onClick={this.handleSubmit} disabled={this.state.submitting} />
|
||||||
<Link button="cancel" onClick={window.history.back} label="Cancel" />
|
<Link button="cancel" onClick={lbry.back} label="Cancel" />
|
||||||
<input type="submit" className="hidden" />
|
<input type="submit" className="hidden" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -18,9 +18,6 @@ var ReportPage = React.createClass({
|
||||||
this._messageArea.value = '';
|
this._messageArea.value = '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
|
||||||
document.title = "Report an Issue";
|
|
||||||
},
|
|
||||||
closeModal: function() {
|
closeModal: function() {
|
||||||
this.setState({
|
this.setState({
|
||||||
modal: null,
|
modal: null,
|
||||||
|
@ -34,7 +31,7 @@ var ReportPage = React.createClass({
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<main className="page">
|
<main className="main--single-column">
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<h3>Report an Issue</h3>
|
<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>
|
<p>Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!</p>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import lbryio from '../lbryio.js';
|
||||||
import {CreditAmount, Icon} from '../component/common.js';
|
import {CreditAmount, Icon} from '../component/common.js';
|
||||||
import rewards from '../rewards.js';
|
import rewards from '../rewards.js';
|
||||||
import Modal from '../component/modal.js';
|
import Modal from '../component/modal.js';
|
||||||
|
import {WalletNav} from './wallet.js';
|
||||||
import {RewardLink} from '../component/link.js';
|
import {RewardLink} from '../component/link.js';
|
||||||
|
|
||||||
const RewardTile = React.createClass({
|
const RewardTile = React.createClass({
|
||||||
|
@ -56,14 +57,15 @@ var RewardsPage = React.createClass({
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<main>
|
<main className="main--single-column">
|
||||||
<form onSubmit={this.handleSubmit}>
|
<WalletNav viewingPage="rewards"/>
|
||||||
|
<div>
|
||||||
{!this.state.userRewards
|
{!this.state.userRewards
|
||||||
? (this.state.failed ? <div className="empty">Failed to load rewards.</div> : '')
|
? (this.state.failed ? <div className="empty">Failed to load rewards.</div> : '')
|
||||||
: this.state.userRewards.map(({RewardType, RewardTitle, RewardDescription, TransactionID, RewardAmount}) => {
|
: 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} />;
|
return <RewardTile key={RewardType} onRewardClaim={this.loadRewards} type={RewardType} title={RewardTitle} description={RewardDescription} claimed={!!TransactionID} value={RewardAmount} />;
|
||||||
})}
|
})}
|
||||||
</form>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
165
ui/js/page/search.js
Normal file
165
ui/js/page/search.js
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
import React from 'react';
|
||||||
|
import lbry from '../lbry.js';
|
||||||
|
import lbryio from '../lbryio.js';
|
||||||
|
import lbryuri from '../lbryuri.js';
|
||||||
|
import lighthouse from '../lighthouse.js';
|
||||||
|
import {FileTile, FileTileStream} from '../component/file-tile.js';
|
||||||
|
import {Link} from '../component/link.js';
|
||||||
|
import {ToolTip} from '../component/tooltip.js';
|
||||||
|
import {BusyMessage} from '../component/common.js';
|
||||||
|
|
||||||
|
var SearchNoResults = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return <section>
|
||||||
|
<span className="empty">
|
||||||
|
No one has checked anything in for {this.props.query} yet.
|
||||||
|
<Link label="Be the first" href="?publish" />
|
||||||
|
</span>
|
||||||
|
</section>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var SearchResultList = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
var rows = [],
|
||||||
|
seenNames = {}; //fix this when the search API returns claim IDs
|
||||||
|
|
||||||
|
for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of this.props.results) {
|
||||||
|
const uri = lbryuri.build({
|
||||||
|
channelName: channel_name,
|
||||||
|
contentName: name,
|
||||||
|
claimId: channel_id || claim_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
rows.push(
|
||||||
|
<FileTileStream key={uri} uri={uri} outpoint={txid + ':' + nout} metadata={claim.stream.metadata} contentType={claim.stream.source.contentType} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>{rows}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let SearchResults = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
query: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
_isMounted: false,
|
||||||
|
|
||||||
|
search: function(term) {
|
||||||
|
lighthouse.search(term).then(this.searchCallback);
|
||||||
|
if (!this.state.searching) {
|
||||||
|
this.setState({ searching: true })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function () {
|
||||||
|
this._isMounted = true;
|
||||||
|
this.search(this.props.query);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function (nextProps) {
|
||||||
|
if (nextProps.query != this.props.query) {
|
||||||
|
this.search(nextProps.query);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function () {
|
||||||
|
this._isMounted = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
results: [],
|
||||||
|
searching: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
searchCallback: function (results) {
|
||||||
|
if (this._isMounted) //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 this.state.searching ?
|
||||||
|
<BusyMessage message="Looking up the Dewey Decimals" /> :
|
||||||
|
(this.state.results && this.state.results.length ?
|
||||||
|
<SearchResultList results={this.state.results} /> :
|
||||||
|
<SearchNoResults query={this.props.query} />);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let SearchPage = React.createClass({
|
||||||
|
|
||||||
|
_isMounted: false,
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
query: React.PropTypes.string.isRequired
|
||||||
|
},
|
||||||
|
|
||||||
|
isValidUri: function(query) {
|
||||||
|
try {
|
||||||
|
lbryuri.parse(query);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this._isMounted = true;
|
||||||
|
lighthouse.search(this.props.query).then(this.searchCallback);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this._isMounted = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
results: [],
|
||||||
|
searching: true
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
searchCallback: function(results) {
|
||||||
|
if (this._isMounted) //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 className="main--single-column">
|
||||||
|
{ this.isValidUri(this.props.query) ?
|
||||||
|
<section className="section-spaced">
|
||||||
|
<h3 className="card-row__header">
|
||||||
|
Exact URL
|
||||||
|
<ToolTip label="?" body="This is the resolution of a LBRY URL and not controlled by LBRY Inc." className="tooltip--header"/>
|
||||||
|
</h3>
|
||||||
|
<FileTile uri={this.props.query} showEmpty={true} />
|
||||||
|
</section> : '' }
|
||||||
|
<section className="section-spaced">
|
||||||
|
<h3 className="card-row__header">
|
||||||
|
Search Results for {this.props.query}
|
||||||
|
<ToolTip label="?" body="These search results are provided by LBRY Inc." className="tooltip--header"/>
|
||||||
|
</h3>
|
||||||
|
<SearchResults query={this.props.query} />
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default SearchPage;
|
|
@ -1,7 +1,17 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {FormField, FormRow} from '../component/form.js';
|
import {FormField, FormRow} from '../component/form.js';
|
||||||
|
import {SubHeader} from '../component/header.js';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
|
|
||||||
|
export let SettingsNav = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return <SubHeader modifier="constrained" viewingPage={this.props.viewingPage} links={{
|
||||||
|
'?settings': 'Settings',
|
||||||
|
'?help' : 'Help'
|
||||||
|
}} />;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var SettingsPage = React.createClass({
|
var SettingsPage = React.createClass({
|
||||||
_onSettingSaveSuccess: function() {
|
_onSettingSaveSuccess: function() {
|
||||||
// This is bad.
|
// This is bad.
|
||||||
|
@ -17,7 +27,7 @@ var SettingsPage = React.createClass({
|
||||||
setClientSetting: function(name, value) {
|
setClientSetting: function(name, value) {
|
||||||
lbry.setClientSetting(name, value)
|
lbry.setClientSetting(name, value)
|
||||||
this._onSettingSaveSuccess()
|
this._onSettingSaveSuccess()
|
||||||
},
|
},
|
||||||
onRunOnStartChange: function (event) {
|
onRunOnStartChange: function (event) {
|
||||||
this.setDaemonSetting('run_on_startup', event.target.checked);
|
this.setDaemonSetting('run_on_startup', event.target.checked);
|
||||||
},
|
},
|
||||||
|
@ -56,9 +66,6 @@ var SettingsPage = React.createClass({
|
||||||
showUnavailable: lbry.getClientSetting('showUnavailable'),
|
showUnavailable: lbry.getClientSetting('showUnavailable'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
|
||||||
document.title = "Settings";
|
|
||||||
},
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
lbry.getDaemonSettings((settings) => {
|
lbry.getDaemonSettings((settings) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -92,7 +99,8 @@ var SettingsPage = React.createClass({
|
||||||
</section>
|
</section>
|
||||||
*/
|
*/
|
||||||
return (
|
return (
|
||||||
<main>
|
<main className="main--single-column">
|
||||||
|
<SettingsNav viewingPage="settings" />
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<h3>Download Directory</h3>
|
<h3>Download Directory</h3>
|
||||||
|
|
|
@ -16,8 +16,11 @@ var FormatItem = React.createClass({
|
||||||
outpoint: React.PropTypes.string,
|
outpoint: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
const {thumbnail, author, title, description, language, license} = this.props.metadata;
|
const {author, language, license} = this.props.metadata;
|
||||||
const mediaType = lbry.getMediaType(this.props.contentType);
|
|
||||||
|
if (!this.props.contentType && [author, language, license].filter((val) => {return !!val; }).length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className="table-standard">
|
<table className="table-standard">
|
||||||
|
@ -40,93 +43,108 @@ var FormatItem = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let ShowPage = React.createClass({
|
let ChannelPage = React.createClass({
|
||||||
_uri: null,
|
render: function() {
|
||||||
|
return <main className="main--single-column">
|
||||||
|
<section className="card">
|
||||||
|
<div className="card__inner">
|
||||||
|
<div className="card__title-identity"><h1>{this.props.title}</h1></div>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
<p>
|
||||||
|
This channel page is a stub.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let FilePage = React.createClass({
|
||||||
|
_isMounted: false,
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
uri: React.PropTypes.string,
|
uri: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
metadata: null,
|
|
||||||
contentType: null,
|
|
||||||
hasSignature: false,
|
|
||||||
signatureIsValid: false,
|
|
||||||
cost: null,
|
cost: null,
|
||||||
costIncludesData: null,
|
costIncludesData: null,
|
||||||
uriLookupComplete: null,
|
|
||||||
isDownloaded: null,
|
isDownloaded: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this._isMounted = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
if (nextProps.outpoint != this.props.outpoint || nextProps.uri != this.props.uri) {
|
||||||
|
this.loadCostAndFileState(nextProps.uri, nextProps.outpoint);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this._uri = lbryuri.normalize(this.props.uri);
|
this._isMounted = true;
|
||||||
document.title = this._uri;
|
this.loadCostAndFileState(this.props.uri, this.props.outpoint);
|
||||||
|
},
|
||||||
|
|
||||||
lbry.resolve({uri: this._uri}).then(({ claim: {txid, nout, has_signature, signature_is_valid, value: {stream: {metadata, source: {contentType}}}}}) => {
|
loadCostAndFileState: function(uri, outpoint) {
|
||||||
const outpoint = txid + ':' + nout;
|
lbry.file_list({outpoint: outpoint}).then((fileInfo) => {
|
||||||
|
if (this._isMounted) {
|
||||||
lbry.file_list({outpoint}).then((fileInfo) => {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isDownloaded: fileInfo.length > 0,
|
isDownloaded: fileInfo.length > 0,
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
this.setState({
|
|
||||||
outpoint: outpoint,
|
|
||||||
metadata: metadata,
|
|
||||||
hasSignature: has_signature,
|
|
||||||
signatureIsValid: signature_is_valid,
|
|
||||||
contentType: contentType,
|
|
||||||
uriLookupComplete: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
lbry.getCostInfo(this._uri).then(({cost, includesData}) => {
|
lbry.getCostInfo(uri).then(({cost, includesData}) => {
|
||||||
this.setState({
|
if (this._isMounted) {
|
||||||
cost: cost,
|
this.setState({
|
||||||
costIncludesData: includesData,
|
cost: cost,
|
||||||
});
|
costIncludesData: includesData,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const metadata = this.state.metadata;
|
const metadata = this.props.metadata,
|
||||||
const title = metadata ? this.state.metadata.title : this._uri;
|
title = metadata ? this.props.metadata.title : this.props.uri,
|
||||||
|
uriIndicator = <UriIndicator uri={this.props.uri} hasSignature={this.props.hasSignature} signatureIsValid={this.props.signatureIsValid} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="constrained-page">
|
<main className="main--single-column">
|
||||||
<section className="show-page-media">
|
<section className="show-page-media">
|
||||||
{ this.state.contentType && this.state.contentType.startsWith('video/') ?
|
{ this.props.contentType && this.props.contentType.startsWith('video/') ?
|
||||||
<Video className="video-embedded" uri={this._uri} metadata={metadata} outpoint={this.state.outpoint} /> :
|
<Video className="video-embedded" uri={this.props.uri} metadata={metadata} outpoint={this.props.outpoint} /> :
|
||||||
(metadata ? <Thumbnail src={metadata.thumbnail} /> : <Thumbnail />) }
|
(metadata ? <Thumbnail src={metadata.thumbnail} /> : <Thumbnail />) }
|
||||||
</section>
|
</section>
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<div className="card__inner">
|
<div className="card__inner">
|
||||||
<div className="card__title-identity">
|
<div className="card__title-identity">
|
||||||
{this.state.isDownloaded === false
|
{this.state.isDownloaded === false
|
||||||
? <span style={{float: "right"}}><FilePrice uri={this._uri} metadata={this.state.metadata} /></span>
|
? <span style={{float: "right"}}><FilePrice uri={this.props.uri} metadata={metadata} /></span>
|
||||||
: null}
|
: null}
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
{ this.state.uriLookupComplete ?
|
<div className="card__subtitle">
|
||||||
<div>
|
{ this.props.channelUri ?
|
||||||
<div className="card__subtitle">
|
<Link href={"?show=" + this.props.channelUri }>{uriIndicator}</Link> :
|
||||||
<UriIndicator uri={this._uri} hasSignature={this.state.hasSignature} signatureIsValid={this.state.signatureIsValid} />
|
uriIndicator}
|
||||||
</div>
|
</div>
|
||||||
<div className="card__actions">
|
<div className="card__actions">
|
||||||
<FileActions uri={this._uri} outpoint={this.state.outpoint} metadata={metadata} contentType={this.state.contentType} />
|
<FileActions uri={this.props.uri} outpoint={this.props.outpoint} metadata={metadata} contentType={this.props.contentType} />
|
||||||
</div>
|
</div>
|
||||||
</div> : '' }
|
</div>
|
||||||
|
<div className="card__content card__subtext card__subtext card__subtext--allow-newlines">
|
||||||
|
{metadata.description}
|
||||||
</div>
|
</div>
|
||||||
{ this.state.uriLookupComplete ?
|
|
||||||
<div>
|
|
||||||
<div className="card__content card__subtext card__subtext card__subtext--allow-newlines">
|
|
||||||
{metadata.description}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
: <div className="card__content"><BusyMessage message="Loading magic decentralized data..." /></div> }
|
|
||||||
</div>
|
</div>
|
||||||
{ metadata ?
|
{ metadata ?
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<FormatItem metadata={metadata} contentType={this.state.contentType} cost={this.state.cost} uri={this._uri} outpoint={this.state.outpoint} costIncludesData={this.state.costIncludesData} />
|
<FormatItem metadata={metadata} contentType={this.state.contentType} cost={this.state.cost} uri={this.props.uri} outpoint={this.props.outpoint} costIncludesData={this.state.costIncludesData} />
|
||||||
</div> : '' }
|
</div> : '' }
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<Link href="https://lbry.io/dmca" label="report" className="button-text-help" />
|
<Link href="https://lbry.io/dmca" label="report" className="button-text-help" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -136,4 +154,131 @@ let ShowPage = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let ShowPage = React.createClass({
|
||||||
|
_uri: null,
|
||||||
|
_isMounted: false,
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
uri: React.PropTypes.string,
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
outpoint: null,
|
||||||
|
metadata: null,
|
||||||
|
contentType: null,
|
||||||
|
hasSignature: false,
|
||||||
|
claimType: null,
|
||||||
|
signatureIsValid: false,
|
||||||
|
cost: null,
|
||||||
|
costIncludesData: null,
|
||||||
|
uriLookupComplete: null,
|
||||||
|
isFailed: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this._isMounted = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
if (nextProps.uri != this.props.uri) {
|
||||||
|
this.setState(this.getInitialState());
|
||||||
|
this.loadUri(nextProps.uri);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this._isMounted = true;
|
||||||
|
this.loadUri(this.props.uri);
|
||||||
|
},
|
||||||
|
|
||||||
|
loadUri: function(uri) {
|
||||||
|
this._uri = lbryuri.normalize(uri);
|
||||||
|
|
||||||
|
lbry.resolve({uri: this._uri}).then((resolveData) => {
|
||||||
|
const isChannel = resolveData && resolveData.claims_in_channel;
|
||||||
|
if (!this._isMounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resolveData) {
|
||||||
|
let newState = { uriLookupComplete: true }
|
||||||
|
if (!isChannel) {
|
||||||
|
let {claim: {txid: txid, nout: nout, has_signature: has_signature, signature_is_valid: signature_is_valid, value: {stream: {metadata: metadata, source: {contentType: contentType}}}}} = resolveData;
|
||||||
|
|
||||||
|
Object.assign(newState, {
|
||||||
|
claimType: "file",
|
||||||
|
metadata: metadata,
|
||||||
|
outpoint: txid + ':' + nout,
|
||||||
|
hasSignature: has_signature,
|
||||||
|
signatureIsValid: signature_is_valid,
|
||||||
|
contentType: contentType
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
lbry.setTitle(metadata.title ? metadata.title : this._uri)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
let {certificate: {txid: txid, nout: nout, has_signature: has_signature}} = resolveData;
|
||||||
|
Object.assign(newState, {
|
||||||
|
claimType: "channel",
|
||||||
|
outpoint: txid + ':' + nout,
|
||||||
|
txid: txid,
|
||||||
|
metadata: {
|
||||||
|
title:resolveData.certificate.name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(newState);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.setState(Object.assign({}, this.getInitialState(), {
|
||||||
|
uriLookupComplete: true,
|
||||||
|
isFailed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const metadata = this.state.metadata,
|
||||||
|
title = metadata ? this.state.metadata.title : this._uri;
|
||||||
|
|
||||||
|
let innerContent = "";
|
||||||
|
|
||||||
|
if (!this.state.uriLookupComplete || this.state.isFailed) {
|
||||||
|
innerContent = <section className="card">
|
||||||
|
<div className="card__inner">
|
||||||
|
<div className="card__title-identity"><h1>{title}</h1></div>
|
||||||
|
</div>
|
||||||
|
<div className="card__content">
|
||||||
|
{ this.state.uriLookupComplete ?
|
||||||
|
<p>This location is not yet in use. { ' ' }<Link href="?publish" label="Put something here" />.</p> :
|
||||||
|
<BusyMessage message="Loading magic decentralized data..." />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>;
|
||||||
|
} else if (this.state.claimType == "channel") {
|
||||||
|
innerContent = <ChannelPage title={this._uri} />
|
||||||
|
} else {
|
||||||
|
let channelUriObj = lbryuri.parse(this._uri)
|
||||||
|
delete channelUriObj.path;
|
||||||
|
delete channelUriObj.contentName;
|
||||||
|
const channelUri = this.state.signatureIsValid && this.state.hasSignature && channelUriObj.isChannel ? lbryuri.build(channelUriObj, false) : null;
|
||||||
|
innerContent = <FilePage
|
||||||
|
uri={this._uri}
|
||||||
|
channelUri={channelUri}
|
||||||
|
outpoint={this.state.outpoint}
|
||||||
|
metadata={metadata}
|
||||||
|
contentType={this.state.contentType}
|
||||||
|
hasSignature={this.state.hasSignature}
|
||||||
|
signatureIsValid={this.state.signatureIsValid}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <main className="main--single-column">{innerContent}</main>;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default ShowPage;
|
export default ShowPage;
|
||||||
|
|
|
@ -5,12 +5,9 @@ var StartPage = React.createClass({
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
lbry.stop();
|
lbry.stop();
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
|
||||||
document.title = "LBRY is Closed";
|
|
||||||
},
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<main className="page">
|
<main className="main--single-column">
|
||||||
<h3>LBRY is Closed</h3>
|
<h3>LBRY is Closed</h3>
|
||||||
<Link href="lbry://lbry" label="Click here to start LBRY" />
|
<Link href="lbry://lbry" label="Click here to start LBRY" />
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import lbry from '../lbry.js';
|
import lbry from '../lbry.js';
|
||||||
import {Link} from '../component/link.js';
|
import {Link} from '../component/link.js';
|
||||||
import Modal from '../component/modal.js';
|
import Modal from '../component/modal.js';
|
||||||
|
import {SubHeader} from '../component/header.js';
|
||||||
import {FormField, FormRow} from '../component/form.js';
|
import {FormField, FormRow} from '../component/form.js';
|
||||||
import {Address, BusyMessage, CreditAmount} from '../component/common.js';
|
import {Address, BusyMessage, CreditAmount} from '../component/common.js';
|
||||||
|
|
||||||
|
@ -263,6 +264,16 @@ var TransactionList = React.createClass({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export let WalletNav = React.createClass({
|
||||||
|
render: function() {
|
||||||
|
return <SubHeader modifier="constrained" viewingPage={this.props.viewingPage} links={{
|
||||||
|
'?wallet': 'Overview',
|
||||||
|
'?send': 'Send',
|
||||||
|
'?receive': 'Receive',
|
||||||
|
'?rewards': 'Rewards'
|
||||||
|
}} />;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var WalletPage = React.createClass({
|
var WalletPage = React.createClass({
|
||||||
_balanceSubscribeId: null,
|
_balanceSubscribeId: null,
|
||||||
|
@ -270,9 +281,6 @@ var WalletPage = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
viewingPage: React.PropTypes.string,
|
viewingPage: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
componentDidMount: function() {
|
|
||||||
document.title = "My Wallet";
|
|
||||||
},
|
|
||||||
/*
|
/*
|
||||||
Below should be refactored so that balance is shared all of wallet page. Or even broader?
|
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?
|
What is the proper React pattern for sharing a global state like balance?
|
||||||
|
@ -296,7 +304,8 @@ var WalletPage = React.createClass({
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<main className="page">
|
<main className="main--single-column">
|
||||||
|
<WalletNav viewingPage={this.props.viewingPage} />
|
||||||
<section className="card">
|
<section className="card">
|
||||||
<div className="card__title-primary">
|
<div className="card__title-primary">
|
||||||
<h3>Balance</h3>
|
<h3>Balance</h3>
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
* Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value
|
* Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value
|
||||||
* is not set yet.
|
* is not set yet.
|
||||||
*/
|
*/
|
||||||
export function getLocal(key) {
|
export function getLocal(key, fallback=undefined) {
|
||||||
const itemRaw = localStorage.getItem(key);
|
const itemRaw = localStorage.getItem(key);
|
||||||
return itemRaw === null ? undefined : JSON.parse(itemRaw);
|
return itemRaw === null ? fallback : JSON.parse(itemRaw);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
"babel-cli": "^6.11.4",
|
"babel-cli": "^6.11.4",
|
||||||
"babel-preset-es2015": "^6.13.2",
|
"babel-preset-es2015": "^6.13.2",
|
||||||
"babel-preset-react": "^6.11.1",
|
"babel-preset-react": "^6.11.1",
|
||||||
"clamp-js-main": "^0.11.1",
|
|
||||||
"mediaelement": "^2.23.4",
|
"mediaelement": "^2.23.4",
|
||||||
"node-sass": "^3.8.0",
|
"node-sass": "^3.8.0",
|
||||||
"rc-progress": "^2.0.6",
|
"rc-progress": "^2.0.6",
|
||||||
|
|
|
@ -1,220 +0,0 @@
|
||||||
@import "global";
|
|
||||||
|
|
||||||
html
|
|
||||||
{
|
|
||||||
height: 100%;
|
|
||||||
font-size: $font-size;
|
|
||||||
}
|
|
||||||
body
|
|
||||||
{
|
|
||||||
font-family: 'Source Sans Pro', sans-serif;
|
|
||||||
line-height: $font-line-height;
|
|
||||||
}
|
|
||||||
|
|
||||||
$drawer-width: 220px;
|
|
||||||
|
|
||||||
#drawer
|
|
||||||
{
|
|
||||||
width: $drawer-width;
|
|
||||||
position: fixed;
|
|
||||||
min-height: 100vh;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
background: $color-bg;
|
|
||||||
z-index: 3;
|
|
||||||
.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;
|
|
||||||
margin-top: $spacing-vertical * 0.25 - 2;
|
|
||||||
background: $color-money;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.drawer-item-selected
|
|
||||||
{
|
|
||||||
background: $color-canvas;
|
|
||||||
color: $color-primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.badge
|
|
||||||
{
|
|
||||||
background: $color-money;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 2px;
|
|
||||||
color: white;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
.credit-amount--indicator
|
|
||||||
{
|
|
||||||
font-weight: bold;
|
|
||||||
color: $color-money;
|
|
||||||
}
|
|
||||||
#drawer-handle
|
|
||||||
{
|
|
||||||
padding: $spacing-vertical / 2;
|
|
||||||
max-height: $height-header - $spacing-vertical;
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
#window.drawer-open
|
|
||||||
{
|
|
||||||
#main-content { margin-left: $drawer-width; }
|
|
||||||
.open-drawer-link { display: none }
|
|
||||||
#header { padding-left: $drawer-width + $spacing-vertical / 2; }
|
|
||||||
}
|
|
||||||
|
|
||||||
#header
|
|
||||||
{
|
|
||||||
background: $color-primary;
|
|
||||||
color: white;
|
|
||||||
&.header-no-subnav {
|
|
||||||
height: $height-header;
|
|
||||||
}
|
|
||||||
&.header-with-subnav {
|
|
||||||
height: $height-header * 2;
|
|
||||||
}
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 2;
|
|
||||||
box-sizing: border-box;
|
|
||||||
h1 { font-size: 1.8em; line-height: $height-header - $spacing-vertical; display: inline-block; float: left; }
|
|
||||||
&.header-scrolled
|
|
||||||
{
|
|
||||||
box-shadow: $default-box-shadow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.header-top-bar
|
|
||||||
{
|
|
||||||
padding: $spacing-vertical / 2;
|
|
||||||
}
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.sub-header
|
|
||||||
{
|
|
||||||
background: $color-primary;
|
|
||||||
text-transform: uppercase;
|
|
||||||
padding: $spacing-vertical / 2;
|
|
||||||
> a
|
|
||||||
{
|
|
||||||
$sub-header-selected-underline-height: 2px;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 15px;
|
|
||||||
padding: 0 5px;
|
|
||||||
line-height: $height-header - $spacing-vertical - $sub-header-selected-underline-height;
|
|
||||||
color: #e8e8e8;
|
|
||||||
&:first-child
|
|
||||||
{
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
&:last-child
|
|
||||||
{
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
&.sub-header-selected
|
|
||||||
{
|
|
||||||
border-bottom: $sub-header-selected-underline-height solid #fff;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
&:hover
|
|
||||||
{
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#main-content
|
|
||||||
{
|
|
||||||
background: $color-canvas;
|
|
||||||
&.no-sub-nav
|
|
||||||
{
|
|
||||||
min-height: calc(100vh - 60px); //should be -$height-header, but I'm dumb I guess? It wouldn't work
|
|
||||||
main { margin-top: $height-header; }
|
|
||||||
}
|
|
||||||
&.with-sub-nav
|
|
||||||
{
|
|
||||||
min-height: calc(100vh - 120px); //should be -$height-header, but I'm dumb I guess? It wouldn't work
|
|
||||||
main { margin-top: $height-header * 2; }
|
|
||||||
}
|
|
||||||
main
|
|
||||||
{
|
|
||||||
padding: $spacing-vertical;
|
|
||||||
&.constrained-page
|
|
||||||
{
|
|
||||||
max-width: $width-page-constrained;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.full-screen
|
|
||||||
{
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
|
@ -34,8 +34,8 @@ $height-header: $spacing-vertical * 2.5;
|
||||||
$height-button: $spacing-vertical * 1.5;
|
$height-button: $spacing-vertical * 1.5;
|
||||||
$height-video-embedded: $width-page-constrained * 9 / 16;
|
$height-video-embedded: $width-page-constrained * 9 / 16;
|
||||||
|
|
||||||
$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);
|
$box-shadow-layer: 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);
|
$box-shadow-focus: 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;
|
$transition-standard: .225s ease;
|
||||||
|
|
||||||
|
@ -160,4 +160,35 @@ $blur-intensity: 8px;
|
||||||
width:1px;
|
width:1px;
|
||||||
height:1px;
|
height:1px;
|
||||||
overflow:hidden;
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin text-link($color: $color-primary, $hover-opacity: 0.70) {
|
||||||
|
.icon
|
||||||
|
{
|
||||||
|
&:first-child {
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
&:last-child:not(:only-child) {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.no-underline) {
|
||||||
|
text-decoration: underline;
|
||||||
|
.icon {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover
|
||||||
|
{
|
||||||
|
opacity: $hover-opacity;
|
||||||
|
transition: opacity $transition-standard;
|
||||||
|
text-decoration: underline;
|
||||||
|
.icon {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
color: $color;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
|
@ -1,87 +0,0 @@
|
||||||
@import "global";
|
|
||||||
|
|
||||||
$gutter_fluid: 4;
|
|
||||||
|
|
||||||
[class*="span"] {
|
|
||||||
min-height: 1px;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.span12 { width: 100%; }
|
|
||||||
.span11 { width: 91.666%; }
|
|
||||||
.span10 { width: 83.333%; }
|
|
||||||
.span9 { width: 75%; }
|
|
||||||
.span8 { width: 66.666%; }
|
|
||||||
.span7 { width: 58.333%; }
|
|
||||||
.span6 { width: 50%; }
|
|
||||||
.span5 { width: 41.666%; }
|
|
||||||
.span4 { width: 33.333%; }
|
|
||||||
.span3 { width: 25%; }
|
|
||||||
.span2 { width: 16.666%; }
|
|
||||||
.span1 { width: 8.333%; }
|
|
||||||
|
|
||||||
.row-fluid {
|
|
||||||
width: 100%;
|
|
||||||
> [class*="span"] {
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
margin-left: 1% * $gutter_fluid;
|
|
||||||
&:first-child
|
|
||||||
{
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$column_width: (100% - $gutter_fluid * 11) / 12;
|
|
||||||
|
|
||||||
> .span12 { width: $column_width * 12 + $gutter_fluid * 11; }
|
|
||||||
> .span11 { width: $column_width * 11 + $gutter_fluid * 10; }
|
|
||||||
> .span10 { width: $column_width * 10 + $gutter_fluid * 9; }
|
|
||||||
> .span9 { width: $column_width * 9 + $gutter_fluid * 8; }
|
|
||||||
> .span8 { width: $column_width * 8 + $gutter_fluid * 7; }
|
|
||||||
> .span7 { width: $column_width * 7 + $gutter_fluid * 6; }
|
|
||||||
> .span6 { width: $column_width * 6 + $gutter_fluid * 5; }
|
|
||||||
> .span5 { width: $column_width * 5 + $gutter_fluid * 4; }
|
|
||||||
> .span4 { width: $column_width * 4 + $gutter_fluid * 3; }
|
|
||||||
> .span3 { width: $column_width * 3 + $gutter_fluid * 2; }
|
|
||||||
> .span2 { width: $column_width * 2 + $gutter_fluid * 1; }
|
|
||||||
> .span1 { width: $column_width; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile-fluid {
|
|
||||||
width: 100%;
|
|
||||||
> [class*="span"] {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.column-fluid {
|
|
||||||
@include display-flex();
|
|
||||||
flex-wrap: wrap;
|
|
||||||
> [class*="span"] {
|
|
||||||
@include display-flex();
|
|
||||||
@include flex(1 0 auto);
|
|
||||||
overflow: hidden;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-fluid, .tile-fluid {
|
|
||||||
@include clearfix();
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: $mobile-width-threshold) {
|
|
||||||
.row-fluid, .tile-fluid, .column-fluid {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.pull-left, .pull-right
|
|
||||||
{
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
[class*="span"] {
|
|
||||||
float: none !important;
|
|
||||||
width: 100% !important;
|
|
||||||
margin-left: 0 !important;
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +1,51 @@
|
||||||
@import "global";
|
@import "global";
|
||||||
|
|
||||||
@mixin text-link($color: $color-primary, $hover-opacity: 0.70) {
|
html
|
||||||
|
{
|
||||||
|
height: 100%;
|
||||||
|
font-size: $font-size;
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
font-family: 'Source Sans Pro', sans-serif;
|
||||||
|
line-height: $font-line-height;
|
||||||
|
}
|
||||||
|
|
||||||
.icon
|
#window
|
||||||
|
{
|
||||||
|
min-height: 100vh;
|
||||||
|
background: $color-canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge
|
||||||
|
{
|
||||||
|
background: $color-money;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px;
|
||||||
|
color: white;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.credit-amount--indicator
|
||||||
|
{
|
||||||
|
font-weight: bold;
|
||||||
|
color: $color-money;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-content
|
||||||
|
{
|
||||||
|
padding: $spacing-vertical;
|
||||||
|
margin-top: $height-header;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
main {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
main.main--single-column
|
||||||
{
|
{
|
||||||
&:first-child {
|
width: $width-page-constrained;
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
&:last-child:not(:only-child) {
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.no-underline) {
|
|
||||||
text-decoration: underline;
|
|
||||||
.icon {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:hover
|
|
||||||
{
|
|
||||||
opacity: $hover-opacity;
|
|
||||||
transition: opacity $transition-standard;
|
|
||||||
text-decoration: underline;
|
|
||||||
.icon {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
color: $color;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-fixed-width {
|
.icon-fixed-width {
|
||||||
|
@ -80,7 +96,10 @@ p
|
||||||
}
|
}
|
||||||
|
|
||||||
.truncated-text {
|
.truncated-text {
|
||||||
display: inline-block;
|
//display: inline-block;
|
||||||
|
display: -webkit-box;
|
||||||
|
overflow: hidden;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
.busy-indicator
|
.busy-indicator
|
||||||
|
@ -126,10 +145,11 @@ p
|
||||||
|
|
||||||
.sort-section {
|
.sort-section {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 5px;
|
margin-bottom: $spacing-vertical * 2/3;
|
||||||
|
|
||||||
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
line-height: 1;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
color: $color-help;
|
color: $color-help;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,12 +25,6 @@
|
||||||
transform: translate(0, 0);
|
transform: translate(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-mega
|
|
||||||
{
|
|
||||||
font-size: 200px;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
|
/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
|
||||||
readers do not read off random characters that represent icons */
|
readers do not read off random characters that represent icons */
|
||||||
.icon-glass:before {
|
.icon-glass:before {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
@import "_reset";
|
@import "_reset";
|
||||||
@import "_grid";
|
|
||||||
@import "_icons";
|
@import "_icons";
|
||||||
@import "_mediaelement";
|
@import "_mediaelement";
|
||||||
@import "_canvas";
|
|
||||||
@import "_gui";
|
@import "_gui";
|
||||||
@import "component/_table";
|
@import "component/_table";
|
||||||
@import "component/_button.scss";
|
@import "component/_button.scss";
|
||||||
|
@ -10,6 +8,7 @@
|
||||||
@import "component/_file-actions.scss";
|
@import "component/_file-actions.scss";
|
||||||
@import "component/_file-tile.scss";
|
@import "component/_file-tile.scss";
|
||||||
@import "component/_form-field.scss";
|
@import "component/_form-field.scss";
|
||||||
|
@import "component/_header.scss";
|
||||||
@import "component/_menu.scss";
|
@import "component/_menu.scss";
|
||||||
@import "component/_tooltip.scss";
|
@import "component/_tooltip.scss";
|
||||||
@import "component/_load-screen.scss";
|
@import "component/_load-screen.scss";
|
||||||
|
|
|
@ -34,6 +34,11 @@ $button-focus-shift: 12%;
|
||||||
{
|
{
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
.icon:only-child
|
||||||
|
{
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.button-block
|
.button-block
|
||||||
{
|
{
|
||||||
|
@ -49,17 +54,17 @@ $button-focus-shift: 12%;
|
||||||
$color-button-text: white;
|
$color-button-text: white;
|
||||||
color: darken($color-button-text, $button-focus-shift * 0.5);
|
color: darken($color-button-text, $button-focus-shift * 0.5);
|
||||||
background-color: $color-primary;
|
background-color: $color-primary;
|
||||||
box-shadow: $default-box-shadow;
|
box-shadow: $box-shadow-layer;
|
||||||
&:focus {
|
&:focus {
|
||||||
color: $color-button-text;
|
color: $color-button-text;
|
||||||
//box-shadow: $focus-box-shadow;
|
//box-shadow: $box-shadow-focus;
|
||||||
background-color: mix(black, $color-primary, $button-focus-shift)
|
background-color: mix(black, $color-primary, $button-focus-shift)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.button-alt
|
.button-alt
|
||||||
{
|
{
|
||||||
background-color: $color-bg-alt;
|
background-color: $color-bg-alt;
|
||||||
box-shadow: $default-box-shadow;
|
box-shadow: $box-shadow-layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-text
|
.button-text
|
||||||
|
@ -76,3 +81,7 @@ $button-focus-shift: 12%;
|
||||||
@include text-link(#aaa);
|
@include text-link(#aaa);
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
.button--flat
|
||||||
|
{
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ $padding-card-horizontal: $spacing-vertical * 2/3;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
max-width: $width-page-constrained;
|
max-width: $width-page-constrained;
|
||||||
background: $color-bg;
|
background: $color-bg;
|
||||||
box-shadow: $default-box-shadow;
|
box-shadow: $box-shadow-layer;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
margin-bottom: $spacing-vertical * 2/3;
|
margin-bottom: $spacing-vertical * 2/3;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
@ -86,7 +86,7 @@ $card-link-scaling: 1.1;
|
||||||
.card--link:hover {
|
.card--link:hover {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
box-shadow: $focus-box-shadow;
|
box-shadow: $box-shadow-focus;
|
||||||
transform: scale($card-link-scaling);
|
transform: scale($card-link-scaling);
|
||||||
transform-origin: 50% 50%;
|
transform-origin: 50% 50%;
|
||||||
overflow-x: visible;
|
overflow-x: visible;
|
||||||
|
@ -139,8 +139,12 @@ $height-card-small: $spacing-vertical * 15;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
/*hacky way to give space for hover */
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
margin-left: -20px; /*hacky way to give space for hover */
|
margin-left: -20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
margin-right: -20px;
|
||||||
}
|
}
|
||||||
.card-row__header {
|
.card-row__header {
|
||||||
margin-bottom: $spacing-vertical / 3;
|
margin-bottom: $spacing-vertical / 3;
|
||||||
|
|
|
@ -1,37 +1,26 @@
|
||||||
@import "../global";
|
@import "../global";
|
||||||
|
|
||||||
$height-file-tile: $spacing-vertical * 8;
|
$height-file-tile: $spacing-vertical * 6;
|
||||||
.file-tile__row {
|
.file-tile__row {
|
||||||
|
overflow: hidden;
|
||||||
height: $height-file-tile;
|
height: $height-file-tile;
|
||||||
.credit-amount {
|
.credit-amount {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
//Hack! Remove below!
|
//also a hack
|
||||||
.card__title-primary {
|
.card__media {
|
||||||
margin-top: $spacing-vertical * 2/3;
|
height: $height-file-tile;
|
||||||
|
max-width: $height-file-tile;
|
||||||
|
width: $height-file-tile;
|
||||||
|
margin-right: $spacing-vertical / 2;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
//basically everything here is a hack now
|
||||||
|
.file-tile__content {
|
||||||
|
padding-top: $spacing-vertical * 1/3;
|
||||||
|
margin-left: $height-file-tile + $spacing-vertical / 2;
|
||||||
|
}
|
||||||
|
.card__title-primary {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.file-tile__thumbnail {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: $height-file-tile;
|
|
||||||
vertical-align: middle;
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
.file-tile__thumbnail-container
|
|
||||||
{
|
|
||||||
height: $height-file-tile;
|
|
||||||
@include absolute-center();
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tile__title {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-tile__description {
|
|
||||||
color: #444;
|
|
||||||
margin-top: 12px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
}
|
94
ui/scss/component/_header.scss
Normal file
94
ui/scss/component/_header.scss
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
@import "../global";
|
||||||
|
|
||||||
|
$color-header: #666;
|
||||||
|
$color-header-active: darken($color-header, 20%);
|
||||||
|
|
||||||
|
#header
|
||||||
|
{
|
||||||
|
color: $color-header;
|
||||||
|
background: #fff;
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
box-shadow: $box-shadow-layer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 2;
|
||||||
|
padding: $spacing-vertical / 2;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.header__item {
|
||||||
|
flex: 0 0 content;
|
||||||
|
padding-left: $spacing-vertical / 4;
|
||||||
|
padding-right: $spacing-vertical / 4;
|
||||||
|
}
|
||||||
|
.header__item--wunderbar {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wunderbar
|
||||||
|
{
|
||||||
|
position: relative;
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
top: $spacing-vertical / 2 - 4px; //hacked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wunderbar--active .icon-search { color: $color-primary; }
|
||||||
|
|
||||||
|
.wunderbar__input {
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
width: 100%;
|
||||||
|
color: $color-header;
|
||||||
|
height: $spacing-vertical * 1.5;
|
||||||
|
line-height: $spacing-vertical * 1.5;
|
||||||
|
padding-left: 38px;
|
||||||
|
padding-right: 5px;
|
||||||
|
border: 1px solid $color-text-dark;
|
||||||
|
@include border-radius(2px);
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
&:focus {
|
||||||
|
color: $color-header-active;
|
||||||
|
box-shadow: $box-shadow-focus;
|
||||||
|
border-color: $color-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.sub-header
|
||||||
|
{
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 0 0 $spacing-vertical;
|
||||||
|
&.sub-header--constrained {
|
||||||
|
max-width: $width-page-constrained;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
> a
|
||||||
|
{
|
||||||
|
$sub-header-selected-underline-height: 2px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 15px;
|
||||||
|
padding: 0 5px;
|
||||||
|
line-height: $height-header - $spacing-vertical - $sub-header-selected-underline-height;
|
||||||
|
color: $color-header;
|
||||||
|
&:first-child
|
||||||
|
{
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
&:last-child
|
||||||
|
{
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
&.sub-header-selected
|
||||||
|
{
|
||||||
|
border-bottom: $sub-header-selected-underline-height solid $color-header-active;
|
||||||
|
color: $color-header-active;
|
||||||
|
}
|
||||||
|
&:hover
|
||||||
|
{
|
||||||
|
color: $color-header-active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ $border-radius-menu: 2px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
box-shadow: $default-box-shadow;
|
box-shadow: $box-shadow-layer;
|
||||||
border-radius: $border-radius-menu;
|
border-radius: $border-radius-menu;
|
||||||
padding-top: ($spacing-vertical / 5) 0px;
|
padding-top: ($spacing-vertical / 5) 0px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: $spacing-vertical;
|
padding: $spacing-vertical;
|
||||||
box-shadow: $default-box-shadow;
|
box-shadow: $box-shadow-layer;
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
margin-left: $tooltip-body-width * -1 / 2;
|
margin-left: $tooltip-body-width * -1 / 2;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: $spacing-vertical / 2;
|
padding: $spacing-vertical / 2;
|
||||||
|
@ -24,7 +25,7 @@
|
||||||
background-color: $color-bg;
|
background-color: $color-bg;
|
||||||
font-size: $font-size * 7/8;
|
font-size: $font-size * 7/8;
|
||||||
line-height: $font-line-height;
|
line-height: $font-line-height;
|
||||||
box-shadow: $default-box-shadow;
|
box-shadow: $box-shadow-layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip--header .tooltip__link {
|
.tooltip--header .tooltip__link {
|
||||||
|
|
Loading…
Reference in a new issue