much progress towards a working merge

This commit is contained in:
Jeremy Kauffman 2017-05-03 23:44:08 -04:00
parent d94ef70f60
commit 36a992343f
42 changed files with 713 additions and 932 deletions

View file

@ -12,7 +12,6 @@ Web UI version numbers should always match the corresponding version of LBRY App
* 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.
* Fixed drawer not highlighting selected page.
* You can now make API calls directly on the lbry module, e.g. lbry.peer_list() * You can now make API calls directly on the lbry module, e.g. lbry.peer_list()
* New-style API calls return promises instead of using callbacks * New-style API calls return promises instead of using callbacks
* Wherever possible, use outpoints for unique IDs instead of names or SD hashes * Wherever possible, use outpoints for unique IDs instead of names or SD hashes

View file

@ -32,18 +32,6 @@ export function doNavigate(path) {
export function doLogoClick() { export function doLogoClick() {
} }
export function doOpenDrawer() {
return {
type: types.OPEN_DRAWER
}
}
export function doCloseDrawer() {
return {
type: types.CLOSE_DRAWER
}
}
export function doOpenModal(modal) { export function doOpenModal(modal) {
return { return {
type: types.OPEN_MODAL, type: types.OPEN_MODAL,
@ -59,6 +47,12 @@ export function doCloseModal() {
} }
} }
export function doHistoryBack() {
return {
type: types.HISTORY_BACK
}
}
export function doUpdateDownloadProgress(percent) { export function doUpdateDownloadProgress(percent) {
return { return {
type: types.UPGRADE_DOWNLOAD_PROGRESSED, type: types.UPGRADE_DOWNLOAD_PROGRESSED,
@ -153,12 +147,8 @@ export function doCheckUpgradeAvailable() {
return function(dispatch, getState) { return function(dispatch, getState) {
const state = getState() const state = getState()
lbry.checkNewVersionAvailable(({isAvailable}) => { lbry.getVersionInfo().then(({remoteVersion, upgradeAvailable}) => {
if (!isAvailable) { if (upgradeAvailable) {
return;
}
lbry.getVersionInfo((versionInfo) => {
dispatch({ dispatch({
type: types.UPDATE_VERSION, type: types.UPDATE_VERSION,
data: { data: {
@ -171,7 +161,7 @@ export function doCheckUpgradeAvailable() {
modal: 'upgrade' modal: 'upgrade'
} }
}) })
}); }
}); });
} }
} }

View file

@ -121,7 +121,7 @@ export function doFetchPublishedContent() {
} }
} }
export function doFetchFeaturedContent() { export function doFetchFeaturedUris() {
return function(dispatch, getState) { return function(dispatch, getState) {
const state = getState() const state = getState()
@ -130,11 +130,20 @@ export function doFetchFeaturedContent() {
}) })
const success = ({ Categories, Uris }) => { const success = ({ Categories, Uris }) => {
let featuredUris = {}
Categories.forEach((category) => {
if (Uris[category] && Uris[category].length) {
featuredUris[category] = Uris[category]
}
})
dispatch({ dispatch({
type: types.FETCH_FEATURED_CONTENT_COMPLETED, type: types.FETCH_FEATURED_CONTENT_COMPLETED,
data: { data: {
categories: Categories, categories: Categories,
uris: Uris, uris: featuredUris,
} }
}) })
@ -146,6 +155,13 @@ export function doFetchFeaturedContent() {
} }
const failure = () => { const failure = () => {
dispatch({
type: types.FETCH_FEATURED_CONTENT_COMPLETED,
data: {
categories: [],
uris: {}
}
})
} }
lbryio.call('discover', 'list', { version: "early-access" } ) lbryio.call('discover', 'list', { version: "early-access" } )

View file

@ -41,6 +41,7 @@ export function doGetNewAddress() {
type: types.GET_NEW_ADDRESS_STARTED type: types.GET_NEW_ADDRESS_STARTED
}) })
console.log('do get new address');
lbry.wallet_new_address().then(function(address) { lbry.wallet_new_address().then(function(address) {
localStorage.setItem('wallet_address', address); localStorage.setItem('wallet_address', address);
dispatch({ dispatch({

View file

@ -14,322 +14,4 @@ const app = {
} }
} }
global.app = app; global.app = app;
module.exports = app; module.exports = app;
//
// import React from 'react';
// import {Line} from 'rc-progress';
//
// import lbry from './lbry.js';
// import SettingsPage from './page/settings.js';
// import HelpPage from './page/help.js';
// import WatchPage from './page/watch.js';
// import ReportPage from './page/report.js';
// import StartPage from './page/start.js';
// import RewardsPage from './page/rewards.js';
// import RewardPage from './page/reward.js';
// import WalletPage from './page/wallet.js';
// import ShowPage from './page/show.js';
// import PublishPage from './page/publish.js';
// import SearchPage from './page/search.js';
// import DiscoverPage from './page/discover.js';
// import DeveloperPage from './page/developer.js';
// import lbryuri from './lbryuri.js';
// import {FileListDownloaded, FileListPublished} from './page/file-list.js';
// import Header from './component/header.js';
// import {Modal, ExpandableModal} from './component/modal.js';
// import {Link} from './component/link';
//
//
// const {remote, ipcRenderer, shell} = require('electron');
// const {download} = remote.require('electron-dl');
// const path = require('path');
// const app = require('electron').remote.app;
// const fs = remote.require('fs');
//
//
// var App = React.createClass({
// _error_key_labels: {
// connectionString: 'API connection string',
// method: 'Method',
// params: 'Parameters',
// code: 'Error code',
// message: 'Error message',
// data: 'Error data',
// },
// _fullScreenPages: ['watch'],
// _storeHistoryOfNextRender: false,
//
// _upgradeDownloadItem: null,
// _isMounted: false,
// _version: null,
// getUpdateUrl: function() {
// switch (process.platform) {
// case 'darwin':
// return 'https://lbry.io/get/lbry.dmg';
// case 'linux':
// return 'https://lbry.io/get/lbry.deb';
// case 'win32':
// return 'https://lbry.io/get/lbry.exe';
// default:
// throw 'Unknown platform';
// }
// },
// // Hard code the filenames as a temporary workaround, because
// // electron-dl throws errors when you try to get the filename
// getUpgradeFilename: function() {
// switch (process.platform) {
// case 'darwin':
// return `LBRY-${this._version}.dmg`;
// case 'linux':
// return `LBRY_${this._version}_amd64.deb`;
// case 'windows':
// return `LBRY.Setup.${this._version}.exe`;
// default:
// throw 'Unknown platform';
// }
// },
// getViewingPageAndArgs: function(address) {
// // For now, routes are in format ?page or ?page=args
// let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/);
// return {
// viewingPage: viewingPage,
// pageArgs: pageArgs === undefined ? null : decodeURIComponent(pageArgs)
// };
// },
// getInitialState: function() {
// return Object.assign(this.getViewingPageAndArgs(window.location.search), {
// viewingPage: 'discover',
// appUrl: null,
// errorInfo: null,
// modal: null,
// downloadProgress: null,
// downloadComplete: false,
// });
// },
// componentWillMount: function() {
// window.addEventListener("popstate", this.onHistoryPop);
//
// document.addEventListener('unhandledError', (event) => {
// this.alertError(event.detail);
// });
//
// //open links in external browser and skip full redraw on changing page
// document.addEventListener('click', (event) => {
// var target = event.target;
// while (target && target !== document) {
// if (target.matches('a[href^="http"]')) {
// event.preventDefault();
// shell.openExternal(target.href);
// return;
// }
// if (target.matches('a[href^="?"]')) {
// event.preventDefault();
// if (this._isMounted) {
// let appUrl = target.getAttribute('href');
// this._storeHistoryOfNextRender = true;
// this.setState(Object.assign({}, this.getViewingPageAndArgs(appUrl), { appUrl: appUrl }));
// document.body.scrollTop = 0;
// }
// }
// target = target.parentNode;
// }
// });
//
// if (!sessionStorage.getItem('upgradeSkipped')) {
// lbry.getVersionInfo().then(({remoteVersion, upgradeAvailable}) => {
// if (upgradeAvailable) {
// this._version = remoteVersion;
// this.setState({
// modal: 'upgrade',
// });
// }
// });
// }
// },
// closeModal: function() {
// this.setState({
// modal: null,
// });
// },
// componentDidMount: function() {
// this._isMounted = true;
// },
// componentWillUnmount: function() {
// this._isMounted = false;
// window.removeEventListener("popstate", this.onHistoryPop);
// },
// onHistoryPop: function() {
// this.setState(this.getViewingPageAndArgs(location.search));
// },
// 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() {
// // 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);
//
// let options = {
// onProgress: (p) => this.setState({downloadProgress: Math.round(p * 100)}),
// directory: dir,
// };
// download(remote.getCurrentWindow(), this.getUpdateUrl(), options)
// .then(downloadItem => {
// /**
// * TODO: get the download path directly from the download object. It should just be
// * downloadItem.getSavePath(), but the copy on the main process is being garbage collected
// * too soon.
// */
//
// this._upgradeDownloadItem = downloadItem;
// this._upgradeDownloadPath = path.join(dir, this.getUpgradeFilename());
// this.setState({
// downloadComplete: true
// });
// });
// this.setState({modal: 'downloading'});
// },
// handleStartUpgradeClicked: function() {
// ipcRenderer.send('upgrade', this._upgradeDownloadPath);
// },
// cancelUpgrade: function() {
// if (this._upgradeDownloadItem) {
// /*
// * Right now the remote reference to the download item gets garbage collected as soon as the
// * the download is over (maybe even earlier), so trying to cancel a finished download may
// * throw an error.
// */
// try {
// this._upgradeDownloadItem.cancel();
// } catch (err) {
// // Do nothing
// }
// }
// this.setState({
// downloadProgress: null,
// downloadComplete: false,
// modal: null,
// });
// },
// handleSkipClicked: function() {
// sessionStorage.setItem('upgradeSkipped', true);
// this.setState({
// modal: null,
// });
// },
// alertError: function(error) {
// var errorInfoList = [];
// for (let key of Object.keys(error)) {
// let val = typeof error[key] == 'string' ? error[key] : JSON.stringify(error[key]);
// let label = this._error_key_labels[key];
// errorInfoList.push(<li key={key}><strong>{label}</strong>: <code>{val}</code></li>);
// }
//
// this.setState({
// modal: 'error',
// errorInfo: <ul className="error-modal__error-list">{errorInfoList}</ul>,
// });
// },
// getContentAndAddress: function()
// {
// switch(this.state.viewingPage)
// {
// case 'search':
// return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', <SearchPage query={this.state.pageArgs} />];
// case 'settings':
// return ["Settings", "icon-gear", <SettingsPage />];
// case 'help':
// return ["Help", "icon-question", <HelpPage />];
// case 'report':
// return ['Report an Issue', 'icon-file', <ReportPage />];
// case 'downloaded':
// return ["Downloads & Purchases", "icon-folder", <FileListDownloaded />];
// case 'published':
// return ["Publishes", "icon-folder", <FileListPublished />];
// case 'start':
// return ["Start", "icon-file", <StartPage />];
// case 'rewards':
// return ["Rewards", "icon-bank", <RewardsPage />];
// case 'wallet':
// case 'send':
// case 'receive':
// return [this.state.viewingPage.charAt(0).toUpperCase() + this.state.viewingPage.slice(1), "icon-bank", <WalletPage viewingPage={this.state.viewingPage} />]
// case 'show':
// return [lbryuri.normalize(this.state.pageArgs), "icon-file", <ShowPage uri={this.state.pageArgs} />];
// case 'publish':
// return ["Publish", "icon-upload", <PublishPage />];
// case 'developer':
// return ["Developer", "icon-file", <DeveloperPage />];
// case 'discover':
// default:
// return ["Home", "icon-home", <DiscoverPage />];
// }
// },
// render: function() {
// let [address, wunderBarIcon, mainContent] = this.getContentAndAddress();
//
// lbry.setTitle(address);
//
// if (this._storeHistoryOfNextRender) {
// this._storeHistoryOfNextRender = false;
// history.pushState({}, document.title, this.state.appUrl);
// }
//
// return (
// this._fullScreenPages.includes(this.state.viewingPage) ?
// mainContent :
// <div id="window">
// <Header onSearch={this.onSearch} onSubmit={this.onSubmit} address={address} wunderBarIcon={wunderBarIcon} viewingPage={this.state.viewingPage} />
// <div id="main-content">
// {mainContent}
// </div>
// <Modal isOpen={this.state.modal == 'upgrade'} contentLabel="Update available"
// type="confirm" confirmButtonLabel="Upgrade" abortButtonLabel="Skip"
// onConfirmed={this.handleUpgradeClicked} onAborted={this.handleSkipClicked}>
// Your version of LBRY is out of date and may be unreliable or insecure.
// </Modal>
// <Modal isOpen={this.state.modal == 'downloading'} contentLabel="Downloading Update" type="custom">
// Downloading Update{this.state.downloadProgress ? `: ${this.state.downloadProgress}%` : null}
// <Line percent={this.state.downloadProgress} strokeWidth="4"/>
// {this.state.downloadComplete ? (
// <div>
// <br />
// <p>Click "Begin Upgrade" to start the upgrade process.</p>
// <p>The app will close, and you will be prompted to install the latest version of LBRY.</p>
// <p>After the install is complete, please reopen the app.</p>
// </div>
// ) : null }
// <div className="modal__buttons">
// {this.state.downloadComplete
// ? <Link button="primary" label="Begin Upgrade" className="modal__button" onClick={this.handleStartUpgradeClicked} />
// : null}
// <Link button="alt" label="Cancel" className="modal__button" onClick={this.cancelUpgrade} />
// </div>
// </Modal>
// <ExpandableModal isOpen={this.state.modal == 'error'} contentLabel="Error" className="error-modal"
// overlayClassName="error-modal-overlay" onConfirmed={this.closeModal}
// extraContent={this.state.errorInfo}>
// <h3 className="modal__header">Error</h3>
//
// <div className="error-modal__content">
// <div><img className="error-modal__warning-symbol" src={lbry.imagePath('warning.png')} /></div>
// <p>We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.</p>
// </div>
// </ExpandableModal>
// </div>
// );

View file

@ -10,8 +10,6 @@ import {
} from 'selectors/app' } from 'selectors/app'
import { import {
doCheckUpgradeAvailable, doCheckUpgradeAvailable,
doOpenDrawer,
doCloseDrawer,
doOpenModal, doOpenModal,
doCloseModal, doCloseModal,
doSearch, doSearch,
@ -21,15 +19,12 @@ import App from './view'
const select = (state) => ({ const select = (state) => ({
currentPage: selectCurrentPage(state), currentPage: selectCurrentPage(state),
modal: selectCurrentModal(state), modal: selectCurrentModal(state),
drawerOpen: selectDrawerOpen(state),
headerLinks: selectHeaderLinks(state), headerLinks: selectHeaderLinks(state),
searchTerm: selectSearchTerm(state) searchTerm: selectSearchTerm(state)
}) })
const perform = (dispatch) => ({ const perform = (dispatch) => ({
checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()), checkUpgradeAvailable: () => dispatch(doCheckUpgradeAvailable()),
openDrawer: () => dispatch(doOpenDrawer()),
closeDrawer: () => dispatch(doCloseDrawer()),
openModal: () => dispatch(doOpenModal()), openModal: () => dispatch(doOpenModal()),
closeModal: () => dispatch(doCloseModal()), closeModal: () => dispatch(doCloseModal()),
}) })

View file

@ -1,26 +1,13 @@
import React from 'react' import React from 'react'
import lbry from 'lbry.js';
import Router from 'component/router' import Router from 'component/router'
import Header from 'component/header'; import Header from 'component/header';
import {Modal, ExpandableModal} from 'component/modal.js';
import ErrorModal from 'component/errorModal' import ErrorModal from 'component/errorModal'
import DownloadingModal from 'component/downloadingModal' import DownloadingModal from 'component/downloadingModal'
import UpgradeModal from 'component/upgradeModal' import UpgradeModal from 'component/upgradeModal'
import Link from 'component/link';
import {Line} from 'rc-progress'; import {Line} from 'rc-progress';
const App = React.createClass({ class App extends React.Component {
// Temporary workaround since electron-dl throws errors when you try to get the filename componentWillMount() {
getViewingPageAndArgs: function(address) {
// For now, routes are in format ?page or ?page=args
let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/);
return {
viewingPage: viewingPage,
pageArgs: pageArgs === undefined ? null : pageArgs
};
},
componentWillMount: function() {
document.addEventListener('unhandledError', (event) => { document.addEventListener('unhandledError', (event) => {
this.props.alertError(event.detail); this.props.alertError(event.detail);
}); });
@ -28,24 +15,20 @@ const App = React.createClass({
if (!this.props.upgradeSkipped) { if (!this.props.upgradeSkipped) {
this.props.checkUpgradeAvailable() this.props.checkUpgradeAvailable()
} }
}, }
render: function() {
render() {
const { const {
currentPage, currentPage,
openDrawer,
closeDrawer,
openModal,
closeModal,
modal, modal,
drawerOpen,
headerLinks, headerLinks,
search,
searchTerm, searchTerm,
} = this.props } = this.props
const searchQuery = (currentPage == 'discover' && searchTerm ? searchTerm : '') const searchQuery = (currentPage == 'discover' && searchTerm ? searchTerm : '')
return <div id="window" className={ drawerOpen ? 'drawer-open' : 'drawer-closed' }> return <div id="window">
<Header onOpenDrawer={openDrawer} initialQuery={searchQuery} onSearch={search} links={headerLinks} /> <Header initialQuery={searchQuery} onSearch={() => { alert('header search'); }}
onSubmit={() => { alert('header submit'); }} links={headerLinks} />
<div id="main-content"> <div id="main-content">
<Router /> <Router />
</div> </div>
@ -54,6 +37,6 @@ const App = React.createClass({
{modal == 'error' && <ErrorModal />} {modal == 'error' && <ErrorModal />}
</div> </div>
} }
}); }
export default App export default App

View file

@ -2,7 +2,8 @@ import React from "react";
import lbryio from "../lbryio.js"; import lbryio from "../lbryio.js";
import Modal from "./modal.js"; import Modal from "./modal.js";
import ModalPage from "./modal-page.js"; import ModalPage from "./modal-page.js";
import {Link, RewardLink} from "../component/link"; import Link from "component/link"
import {RewardLink} from 'component/reward-link';
import {FormRow} from "../component/form.js"; import {FormRow} from "../component/form.js";
import {CreditAmount, Address} from "../component/common.js"; import {CreditAmount, Address} from "../component/common.js";
import {getLocal, getSession, setSession, setLocal} from '../utils.js'; import {getLocal, getSession, setSession, setLocal} from '../utils.js';

View file

@ -1,33 +0,0 @@
import React from 'react'
import {
connect
} from 'react-redux'
import Drawer from './view'
import {
doNavigate,
doCloseDrawer,
doLogoClick,
} from 'actions/app'
import {
doUpdateBalance,
} from 'actions/wallet'
import {
selectCurrentPage,
} from 'selectors/app'
import {
selectBalance,
} from 'selectors/wallet'
const select = (state) => ({
currentPage: selectCurrentPage(state),
balance: selectBalance(state),
})
const perform = {
linkClick: doNavigate,
logoClick: doLogoClick,
closeDrawerClick: doCloseDrawer,
updateBalance: doUpdateBalance,
}
export default connect(select, perform)(Drawer)

View file

@ -1,68 +0,0 @@
import lbry from 'lbry.js';
import React from 'react';
import Link from 'component/link';
const DrawerItem = (props) => {
const {
currentPage,
href,
subPages,
badge,
label,
linkClick,
icon,
} = props
const isSelected = (
currentPage == href.substr(0) ||
(subPages && subPages.indexOf(currentPage) != -1)
)
return <Link icon={icon} badge={badge} label={label} onClick={() => linkClick(href)} className={ 'drawer-item ' + (isSelected ? 'drawer-item-selected' : '') } />
}
var drawerImageStyle = { //@TODO: remove this, img should be properly scaled once size is settled
height: '36px'
};
class Drawer extends React.Component {
constructor(props) {
super(props)
this._balanceSubscribeId = null
}
componentDidMount() {
const { updateBalance } = this.props
this._balanceSubscribeId = lbry.balanceSubscribe((balance) => {
updateBalance(balance)
});
}
componentWillUnmount() {
if (this._balanceSubscribeId) {
lbry.balanceUnsubscribe(this._balanceSubscribeId)
}
}
render() {
const {
closeDrawerClick,
logoClick,
balance,
} = this.props
return(<nav id="drawer">
<div id="drawer-handle">
<Link title="Close" onClick={closeDrawerClick} icon="icon-bars" className="close-drawer-link"/>
<a href="discover" onMouseUp={logoClick}><img src={lbry.imagePath("lbry-dark-1600x528.png")} style={drawerImageStyle}/></a>
</div>
<DrawerItem {...this.props} href='discover' label="Discover" icon="icon-search" />
<DrawerItem {...this.props} href='publish' label="Publish" icon="icon-upload" />
<DrawerItem {...this.props} href='downloaded' subPages={['published']} label="My Files" icon='icon-cloud-download' />
<DrawerItem {...this.props} href="wallet" subPages={['send', 'receive', 'claim']} label="My Wallet" badge={lbry.formatCredits(balance) } icon="icon-bank" />
<DrawerItem {...this.props} href='settings' label="Settings" icon='icon-gear' />
<DrawerItem {...this.props} href='help' label="Help" icon='icon-question-circle' />
</nav>)
}
}
export default Drawer;

View file

@ -3,31 +3,21 @@ import {
connect connect
} from 'react-redux' } from 'react-redux'
import { import {
selectCurrentPage, selectBalance
selectHeaderLinks, } from 'selectors/wallet'
selectPageTitle,
} from 'selectors/app'
import { import {
doNavigate, doNavigate,
doHistoryBack,
} from 'actions/app' } from 'actions/app'
import {
doSearchContent,
doActivateSearch,
doDeactivateSearch,
} from 'actions/search'
import Header from './view' import Header from './view'
const select = (state) => ({ const select = (state) => ({
currentPage: selectCurrentPage(state), balance: lbry.formatCredits(selectBalance(state), 1)
subLinks: selectHeaderLinks(state),
pageTitle: selectPageTitle(state),
}) })
const perform = (dispatch) => ({ const perform = (dispatch) => ({
navigate: (path) => dispatch(doNavigate(path)), navigate: (path) => dispatch(doNavigate(path)),
search: (query) => dispatch(doSearchContent(query)), back: () => dispatch(doHistoryBack()),
activateSearch: () => dispatch(doActivateSearch()),
deactivateSearch: () => setTimeout(() => { dispatch(doDeactivateSearch()) }, 50),
}) })
export default connect(select, perform)(Header) export default connect(select, perform)(Header)

View file

@ -1,193 +1,37 @@
import React from 'react'; import React from 'react';
import lbryuri from 'lbryuri.js';
import {Icon, CreditAmount} from 'component/common.js';
import Link from 'component/link'; import Link from 'component/link';
import WunderBar from 'component/wunderbar';
let Header = React.createClass({ export const Header = (props) => {
_balanceSubscribeId: null, const {
_isMounted: false, balance,
back,
propTypes: { navigate
onSearch: React.PropTypes.func.isRequired, } = props
onSubmit: React.PropTypes.func.isRequired
},
getInitialState: function() {
return {
balance: 0
};
},
componentDidMount: function() {
this._isMounted = true;
this._balanceSubscribeId = lbry.balanceSubscribe((balance) => {
if (this._isMounted) {
this.setState({balance: balance});
}
});
},
componentWillUnmount: function() {
this._isMounted = false;
if (this._balanceSubscribeId) {
lbry.balanceUnsubscribe(this._balanceSubscribeId)
}
},
render: function() {
return <header id="header">
<div className="header__item">
<Link onClick={() => { lbry.back() }} button="alt button--flat" icon="icon-arrow-left" />
</div>
<div className="header__item">
<Link href="?discover" button="alt button--flat" icon="icon-home" />
</div>
<div className="header__item header__item--wunderbar">
<WunderBar address={this.props.address} icon={this.props.wunderBarIcon}
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>
</header>
}
});
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) { return <header id="header">
if (nextProps.viewingPage !== this.props.viewingPage || nextProps.address != this.props.address) { <div className="header__item">
this.setState({ address: nextProps.address, icon: nextProps.icon }); <Link onClick={back} button="alt button--flat" icon="icon-arrow-left" />
} </div>
} <div className="header__item">
<Link onClick={() => navigate('discover')} button="alt button--flat" icon="icon-home" />
onFocus() { </div>
this._stateBeforeSearch = this.state; <div className="header__item header__item--wunderbar">
let newState = { <WunderBar/>
icon: "icon-search", </div>
isActive: true <div className="header__item">
} <Link onClick={() => navigate('wallet')} button="text" icon="icon-bank" label={balance} ></Link>
</div>
this._focusPending = true; <div className="header__item">
//below is hacking, improved when we have proper routing <Link onClick={() => navigate('publish')} button="primary button--flat" icon="icon-upload" label="Publish" />
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 </div>
{ <div className="header__item">
newState.address = ''; <Link onClick={() => navigate('downloaded')} button="alt button--flat" icon="icon-folder" />
} </div>
this.setState(newState); <div className="header__item">
} <Link onClick={() => navigate('settings')} button="alt button--flat" icon="icon-gear" />
</div>
onBlur() { </header>
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 default Header; export default Header;

View file

@ -0,0 +1,21 @@
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

@ -0,0 +1,22 @@
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

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

View file

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

View file

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

View file

@ -0,0 +1,13 @@
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,13 +4,15 @@ 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 ShowPage from 'page/showPage'; import FilePage from 'page/filePage';
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';
import DeveloperPage from 'page/developer.js'; import DeveloperPage from 'page/developer.js';
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'
const route = (page, routesMap) => { const route = (page, routesMap) => {
const component = routesMap[page] const component = routesMap[page]
@ -34,10 +36,12 @@ const Router = (props) => {
'wallet': <WalletPage {...props} />, 'wallet': <WalletPage {...props} />,
'send': <WalletPage {...props} />, 'send': <WalletPage {...props} />,
'receive': <WalletPage {...props} />, 'receive': <WalletPage {...props} />,
'show': <ShowPage {...props} />, 'show': <FilePage {...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} />,
}) })
} }

View file

@ -1,22 +0,0 @@
const SubHeader = (props) => {
const {
subLinks,
currentPage,
navigate,
} = props
const links = [],
viewingUrl = '?' + this.props.viewingPage;
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" + (this.props.modifier ? ' sub-header--' + this.props.modifier : '')}>{links}</nav>
)
}

View file

@ -1,12 +0,0 @@
import {SubHeader} from '../component/sub-header.js';
export let WalletNav = React.createClass({
render: function () {
return <SubHeader modifier="constrained" viewingPage={this.props.viewingPage} links={{
'?wallet': 'Overview',
'?send': 'Send',
'?receive': 'Receive',
'?rewards': 'Rewards'
}} />;
}
});

View file

@ -0,0 +1,32 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
selectWunderBarAddress,
selectWunderBarIcon
} from 'selectors/app'
import {
doNavigate,
} from 'actions/app'
import {
doSearchContent,
doActivateSearch,
doDeactivateSearch,
} from 'actions/search'
import Wunderbar from './view'
const select = (state) => ({
address: selectWunderBarAddress(state),
icon: selectWunderBarIcon(state)
})
const perform = (dispatch) => ({
// navigate: (path) => dispatch(doNavigate(path)),
onSearch: (query) => dispatch(doSearchContent(query)),
onSubmit: (query) => dispatch(doSearchContent(query)),
// activateSearch: () => dispatch(doActivateSearch()),
// deactivateSearch: () => setTimeout(() => { dispatch(doDeactivateSearch()) }, 50),
})
export default connect(select, perform)(Wunderbar)

View file

@ -0,0 +1,136 @@
import React from 'react';
import lbryuri from 'lbryuri.js';
import {Icon} from 'component/common.js';
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 default WunderBar;

View file

@ -1,9 +1,7 @@
export const NAVIGATE = 'NAVIGATE' export const NAVIGATE = 'NAVIGATE'
export const OPEN_MODAL = 'OPEN_MODAL' export const OPEN_MODAL = 'OPEN_MODAL'
export const CLOSE_MODAL = 'CLOSE_MODAL' export const CLOSE_MODAL = 'CLOSE_MODAL'
export const HISTORY_BACK = 'HISTORY_BACK'
export const OPEN_DRAWER = 'OPEN_DRAWER'
export const CLOSE_DRAWER = 'CLOSE_DRAWER'
export const DAEMON_READY = 'DAEMON_READY' export const DAEMON_READY = 'DAEMON_READY'

View file

@ -155,10 +155,6 @@ lbry.checkFirstRun = function(callback) {
lbry.call('is_first_run', {}, callback); lbry.call('is_first_run', {}, callback);
} }
lbry.getNewAddress = function(callback) {
lbry.call('wallet_new_address', {}, callback);
}
lbry.getUnusedAddress = function(callback) { lbry.getUnusedAddress = function(callback) {
lbry.call('wallet_unused_address', {}, callback); lbry.call('wallet_unused_address', {}, callback);
} }
@ -174,7 +170,7 @@ lbry.getDaemonSettings = function(callback) {
lbry.setDaemonSettings = function(settings, callback) { lbry.setDaemonSettings = function(settings, callback) {
lbry.call('set_settings', settings, callback); lbry.call('set_settings', settings, callback);
} }
lbry.setDaemonSetting = function(setting, value, callback) { lbry.setDaemonSetting = function(setting, value, callback) {
var setSettingsArgs = {}; var setSettingsArgs = {};
setSettingsArgs[setting] = value; setSettingsArgs[setting] = value;
@ -640,19 +636,19 @@ lbry.claim_list_mine = function(params={}) {
}); });
} }
const claimCacheKey = 'resolve_claim_cache';
lbry._claimCache = getLocal(claimCacheKey, {});
lbry.resolve = function(params={}) { lbry.resolve = function(params={}) {
const claimCacheKey = 'resolve_claim_cache',
claimCache = getSession(claimCacheKey, {})
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
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] !== undefined) { if (params.uri && lbry._claimCache[params.uri] !== undefined) {
resolve(claimCache[params.uri]); resolve(lbry._claimCache[params.uri]);
} else { } else {
lbry.call('resolve', params, function(data) { lbry.call('resolve', params, function(data) {
claimCache[params.uri] = data; lbry._claimCache[params.uri] = data;
setSession(claimCacheKey, claimCache) setLocal(claimCacheKey, lbry._claimCache)
resolve(data) resolve(data)
}, reject) }, reject)
} }
@ -660,20 +656,18 @@ lbry.resolve = function(params={}) {
} }
// Adds caching. // Adds caching.
lbry._settingsPromise = null;
lbry.settings_get = function(params={}) { lbry.settings_get = function(params={}) {
return new Promise((resolve, reject) => { if (params.allow_cached && lbry._settingsPromise) {
if (params.allow_cached) { return lbry._settingsPromise;
const cached = getSession('settings'); }
if (cached) { lbry._settingsPromise = new Promise((resolve, reject) => {
return resolve(cached);
}
}
lbry.call('settings_get', {}, (settings) => { lbry.call('settings_get', {}, (settings) => {
setSession('settings', settings); setSession('settings', settings);
resolve(settings); resolve(settings);
}); }, reject);
}); });
return lbry._settingsPromise;
} }
// lbry.get = function(params={}) { // lbry.get = function(params={}) {

View file

@ -0,0 +1,17 @@
import React from 'react'
import {
connect
} from 'react-redux'
import {
selectCurrentUriTitle,
} from 'selectors/app'
import ChannelPage from './view'
const select = (state) => ({
title: selectCurrentUriTitle(state)
})
const perform = (dispatch) => ({
})
export default connect(select, perform)(ChannelPage)

View file

@ -0,0 +1,22 @@
import React from 'react';
const ChannelPage = (props) => {
const {
title
} = props
return <main className="main--single-column">
<section className="card">
<div className="card__inner">
<div className="card__title-identity"><h1>{title}</h1></div>
</div>
<div className="card__content">
<p>
This channel page is a stub.
</p>
</div>
</section>
</main>
}
export default ChannelPage;

View file

@ -3,7 +3,7 @@ import {
connect connect
} from 'react-redux' } from 'react-redux'
import { import {
selectFeaturedContentByCategory selectFeaturedUris
} from 'selectors/content' } from 'selectors/content'
import { import {
doSearchContent, doSearchContent,
@ -17,11 +17,7 @@ import {
import DiscoverPage from './view' import DiscoverPage from './view'
const select = (state) => ({ const select = (state) => ({
featuredContentByCategory: selectFeaturedContentByCategory(state), featuredUris: selectFeaturedUris(state),
isSearching: selectIsSearching(state),
query: selectSearchQuery(state),
results: selectCurrentSearchResults(state),
searchActive: selectSearchActivated(state),
}) })
const perform = (dispatch) => ({ const perform = (dispatch) => ({

View file

@ -22,44 +22,65 @@ const FeaturedCategory = (props) => {
</div> </div>
} }
let DiscoverPage = React.createClass({ const DiscoverPage = (props) => {
getInitialState: function() { const {
return { featuredUris
featuredUris: {}, } = props
failed: false
}; return <main>{
}, Object.keys(featuredUris).length === 0 ?
componentWillMount: function() { <div className="empty">Failed to load landing content.</div> :
lbryio.call('discover', 'list', { version: "early-access" } ).then(({Categories, Uris}) => { <div>
let featuredUris = {} {
Categories.forEach((category) => { Object.keys(featuredUris).map((category) => {
if (Uris[category] && Uris[category].length) { return featuredUris[category].length ?
featuredUris[category] = Uris[category] <FeaturedCategory key={category} category={category} names={featuredUris[category]} /> :
} '';
}) })
this.setState({ featuredUris: featuredUris }); }
}, () => { </div>
this.setState({ }</main>
failed: true }
})
}); //
}, // let DiscoverPage = React.createClass({
render: function() { // getInitialState: function() {
return <main>{ // return {
this.state.failed ? // featuredUris: {},
<div className="empty">Failed to load landing content.</div> : // failed: false
<div> // };
{ // },
Object.keys(this.state.featuredUris).map((category) => { // componentWillMount: function() {
return this.state.featuredUris[category].length ? // lbryio.call('discover', 'list', { version: "early-access" } ).then(({Categories, Uris}) => {
<FeaturedCategory key={category} category={category} names={this.state.featuredUris[category]} /> : // let featuredUris = {}
''; // Categories.forEach((category) => {
}) // if (Uris[category] && Uris[category].length) {
} // featuredUris[category] = Uris[category]
</div> // }
}</main>; // })
} // this.setState({ featuredUris: featuredUris });
}) // }, () => {
// this.setState({
// failed: true
// })
// });
// },
// render: function() {
// return <main>{
// this.state.failed ?
// <div className="empty">Failed to load landing content.</div> :
// <div>
// {
// Object.keys(this.state.featuredUris).map((category) => {
// return this.state.featuredUris[category].length ?
// <FeaturedCategory key={category} category={category} names={this.state.featuredUris[category]} /> :
// '';
// })
// }
// </div>
// }</main>;
// }
// })
// const DiscoverPage = (props) => { // const DiscoverPage = (props) => {
// const { // const {

View file

@ -17,7 +17,7 @@ import {
import { import {
selectCurrentUriCostInfo, selectCurrentUriCostInfo,
} from 'selectors/cost_info' } from 'selectors/cost_info'
import ShowPage from './view' import FilePage from './view'
const select = (state) => ({ const select = (state) => ({
claim: selectCurrentUriClaim(state), claim: selectCurrentUriClaim(state),
@ -30,4 +30,4 @@ const select = (state) => ({
const perform = (dispatch) => ({ const perform = (dispatch) => ({
}) })
export default connect(select, perform)(ShowPage) export default connect(select, perform)(FilePage)

View file

@ -16,27 +16,20 @@ import UriIndicator from 'component/uriIndicator';
const FormatItem = (props) => { const FormatItem = (props) => {
const { const {
contentType, contentType,
metadata,
metadata: { metadata: {
thumbnail,
author, author,
title,
description,
language, language,
license, license,
}, }
cost,
uri,
outpoint,
costIncludesData,
} = props } = props
const mediaType = lbry.getMediaType(contentType); const mediaType = lbry.getMediaType(contentType);
return ( return (
<table className="table-standard"> <table className="table-standard">
<tbody> <tbody>
<tr> <tr>
<td>Content-Type</td><td>{contentType}</td> <td>Content-Type</td><td>{mediaType}</td>
</tr> </tr>
<tr> <tr>
<td>Author</td><td>{author}</td> <td>Author</td><td>{author}</td>
@ -52,23 +45,6 @@ const FormatItem = (props) => {
) )
} }
let ChannelPage = React.createClass({
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({ let FilePage = React.createClass({
_isMounted: false, _isMounted: false,
@ -267,8 +243,6 @@ let ShowPage = React.createClass({
} }
</div> </div>
</section>; </section>;
} else if (this.state.claimType == "channel") {
innerContent = <ChannelPage title={this._uri} />
} else { } else {
let channelUriObj = lbryuri.parse(this._uri) let channelUriObj = lbryuri.parse(this._uri)
delete channelUriObj.path; delete channelUriObj.path;
@ -289,4 +263,105 @@ let ShowPage = React.createClass({
} }
}); });
export default ShowPage; 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,6 +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 {version as uiVersion} from 'json!../../../package.json'; import {version as uiVersion} from 'json!../../../package.json';
var HelpPage = React.createClass({ var HelpPage = React.createClass({
@ -49,58 +50,71 @@ var HelpPage = React.createClass({
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<NavSettings />
<section className="card"> <section className="card">
<h3>Read the FAQ</h3> <div className="card__title-primary">
<p>Our FAQ answers many common questions.</p> <h3>Read the FAQ</h3>
<p><Link href="https://lbry.io/faq" label="Read the FAQ" icon="icon-question" button="alt"/></p> </div>
<div className="card__content">
<p>Our FAQ answers many common questions.</p>
<p><Link href="https://lbry.io/faq" label="Read the FAQ" icon="icon-question" button="alt"/></p>
</div>
</section> </section>
<section className="card"> <section className="card">
<h3>Get Live Help</h3> <div className="card__title-primary">
<p> <h3>Get Live Help</h3>
Live help is available most hours in the <strong>#help</strong> channel of our Slack chat room. </div>
</p> <div className="card__content">
<p> <p>
<Link button="alt" label="Join Our Slack" icon="icon-slack" href="https://slack.lbry.io" /> Live help is available most hours in the <strong>#help</strong> channel of our Slack chat room.
</p> </p>
<p>
<Link button="alt" label="Join Our Slack" icon="icon-slack" href="https://slack.lbry.io" />
</p>
</div>
</section> </section>
<section className="card"> <section className="card">
<h3>Report a Bug</h3> <div className="card__title-primary"><h3>Report a Bug</h3></div>
<p>Did you find something wrong?</p> <div className="card__content">
<p><Link href="?report" label="Submit a Bug Report" icon="icon-bug" button="alt" /></p> <p>Did you find something wrong?</p>
<div className="meta">Thanks! LBRY is made by its users.</div> <p><Link href="?report" label="Submit a Bug Report" icon="icon-bug" button="alt" /></p>
<div className="meta">Thanks! LBRY is made by its users.</div>
</div>
</section> </section>
{!ver ? null : {!ver ? null :
<section className="card"> <section className="card">
<h3>About</h3> <div className="card__title-primary"><h3>About</h3></div>
{ver.lbrynet_update_available || ver.lbryum_update_available ? <div className="card__content">
{ver.lbrynet_update_available || ver.lbryum_update_available ?
<p>A newer version of LBRY is available. <Link href={newVerLink} label={`Download LBRY ${ver.remote_lbrynet} now!`} /></p> <p>A newer version of LBRY is available. <Link href={newVerLink} label={`Download LBRY ${ver.remote_lbrynet} now!`} /></p>
: <p>Your copy of LBRY is up to date.</p> : <p>Your copy of LBRY is up to date.</p>
} }
<table className="table-standard"> <table className="table-standard">
<tbody> <tbody>
<tr> <tr>
<th>daemon (lbrynet)</th> <th>daemon (lbrynet)</th>
<td>{ver.lbrynet_version}</td> <td>{ver.lbrynet_version}</td>
</tr> </tr>
<tr> <tr>
<th>wallet (lbryum)</th> <th>wallet (lbryum)</th>
<td>{ver.lbryum_version}</td> <td>{ver.lbryum_version}</td>
</tr> </tr>
<tr> <tr>
<th>interface</th> <th>interface</th>
<td>{uiVersion}</td> <td>{uiVersion}</td>
</tr> </tr>
<tr> <tr>
<th>Platform</th> <th>Platform</th>
<td>{platform}</td> <td>{platform}</td>
</tr> </tr>
<tr> <tr>
<th>Installation ID</th> <th>Installation ID</th>
<td>{this.state.lbryId}</td> <td>{this.state.lbryId}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</section> </div>
</section>
} }
</main> </main>
); );

View file

@ -1,10 +1,7 @@
import React from 'react'; import React from 'react';
import lbry from 'lbry';
import lbryio from 'lbryio'; import lbryio from 'lbryio';
import {CreditAmount, Icon} from 'component/common.js'; import {CreditAmount, Icon} from 'component/common.js';
import rewards from 'rewards'; import NavWallet from 'component/navWallet';
import Modal from 'component/modal';
import {WalletNav} from 'component/wallet-nav ewar';
import {RewardLink} from 'component/reward-link'; import {RewardLink} from 'component/reward-link';
const RewardTile = React.createClass({ const RewardTile = React.createClass({
@ -36,7 +33,7 @@ const RewardTile = React.createClass({
} }
}); });
var RewardsPage = React.createClass({ export let RewardsPage = React.createClass({
componentWillMount: function() { componentWillMount: function() {
this.loadRewards() this.loadRewards()
}, },
@ -58,7 +55,7 @@ var RewardsPage = React.createClass({
render: function() { render: function() {
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<WalletNav viewingPage="rewards"/> <NavWallet />
<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,17 +1,8 @@
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/sub-header.js'; import NavSettings from 'component/navSettings';
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.
@ -100,7 +91,7 @@ var SettingsPage = React.createClass({
*/ */
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<SettingsNav viewingPage="settings" /> <NavSettings />
<section className="card"> <section className="card">
<div className="card__content"> <div className="card__content">
<h3>Download Directory</h3> <h3>Download Directory</h3>

View file

@ -2,6 +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 { import {
FormField, FormField,
FormRow FormRow
@ -247,6 +248,7 @@ const WalletPage = (props) => {
return ( return (
<main className="main--single-column"> <main className="main--single-column">
<NavWallet />
<section className="card"> <section className="card">
<div className="card__title-primary"> <div className="card__title-primary">
<h3>Balance</h3> <h3>Balance</h3>

View file

@ -6,7 +6,6 @@ const defaultState = {
isLoaded: false, isLoaded: false,
currentPath: 'discover', currentPath: 'discover',
platform: process.platform, platform: process.platform,
drawerOpen: sessionStorage.getItem('drawerOpen') || true,
upgradeSkipped: sessionStorage.getItem('upgradeSkipped'), upgradeSkipped: sessionStorage.getItem('upgradeSkipped'),
daemonReady: false, daemonReady: false,
platform: window.navigator.platform, platform: window.navigator.platform,
@ -84,20 +83,6 @@ reducers[types.CLOSE_MODAL] = function(state, action) {
}) })
} }
reducers[types.OPEN_DRAWER] = function(state, action) {
sessionStorage.setItem('drawerOpen', false)
return Object.assign({}, state, {
drawerOpen: true
})
}
reducers[types.CLOSE_DRAWER] = function(state, action) {
sessionStorage.setItem('drawerOpen', false)
return Object.assign({}, state, {
drawerOpen: false
})
}
reducers[types.UPGRADE_DOWNLOAD_PROGRESSED] = function(state, action) { reducers[types.UPGRADE_DOWNLOAD_PROGRESSED] = function(state, action) {
return Object.assign({}, state, { return Object.assign({}, state, {
downloadProgress: action.data.percent downloadProgress: action.data.percent

View file

@ -12,15 +12,15 @@ reducers[types.FETCH_FEATURED_CONTENT_STARTED] = function(state, action) {
reducers[types.FETCH_FEATURED_CONTENT_COMPLETED] = function(state, action) { reducers[types.FETCH_FEATURED_CONTENT_COMPLETED] = function(state, action) {
const { const {
uris uris,
success
} = action.data } = action.data
const newFeaturedContent = Object.assign({}, state.featuredContent, {
byCategory: uris,
})
return Object.assign({}, state, { return Object.assign({}, state, {
fetchingFeaturedContent: false, fetchingFeaturedContent: false,
featuredContent: newFeaturedContent fetchingFeaturedContentFailed: !success,
featuredUris: uris
}) })
} }

View file

@ -56,7 +56,7 @@ rewards.claimReward = function (type) {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
lbry.wallet_new_address().then((address) => { lbry.wallet_unused_address().then((address) => {
const params = { const params = {
reward_type: type, reward_type: type,
wallet_address: address, wallet_address: address,

View file

@ -1,4 +1,4 @@
import { createSelector } from 'reselect' import {createSelector} from 'reselect'
export const _selectState = state => state.app || {} export const _selectState = state => state.app || {}
@ -22,35 +22,98 @@ export const selectCurrentUri = createSelector(
(path) => { (path) => {
if (path.match(/=/)) { if (path.match(/=/)) {
return path.split('=')[1] return path.split('=')[1]
} else { }
else {
return undefined return undefined
} }
} }
) )
export const selectCurrentUriTitle = createSelector(
_selectState,
(state) => "fix me"
)
export const selectPageTitle = createSelector( export const selectPageTitle = createSelector(
selectCurrentPage, selectCurrentPage,
selectCurrentUri, selectCurrentUri,
(page, uri) => { (page, uri) => {
switch(page) switch (page) {
{ case 'search':
case 'discover': return 'Search'
return 'Discover' case 'settings':
return 'Settings'
case 'help':
return 'Help'
case 'report':
return 'Report'
case 'wallet': case 'wallet':
case 'send': case 'send':
case 'receive': case 'receive':
case 'rewards': case 'rewards':
return 'Wallet' return page.charAt(0).toUpperCase() + page.slice(1)
case 'show':
return lbryuri.normalize(page)
case 'downloaded': case 'downloaded':
return 'My Files' return 'Downloads & Purchases'
case 'published': case 'published':
return 'My Files' return 'Publishes'
case 'start':
return 'Start'
case 'publish': case 'publish':
return 'Publish' return 'Publish'
case 'help': case 'help':
return 'Help' return 'Help'
case 'developer':
return 'Developer'
case 'discover':
return 'Home'
default: default:
return 'LBRY'; return '';
}
}
)
export const selectWunderBarAddress = createSelector(
selectPageTitle,
(title) => title
)
export const selectWunderBarIcon = createSelector(
selectCurrentPage,
selectCurrentUri,
(page, uri) => {
switch (page) {
case 'search':
return 'icon-search'
case 'settings':
return 'icon-gear'
case 'help':
return 'icon-question'
case 'report':
return 'icon-file'
case 'downloaded':
return 'icon-folder'
case 'published':
return 'icon-folder'
case 'start':
return 'icon-file'
case 'rewards':
return 'icon-bank'
case 'wallet':
case 'send':
case 'receive':
return 'icon-bank'
case 'show':
return 'icon-file'
case 'publish':
return 'icon-upload'
case 'developer':
return 'icon-file'
case 'developer':
return 'icon-code'
case 'discover':
return 'icon-home'
} }
} }
) )
@ -115,24 +178,18 @@ export const selectDownloadComplete = createSelector(
(state) => state.upgradeDownloadCompleted (state) => state.upgradeDownloadCompleted
) )
export const selectDrawerOpen = createSelector(
_selectState,
(state) => state.drawerOpen
)
export const selectHeaderLinks = createSelector( export const selectHeaderLinks = createSelector(
selectCurrentPage, selectCurrentPage,
(page) => { (page) => {
switch(page) switch (page) {
{
case 'wallet': case 'wallet':
case 'send': case 'send':
case 'receive': case 'receive':
case 'rewards': case 'rewards':
return { return {
'wallet' : 'Overview', 'wallet': 'Overview',
'send' : 'Send', 'send': 'Send',
'receive' : 'Receive', 'receive': 'Receive',
'rewards': 'Rewards', 'rewards': 'Rewards',
}; };
case 'downloaded': case 'downloaded':

View file

@ -7,26 +7,21 @@ import {
export const _selectState = state => state.content || {} export const _selectState = state => state.content || {}
export const selectFeaturedContent = createSelector( export const selectFeaturedUris = createSelector(
_selectState, _selectState,
(state) => state.featuredContent || {} (state) => state.featuredUris || {}
) )
export const selectFeaturedContentByCategory = createSelector( export const selectFetchingFeaturedUris = createSelector(
selectFeaturedContent,
(featuredContent) => featuredContent.byCategory || {}
)
export const selectFetchingFeaturedContent = createSelector(
_selectState, _selectState,
(state) => !!state.fetchingFeaturedContent (state) => !!state.fetchingFeaturedContent
) )
export const shouldFetchFeaturedContent = createSelector( export const shouldFetchFeaturedUris = createSelector(
selectDaemonReady, selectDaemonReady,
selectCurrentPage, selectCurrentPage,
selectFetchingFeaturedContent, selectFetchingFeaturedUris,
selectFeaturedContentByCategory, selectFeaturedUris,
(daemonReady, page, fetching, byCategory) => { (daemonReady, page, fetching, byCategory) => {
if (!daemonReady) return false if (!daemonReady) return false
if (page != 'discover') return false if (page != 'discover') return false

View file

@ -3,7 +3,7 @@ import {
shouldGetReceiveAddress, shouldGetReceiveAddress,
} from 'selectors/wallet' } from 'selectors/wallet'
import { import {
shouldFetchFeaturedContent, shouldFetchFeaturedUris,
shouldFetchDownloadedContent, shouldFetchDownloadedContent,
shouldFetchPublishedContent, shouldFetchPublishedContent,
} from 'selectors/content' } from 'selectors/content'
@ -21,7 +21,7 @@ import {
doGetNewAddress, doGetNewAddress,
} from 'actions/wallet' } from 'actions/wallet'
import { import {
doFetchFeaturedContent, doFetchFeaturedUris,
doFetchDownloadedContent, doFetchDownloadedContent,
doFetchPublishedContent, doFetchPublishedContent,
} from 'actions/content' } from 'actions/content'
@ -48,8 +48,8 @@ triggers.push({
}) })
triggers.push({ triggers.push({
selector: shouldFetchFeaturedContent, selector: shouldFetchFeaturedUris,
action: doFetchFeaturedContent, action: doFetchFeaturedUris,
}) })
triggers.push({ triggers.push({

View file

@ -60,11 +60,9 @@ nav.sub-header
{ {
text-transform: uppercase; text-transform: uppercase;
padding: 0 0 $spacing-vertical; padding: 0 0 $spacing-vertical;
&.sub-header--constrained { max-width: $width-page-constrained;
max-width: $width-page-constrained; margin-left: auto;
margin-left: auto; margin-right: auto;
margin-right: auto;
}
> a > a
{ {
$sub-header-selected-underline-height: 2px; $sub-header-selected-underline-height: 2px;