many bug fixes, working back button, progress towards working search

This commit is contained in:
Jeremy Kauffman 2017-04-30 20:15:21 -04:00
parent 0b8d9e3d82
commit da538a7a23
24 changed files with 247 additions and 343 deletions

View file

@ -12,10 +12,10 @@ 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 {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 +38,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,
@ -71,18 +72,17 @@ var App = React.createClass({
getViewingPageAndArgs: function(address) { getViewingPageAndArgs: function(address) {
// For now, routes are in format ?page or ?page=args // For now, routes are in format ?page or ?page=args
let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/); let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/);
console.log(pageArgs);
console.log(decodeURIComponent(pageArgs));
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), {
viewingPage: 'discover', viewingPage: 'discover',
drawerOpen: drawerOpenRaw !== null ? JSON.parse(drawerOpenRaw) : true, appUrl: null,
errorInfo: null, errorInfo: null,
modal: null, modal: null,
downloadProgress: null, downloadProgress: null,
@ -90,6 +90,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);
}); });
@ -106,9 +108,9 @@ 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 }));
} }
} }
target = target.parentNode; target = target.parentNode;
@ -126,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,
@ -144,10 +138,17 @@ 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;
this.setState({
viewingPage: "search",
appUrl: "?search=" + encodeURIComponent(term),
pageArgs: term
}); });
}, },
handleUpgradeClicked: function() { handleUpgradeClicked: function() {
@ -202,12 +203,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)) {
@ -225,12 +220,14 @@ var App = React.createClass({
{ {
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 ["Settings", "icon-gear", <SettingsPage />]; return ["Settings", "icon-gear", <SettingsPage />];
case 'help': case 'help':
return ["Help", "icon-question", <HelpPage />]; return ["Help", "icon-question", <HelpPage />];
case 'report': case 'report':
return ['Report', 'icon-file', <ReportPage />]; return ['Report an Issue', 'icon-file', <ReportPage />];
case 'downloaded': case 'downloaded':
return ["Downloads & Purchases", "icon-folder", <FileListDownloaded />]; return ["Downloads & Purchases", "icon-folder", <FileListDownloaded />];
case 'published': case 'published':
@ -250,18 +247,25 @@ var App = React.createClass({
case 'developer': case 'developer':
return ["Developer", "icon-file", <DeveloperPage />]; return ["Developer", "icon-file", <DeveloperPage />];
case 'discover': case 'discover':
return ["Home", "icon-home", <DiscoverPage showWelcome={this.state.justRegistered} {... this.state.pageArgs !== null ? {query: this.state.pageArgs} : {} } />]; default:
return ["Home", "icon-home", <DiscoverPage />];
} }
}, },
render: function() { render: function() {
let [address, wunderBarIcon, mainContent] = this.getContentAndAddress(); let [address, wunderBarIcon, mainContent] = this.getContentAndAddress();
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"> <div id="window">
<Header onOpenDrawer={this.openDrawer} address={address} wunderBarIcon={wunderBarIcon} <Header onSearch={this.onSearch} address={address} wunderBarIcon={wunderBarIcon} viewingPage={this.state.viewingPage} />
onSearch={this.onSearch} viewingPage={this.state.viewingPage} />
<div id="main-content"> <div id="main-content">
{mainContent} {mainContent}
</div> </div>

View file

@ -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>;
} }
}); });

View file

@ -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;

View file

@ -28,13 +28,14 @@ var Header = React.createClass({
render: function() { render: function() {
return <header id="header"> return <header id="header">
<div className="header__item"> <div className="header__item">
<Link onClick={() => { history.back() }} button="alt button--flat" icon="icon-arrow-left" /> <Link onClick={() => { lbry.back() }} button="alt button--flat" icon="icon-arrow-left" />
</div> </div>
<div className="header__item"> <div className="header__item">
<Link href="?discover" button="alt button--flat" icon="icon-home" /> <Link href="?discover" button="alt button--flat" icon="icon-home" />
</div> </div>
<div className="header__item header__item--wunderbar"> <div className="header__item header__item--wunderbar">
<WunderBar address={this.props.address} icon={this.props.wunderBarIcon} onSearch={this.props.onSearch} viewingPage={this.props.viewingPage} /> <WunderBar address={this.props.address} icon={this.props.wunderBarIcon}
onSearch={this.props.onSearch} viewingPage={this.props.viewingPage} />
</div> </div>
<div className="header__item"> <div className="header__item">
<Link href="?wallet" button="text" icon="icon-bank" label={lbry.formatCredits(this.state.balance, 1)} ></Link> <Link href="?wallet" button="text" icon="icon-bank" label={lbry.formatCredits(this.state.balance, 1)} ></Link>
@ -57,6 +58,10 @@ let WunderBar = React.createClass({
_input: null, _input: null,
_stateBeforeSearch: null, _stateBeforeSearch: null,
propTypes: {
onSearch: React.PropTypes.func.isRequired
},
getInitialState: function() { getInitialState: function() {
return { return {
address: this.props.address, address: this.props.address,
@ -77,13 +82,11 @@ let WunderBar = React.createClass({
this.setState({ address: event.target.value }) this.setState({ address: event.target.value })
//@TODO: Switch to React.js timing let searchTerm = event.target.value;
var searchTerm = event.target.value;
this._userTypingTimer = setTimeout(() => { this._userTypingTimer = setTimeout(() => {
this.props.onSearch(searchTerm); this.props.onSearch(searchTerm);
}, 800); // 800ms delay, tweak for faster/slower }, 800); // 800ms delay, tweak for faster/slower
}, },
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.viewingPage !== this.props.viewingPage) { if (nextProps.viewingPage !== this.props.viewingPage) {
@ -98,7 +101,8 @@ let WunderBar = React.createClass({
} }
// this._input.value = ""; //trigger placeholder // this._input.value = ""; //trigger placeholder
this._focusPending = true; this._focusPending = true;
if (!this.state.address.startsWith('lbry://')) //onFocus, if they are not on an exact URL, clear the bar //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 = ''; newState.address = '';
} }

View file

@ -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>
); );

View file

@ -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,24 @@ 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() {
console.log(window.history);
if (window.history.length > 1) {
console.log('history exists, go back');
window.history.back();
} else {
console.log('no history, reload');
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))

View file

@ -151,20 +151,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);
}); });
} }

View file

@ -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 = "Home";
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>
);
} }
}); });

View file

@ -56,7 +56,7 @@ export let FileListDownloaded = React.createClass({
content = <FileList fileInfos={this.state.fileInfos} hidePrices={true} />; content = <FileList fileInfos={this.state.fileInfos} hidePrices={true} />;
} }
return ( return (
<main className="constrained-page"> <main className="main--single-column">
<FileListNav viewingPage="downloaded" /> <FileListNav viewingPage="downloaded" />
{content} {content}
</main> </main>
@ -83,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; }
@ -118,7 +117,7 @@ export let FileListPublished = React.createClass({
content = <FileList fileInfos={this.state.fileInfos} />; content = <FileList fileInfos={this.state.fileInfos} />;
} }
return ( return (
<main className="constrained-page"> <main className="main--single-column">
<FileListNav viewingPage="published" /> <FileListNav viewingPage="published" />
{content} {content}
</main> </main>

View file

@ -25,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) {
@ -50,7 +47,7 @@ var HelpPage = React.createClass({
} }
return ( return (
<main className="constrained-page"> <main className="main--single-column">
<SettingsNav viewingPage="help" /> <SettingsNav viewingPage="help" />
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">

View file

@ -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({
@ -384,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">
@ -548,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>

View file

@ -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>

View file

@ -57,7 +57,7 @@ var RewardsPage = React.createClass({
}, },
render: function() { render: function() {
return ( return (
<main className="constrained-page"> <main className="main--single-column">
<WalletNav viewingPage="rewards"/> <WalletNav viewingPage="rewards"/>
<div> <div>
{!this.state.userRewards {!this.state.userRewards

139
ui/js/page/search.js Normal file
View file

@ -0,0 +1,139 @@
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,
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 this.state.searching ?
<BusyMessage message="Looking up the Dewey Decimals" /> :
(this.state.results.length ?
<SearchResultList results={this.state.results} /> :
<SearchNoResults query={thisprops.query} />);
}
});
let SearchPage = React.createClass({
_isMounted: false,
propTypes: {
query: React.PropTypes.string.isRequired
},
isValidUri: function(query) {
return true;
},
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>
{ this.isValidUri(this.props.query) ?
<div>
<h3>lbry://{this.props.query}</h3>
<div><BusyMessage message="Resolving the URL" /></div>
</div> : '' }
<h3>Search</h3>
<SearchResults query={this.props.query} />
</main>
);
}
});
export default SearchPage;

View file

@ -66,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({
@ -102,7 +99,7 @@ var SettingsPage = React.createClass({
</section> </section>
*/ */
return ( return (
<main className="constrained-page"> <main className="main--single-column">
<SettingsNav viewingPage="settings" /> <SettingsNav viewingPage="settings" />
<section className="card"> <section className="card">
<div className="card__content"> <div className="card__content">

View file

@ -70,7 +70,7 @@ let ShowPage = React.createClass({
}); });
}); });
document.title = metadata.title ? metadata.title : this._uri; lbry.setTitle(metadata.title ? metadata.title : this._uri)
this.setState({ this.setState({
outpoint: outpoint, outpoint: outpoint,
@ -94,7 +94,7 @@ let ShowPage = React.createClass({
const title = metadata ? this.state.metadata.title : this._uri; const title = metadata ? this.state.metadata.title : this._uri;
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.state.contentType && this.state.contentType.startsWith('video/') ?
<Video className="video-embedded" uri={this._uri} metadata={metadata} outpoint={this.state.outpoint} /> : <Video className="video-embedded" uri={this._uri} metadata={metadata} outpoint={this.state.outpoint} /> :

View file

@ -7,7 +7,7 @@ var StartPage = React.createClass({
}, },
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>

View file

@ -304,7 +304,7 @@ var WalletPage = React.createClass({
}, },
render: function() { render: function() {
return ( return (
<main className="page"> <main className="main--single-column">
<WalletNav viewingPage={this.props.viewingPage} /> <WalletNav viewingPage={this.props.viewingPage} />
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">

View file

@ -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",

View file

@ -35,10 +35,15 @@ body
{ {
padding: $spacing-vertical; padding: $spacing-vertical;
margin-top: $height-header; margin-top: $height-header;
main.constrained-page display: flex;
flex-direction: column;
main {
margin-left: auto;
margin-right: auto;
max-width: 100%;
}
main.main--single-column
{ {
max-width: $width-page-constrained; width: $width-page-constrained;
margin-left: auto;
margin-right: auto;
} }
} }

View file

@ -80,7 +80,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

View file

@ -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;

View file

@ -3,28 +3,11 @@
$color-header: #666; $color-header: #666;
$color-header-active: darken($color-header, 20%); $color-header-active: darken($color-header, 20%);
$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;
}
#header #header
{ {
color: $color-header; color: $color-header;
background: #fff; background: #fff;
display: flex; display: flex;
/*
&.header-no-subnav {
height: $height-header;
}
&.header-with-subnav {
height: $height-header * 2;
}*/
position: fixed; position: fixed;
box-shadow: $box-shadow-layer; box-shadow: $box-shadow-layer;
top: 0; top: 0;

View file

@ -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;