From da538a7a239c63cfb5b2237549db2b660902e46b Mon Sep 17 00:00:00 2001 From: Jeremy Kauffman Date: Sun, 30 Apr 2017 20:15:21 -0400 Subject: [PATCH] many bug fixes, working back button, progress towards working search --- ui/js/app.js | 64 +++++++------ ui/js/component/common.js | 19 +--- ui/js/component/drawer.js | 67 ------------- ui/js/component/header.js | 16 ++-- ui/js/component/load_screen.js | 6 -- ui/js/lbry.js | 19 +++- ui/js/lbryio.js | 14 --- ui/js/page/discover.js | 165 +++----------------------------- ui/js/page/file-list.js | 7 +- ui/js/page/help.js | 5 +- ui/js/page/publish.js | 6 +- ui/js/page/report.js | 5 +- ui/js/page/rewards.js | 2 +- ui/js/page/search.js | 139 +++++++++++++++++++++++++++ ui/js/page/settings.js | 5 +- ui/js/page/show.js | 4 +- ui/js/page/start.js | 2 +- ui/js/page/wallet.js | 2 +- ui/package.json | 1 - ui/scss/_canvas.scss | 13 ++- ui/scss/_gui.scss | 5 +- ui/scss/component/_card.scss | 6 +- ui/scss/component/_header.scss | 17 ---- ui/scss/component/_tooltip.scss | 1 + 24 files changed, 247 insertions(+), 343 deletions(-) delete mode 100644 ui/js/component/drawer.js create mode 100644 ui/js/page/search.js diff --git a/ui/js/app.js b/ui/js/app.js index 4a11a858b..a416a6e80 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -12,10 +12,10 @@ 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 {FileListDownloaded, FileListPublished} from './page/file-list.js'; -import Drawer from './component/drawer.js'; import Header from './component/header.js'; import {Modal, ExpandableModal} from './component/modal.js'; import {Link} from './component/link.js'; @@ -38,6 +38,7 @@ var App = React.createClass({ data: 'Error data', }, _fullScreenPages: ['watch'], + _storeHistoryOfNextRender: false, _upgradeDownloadItem: null, _isMounted: false, @@ -71,18 +72,17 @@ var App = React.createClass({ getViewingPageAndArgs: function(address) { // For now, routes are in format ?page or ?page=args let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/); + console.log(pageArgs); + console.log(decodeURIComponent(pageArgs)); return { viewingPage: viewingPage, - pageArgs: pageArgs === undefined ? null : pageArgs + pageArgs: pageArgs === undefined ? null : decodeURIComponent(pageArgs) }; }, getInitialState: function() { - var match, param, val, viewingPage, pageArgs, - drawerOpenRaw = sessionStorage.getItem('drawerOpen'); - return Object.assign(this.getViewingPageAndArgs(window.location.search), { viewingPage: 'discover', - drawerOpen: drawerOpenRaw !== null ? JSON.parse(drawerOpenRaw) : true, + appUrl: null, errorInfo: null, modal: null, downloadProgress: null, @@ -90,6 +90,8 @@ var App = React.createClass({ }); }, componentWillMount: function() { + window.addEventListener("popstate", this.onHistoryPop); + document.addEventListener('unhandledError', (event) => { this.alertError(event.detail); }); @@ -106,9 +108,9 @@ var App = React.createClass({ if (target.matches('a[href^="?"]')) { event.preventDefault(); if (this._isMounted) { - history.pushState({}, document.title, target.getAttribute('href')); - this.registerHistoryPop(); - this.setState(this.getViewingPageAndArgs(target.getAttribute('href'))); + let appUrl = target.getAttribute('href'); + this._storeHistoryOfNextRender = true; + this.setState(Object.assign({}, this.getViewingPageAndArgs(appUrl), { appUrl: appUrl })); } } target = target.parentNode; @@ -126,14 +128,6 @@ var App = React.createClass({ }); } }, - openDrawer: function() { - sessionStorage.setItem('drawerOpen', true); - this.setState({ drawerOpen: true }); - }, - closeDrawer: function() { - sessionStorage.setItem('drawerOpen', false); - this.setState({ drawerOpen: false }); - }, closeModal: function() { this.setState({ modal: null, @@ -144,10 +138,17 @@ var App = React.createClass({ }, componentWillUnmount: function() { this._isMounted = false; + window.removeEventListener("popstate", this.onHistoryPop); }, - registerHistoryPop: function() { - window.addEventListener("popstate", () => { - this.setState(this.getViewingPageAndArgs(location.pathname)); + onHistoryPop: function() { + this.setState(this.getViewingPageAndArgs(location.search)); + }, + onSearch: function(term) { + this._storeHistoryOfNextRender = true; + this.setState({ + viewingPage: "search", + appUrl: "?search=" + encodeURIComponent(term), + pageArgs: term }); }, handleUpgradeClicked: function() { @@ -202,12 +203,6 @@ var App = React.createClass({ modal: null, }); }, - onSearch: function(term) { - this.setState({ - viewingPage: 'discover', - pageArgs: term - }); - }, alertError: function(error) { var errorInfoList = []; for (let key of Object.keys(error)) { @@ -225,12 +220,14 @@ var App = React.createClass({ { switch(this.state.viewingPage) { + case 'search': + return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', ]; case 'settings': return ["Settings", "icon-gear", ]; case 'help': return ["Help", "icon-question", ]; case 'report': - return ['Report', 'icon-file', ]; + return ['Report an Issue', 'icon-file', ]; case 'downloaded': return ["Downloads & Purchases", "icon-folder", ]; case 'published': @@ -250,18 +247,25 @@ var App = React.createClass({ case 'developer': return ["Developer", "icon-file", ]; case 'discover': - return ["Home", "icon-home", ]; + default: + return ["Home", "icon-home", ]; } }, 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 :
-
+
{mainContent}
diff --git a/ui/js/component/common.js b/ui/js/component/common.js index 7139e5dfe..8da20ca8e 100644 --- a/ui/js/component/common.js +++ b/ui/js/component/common.js @@ -1,6 +1,5 @@ import React from 'react'; import lbry from '../lbry.js'; -import $clamp from 'clamp-js-main'; //component/icon.js export let Icon = React.createClass({ @@ -19,29 +18,15 @@ export let Icon = React.createClass({ export let TruncatedText = React.createClass({ propTypes: { - lines: React.PropTypes.number, - height: React.PropTypes.string, - auto: React.PropTypes.bool, + lines: React.PropTypes.number }, getDefaultProps: function() { return { lines: null, - height: null, - auto: true, } }, - componentDidMount: function() { - // Manually round up the line height, because clamp.js doesn't like fractional-pixel line heights. - - // Need to work directly on the style object because setting the style prop doesn't update internal styles right away. - this.refs.span.style.lineHeight = Math.ceil(parseFloat(getComputedStyle(this.refs.span).lineHeight)) + 'px'; - - $clamp(this.refs.span, { - clamp: this.props.lines || this.props.height || 'auto', - }); - }, render: function() { - return {this.props.children}; + return {this.props.children}; } }); diff --git a/ui/js/component/drawer.js b/ui/js/component/drawer.js deleted file mode 100644 index b19e901f5..000000000 --- a/ui/js/component/drawer.js +++ /dev/null @@ -1,67 +0,0 @@ -import lbry from '../lbry.js'; -import React from 'react'; -import {Link} from './link.js'; - -var DrawerItem = React.createClass({ - getDefaultProps: function() { - return { - subPages: [], - }; - }, - render: function() { - var isSelected = (this.props.viewingPage == this.props.href.substr(1) || - this.props.subPages.indexOf(this.props.viewingPage) != -1); - return - } -}); - -var drawerImageStyle = { //@TODO: remove this, img should be properly scaled once size is settled - height: '36px' -}; - -var Drawer = React.createClass({ - _balanceSubscribeId: null, - - handleLogoClicked: function(event) { - if ((event.ctrlKey || event.metaKey) && event.shiftKey) { - window.location.href = '?developer' - event.preventDefault(); - } - }, - getInitialState: function() { - return { - balance: 0, - }; - }, - componentDidMount: function() { - this._balanceSubscribeId = lbry.balanceSubscribe((balance) => { - this.setState({ - balance: balance - }); - }); - }, - componentWillUnmount: function() { - if (this._balanceSubscribeId) { - lbry.balanceUnsubscribe(this._balanceSubscribeId) - } - }, - render: function() { - return ( - - ); - } -}); - - -export default Drawer; diff --git a/ui/js/component/header.js b/ui/js/component/header.js index 464b11f9a..308cd633b 100644 --- a/ui/js/component/header.js +++ b/ui/js/component/header.js @@ -28,13 +28,14 @@ var Header = React.createClass({ render: function() { return
); diff --git a/ui/js/lbry.js b/ui/js/lbry.js index c13635a09..356887483 100644 --- a/ui/js/lbry.js +++ b/ui/js/lbry.js @@ -31,7 +31,6 @@ function savePendingPublish({name, channel_name}) { return newPendingPublish; } - /** * If there is a pending publish with the given name or outpoint, remove it. * A channel name may also be provided along with name. @@ -132,6 +131,24 @@ lbry.connect = function() { return lbry._connectPromise; } + +//kill this but still better than document.title =, which this replaced +lbry.setTitle = function(title) { + document.title = title + " - LBRY"; +} + +//kill this with proper routing +lbry.back = function() { + console.log(window.history); + if (window.history.length > 1) { + console.log('history exists, go back'); + window.history.back(); + } else { + console.log('no history, reload'); + window.location.href = "?discover"; + } +} + lbry.isDaemonAcceptingConnections = function (callback) { // Returns true/false whether the daemon is at a point it will start returning status lbry.call('status', {}, () => callback(true), null, () => callback(false)) diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index 7a1ab58c2..f37cccf62 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -151,20 +151,6 @@ lbryio.authenticate = function() { } else { setCurrentUser() } - // if (!lbryio._ - //(data) => { - // resolve(data) - // localStorage.setItem('accessToken', ID); - // localStorage.setItem('appId', installation_id); - // this.setState({ - // registrationCheckComplete: true, - // justRegistered: true, - // }); - //}); - // lbryio.call('user_install', 'exists', {app_id: installation_id}).then((userExists) => { - // // TODO: deal with case where user exists already with the same app ID, but we have no access token. - // // Possibly merge in to the existing user with the same app ID. - // }) }).catch(reject); }); } diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js index 3c807e831..d522a99f8 100644 --- a/ui/js/page/discover.js +++ b/ui/js/page/discover.js @@ -1,79 +1,18 @@ import React from 'react'; -import lbry from '../lbry.js'; import lbryio from '../lbryio.js'; -import lbryuri from '../lbryuri.js'; -import lighthouse from '../lighthouse.js'; import {FileTile, FileTileStream} from '../component/file-tile.js'; -import {Link} from '../component/link.js'; import {ToolTip} from '../component/tooltip.js'; -import {BusyMessage} from '../component/common.js'; - -var fetchResultsStyle = { - color: '#888', - textAlign: 'center', - fontSize: '1.2em' - }; - -var SearchActive = React.createClass({ - render: function() { - return ( -
- -
- ); - } -}); - -var searchNoResultsStyle = { - textAlign: 'center' -}, searchNoResultsMessageStyle = { - fontStyle: 'italic', - marginRight: '5px' -}; - -var SearchNoResults = React.createClass({ - render: function() { - return ( -
- No one has checked anything in for {this.props.query} yet. - -
- ); - } -}); - -var SearchResults = React.createClass({ - render: function() { - var rows = [], - seenNames = {}; //fix this when the search API returns claim IDs - - for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of this.props.results) { - const uri = lbryuri.build({ - channelName: channel_name, - contentName: name, - claimId: channel_id || claim_id, - }); - - rows.push( - - ); - } - return ( -
{rows}
- ); - } -}); const communityCategoryToolTipText = ('Community Content is a public space where anyone can share content with the ' + -'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' + + 'rest of the LBRY community. Bid on the names "one," "two," "three," "four" and ' + '"five" to put your content here!'); -var FeaturedCategory = React.createClass({ +let FeaturedCategory = React.createClass({ render: function() { return (
{ this.props.category ?

{this.props.category} - { this.props.category == "community" ? + { this.props.category.match(/^community/i) ? : '' }

: '' } @@ -82,7 +21,7 @@ var FeaturedCategory = React.createClass({ } }) -var FeaturedContent = React.createClass({ +let DiscoverPage = React.createClass({ getInitialState: function() { return { featuredUris: {}, @@ -105,101 +44,19 @@ var FeaturedContent = React.createClass({ }); }, render: function() { - return ( + return
{ this.state.failed ?
Failed to load landing content.
:
{ - Object.keys(this.state.featuredUris).map((category) => { - return this.state.featuredUris[category].length ? - : - ''; - }) + Object.keys(this.state.featuredUris).map((category) => { + return this.state.featuredUris[category].length ? + : + ''; + }) }
- ); - } -}); - -var DiscoverPage = React.createClass({ - userTypingTimer: null, - - propTypes: { - showWelcome: React.PropTypes.bool.isRequired, - }, - - componentDidUpdate: function() { - if (this.props.query != this.state.query) - { - this.handleSearchChanged(this.props.query); - } - }, - - getDefaultProps: function() { - return { - showWelcome: false, - } - }, - - componentWillReceiveProps: function(nextProps, nextState) { - if (nextProps.query != nextState.query) - { - this.handleSearchChanged(nextProps.query); - } - }, - - handleSearchChanged: function(query) { - this.setState({ - searching: true, - query: query, - }); - - lighthouse.search(query).then(this.searchCallback); - }, - - handleWelcomeDone: function() { - this.setState({ - welcomeComplete: true, - }); - }, - - componentWillMount: function() { - document.title = "Home"; - - if (this.props.query) { - // Rendering with a query already typed - this.handleSearchChanged(this.props.query); - } - }, - - getInitialState: function() { - return { - welcomeComplete: false, - results: [], - query: this.props.query, - searching: ('query' in this.props) && (this.props.query.length > 0) - }; - }, - - searchCallback: function(results) { - if (this.state.searching) //could have canceled while results were pending, in which case nothing to do - { - this.setState({ - results: results, - searching: false //multiple searches can be out, we're only done if we receive one we actually care about - }); - } - }, - - render: function() { - return ( -
- { this.state.searching ? : null } - { !this.state.searching && this.props.query && this.state.results.length ? : null } - { !this.state.searching && this.props.query && !this.state.results.length ? : null } - { !this.props.query && !this.state.searching ? : null } -
- ); + }
; } }); diff --git a/ui/js/page/file-list.js b/ui/js/page/file-list.js index 41a2254b5..71f8e2fc2 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -56,7 +56,7 @@ export let FileListDownloaded = React.createClass({ content = ; } return ( -
+
{content}
@@ -83,12 +83,11 @@ export let FileListPublished = React.createClass({ else { rewards.claimReward(rewards.TYPE_FIRST_PUBLISH).catch(() => {}) } - }); + }, () => {}); }, componentDidMount: function () { this._isMounted = true; this._requestPublishReward(); - document.title = "Published Files"; lbry.claim_list_mine().then((claimInfos) => { if (!this._isMounted) { return; } @@ -118,7 +117,7 @@ export let FileListPublished = React.createClass({ content = ; } return ( -
+
{content}
diff --git a/ui/js/page/help.js b/ui/js/page/help.js index 42e65df14..d6a28ae99 100644 --- a/ui/js/page/help.js +++ b/ui/js/page/help.js @@ -25,9 +25,6 @@ var HelpPage = React.createClass({ }); }); }, - componentDidMount: function() { - document.title = "Help"; - }, render: function() { let ver, osName, platform, newVerLink; if (this.state.versionInfo) { @@ -50,7 +47,7 @@ var HelpPage = React.createClass({ } return ( -
+
diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index dc34ea500..03583136b 100644 --- a/ui/js/page/publish.js +++ b/ui/js/page/publish.js @@ -148,7 +148,7 @@ var PublishPage = React.createClass({ }); }, handlePublishStartedConfirmed: function() { - window.location = "?published"; + window.location.href = "?published"; }, handlePublishError: function(error) { this.setState({ @@ -384,7 +384,7 @@ var PublishPage = React.createClass({ const lbcInputHelp = "This LBC remains yours and the deposit can be undone at any time." return ( -
+
@@ -548,7 +548,7 @@ var PublishPage = React.createClass({
- +
diff --git a/ui/js/page/report.js b/ui/js/page/report.js index 47a4d2a7a..e76905d4b 100644 --- a/ui/js/page/report.js +++ b/ui/js/page/report.js @@ -18,9 +18,6 @@ var ReportPage = React.createClass({ this._messageArea.value = ''; } }, - componentDidMount: function() { - document.title = "Report an Issue"; - }, closeModal: function() { this.setState({ modal: null, @@ -34,7 +31,7 @@ var ReportPage = React.createClass({ }, render: function() { return ( -
+

Report an Issue

Please describe the problem you experienced and any information you think might be useful to us. Links to screenshots are great!

diff --git a/ui/js/page/rewards.js b/ui/js/page/rewards.js index c4261804f..3462517c9 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -57,7 +57,7 @@ var RewardsPage = React.createClass({ }, render: function() { return ( -
+
{!this.state.userRewards diff --git a/ui/js/page/search.js b/ui/js/page/search.js new file mode 100644 index 000000000..3df3dd07d --- /dev/null +++ b/ui/js/page/search.js @@ -0,0 +1,139 @@ +import React from 'react'; +import lbry from '../lbry.js'; +import lbryio from '../lbryio.js'; +import lbryuri from '../lbryuri.js'; +import lighthouse from '../lighthouse.js'; +import {FileTile, FileTileStream} from '../component/file-tile.js'; +import {Link} from '../component/link.js'; +import {ToolTip} from '../component/tooltip.js'; +import {BusyMessage} from '../component/common.js'; + +var SearchNoResults = React.createClass({ + render: function() { + return
+ + No one has checked anything in for {this.props.query} yet. + + +
; + } +}); + +var SearchResultList = React.createClass({ + render: function() { + var rows = [], + seenNames = {}; //fix this when the search API returns claim IDs + + for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of this.props.results) { + const uri = lbryuri.build({ + channelName: channel_name, + contentName: name, + claimId: channel_id || claim_id, + }); + + rows.push( + + ); + } + return ( +
{rows}
+ ); + } +}); + +let SearchResults = React.createClass({ + propTypes: { + query: React.PropTypes.string.isRequired + }, + + _isMounted: false, + + componentWillMount: function () { + this._isMounted = true; + lighthouse.search(this.props.query).then(this.searchCallback); + }, + + componentWillUnmount: function () { + this._isMounted = false; + }, + + getInitialState: function () { + return { + results: [], + searching: true + }; + }, + + searchCallback: function (results) { + if (this._isMounted) //could have canceled while results were pending, in which case nothing to do + { + this.setState({ + results: results, + searching: false //multiple searches can be out, we're only done if we receive one we actually care about + }); + } + }, + + render: function () { + return this.state.searching ? + : + (this.state.results.length ? + : + ); + } +}); + +let SearchPage = React.createClass({ + + _isMounted: false, + + propTypes: { + query: React.PropTypes.string.isRequired + }, + + isValidUri: function(query) { + return true; + }, + + componentWillMount: function() { + this._isMounted = true; + lighthouse.search(this.props.query).then(this.searchCallback); + }, + + componentWillUnmount: function() { + this._isMounted = false; + }, + + getInitialState: function() { + return { + results: [], + searching: true + }; + }, + + searchCallback: function(results) { + if (this._isMounted) //could have canceled while results were pending, in which case nothing to do + { + this.setState({ + results: results, + searching: false //multiple searches can be out, we're only done if we receive one we actually care about + }); + } + }, + + render: function() { + return ( +
+ { this.isValidUri(this.props.query) ? +
+

lbry://{this.props.query}

+
+
: '' } +

Search

+ +
+ ); + } +}); + +export default SearchPage; diff --git a/ui/js/page/settings.js b/ui/js/page/settings.js index abc65271d..cbc0765a8 100644 --- a/ui/js/page/settings.js +++ b/ui/js/page/settings.js @@ -66,9 +66,6 @@ var SettingsPage = React.createClass({ showUnavailable: lbry.getClientSetting('showUnavailable'), } }, - componentDidMount: function() { - document.title = "Settings"; - }, componentWillMount: function() { lbry.getDaemonSettings((settings) => { this.setState({ @@ -102,7 +99,7 @@ var SettingsPage = React.createClass({
*/ return ( -
+
diff --git a/ui/js/page/show.js b/ui/js/page/show.js index f41039868..8622a814d 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -70,7 +70,7 @@ let ShowPage = React.createClass({ }); }); - document.title = metadata.title ? metadata.title : this._uri; + lbry.setTitle(metadata.title ? metadata.title : this._uri) this.setState({ outpoint: outpoint, @@ -94,7 +94,7 @@ let ShowPage = React.createClass({ const title = metadata ? this.state.metadata.title : this._uri; return ( -
+
{ this.state.contentType && this.state.contentType.startsWith('video/') ?