diff --git a/ui/CHANGELOG.md b/CHANGELOG.md similarity index 83% rename from ui/CHANGELOG.md rename to CHANGELOG.md index b8c82b321..c621b006e 100644 --- a/ui/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/app/main.js b/app/main.js index d10d250ef..c68e9c7e0 100644 --- a/app/main.js +++ b/app/main.js @@ -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`) diff --git a/ui/js/app.js b/ui/js/app.js index 3a639c945..76ee648bd 100644 --- a/ui/js/app.js +++ b/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); diff --git a/ui/js/component/drawer.js b/ui/js/component/drawer.js index bc8f6d666..eaf11506b 100644 --- a/ui/js/component/drawer.js +++ b/ui/js/component/drawer.js @@ -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 } @@ -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 ( ); } diff --git a/ui/js/component/file-actions.js b/ui/js/component/file-actions.js index 9a1d42af3..b9d831e69 100644 --- a/ui/js/component/file-actions.js +++ b/ui/js/component/file-actions.js @@ -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; diff --git a/ui/js/component/file-tile.js b/ui/js/component/file-tile.js index 4cc3166ae..5c9ce2890 100644 --- a/ui/js/component/file-tile.js +++ b/ui/js/component/file-tile.js @@ -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({
- +
{ !this.props.hidePrice ? : null} -
{'lbry://' + this.props.name}
+
{'lbry://' + this.props.name}

- + {title} diff --git a/ui/js/lbry.js b/ui/js/lbry.js index 5d41abc42..4f5f6cbec 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -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() { diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index 734cb75eb..8134be11f 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -41,7 +41,7 @@ export let FileListDownloaded = React.createClass({ } else if (!this.state.fileInfos.length) { return (
- You haven't downloaded anything from LBRY yet. Go ! + You haven't downloaded anything from LBRY yet. Go !
); } else { @@ -90,7 +90,7 @@ export let FileListPublished = React.createClass({ else if (!this.state.fileInfos.length) { return (
- You haven't published anything to LBRY yet. Try ! + You haven't published anything to LBRY yet. Try !
); } diff --git a/ui/js/page/help.js b/ui/js/page/help.js index 6bf964205..632c3abd0 100644 --- a/ui/js/page/help.js +++ b/ui/js/page/help.js @@ -67,7 +67,7 @@ var HelpPage = React.createClass({

Report a Bug

Did you find something wrong?

-

+

Thanks! LBRY is made by its users.
{!ver ? null : diff --git a/ui/js/page/show.js b/ui/js/page/show.js index a2bb1b5f1..8f4d450c9 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -155,7 +155,7 @@ var DetailPage = React.createClass({ ) : (

No content

- There is no content available at the name lbry://{this.props.name}. If you reached this page from a link within the LBRY interface, please . Thanks! + There is no content available at the name lbry://{this.props.name}. If you reached this page from a link within the LBRY interface, please . Thanks!
)}

diff --git a/ui/js/page/wallet.js b/ui/js/page/wallet.js index bdd6eff0e..72e683ad0 100644 --- a/ui/js/page/wallet.js +++ b/ui/js/page/wallet.js @@ -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 (
diff --git a/ui/scss/component/_load-screen.scss b/ui/scss/component/_load-screen.scss index 06540324f..e56eb12c0 100644 --- a/ui/scss/component/_load-screen.scss +++ b/ui/scss/component/_load-screen.scss @@ -2,6 +2,7 @@ .load-screen { color: white; + background: $color-primary; background-size: cover; min-height: 100vh; min-width: 100vw;