make page changes fast #25
12 changed files with 111 additions and 41 deletions
|
@ -8,6 +8,10 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
|||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
* 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.
|
||||
* 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()
|
||||
* New-style API calls return promises instead of using callbacks
|
||||
* Wherever possible, use outpoints for unique IDs instead of names or SD hashes
|
|
@ -61,7 +61,7 @@ function getPidsForProcessName(name) {
|
|||
}
|
||||
|
||||
function createWindow () {
|
||||
win = new BrowserWindow({backgroundColor: '#155b4a'})
|
||||
win = new BrowserWindow({backgroundColor: '#155B4A'}) //$color-primary
|
||||
win.maximize()
|
||||
//win.webContents.openDevTools()
|
||||
win.loadURL(`file://${__dirname}/dist/index.html`)
|
||||
|
|
43
ui/js/app.js
43
ui/js/app.js
|
@ -40,8 +40,15 @@ var App = React.createClass({
|
|||
},
|
||||
|
||||
_upgradeDownloadItem: null,
|
||||
_isMounted: false,
|
||||
_version: null,
|
||||
|
||||
// Temporary workaround since electron-dl throws errors when you try to get the filename
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
address: window.location.search
|
||||
};
|
||||
},
|
||||
getUpdateUrl: function() {
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
|
@ -68,31 +75,33 @@ var App = React.createClass({
|
|||
throw 'Unknown platform';
|
||||
}
|
||||
},
|
||||
getInitialState: function() {
|
||||
getViewingPageAndArgs: function(address) {
|
||||
// For now, routes are in format ?page or ?page=args
|
||||
var match, param, val, viewingPage,
|
||||
drawerOpenRaw = sessionStorage.getItem('drawerOpen');
|
||||
|
||||
[match, viewingPage, val] = window.location.search.match(/\??([^=]*)(?:=(.*))?/);
|
||||
|
||||
|
||||
let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/);
|
||||
return {
|
||||
viewingPage: viewingPage,
|
||||
pageArgs: pageArgs === undefined ? null : pageArgs
|
||||
};
|
||||
},
|
||||
getInitialState: function() {
|
||||
var match, param, val, viewingPage, pageArgs,
|
||||
drawerOpenRaw = sessionStorage.getItem('drawerOpen');
|
||||
|
||||
return Object.assign(this.getViewingPageAndArgs(this.props.address), {
|
||||
drawerOpen: drawerOpenRaw !== null ? JSON.parse(drawerOpenRaw) : true,
|
||||
pageArgs: typeof val !== 'undefined' ? val : null,
|
||||
errorInfo: null,
|
||||
modal: null,
|
||||
downloadProgress: null,
|
||||
downloadComplete: false,
|
||||
};
|
||||
});
|
||||
},
|
||||
componentWillMount: function() {
|
||||
document.addEventListener('unhandledError', (event) => {
|
||||
this.alertError(event.detail);
|
||||
});
|
||||
|
||||
//open links in external browser
|
||||
document.addEventListener('click', function(event) {
|
||||
//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"]')) {
|
||||
|
@ -100,6 +109,12 @@ var App = React.createClass({
|
|||
shell.openExternal(target.href);
|
||||
return;
|
||||
}
|
||||
if (target.matches('a[href^="?"]')) {
|
||||
event.preventDefault();
|
||||
if (this._isMounted) {
|
||||
this.setState(this.getViewingPageAndArgs(target.getAttribute('href')));
|
||||
}
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
});
|
||||
|
@ -132,6 +147,12 @@ var App = React.createClass({
|
|||
modal: null,
|
||||
});
|
||||
},
|
||||
componentDidMount: function() {
|
||||
this._isMounted = true;
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
this._isMounted = false;
|
||||
},
|
||||
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);
|
||||
|
|
|
@ -9,7 +9,7 @@ var DrawerItem = React.createClass({
|
|||
};
|
||||
},
|
||||
render: function() {
|
||||
var isSelected = (this.props.viewingPage == this.props.href.substr(2) ||
|
||||
var isSelected = (this.props.viewingPage == this.props.href.substr(1) ||
|
||||
this.props.subPages.indexOf(this.props.viewingPage) != -1);
|
||||
return <Link {...this.props} className={ 'drawer-item ' + (isSelected ? 'drawer-item-selected' : '') } />
|
||||
}
|
||||
|
@ -20,9 +20,11 @@ var drawerImageStyle = { //@TODO: remove this, img should be properly scaled onc
|
|||
};
|
||||
|
||||
var Drawer = React.createClass({
|
||||
_balanceSubscribeId: null,
|
||||
|
||||
handleLogoClicked: function(event) {
|
||||
if ((event.ctrlKey || event.metaKey) && event.shiftKey) {
|
||||
window.location.href = 'index.html?developer'
|
||||
window.location.href = '?developer'
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
|
@ -32,25 +34,30 @@ var Drawer = React.createClass({
|
|||
};
|
||||
},
|
||||
componentDidMount: function() {
|
||||
lbry.getBalance(function(balance) {
|
||||
this._balanceSubscribeId = lbry.balanceSubscribe(function(balance) {
|
||||
this.setState({
|
||||
balance: balance
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
if (this._balanceSubscribeId) {
|
||||
lbry.balanceUnsubscribe(this._balanceSubscribeId)
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<nav id="drawer">
|
||||
<div id="drawer-handle">
|
||||
<Link title="Close" onClick={this.props.onCloseDrawer} icon="icon-bars" className="close-drawer-link"/>
|
||||
<a href="index.html?discover" onMouseUp={this.handleLogoClicked}><img src={lbry.imagePath("lbry-dark-1600x528.png")} style={drawerImageStyle}/></a>
|
||||
<a href="?discover" onMouseUp={this.handleLogoClicked}><img src={lbry.imagePath("lbry-dark-1600x528.png")} style={drawerImageStyle}/></a>
|
||||
</div>
|
||||
<DrawerItem href='index.html?discover' viewingPage={this.props.viewingPage} label="Discover" icon="icon-search" />
|
||||
<DrawerItem href='index.html?publish' viewingPage={this.props.viewingPage} label="Publish" icon="icon-upload" />
|
||||
<DrawerItem href='index.html?downloaded' subPages={['published']} viewingPage={this.props.viewingPage} label="My Files" icon='icon-cloud-download' />
|
||||
<DrawerItem href="index.html?wallet" subPages={['send', 'receive', 'claim', 'referral']} viewingPage={this.props.viewingPage} label="My Wallet" badge={lbry.formatCredits(this.state.balance) } icon="icon-bank" />
|
||||
<DrawerItem href='index.html?settings' viewingPage={this.props.viewingPage} label="Settings" icon='icon-gear' />
|
||||
<DrawerItem href='index.html?help' viewingPage={this.props.viewingPage} label="Help" icon='icon-question-circle' />
|
||||
<DrawerItem href='?discover' viewingPage={this.props.viewingPage} label="Discover" icon="icon-search" />
|
||||
<DrawerItem href='?publish' viewingPage={this.props.viewingPage} label="Publish" icon="icon-upload" />
|
||||
<DrawerItem href='?downloaded' subPages={['published']} viewingPage={this.props.viewingPage} label="My Files" icon='icon-cloud-download' />
|
||||
<DrawerItem href="?wallet" subPages={['send', 'receive', 'claim', 'referral']} viewingPage={this.props.viewingPage} label="My Wallet" badge={lbry.formatCredits(this.state.balance) } icon="icon-bank" />
|
||||
<DrawerItem href='?settings' viewingPage={this.props.viewingPage} label="Settings" icon='icon-gear' />
|
||||
<DrawerItem href='?help' viewingPage={this.props.viewingPage} label="Help" icon='icon-question-circle' />
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -253,7 +253,7 @@ export let FileActions = React.createClass({
|
|||
if (this.isMounted) {
|
||||
this.setState({
|
||||
fileInfo: fileInfo,
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
|
@ -276,6 +276,9 @@ export let FileActions = React.createClass({
|
|||
},
|
||||
componentWillUnmount: function() {
|
||||
this._isMounted = false;
|
||||
if (this._fileInfoSubscribeId) {
|
||||
lbry.fileInfoUnsubscribe(this.props.outpoint, this._fileInfoSubscribeId);
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
const fileInfo = this.state.fileInfo;
|
||||
|
|
|
@ -79,7 +79,7 @@ export let FileTileStream = React.createClass({
|
|||
componentDidMount: function() {
|
||||
this._isMounted = true;
|
||||
if (this.props.hideOnRemove) {
|
||||
lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
|
||||
this._fileInfoSubscribeId = lbry.fileInfoSubscribe(this.props.outpoint, this.onFileInfoUpdate);
|
||||
}
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
|
@ -121,15 +121,15 @@ export let FileTileStream = React.createClass({
|
|||
<section className={ 'file-tile card ' + (obscureNsfw ? 'card-obscured ' : '') } onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<div className={"row-fluid card-content file-tile__row"}>
|
||||
<div className="span3">
|
||||
<a href={'/?show=' + this.props.name}><Thumbnail className="file-tile__thumbnail" src={metadata.thumbnail} alt={'Photo for ' + (title || this.props.name)} /></a>
|
||||
<a href={'?show=' + this.props.name}><Thumbnail className="file-tile__thumbnail" src={metadata.thumbnail} alt={'Photo for ' + (title || this.props.name)} /></a>
|
||||
</div>
|
||||
<div className="span9">
|
||||
{ !this.props.hidePrice
|
||||
? <FilePrice name={this.props.name} />
|
||||
: null}
|
||||
<div className="meta"><a href={'index.html?show=' + this.props.name}>{'lbry://' + this.props.name}</a></div>
|
||||
<div className="meta"><a href={'?show=' + this.props.name}>{'lbry://' + this.props.name}</a></div>
|
||||
<h3 className="file-tile__title">
|
||||
<a href={'index.html?show=' + this.props.name}>
|
||||
<a href={'?show=' + this.props.name}>
|
||||
<TruncatedText lines={1}>
|
||||
{title}
|
||||
</TruncatedText>
|
||||
|
|
|
@ -223,7 +223,7 @@ lbry.stopFile = function(name, callback) {
|
|||
|
||||
lbry.removeFile = function(outpoint, deleteTargetFile=true, callback) {
|
||||
this._removedFiles.push(outpoint);
|
||||
this._updateSubscribedFileInfo(outpoint);
|
||||
this._updateFileInfoSubscribers(outpoint);
|
||||
|
||||
lbry.file_delete({
|
||||
outpoint: outpoint,
|
||||
|
@ -405,9 +405,11 @@ lbry.stop = function(callback) {
|
|||
};
|
||||
|
||||
lbry.fileInfo = {};
|
||||
lbry._fileInfoSubscribeIdCounter = 0;
|
||||
lbry._subscribeIdCount = 0;
|
||||
lbry._fileInfoSubscribeCallbacks = {};
|
||||
lbry._fileInfoSubscribeInterval = 5000;
|
||||
lbry._balanceSubscribeCallbacks = {};
|
||||
lbry._balanceSubscribeInterval = 5000;
|
||||
lbry._removedFiles = [];
|
||||
lbry._claimIdOwnershipCache = {};
|
||||
|
||||
|
@ -419,9 +421,9 @@ lbry._updateClaimOwnershipCache = function(claimId) {
|
|||
});
|
||||
};
|
||||
|
||||
lbry._updateSubscribedFileInfo = function(outpoint) {
|
||||
lbry._updateFileInfoSubscribers = function(outpoint) {
|
||||
const callSubscribedCallbacks = (outpoint, fileInfo) => {
|
||||
for (let [subscribeId, callback] of Object.entries(this._fileInfoSubscribeCallbacks[outpoint])) {
|
||||
for (let callback of Object.values(this._fileInfoSubscribeCallbacks[outpoint])) {
|
||||
callback(fileInfo);
|
||||
}
|
||||
}
|
||||
|
@ -446,7 +448,7 @@ lbry._updateSubscribedFileInfo = function(outpoint) {
|
|||
|
||||
if (Object.keys(this._fileInfoSubscribeCallbacks[outpoint]).length) {
|
||||
setTimeout(() => {
|
||||
this._updateSubscribedFileInfo(outpoint);
|
||||
this._updateFileInfoSubscribers(outpoint);
|
||||
}, lbry._fileInfoSubscribeInterval);
|
||||
}
|
||||
}
|
||||
|
@ -457,14 +459,39 @@ lbry.fileInfoSubscribe = function(outpoint, callback) {
|
|||
lbry._fileInfoSubscribeCallbacks[outpoint] = {};
|
||||
}
|
||||
|
||||
const subscribeId = ++lbry._fileInfoSubscribeIdCounter;
|
||||
const subscribeId = ++lbry._subscribeIdCount;
|
||||
lbry._fileInfoSubscribeCallbacks[outpoint][subscribeId] = callback;
|
||||
lbry._updateSubscribedFileInfo(outpoint);
|
||||
lbry._updateFileInfoSubscribers(outpoint);
|
||||
return subscribeId;
|
||||
}
|
||||
|
||||
lbry.fileInfoUnsubscribe = function(name, subscribeId) {
|
||||
delete lbry._fileInfoSubscribeCallbacks[name][subscribeId];
|
||||
lbry.fileInfoUnsubscribe = function(outpoint, subscribeId) {
|
||||
delete lbry._fileInfoSubscribeCallbacks[outpoint][subscribeId];
|
||||
}
|
||||
|
||||
lbry._updateBalanceSubscribers = function() {
|
||||
lbry.get_balance().then(function(balance) {
|
||||
for (let callback of Object.values(lbry._balanceSubscribeCallbacks)) {
|
||||
callback(balance);
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(lbry._balanceSubscribeCallbacks).length) {
|
||||
setTimeout(() => {
|
||||
lbry._updateBalanceSubscribers();
|
||||
}, lbry._balanceSubscribeInterval);
|
||||
}
|
||||
}
|
||||
|
||||
lbry.balanceSubscribe = function(callback) {
|
||||
const subscribeId = ++lbry._subscribeIdCount;
|
||||
lbry._balanceSubscribeCallbacks[subscribeId] = callback;
|
||||
lbry._updateBalanceSubscribers();
|
||||
return subscribeId;
|
||||
}
|
||||
|
||||
lbry.balanceUnsubscribe = function(subscribeId) {
|
||||
delete lbry._balanceSubscribeCallbacks[subscribeId];
|
||||
}
|
||||
|
||||
lbry.showMenuIfNeeded = function() {
|
||||
|
|
|
@ -41,7 +41,7 @@ export let FileListDownloaded = React.createClass({
|
|||
} else if (!this.state.fileInfos.length) {
|
||||
return (
|
||||
<main className="page">
|
||||
<span>You haven't downloaded anything from LBRY yet. Go <Link href="/index.html?discover" label="search for your first download" />!</span>
|
||||
<span>You haven't downloaded anything from LBRY yet. Go <Link href="?discover" label="search for your first download" />!</span>
|
||||
</main>
|
||||
);
|
||||
} else {
|
||||
|
@ -90,7 +90,7 @@ export let FileListPublished = React.createClass({
|
|||
else if (!this.state.fileInfos.length) {
|
||||
return (
|
||||
<main className="page">
|
||||
<span>You haven't published anything to LBRY yet.</span> Try <Link href="index.html?publish" label="publishing" />!
|
||||
<span>You haven't published anything to LBRY yet.</span> Try <Link href="?publish" label="publishing" />!
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ var HelpPage = React.createClass({
|
|||
<section className="card">
|
||||
<h3>Report a Bug</h3>
|
||||
<p>Did you find something wrong?</p>
|
||||
<p><Link href="index.html?report" label="Submit a Bug Report" icon="icon-bug" button="alt" /></p>
|
||||
<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>
|
||||
</section>
|
||||
{!ver ? null :
|
||||
|
|
|
@ -155,7 +155,7 @@ var DetailPage = React.createClass({
|
|||
) : (
|
||||
<div>
|
||||
<h2>No content</h2>
|
||||
There is no content available at the name <strong>lbry://{this.props.name}</strong>. If you reached this page from a link within the LBRY interface, please <Link href="index.html?report" label="report a bug" />. Thanks!
|
||||
There is no content available at the name <strong>lbry://{this.props.name}</strong>. If you reached this page from a link within the LBRY interface, please <Link href="?report" label="report a bug" />. Thanks!
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
|
|
@ -243,6 +243,8 @@ var TransactionList = React.createClass({
|
|||
|
||||
|
||||
var WalletPage = React.createClass({
|
||||
_balanceSubscribeId: null,
|
||||
|
||||
propTypes: {
|
||||
viewingPage: React.PropTypes.string,
|
||||
},
|
||||
|
@ -259,12 +261,17 @@ var WalletPage = React.createClass({
|
|||
}
|
||||
},
|
||||
componentWillMount: function() {
|
||||
lbry.getBalance((results) => {
|
||||
this._balanceSubscribeId = lbry.balanceSubscribe((results) => {
|
||||
this.setState({
|
||||
balance: results,
|
||||
})
|
||||
});
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
if (this._balanceSubscribeId) {
|
||||
lbry.balanceUnsubscribe(this._balanceSubscribeId);
|
||||
}
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<main className="page">
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
.load-screen {
|
||||
color: white;
|
||||
background: $color-primary;
|
||||
background-size: cover;
|
||||
min-height: 100vh;
|
||||
min-width: 100vw;
|
||||
|
|
Loading…
Reference in a new issue