Redux #115

Merged
6ea86b96 merged 57 commits from redux into redux 2017-05-05 22:55:12 +02:00
31 changed files with 451 additions and 687 deletions

View file

@ -6,6 +6,7 @@ import {
selectUpgradeDownloadItem, selectUpgradeDownloadItem,
selectUpgradeFilename, selectUpgradeFilename,
selectPageTitle, selectPageTitle,
selectCurrentPath,
} from 'selectors/app' } from 'selectors/app'
const {remote, ipcRenderer, shell} = require('electron'); const {remote, ipcRenderer, shell} = require('electron');
@ -16,6 +17,11 @@ const fs = remote.require('fs');
export function doNavigate(path) { export function doNavigate(path) {
return function(dispatch, getState) { return function(dispatch, getState) {
const state = getState()
const previousPath = selectCurrentPath(state)
const previousTitle = selectPageTitle(state)
history.pushState(state, previousTitle, previousPath);
dispatch({ dispatch({
type: types.NAVIGATE, type: types.NAVIGATE,
data: { data: {
@ -23,8 +29,8 @@ export function doNavigate(path) {
} }
}) })
const state = getState()
const pageTitle = selectPageTitle(state) const pageTitle = selectPageTitle(state)
window.document.title = pageTitle window.document.title = pageTitle
} }
} }
@ -48,8 +54,12 @@ export function doCloseModal() {
} }
export function doHistoryBack() { export function doHistoryBack() {
return { return function(dispatch, getState) {
type: types.HISTORY_BACK if (window.history.length > 1) {
window.history.back();
} else {
dispatch(doNavigate('discover'))
}
} }
} }
@ -152,7 +162,7 @@ export function doCheckUpgradeAvailable() {
dispatch({ dispatch({
type: types.UPDATE_VERSION, type: types.UPDATE_VERSION,
data: { data: {
version: versionInfo.lbrynet_version version: remoteVersion,
} }
}) })
dispatch({ dispatch({

View file

@ -1,4 +1,5 @@
import React from 'react' import React from 'react'
import lbry from 'lbry'
import { import {
connect connect
} from 'react-redux' } from 'react-redux'

View file

@ -8,7 +8,7 @@ export const Header = (props) => {
back, back,
navigate navigate
} = props } = props
return <header id="header"> return <header id="header">
<div className="header__item"> <div className="header__item">
<Link onClick={back} button="alt button--flat" icon="icon-arrow-left" /> <Link onClick={back} button="alt button--flat" icon="icon-arrow-left" />

View file

@ -13,8 +13,9 @@ const Link = (props) => {
button, button,
hidden, hidden,
disabled, disabled,
children,
} = props } = props
const className = (props.className || '') + const className = (props.className || '') +
(!props.className && !props.button ? 'button-text' : '') + // Non-button links get the same look as text buttons (!props.className && !props.button ? 'button-text' : '') + // Non-button links get the same look as text buttons
(props.button ? ' button-block button-' + props.button + ' button-set-item' : '') + (props.button ? ' button-block button-' + props.button + ' button-set-item' : '') +
@ -22,8 +23,8 @@ const Link = (props) => {
let content; let content;
if (props.children) { if (children) {
content = this.props.children content = children
} else { } else {
content = ( content = (
<span {... 'button' in props ? {className: 'button__content'} : {}}> <span {... 'button' in props ? {className: 'button__content'} : {}}>

View file

@ -1,21 +0,0 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
selectCurrentPage,
} from 'selectors/app'
import {
doNavigate,
} from 'actions/app'
import NavMain from './view'
const select = (state) => ({
currentPage: selectCurrentPage(state)
})
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path))
})
export default connect(select, perform)(NavMain)

View file

@ -1,22 +0,0 @@
import React from 'react';
export const NavMain = (props) => {
const {
links,
currentPage,
navigate,
} = props
return (
<nav className="sub-header">{
Object.keys(links).map((link) => {
console.log(link + " vs " + currentPage);
return <a href="#" onClick={() => navigate(link)} key={link} className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected' }>
{links[link]}
</a>
})
}</nav>
)
}
export default NavMain

View file

@ -1,7 +0,0 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import NavSettings from './view'
export default connect(null, null)(NavSettings)

View file

@ -1,11 +0,0 @@
import React from 'react';
import NavMain from 'component/navMain'
export const NavSettings = () => {
return <NavMain links={{
'settings': 'Settings',
'help' : 'Help'
}} />;
}
export default NavSettings

View file

@ -1,7 +0,0 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import NavWallet from './view'
export default connect(null, null)(NavWallet)

View file

@ -1,13 +0,0 @@
import React from 'react';
import NavMain from 'component/navMain'
export const NavWallet = () => {
return <NavMain links={{
'wallet': 'Overview',
'send': 'Send',
'receive': 'Receive',
'rewards': 'Rewards'
}} />
}
export default NavWallet

View file

@ -4,7 +4,7 @@ import HelpPage from 'page/help';
import ReportPage from 'page/report.js'; import ReportPage from 'page/report.js';
import StartPage from 'page/start.js'; import StartPage from 'page/start.js';
import WalletPage from 'page/wallet'; import WalletPage from 'page/wallet';
import FilePage from 'page/filePage'; import ShowPage from 'page/showPage'
import PublishPage from 'page/publish'; import PublishPage from 'page/publish';
import DiscoverPage from 'page/discover'; import DiscoverPage from 'page/discover';
import SplashScreen from 'component/splash.js'; import SplashScreen from 'component/splash.js';
@ -13,6 +13,7 @@ import RewardsPage from 'page/rewards.js';
import FileListDownloaded from 'page/fileListDownloaded' import FileListDownloaded from 'page/fileListDownloaded'
import FileListPublished from 'page/fileListPublished' import FileListPublished from 'page/fileListPublished'
import ChannelPage from 'page/channel' import ChannelPage from 'page/channel'
import SearchPage from 'page/search'
const route = (page, routesMap) => { const route = (page, routesMap) => {
const component = routesMap[page] const component = routesMap[page]
@ -36,12 +37,13 @@ const Router = (props) => {
'wallet': <WalletPage {...props} />, 'wallet': <WalletPage {...props} />,
'send': <WalletPage {...props} />, 'send': <WalletPage {...props} />,
'receive': <WalletPage {...props} />, 'receive': <WalletPage {...props} />,
'show': <FilePage {...props} />, 'show': <ShowPage {...props} />,
'channel': <ChannelPage {...props} />, 'channel': <ChannelPage {...props} />,
'publish': <PublishPage {...props} />, 'publish': <PublishPage {...props} />,
'developer': <DeveloperPage {...props} />, 'developer': <DeveloperPage {...props} />,
'discover': <DiscoverPage {...props} />, 'discover': <DiscoverPage {...props} />,
'rewards': <RewardsPage {...props} />, 'rewards': <RewardsPage {...props} />,
'search': <SearchPage {...props} />,
}) })
} }

View file

@ -0,0 +1,23 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
selectCurrentPage,
selectHeaderLinks,
} from 'selectors/app'
import {
doNavigate,
} from 'actions/app'
import SubHeader from './view'
const select = (state, props) => ({
currentPage: selectCurrentPage(state),
subLinks: selectHeaderLinks(state),
})
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)),
})
export default connect(select, perform)(SubHeader)

View file

@ -0,0 +1,28 @@
import React from 'react'
const SubHeader = (props) => {
const {
subLinks,
currentPage,
navigate,
modifier,
} = props
const links = []
for(let link of Object.keys(subLinks)) {
links.push(
<a href="#" onClick={() => navigate(link)} key={link} className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected' }>
{subLinks[link]}
</a>
)
}
return (
<nav className={'sub-header' + (modifier ? ' sub-header--' + modifier : '')}>
{links}
</nav>
)
}
export default SubHeader

View file

@ -25,8 +25,8 @@ const perform = (dispatch) => ({
// navigate: (path) => dispatch(doNavigate(path)), // navigate: (path) => dispatch(doNavigate(path)),
onSearch: (query) => dispatch(doSearchContent(query)), onSearch: (query) => dispatch(doSearchContent(query)),
onSubmit: (query) => dispatch(doSearchContent(query)), onSubmit: (query) => dispatch(doSearchContent(query)),
// activateSearch: () => dispatch(doActivateSearch()), activateSearch: () => dispatch(doActivateSearch()),
// deactivateSearch: () => setTimeout(() => { dispatch(doDeactivateSearch()) }, 50), deactivateSearch: () => setTimeout(() => { dispatch(doDeactivateSearch()) }, 50),
}) })
export default connect(select, perform)(Wunderbar) export default connect(select, perform)(Wunderbar)

View file

@ -61,6 +61,8 @@ class WunderBar extends React.PureComponent {
isActive: true isActive: true
} }
this.props.activateSearch()
this._focusPending = true; this._focusPending = true;
//below is hacking, improved when we have proper routing //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 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
@ -71,6 +73,7 @@ class WunderBar extends React.PureComponent {
} }
onBlur() { onBlur() {
this.props.deactivateSearch()
let commonState = {isActive: false}; let commonState = {isActive: false};
if (this._resetOnNextBlur) { if (this._resetOnNextBlur) {
this.setState(Object.assign({}, this._stateBeforeSearch, commonState)); this.setState(Object.assign({}, this._stateBeforeSearch, commonState));

View file

@ -8,6 +8,7 @@ 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';
import FileList from 'component/fileList' import FileList from 'component/fileList'
import SubHeader from 'component/subHeader'
class FileListDownloaded extends React.Component { class FileListDownloaded extends React.Component {
render() { render() {
@ -17,25 +18,21 @@ class FileListDownloaded extends React.Component {
navigate, navigate,
} = this.props } = this.props
let content
if (fetching) { if (fetching) {
return ( content = <BusyMessage message="Loading" />
<main className="page">
<BusyMessage message="Loading" />
</main>
);
} else if (!downloadedContent.length) { } else if (!downloadedContent.length) {
return ( content = <span>You haven't downloaded anything from LBRY yet. Go <Link href="#" onClick={() => navigate('discover')} label="search for your first download" />!</span>
<main className="page">
<span>You haven't downloaded anything from LBRY yet. Go <Link href="#" onClick={() => navigate('discover')} label="search for your first download" />!</span>
</main>
);
} else { } else {
return ( content = <FileList fileInfos={downloadedContent} hidePrices={true} />
<main className="page">
<FileList fileInfos={downloadedContent} hidePrices={true} />
</main>
);
} }
return (
<main className="page">
<SubHeader />
{content}
</main>
)
} }
} }

View file

@ -8,6 +8,7 @@ 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';
import FileList from 'component/fileList' import FileList from 'component/fileList'
import SubHeader from 'component/subHeader'
class FileListPublished extends React.Component { class FileListPublished extends React.Component {
componentDidUpdate() { componentDidUpdate() {
@ -15,17 +16,20 @@ class FileListPublished extends React.Component {
} }
_requestPublishReward() { _requestPublishReward() {
lbryio.call('reward', 'list', {}).then(function(userRewards) { // TODO this is throwing an error now
//already rewarded // Error: LBRY internal API is disabled
if (userRewards.filter(function (reward) { //
return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID // lbryio.call('reward', 'list', {}).then(function(userRewards) {
}).length) { // //already rewarded
return // if (userRewards.filter(function (reward) {
} // return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID
else { // }).length) {
rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {}) // return
} // }
}) // else {
// rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {})
// }
// })
} }
render() { render() {
@ -35,92 +39,22 @@ class FileListPublished extends React.Component {
navigate, navigate,
} = this.props } = this.props
let content
if (fetching) { if (fetching) {
return ( content = <BusyMessage message="Loading" />
<main className="page">
<BusyMessage message="Loading" />
</main>
);
} else if (!publishedContent.length) { } else if (!publishedContent.length) {
return ( content = <span>You haven't downloaded anything from LBRY yet. Go <Link href="#" onClick={() => navigate('discover')} label="search for your first download" />!</span>
<main className="page">
<span>You haven't downloaded anything from LBRY yet. Go <Link href="#" onClick={() => navigate('discover')} label="search for your first download" />!</span>
</main>
);
} else { } else {
return ( content = <FileList fileInfos={publishedContent} hidePrices={true} />
<main className="page">
<FileList fileInfos={publishedContent} hidePrices={true} />
</main>
);
} }
return (
<main className="page">
<SubHeader />
{content}
</main>
)
} }
} }
// const FileListPublished = React.createClass({
// _isMounted: false,
// getInitialState: function () {
// return {
// fileInfos: null,
// };
// },
// _requestPublishReward: function() {
// lbryio.call('reward', 'list', {}).then(function(userRewards) {
// //already rewarded
// if (userRewards.filter(function (reward) {
// return reward.RewardType == rewards.TYPE_FIRST_PUBLISH && reward.TransactionID;
// }).length) {
// return;
// }
// else {
// rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {})
// }
// });
// },
// componentDidMount: function () {
// this._isMounted = true;
// this._requestPublishReward();
// document.title = "Published Files";
// lbry.claim_list_mine().then((claimInfos) => {
// if (!this._isMounted) { return; }
// lbry.file_list().then((fileInfos) => {
// if (!this._isMounted) { return; }
// const myClaimOutpoints = claimInfos.map(({txid, nout}) => txid + ':' + nout);
// this.setState({
// fileInfos: fileInfos.filter(({outpoint}) => myClaimOutpoints.includes(outpoint)),
// });
// });
// });
// },
// componentWillUnmount: function() {
// this._isMounted = false;
// },
// render: function () {
// if (this.state.fileInfos === null) {
// return (
// <main className="page">
// <BusyMessage message="Loading" />
// </main>
// );
// }
// else if (!this.state.fileInfos.length) {
// return (
// <main className="page">
// <span>You haven't published anything to LBRY yet.</span> Try <Link href="?publish" label="publishing" />!
// </main>
// );
// }
// else {
// return (
// <main className="page">
// <FileList fileInfos={this.state.fileInfos} />
// </main>
// );
// }
// }
// });
export default FileListPublished export default FileListPublished

View file

@ -45,323 +45,85 @@ const FormatItem = (props) => {
) )
} }
let FilePage = React.createClass({ const FilePage = (props) => {
_isMounted: false, const {
claim,
navigate,
claim: {
txid,
nout,
has_signature: hasSignature,
signature_is_valid: signatureIsValid,
value,
value: {
stream,
stream: {
metadata,
source,
metadata: {
title,
} = {},
source: {
contentType,
} = {},
} = {},
} = {},
},
uri,
isDownloaded,
fileInfo,
costInfo,
costInfo: {
cost,
includesData: costIncludesData,
} = {},
} = props
propTypes: { const outpoint = txid + ':' + nout;
uri: React.PropTypes.string, const uriLookupComplete = !!claim && Object.keys(claim).length
},
getInitialState: function() { const channelUriObj = lbryuri.parse(uri)
return { delete channelUriObj.path;
cost: null, delete channelUriObj.contentName;
costIncludesData: null, const channelUri = signatureIsValid && hasSignature && channelUriObj.isChannel ? lbryuri.build(channelUriObj, false) : null;
isDownloaded: null, const uriIndicator = <UriIndicator uri={uri} />
};
},
componentWillUnmount: function() { return (
this._isMounted = false; <main className="main--single-column">
}, <section className="show-page-media">
{ contentType && contentType.startsWith('video/') ?
componentWillReceiveProps: function(nextProps) { <Video className="video-embedded" uri={uri} metadata={metadata} outpoint={outpoint} /> :
if (nextProps.outpoint != this.props.outpoint || nextProps.uri != this.props.uri) { (Object.keys(metadata).length > 0 ? <Thumbnail src={metadata.thumbnail} /> : <Thumbnail />) }
this.loadCostAndFileState(nextProps.uri, nextProps.outpoint); </section>
} <section className="card">
},
componentWillMount: function() {
this._isMounted = true;
this.loadCostAndFileState(this.props.uri, this.props.outpoint);
},
loadCostAndFileState: function(uri, outpoint) {
lbry.file_list({outpoint: outpoint}).then((fileInfo) => {
if (this._isMounted) {
this.setState({
isDownloaded: fileInfo.length > 0,
});
}
});
lbry.getCostInfo(uri).then(({cost, includesData}) => {
if (this._isMounted) {
this.setState({
cost: cost,
costIncludesData: includesData,
});
}
});
},
render: function() {
const metadata = this.props.metadata,
title = metadata ? this.props.metadata.title : this.props.uri,
uriIndicator = <UriIndicator uri={uri} hasSignature={hasSignature} signatureIsValid={signatureIsValid} />
return (
<main className="main--single-column">
<section className="show-page-media">
{ this.props.contentType && this.props.contentType.startsWith('video/') ?
<Video className="video-embedded" uri={this.props.uri} metadata={metadata} outpoint={this.props.outpoint} /> :
(metadata ? <Thumbnail src={metadata.thumbnail} /> : <Thumbnail />) }
</section>
<section className="card">
<div className="card__inner">
<div className="card__title-identity">
{isDownloaded === false
? <span style={{float: "right"}}><FilePrice uri={lbryuri.normalize(uri)} metadata={metadata} /></span>
: null}
<h1>{title}</h1>
<div className="card__subtitle">
{ this.props.channelUri ?
<Link href={"?show=" + this.props.channelUri }>{uriIndicator}</Link> :
uriIndicator}
</div>
<div className="card__actions">
<FileActions uri={uri} outpoint={outpoint} metadata={metadata} contentType={contentType} /></div>
</div>
<div className="card__content card__subtext card__subtext card__subtext--allow-newlines">
{metadata.description}
</div>
</div>
{ metadata ?
<div className="card__content">
<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 className="card__content">
<Link href="https://lbry.io/dmca" label="report" className="button-text-help" />
</div>
</section>
</main>
);
}
});
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__inner">
<div className="card__title-identity"><h1>{title}</h1></div> <div className="card__title-identity">
{isDownloaded === false
? <span style={{float: "right"}}><FilePrice uri={lbryuri.normalize(uri)} /></span>
: null}
<h1>{title}</h1>
<div className="card__subtitle">
{ channelUri ?
<Link href={"?show=" + channelUri }>{uriIndicator}</Link> :
uriIndicator}
</div>
<div className="card__actions">
<FileActions uri={uri} /></div>
</div>
<div className="card__content card__subtext card__subtext card__subtext--allow-newlines">
{metadata.description}
</div>
</div> </div>
{ metadata ?
<div className="card__content">
<FormatItem metadata={metadata} contentType={contentType} cost={cost} uri={uri} outpoint={outpoint} costIncludesData={costIncludesData} />
</div> : '' }
<div className="card__content"> <div className="card__content">
{ this.state.uriLookupComplete ? <Link href="https://lbry.io/dmca" label="report" className="button-text-help" />
<p>This location is not yet in use. { ' ' }<Link href="?publish" label="Put something here" />.</p> :
<BusyMessage message="Loading magic decentralized data..." />
}
</div> </div>
</section>; </section>
} else { </main>
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 FilePage;
}
});
export default FilePage;
//
// const ShowPage = (props) => {
// const {
// claim,
// navigate,
// claim: {
// txid,
// nout,
// has_signature: hasSignature,
// signature_is_valid: signatureIsValid,
// value,
// value: {
// stream,
// stream: {
// metadata,
// source,
// metadata: {
// title,
// } = {},
// source: {
// contentType,
// } = {},
// } = {},
// } = {},
// },
// uri,
// isDownloaded,
// fileInfo,
// costInfo,
// costInfo: {
// cost,
// includesData: costIncludesData,
// } = {},
// } = props
//
// const outpoint = txid + ':' + nout;
// const uriLookupComplete = !!claim && Object.keys(claim).length
//
// if (props.isFailed) {
// return (
// <main className="main--single-column">
// <section className="card">
// <div className="card__inner">
// <div className="card__title-identity"><h1>{uri}</h1></div>
// </div>
// <div className="card__content">
// <p>
// This location is not yet in use.
// { ' ' }
// <Link href="#" onClick={() => navigate('publish')} label="Put something here" />.
// </p>
// </div>
// </section>
// </main>
// )
// }
//
// return (
// <main className="main--single-column">
// <section className="show-page-media">
// { contentType && contentType.startsWith('video/') ?
// <Video className="video-embedded" uri={uri} metadata={metadata} outpoint={outpoint} /> :
// (metadata ? <Thumbnail src={metadata.thumbnail} /> : <Thumbnail />) }
// </section>
// <section className="card">
// <div className="card__inner">
// <div className="card__title-identity">
// {isDownloaded === false
// ? <span style={{float: "right"}}><FilePrice uri={lbryuri.normalize(uri)} /></span>
// : null}
// <h1>{title}</h1>
// { uriLookupComplete ?
// <div>
// <div className="card__subtitle">
// <UriIndicator uri={uri} />
// </div>
// <div className="card__actions">
// <FileActions uri={uri} outpoint={outpoint} metadata={metadata} contentType={contentType} />
// </div>
// </div> : '' }
// </div>
// { 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>
// { metadata ?
// <div className="card__content">
// <FormatItem metadata={metadata} contentType={contentType} cost={cost} uri={uri} outpoint={outpoint} costIncludesData={costIncludesData} />
// </div> : '' }
// <div className="card__content">
// <Link href="https://lbry.io/dmca" label="report" className="button-text-help" />
// </div>
// </section>
// </main>
// )
// }

View file

@ -2,7 +2,7 @@
import React from 'react'; import React from 'react';
import lbry from 'lbry.js'; import lbry from 'lbry.js';
import Link from 'component/link'; import Link from 'component/link';
import NavSettings from 'component/navSettings'; import SubHeader from 'component/subHeader'
import {version as uiVersion} from 'json!../../../package.json'; import {version as uiVersion} from 'json!../../../package.json';
var HelpPage = React.createClass({ var HelpPage = React.createClass({
@ -50,7 +50,7 @@ var HelpPage = React.createClass({
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<NavSettings /> <SubHeader />
<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>

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import lbryio from 'lbryio'; import lbryio from 'lbryio';
import {CreditAmount, Icon} from 'component/common.js'; import {CreditAmount, Icon} from 'component/common.js';
import NavWallet from 'component/navWallet'; import SubHeader from 'component/subHeader'
import {RewardLink} from 'component/reward-link'; import {RewardLink} from 'component/reward-link';
const RewardTile = React.createClass({ const RewardTile = React.createClass({
@ -55,7 +55,7 @@ export let RewardsPage = React.createClass({
render: function() { render: function() {
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<NavWallet /> <SubHeader />
<div> <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> : '')

View file

@ -1,165 +0,0 @@
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';
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;

View file

@ -0,0 +1,30 @@
import React from 'react'
import {
connect,
} from 'react-redux'
import {
doSearchContent,
} from 'actions/search'
import {
selectIsSearching,
selectSearchQuery,
selectCurrentSearchResults,
selectSearchActivated,
} from 'selectors/search'
import {
doNavigate,
} from 'actions/app'
import SearchPage from './view'
const select = (state) => ({
isSearching: selectIsSearching(state),
query: selectSearchQuery(state),
results: selectCurrentSearchResults(state),
searchActive: selectSearchActivated(state),
})
const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)),
})
export default connect(select, perform)(SearchPage)

View file

@ -0,0 +1,92 @@
import React from 'react';
import lbry from 'lbry';
import lbryio from 'lbryio';
import lbryuri from 'lbryuri';
import lighthouse from 'lighthouse';
import FileTile from 'component/fileTile'
import FileTileStream from 'component/fileTileStream'
import Link from 'component/link'
import {ToolTip} from 'component/tooltip.js';
import {BusyMessage} from 'component/common.js';
const SearchNoResults = (props) => {
const {
navigate,
query,
} = props
return <section>
<span className="empty">
No one has checked anything in for {query} yet.
<Link label="Be the first" href="#" onClick={() => navigate('publish')} />
</span>
</section>;
}
const SearchResultList = (props) => {
const {
results,
} = props
const rows = [],
seenNames = {}; //fix this when the search API returns claim IDs
for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of results) {
const uri = lbryuri.build({
channelName: channel_name,
contentName: name,
claimId: channel_id || claim_id,
});
rows.push(
<FileTileStream key={uri} uri={uri} />
);
}
return (
<div>{rows}</div>
);
}
const SearchResults = (props) => {
const {
searching,
results,
query,
} = props
return (
searching ?
<BusyMessage message="Looking up the Dewey Decimals" /> :
(results && results.length) ?
<SearchResultList {...props} /> :
<SearchNoResults {...props} />
)
}
const SearchPage = (props) => {
const isValidUri = (query) => true
const {
query,
} = props
return (
<main className="main--single-column">
{ isValidUri(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={query} showEmpty={true} />
</section> : '' }
<section className="section-spaced">
<h3 className="card-row__header">
Search Results for {query}
<ToolTip label="?" body="These search results are provided by LBRY, Inc." className="tooltip--header" />
</h3>
<SearchResults {...props} />
</section>
</main>
)
}
export default SearchPage;

View file

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import {FormField, FormRow} from '../component/form.js'; import {FormField, FormRow} from '../component/form.js';
import NavSettings from 'component/navSettings'; import SubHeader from 'component/subHeader'
import lbry from '../lbry.js'; import lbry from '../lbry.js';
var SettingsPage = React.createClass({ var SettingsPage = React.createClass({
@ -91,7 +91,7 @@ var SettingsPage = React.createClass({
*/ */
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<NavSettings /> <SubHeader />
<section className="card"> <section className="card">
<div className="card__content"> <div className="card__content">
<h3>Download Directory</h3> <h3>Download Directory</h3>

View file

@ -0,0 +1,35 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
selectCurrentUri,
} from 'selectors/app'
import {
selectCurrentUriIsDownloaded,
} from 'selectors/file_info'
import {
selectCurrentUriClaim,
} from 'selectors/claims'
import {
selectCurrentUriFileInfo,
} from 'selectors/file_info'
import {
selectCurrentUriCostInfo,
} from 'selectors/cost_info'
import ShowPage from './view'
const select = (state) => ({
claim: selectCurrentUriClaim(state),
uri: selectCurrentUri(state),
isDownloaded: selectCurrentUriIsDownloaded(state),
fileInfo: selectCurrentUriFileInfo(state),
costInfo: selectCurrentUriCostInfo(state),
isFailed: false,
claimType: 'file',
})
const perform = (dispatch) => ({
})
export default connect(select, perform)(ShowPage)

View file

@ -0,0 +1,72 @@
import React from 'react';
import {
BusyMessage,
} from 'component/common';
import FilePage from 'page/filePage'
const ShowPage = (props) => {
const {
claim,
navigate,
claim: {
txid,
nout,
has_signature: hasSignature,
signature_is_valid: signatureIsValid,
value,
value: {
stream,
stream: {
metadata,
source,
metadata: {
title,
} = {},
source: {
contentType,
} = {},
} = {},
} = {},
},
uri,
isDownloaded,
fileInfo,
costInfo,
costInfo: {
cost,
includesData: costIncludesData,
} = {},
isFailed,
claimType,
} = props
const outpoint = txid + ':' + nout;
const uriLookupComplete = !!claim && Object.keys(claim).length
const pageTitle = metadata ? metadata.title : uri;
let innerContent = "";
if (!uriLookupComplete || isFailed) {
innerContent = <section className="card">
<div className="card__inner">
<div className="card__title-identity"><h1>{pageTitle}</h1></div>
</div>
<div className="card__content">
{ uriLookupComplete ?
<p>This location is not yet in use. { ' ' }<Link href="#" onClick={() => navigate('publish')} label="Put something here" />.</p> :
<BusyMessage message="Loading magic decentralized data..." />
}
</div>
</section>;
} else if (claimType == "channel") {
innerContent = <ChannelPage title={uri} />
} else {
innerContent = <FilePage uri={uri} />
}
return (
<main className="main--single-column">{innerContent}</main>
)
}
export default ShowPage

View file

@ -2,7 +2,7 @@ import React from 'react';
import lbry from 'lbry.js'; import lbry from 'lbry.js';
import Link from 'component/link'; import Link from 'component/link';
import Modal from 'component/modal'; import Modal from 'component/modal';
import NavWallet from 'component/navWallet'; import SubHeader from 'component/subHeader'
import { import {
FormField, FormField,
FormRow FormRow
@ -248,7 +248,7 @@ const WalletPage = (props) => {
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<NavWallet /> <SubHeader />
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>Balance</h3> <h3>Balance</h3>

View file

@ -8,7 +8,6 @@ const defaultState = {
platform: process.platform, platform: process.platform,
upgradeSkipped: sessionStorage.getItem('upgradeSkipped'), upgradeSkipped: sessionStorage.getItem('upgradeSkipped'),
daemonReady: false, daemonReady: false,
platform: window.navigator.platform,
obscureNsfw: !lbry.getClientSetting('showNsfw'), obscureNsfw: !lbry.getClientSetting('showNsfw'),
hidePrice: false, hidePrice: false,
hasSignature: false, hasSignature: false,

View file

@ -1,4 +1,8 @@
import {createSelector} from 'reselect' import {createSelector} from 'reselect'
import {
selectIsSearching,
selectSearchActivated,
} from 'selectors/search'
export const _selectState = state => state.app || {} export const _selectState = state => state.app || {}
@ -14,7 +18,12 @@ export const selectCurrentPath = createSelector(
export const selectCurrentPage = createSelector( export const selectCurrentPage = createSelector(
selectCurrentPath, selectCurrentPath,
(path) => path.split('=')[0] selectSearchActivated,
(path, searchActivated) => {
if (searchActivated) return 'search'
return path.split('=')[0]
}
) )
export const selectCurrentUri = createSelector( export const selectCurrentUri = createSelector(
@ -198,6 +207,12 @@ export const selectHeaderLinks = createSelector(
'downloaded': 'Downloaded', 'downloaded': 'Downloaded',
'published': 'Published', 'published': 'Published',
}; };
case 'settings':
case 'help':
return {
'settings': 'Settings',
'help': 'Help',
}
default: default:
return null; return null;
} }

View file

@ -54,6 +54,7 @@
"node-sass": "^3.13.0", "node-sass": "^3.13.0",
"webpack": "^1.13.3", "webpack": "^1.13.3",
"webpack-dev-server": "^2.4.4", "webpack-dev-server": "^2.4.4",
"webpack-notifier": "^1.5.0",
"webpack-target-electron-renderer": "^0.4.0" "webpack-target-electron-renderer": "^0.4.0"
} }
} }

View file

@ -1,4 +1,6 @@
const path = require('path'); const path = require('path');
const WebpackNotifierPlugin = require('webpack-notifier')
const appPath = path.resolve(__dirname, 'js'); const appPath = path.resolve(__dirname, 'js');
const PATHS = { const PATHS = {
@ -21,6 +23,9 @@ module.exports = {
root: appPath, root: appPath,
extensions: ['', '.js', '.jsx', '.css'], extensions: ['', '.js', '.jsx', '.css'],
}, },
plugins: [
new WebpackNotifierPlugin(),
],
module: { module: {
preLoaders: [ preLoaders: [
{ {