diff --git a/CHANGELOG.md b/CHANGELOG.md index ead250225..54c22c93c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Web UI version numbers should always match the corresponding version of LBRY App ## [Unreleased] ### Added + * The UI has been overhauled to use an omnibar and drop the sidebar. * 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. diff --git a/ui/js/app.js b/ui/js/app.js index b7a552e5e..85f8891a6 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -12,10 +12,11 @@ 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 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 +39,7 @@ var App = React.createClass({ data: 'Error data', }, _fullScreenPages: ['watch'], + _storeHistoryOfNextRender: false, _upgradeDownloadItem: null, _isMounted: false, @@ -73,15 +75,13 @@ var App = React.createClass({ let [isMatch, viewingPage, pageArgs] = address.match(/\??([^=]*)(?:=(.*))?/); 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), { - drawerOpen: drawerOpenRaw !== null ? JSON.parse(drawerOpenRaw) : true, + viewingPage: 'discover', + appUrl: null, errorInfo: null, modal: null, downloadProgress: null, @@ -89,6 +89,8 @@ var App = React.createClass({ }); }, componentWillMount: function() { + window.addEventListener("popstate", this.onHistoryPop); + document.addEventListener('unhandledError', (event) => { this.alertError(event.detail); }); @@ -105,9 +107,10 @@ 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 })); + document.body.scrollTop = 0; } } target = target.parentNode; @@ -125,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, @@ -143,12 +138,29 @@ 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; + 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); @@ -201,12 +213,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)) { @@ -220,75 +226,57 @@ var App = React.createClass({ errorInfo: , }); }, - getHeaderLinks: function() - { - switch(this.state.viewingPage) - { - case 'wallet': - case 'send': - case 'receive': - case 'rewards': - return { - '?wallet': 'Overview', - '?send': 'Send', - '?receive': 'Receive', - '?rewards': 'Rewards', - }; - case 'downloaded': - case 'published': - return { - '?downloaded': 'Downloaded', - '?published': 'Published', - }; - default: - return null; - } - }, - getMainContent: function() + getContentAndAddress: function() { switch(this.state.viewingPage) { + case 'search': + return [this.state.pageArgs ? this.state.pageArgs : "Search", 'icon-search', ]; case 'settings': - return ; + return ["Settings", "icon-gear", ]; case 'help': - return ; + return ["Help", "icon-question", ]; case 'report': - return ; + return ['Report an Issue', 'icon-file', ]; case 'downloaded': - return ; + return ["Downloads & Purchases", "icon-folder", ]; case 'published': - return ; + return ["Publishes", "icon-folder", ]; case 'start': - return ; + return ["Start", "icon-file", ]; case 'rewards': - return ; + return ["Rewards", "icon-bank", ]; case 'wallet': case 'send': case 'receive': - return ; + return [this.state.viewingPage.charAt(0).toUpperCase() + this.state.viewingPage.slice(1), "icon-bank", ] case 'show': - return ; + return [lbryuri.normalize(this.state.pageArgs), "icon-file", ]; case 'publish': - return ; + return ["Publish", "icon-upload", ]; case 'developer': - return ; + return ["Developer", "icon-file", ]; case 'discover': default: - return ; + return ["Home", "icon-home", ]; } }, render: function() { - var mainContent = this.getMainContent(), - headerLinks = this.getHeaderLinks(), - searchQuery = this.state.viewingPage == 'discover' && this.state.pageArgs ? this.state.pageArgs : ''; - + 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}
{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/file-tile.js b/ui/js/component/file-tile.js index 51413bd02..73adb63b2 100644 --- a/ui/js/component/file-tile.js +++ b/ui/js/component/file-tile.js @@ -3,7 +3,7 @@ import lbry from '../lbry.js'; import lbryuri from '../lbryuri.js'; import {Link} from '../component/link.js'; import {FileActions} from '../component/file-actions.js'; -import {Thumbnail, TruncatedText, FilePrice} from '../component/common.js'; +import {BusyMessage, TruncatedText, FilePrice} from '../component/common.js'; import UriIndicator from '../component/channel-indicator.js'; /*should be merged into FileTile once FileTile is refactored to take a single id*/ @@ -77,40 +77,32 @@ export let FileTileStream = React.createClass({ const isConfirmed = !!metadata; const title = isConfirmed ? metadata.title : uri; const obscureNsfw = this.props.obscureNsfw && isConfirmed && metadata.nsfw; + const primaryUrl = "?show=" + uri; return (
-
-
- -
-
-
- { !this.props.hidePrice - ? - : null} - -

- - - {title} - - -

+ +
+
-
- -
-
-

- +

+
+ { !this.props.hidePrice + ? + : null} +
{uri}
+

{title}

+
+
+ {isConfirmed ? metadata.description : This file is pending confirmation.} -

+
-
+
{this.state.showNsfwHelp ?

@@ -227,6 +219,7 @@ export let FileCardStream = React.createClass({ export let FileTile = React.createClass({ _isMounted: false, + _isResolvePending: false, propTypes: { uri: React.PropTypes.string.isRequired, @@ -238,13 +231,12 @@ export let FileTile = React.createClass({ claimInfo: null } }, - - componentDidMount: function() { - this._isMounted = true; - - lbry.resolve({uri: this.props.uri}).then((resolutionInfo) => { + resolve: function(uri) { + this._isResolvePending = true; + lbry.resolve({uri: uri}).then((resolutionInfo) => { + this._isResolvePending = false; if (this._isMounted && resolutionInfo && resolutionInfo.claim && resolutionInfo.claim.value && - resolutionInfo.claim.value.stream && resolutionInfo.claim.value.stream.metadata) { + resolutionInfo.claim.value.stream && resolutionInfo.claim.value.stream.metadata) { // In case of a failed lookup, metadata will be null, in which case the component will never display this.setState({ claimInfo: resolutionInfo.claim, @@ -252,6 +244,16 @@ export let FileTile = React.createClass({ } }); }, + componentWillReceiveProps: function(nextProps) { + if (nextProps.uri != this.props.uri) { + this.setState(this.getInitialState()); + this.resolve(nextProps.uri); + } + }, + componentDidMount: function() { + this._isMounted = true; + this.resolve(this.props.uri); + }, componentWillUnmount: function() { this._isMounted = false; }, @@ -261,6 +263,12 @@ export let FileTile = React.createClass({ return } + if (this.props.showEmpty) + { + return this._isResolvePending ? + : +

{lbryuri.normalize(this.props.uri)} is unclaimed.
; + } return null; } diff --git a/ui/js/component/header.js b/ui/js/component/header.js index 32315ef2f..848f076a2 100644 --- a/ui/js/component/header.js +++ b/ui/js/component/header.js @@ -1,76 +1,198 @@ import React from 'react'; +import lbryuri from '../lbryuri.js'; import {Link} from './link.js'; -import {Icon} from './common.js'; +import {Icon, CreditAmount} from './common.js'; var Header = React.createClass({ + _balanceSubscribeId: null, + _isMounted: false, + + propTypes: { + onSearch: React.PropTypes.func.isRequired, + onSubmit: React.PropTypes.func.isRequired + }, + getInitialState: function() { return { - title: "LBRY", - isScrolled: false + balance: 0 }; }, - componentWillMount: function() { - new MutationObserver((mutations) => { - this.setState({ title: mutations[0].target.textContent }); - }).observe( - document.querySelector('title'), - { subtree: true, characterData: true, childList: true } - ); - }, componentDidMount: function() { - document.addEventListener('scroll', this.handleScroll); - }, - componentWillUnmount: function() { - document.removeEventListener('scroll', this.handleScroll); - if (this.userTypingTimer) - { - clearTimeout(this.userTypingTimer); - } - }, - handleScroll: function() { - this.setState({ - isScrolled: document.body.scrollTop > 0 + this._isMounted = true; + this._balanceSubscribeId = lbry.balanceSubscribe((balance) => { + if (this._isMounted) { + this.setState({balance: balance}); + } }); }, - onQueryChange: function(event) { - - if (this.userTypingTimer) - { - clearTimeout(this.userTypingTimer); + componentWillUnmount: function() { + this._isMounted = false; + if (this._balanceSubscribeId) { + lbry.balanceUnsubscribe(this._balanceSubscribeId) } - - //@TODO: Switch to React.js timing - var searchTerm = event.target.value; - this.userTypingTimer = setTimeout(() => { - this.props.onSearch(searchTerm); - }, 800); // 800ms delay, tweak for faster/slower - }, render: function() { - return ( -
); diff --git a/ui/js/lbry.js b/ui/js/lbry.js index e2807090a..4c0d511d4 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,21 @@ 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() { + if (window.history.length > 1) { + window.history.back(); + } else { + 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)) @@ -633,7 +647,7 @@ lbry.resolve = function(params={}) { if (!params.uri) { throw "Resolve has hacked cache on top of it that requires a URI" } - if (params.uri && claimCache[params.uri]) { + if (params.uri && claimCache[params.uri] !== undefined) { resolve(claimCache[params.uri]); } else { lbry.call('resolve', params, function(data) { diff --git a/ui/js/lbryio.js b/ui/js/lbryio.js index bbe6b9ccd..20934bbbb 100644 --- a/ui/js/lbryio.js +++ b/ui/js/lbryio.js @@ -7,7 +7,7 @@ const lbryio = { _accessToken: getLocal('accessToken'), _authenticationPromise: null, _user : null, - enabled: true + enabled: false }; const CONNECTION_STRING = process.env.LBRY_APP_API_URL ? process.env.LBRY_APP_API_URL : 'https://api.lbry.io/'; @@ -150,20 +150,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 42ed3999d..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 = "Discover"; - - 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 063730e7f..71f8e2fc2 100644 --- a/ui/js/page/file-list.js +++ b/ui/js/page/file-list.js @@ -3,12 +3,22 @@ import lbry from '../lbry.js'; import lbryuri from '../lbryuri.js'; import {Link} from '../component/link.js'; import {FormField} from '../component/form.js'; +import {SubHeader} from '../component/header.js'; import {FileTileStream} from '../component/file-tile.js'; import rewards from '../rewards.js'; import lbryio from '../lbryio.js'; import {BusyMessage, Thumbnail} from '../component/common.js'; +export let FileListNav = React.createClass({ + render: function() { + return ; + } +}); + export let FileListDownloaded = React.createClass({ _isMounted: false, @@ -19,7 +29,6 @@ export let FileListDownloaded = React.createClass({ }, componentDidMount: function() { this._isMounted = true; - document.title = "Downloaded Files"; lbry.claim_list_mine().then((myClaimInfos) => { if (!this._isMounted) { return; } @@ -38,25 +47,20 @@ export let FileListDownloaded = React.createClass({ this._isMounted = false; }, render: function() { + let content = ""; if (this.state.fileInfos === null) { - return ( -
- -
- ); + content = ; } else if (!this.state.fileInfos.length) { - return ( -
- You haven't downloaded anything from LBRY yet. Go ! -
- ); + content = You haven't downloaded anything from LBRY yet. Go !; } else { - return ( -
- -
- ); + content = ; } + return ( +
+ + {content} +
+ ); } }); @@ -79,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; } @@ -103,27 +106,22 @@ export let FileListPublished = React.createClass({ this._isMounted = false; }, render: function () { + let content = null; if (this.state.fileInfos === null) { - return ( -
- -
- ); + content = ; } else if (!this.state.fileInfos.length) { - return ( -
- You haven't published anything to LBRY yet. Try ! -
- ); + content = You haven't published anything to LBRY yet. Try !; } else { - return ( -
- -
- ); + content = ; } + return ( +
+ + {content} +
+ ); } }); diff --git a/ui/js/page/help.js b/ui/js/page/help.js index 99e6ad0d7..d6a28ae99 100644 --- a/ui/js/page/help.js +++ b/ui/js/page/help.js @@ -3,6 +3,7 @@ import React from 'react'; import lbry from '../lbry.js'; import {Link} from '../component/link.js'; +import {SettingsNav} from './settings.js'; import {version as uiVersion} from 'json!../../package.json'; var HelpPage = React.createClass({ @@ -24,9 +25,6 @@ var HelpPage = React.createClass({ }); }); }, - componentDidMount: function() { - document.title = "Help"; - }, render: function() { let ver, osName, platform, newVerLink; if (this.state.versionInfo) { @@ -49,7 +47,8 @@ var HelpPage = React.createClass({ } return ( -
+
+

Read the FAQ

diff --git a/ui/js/page/publish.js b/ui/js/page/publish.js index 36bcae479..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({ @@ -348,9 +348,6 @@ var PublishPage = React.createClass({ componentWillMount: function() { this._updateChannelList(); }, - componentDidMount: function() { - document.title = "Publish"; - }, componentDidUpdate: function() { }, onFileChange: function() { @@ -387,7 +384,7 @@ var PublishPage = React.createClass({ const lbcInputHelp = "This LBC remains yours and the deposit can be undone at any time." return ( -
+
@@ -551,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 18e936aee..3462517c9 100644 --- a/ui/js/page/rewards.js +++ b/ui/js/page/rewards.js @@ -4,6 +4,7 @@ import lbryio from '../lbryio.js'; import {CreditAmount, Icon} from '../component/common.js'; import rewards from '../rewards.js'; import Modal from '../component/modal.js'; +import {WalletNav} from './wallet.js'; import {RewardLink} from '../component/link.js'; const RewardTile = React.createClass({ @@ -56,14 +57,15 @@ var RewardsPage = React.createClass({ }, render: function() { return ( -
-
+
+ +
{!this.state.userRewards ? (this.state.failed ?
Failed to load rewards.
: '') : this.state.userRewards.map(({RewardType, RewardTitle, RewardDescription, TransactionID, RewardAmount}) => { return ; })} - +
); } diff --git a/ui/js/page/search.js b/ui/js/page/search.js new file mode 100644 index 000000000..dafeb30cf --- /dev/null +++ b/ui/js/page/search.js @@ -0,0 +1,165 @@ +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, + + search: function(term) { + lighthouse.search(term).then(this.searchCallback); + if (!this.state.searching) { + this.setState({ searching: true }) + } + }, + + componentWillMount: function () { + this._isMounted = true; + this.search(this.props.query); + }, + + componentWillReceiveProps: function (nextProps) { + if (nextProps.query != this.props.query) { + this.search(nextProps.query); + } + }, + + componentWillUnmount: function () { + this._isMounted = false; + }, + + getInitialState: function () { + return { + results: [], + searching: true + }; + }, + + searchCallback: function (results) { + if (this._isMounted) //could have canceled while results were pending, in which case nothing to do + { + this.setState({ + results: results, + searching: false //multiple searches can be out, we're only done if we receive one we actually care about + }); + } + }, + + render: function () { + return this.state.searching ? + : + (this.state.results && this.state.results.length ? + : + ); + } +}); + +let SearchPage = React.createClass({ + + _isMounted: false, + + propTypes: { + query: React.PropTypes.string.isRequired + }, + + isValidUri: function(query) { + try { + lbryuri.parse(query); + return true; + } catch (e) { + return false; + } + }, + + componentWillMount: function() { + this._isMounted = true; + lighthouse.search(this.props.query).then(this.searchCallback); + }, + + componentWillUnmount: function() { + this._isMounted = false; + }, + + getInitialState: function() { + return { + results: [], + searching: true + }; + }, + + searchCallback: function(results) { + if (this._isMounted) //could have canceled while results were pending, in which case nothing to do + { + this.setState({ + results: results, + searching: false //multiple searches can be out, we're only done if we receive one we actually care about + }); + } + }, + + render: function() { + return ( +
+ { this.isValidUri(this.props.query) ? +
+

+ Exact URL + +

+ +
: '' } +
+

+ Search Results for {this.props.query} + +

+ +
+
+ ); + } +}); + +export default SearchPage; diff --git a/ui/js/page/settings.js b/ui/js/page/settings.js index b0f4ff9d9..cbc0765a8 100644 --- a/ui/js/page/settings.js +++ b/ui/js/page/settings.js @@ -1,7 +1,17 @@ import React from 'react'; import {FormField, FormRow} from '../component/form.js'; +import {SubHeader} from '../component/header.js'; import lbry from '../lbry.js'; +export let SettingsNav = React.createClass({ + render: function() { + return ; + } +}); + var SettingsPage = React.createClass({ _onSettingSaveSuccess: function() { // This is bad. @@ -17,7 +27,7 @@ var SettingsPage = React.createClass({ setClientSetting: function(name, value) { lbry.setClientSetting(name, value) this._onSettingSaveSuccess() - }, + }, onRunOnStartChange: function (event) { this.setDaemonSetting('run_on_startup', event.target.checked); }, @@ -56,9 +66,6 @@ var SettingsPage = React.createClass({ showUnavailable: lbry.getClientSetting('showUnavailable'), } }, - componentDidMount: function() { - document.title = "Settings"; - }, componentWillMount: function() { lbry.getDaemonSettings((settings) => { this.setState({ @@ -92,7 +99,8 @@ var SettingsPage = React.createClass({
*/ return ( -
+
+

Download Directory

diff --git a/ui/js/page/show.js b/ui/js/page/show.js index cc2fb5cfc..c72a8bde1 100644 --- a/ui/js/page/show.js +++ b/ui/js/page/show.js @@ -16,8 +16,11 @@ var FormatItem = React.createClass({ outpoint: React.PropTypes.string, }, render: function() { - const {thumbnail, author, title, description, language, license} = this.props.metadata; - const mediaType = lbry.getMediaType(this.props.contentType); + const {author, language, license} = this.props.metadata; + + if (!this.props.contentType && [author, language, license].filter((val) => {return !!val; }).length === 0) { + return null; + } return ( @@ -40,93 +43,108 @@ var FormatItem = React.createClass({ } }); -let ShowPage = React.createClass({ - _uri: null, +let ChannelPage = React.createClass({ + render: function() { + return
+
+
+

{this.props.title}

+
+
+

+ This channel page is a stub. +

+
+
+
+ } +}); + +let FilePage = React.createClass({ + _isMounted: false, propTypes: { uri: React.PropTypes.string, }, + getInitialState: function() { return { - metadata: null, - contentType: null, - hasSignature: false, - signatureIsValid: false, cost: null, costIncludesData: null, - uriLookupComplete: null, isDownloaded: null, }; }, + + componentWillUnmount: function() { + this._isMounted = false; + }, + + componentWillReceiveProps: function(nextProps) { + if (nextProps.outpoint != this.props.outpoint || nextProps.uri != this.props.uri) { + this.loadCostAndFileState(nextProps.uri, nextProps.outpoint); + } + }, + componentWillMount: function() { - this._uri = lbryuri.normalize(this.props.uri); - document.title = this._uri; + this._isMounted = true; + this.loadCostAndFileState(this.props.uri, this.props.outpoint); + }, - lbry.resolve({uri: this._uri}).then(({ claim: {txid, nout, has_signature, signature_is_valid, value: {stream: {metadata, source: {contentType}}}}}) => { - const outpoint = txid + ':' + nout; - - lbry.file_list({outpoint}).then((fileInfo) => { + loadCostAndFileState: function(uri, outpoint) { + lbry.file_list({outpoint: outpoint}).then((fileInfo) => { + if (this._isMounted) { this.setState({ isDownloaded: fileInfo.length > 0, }); - }); - - this.setState({ - outpoint: outpoint, - metadata: metadata, - hasSignature: has_signature, - signatureIsValid: signature_is_valid, - contentType: contentType, - uriLookupComplete: true, - }); + } }); - lbry.getCostInfo(this._uri).then(({cost, includesData}) => { - this.setState({ - cost: cost, - costIncludesData: includesData, - }); + lbry.getCostInfo(uri).then(({cost, includesData}) => { + if (this._isMounted) { + this.setState({ + cost: cost, + costIncludesData: includesData, + }); + } }); }, + render: function() { - const metadata = this.state.metadata; - const title = metadata ? this.state.metadata.title : this._uri; + const metadata = this.props.metadata, + title = metadata ? this.props.metadata.title : this.props.uri, + uriIndicator = ; + return ( -
+
- { this.state.contentType && this.state.contentType.startsWith('video/') ? -
{this.state.isDownloaded === false - ? + ? : null}

{title}

- { this.state.uriLookupComplete ? -
-
- -
-
- -
-
: '' } +
+ { this.props.channelUri ? + {uriIndicator} : + uriIndicator} +
+
+ +
+
+
+ {metadata.description}
- { this.state.uriLookupComplete ? -
-
- {metadata.description} -
-
- :
}
{ metadata ? -
- -
: '' } +
+ +
: '' }
@@ -136,4 +154,131 @@ let ShowPage = React.createClass({ } }); +let ShowPage = React.createClass({ + _uri: null, + _isMounted: false, + + propTypes: { + uri: React.PropTypes.string, + }, + + getInitialState: function() { + return { + outpoint: null, + metadata: null, + contentType: null, + hasSignature: false, + claimType: null, + signatureIsValid: false, + cost: null, + costIncludesData: null, + uriLookupComplete: null, + isFailed: false, + }; + }, + + componentWillUnmount: function() { + this._isMounted = false; + }, + + componentWillReceiveProps: function(nextProps) { + if (nextProps.uri != this.props.uri) { + this.setState(this.getInitialState()); + this.loadUri(nextProps.uri); + } + }, + + componentWillMount: function() { + this._isMounted = true; + this.loadUri(this.props.uri); + }, + + loadUri: function(uri) { + this._uri = lbryuri.normalize(uri); + + lbry.resolve({uri: this._uri}).then((resolveData) => { + const isChannel = resolveData && resolveData.claims_in_channel; + if (!this._isMounted) { + return; + } + if (resolveData) { + let newState = { uriLookupComplete: true } + if (!isChannel) { + let {claim: {txid: txid, nout: nout, has_signature: has_signature, signature_is_valid: signature_is_valid, value: {stream: {metadata: metadata, source: {contentType: contentType}}}}} = resolveData; + + Object.assign(newState, { + claimType: "file", + metadata: metadata, + outpoint: txid + ':' + nout, + hasSignature: has_signature, + signatureIsValid: signature_is_valid, + contentType: contentType + }); + + + lbry.setTitle(metadata.title ? metadata.title : this._uri) + + } else { + let {certificate: {txid: txid, nout: nout, has_signature: has_signature}} = resolveData; + Object.assign(newState, { + claimType: "channel", + outpoint: txid + ':' + nout, + txid: txid, + metadata: { + title:resolveData.certificate.name + } + }); + } + + this.setState(newState); + + } else { + this.setState(Object.assign({}, this.getInitialState(), { + uriLookupComplete: true, + isFailed: true + })); + } + }); + }, + + render: function() { + const metadata = this.state.metadata, + title = metadata ? this.state.metadata.title : this._uri; + + let innerContent = ""; + + if (!this.state.uriLookupComplete || this.state.isFailed) { + innerContent =
+
+

{title}

+
+
+ { this.state.uriLookupComplete ? +

This location is not yet in use. { ' ' }.

: + + } +
+
; + } else if (this.state.claimType == "channel") { + innerContent = + } else { + let channelUriObj = lbryuri.parse(this._uri) + delete channelUriObj.path; + delete channelUriObj.contentName; + const channelUri = this.state.signatureIsValid && this.state.hasSignature && channelUriObj.isChannel ? lbryuri.build(channelUriObj, false) : null; + innerContent = ; + } + + return
{innerContent}
; + } +}); + export default ShowPage; diff --git a/ui/js/page/start.js b/ui/js/page/start.js index 9f918db27..e1cf6e7dd 100644 --- a/ui/js/page/start.js +++ b/ui/js/page/start.js @@ -5,12 +5,9 @@ var StartPage = React.createClass({ componentWillMount: function() { lbry.stop(); }, - componentDidMount: function() { - document.title = "LBRY is Closed"; - }, render: function() { return ( -
+

LBRY is Closed

diff --git a/ui/js/page/wallet.js b/ui/js/page/wallet.js index 5f9e4e53a..f50f4e1be 100644 --- a/ui/js/page/wallet.js +++ b/ui/js/page/wallet.js @@ -2,6 +2,7 @@ import React from 'react'; import lbry from '../lbry.js'; import {Link} from '../component/link.js'; import Modal from '../component/modal.js'; +import {SubHeader} from '../component/header.js'; import {FormField, FormRow} from '../component/form.js'; import {Address, BusyMessage, CreditAmount} from '../component/common.js'; @@ -263,6 +264,16 @@ var TransactionList = React.createClass({ } }); +export let WalletNav = React.createClass({ + render: function() { + return ; + } +}); var WalletPage = React.createClass({ _balanceSubscribeId: null, @@ -270,9 +281,6 @@ var WalletPage = React.createClass({ propTypes: { viewingPage: React.PropTypes.string, }, - componentDidMount: function() { - document.title = "My Wallet"; - }, /* Below should be refactored so that balance is shared all of wallet page. Or even broader? What is the proper React pattern for sharing a global state like balance? @@ -296,7 +304,8 @@ var WalletPage = React.createClass({ }, render: function() { return ( -
+
+

Balance

diff --git a/ui/js/utils.js b/ui/js/utils.js index b24eb25b6..61bf53188 100644 --- a/ui/js/utils.js +++ b/ui/js/utils.js @@ -2,9 +2,9 @@ * Thin wrapper around localStorage.getItem(). Parses JSON and returns undefined if the value * is not set yet. */ -export function getLocal(key) { +export function getLocal(key, fallback=undefined) { const itemRaw = localStorage.getItem(key); - return itemRaw === null ? undefined : JSON.parse(itemRaw); + return itemRaw === null ? fallback : JSON.parse(itemRaw); } /** diff --git a/ui/package.json b/ui/package.json index cb9fe5663..b8514c6c1 100644 --- a/ui/package.json +++ b/ui/package.json @@ -21,7 +21,6 @@ "babel-cli": "^6.11.4", "babel-preset-es2015": "^6.13.2", "babel-preset-react": "^6.11.1", - "clamp-js-main": "^0.11.1", "mediaelement": "^2.23.4", "node-sass": "^3.8.0", "rc-progress": "^2.0.6", diff --git a/ui/scss/_canvas.scss b/ui/scss/_canvas.scss deleted file mode 100644 index 8aa4227e3..000000000 --- a/ui/scss/_canvas.scss +++ /dev/null @@ -1,220 +0,0 @@ -@import "global"; - -html -{ - height: 100%; - font-size: $font-size; -} -body -{ - font-family: 'Source Sans Pro', sans-serif; - line-height: $font-line-height; -} - -$drawer-width: 220px; - -#drawer -{ - width: $drawer-width; - position: fixed; - min-height: 100vh; - left: 0; - top: 0; - background: $color-bg; - z-index: 3; - .drawer-item - { - display: block; - padding: $spacing-vertical / 2; - font-size: 1.2em; - height: $spacing-vertical * 1.5; - .icon - { - margin-right: 6px; - } - .link-label - { - line-height: $spacing-vertical * 1.5; - } - .badge - { - float: right; - margin-top: $spacing-vertical * 0.25 - 2; - background: $color-money; - } - } - .drawer-item-selected - { - background: $color-canvas; - color: $color-primary; - } -} -.badge -{ - background: $color-money; - display: inline-block; - padding: 2px; - color: white; - border-radius: 2px; -} -.credit-amount--indicator -{ - font-weight: bold; - color: $color-money; -} -#drawer-handle -{ - padding: $spacing-vertical / 2; - max-height: $height-header - $spacing-vertical; - text-align: center; -} - -#window -{ - position: relative; /*window has it's own z-index inside of it*/ - z-index: 1; -} -#window.drawer-closed -{ - #drawer { display: none } -} -#window.drawer-open -{ - #main-content { margin-left: $drawer-width; } - .open-drawer-link { display: none } - #header { padding-left: $drawer-width + $spacing-vertical / 2; } -} - -#header -{ - background: $color-primary; - color: white; - &.header-no-subnav { - height: $height-header; - } - &.header-with-subnav { - height: $height-header * 2; - } - position: fixed; - top: 0; - left: 0; - width: 100%; - z-index: 2; - box-sizing: border-box; - h1 { font-size: 1.8em; line-height: $height-header - $spacing-vertical; display: inline-block; float: left; } - &.header-scrolled - { - box-shadow: $default-box-shadow; - } -} -.header-top-bar -{ - padding: $spacing-vertical / 2; -} -.header-search -{ - margin-left: 60px; - $padding-adjust: 36px; - text-align: center; - .icon { - position: absolute; - top: $spacing-vertical * 1.5 / 2 + 2px; //hacked - margin-left: -$padding-adjust + 14px; //hacked - } - input[type="search"] { - position: relative; - left: -$padding-adjust; - background: rgba(255, 255, 255, 0.3); - color: white; - width: 400px; - height: $spacing-vertical * 1.5; - line-height: $spacing-vertical * 1.5; - padding-left: $padding-adjust + 3; - padding-right: 3px; - @include border-radius(2px); - @include placeholder-color(#e8e8e8); - &:focus { - box-shadow: $focus-box-shadow; - } - } -} - -nav.sub-header -{ - background: $color-primary; - text-transform: uppercase; - padding: $spacing-vertical / 2; - > a - { - $sub-header-selected-underline-height: 2px; - display: inline-block; - margin: 0 15px; - padding: 0 5px; - line-height: $height-header - $spacing-vertical - $sub-header-selected-underline-height; - color: #e8e8e8; - &:first-child - { - margin-left: 0; - } - &:last-child - { - margin-right: 0; - } - &.sub-header-selected - { - border-bottom: $sub-header-selected-underline-height solid #fff; - color: #fff; - } - &:hover - { - color: #fff; - } - } -} - -#main-content -{ - background: $color-canvas; - &.no-sub-nav - { - min-height: calc(100vh - 60px); //should be -$height-header, but I'm dumb I guess? It wouldn't work - main { margin-top: $height-header; } - } - &.with-sub-nav - { - min-height: calc(100vh - 120px); //should be -$height-header, but I'm dumb I guess? It wouldn't work - main { margin-top: $height-header * 2; } - } - main - { - padding: $spacing-vertical; - &.constrained-page - { - max-width: $width-page-constrained; - margin-left: auto; - margin-right: auto; - } - } -} - -$header-icon-size: 1.5em; - -.open-drawer-link, .close-drawer-link -{ - display: inline-block; - font-size: $header-icon-size; - padding: 2px 6px 0 6px; - float: left; -} -.close-lbry-link -{ - font-size: $header-icon-size; - float: right; - padding: 0 6px 0 18px; -} - -.full-screen -{ - width: 100%; - height: 100%; -} \ No newline at end of file diff --git a/ui/scss/_global.scss b/ui/scss/_global.scss index d829d8245..180a9d41c 100644 --- a/ui/scss/_global.scss +++ b/ui/scss/_global.scss @@ -34,8 +34,8 @@ $height-header: $spacing-vertical * 2.5; $height-button: $spacing-vertical * 1.5; $height-video-embedded: $width-page-constrained * 9 / 16; -$default-box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); -$focus-box-shadow: 2px 4px 4px 0 rgba(0,0,0,.14),2px 5px 3px -2px rgba(0,0,0,.2),2px 3px 7px 0 rgba(0,0,0,.12); +$box-shadow-layer: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12); +$box-shadow-focus: 2px 4px 4px 0 rgba(0,0,0,.14),2px 5px 3px -2px rgba(0,0,0,.2),2px 3px 7px 0 rgba(0,0,0,.12); $transition-standard: .225s ease; @@ -160,4 +160,35 @@ $blur-intensity: 8px; width:1px; height:1px; overflow:hidden; +} + +@mixin text-link($color: $color-primary, $hover-opacity: 0.70) { + .icon + { + &:first-child { + padding-right: 5px; + } + &:last-child:not(:only-child) { + padding-left: 5px; + } + } + + &:not(.no-underline) { + text-decoration: underline; + .icon { + text-decoration: none; + } + } + &:hover + { + opacity: $hover-opacity; + transition: opacity $transition-standard; + text-decoration: underline; + .icon { + text-decoration: none; + } + } + + color: $color; + cursor: pointer; } \ No newline at end of file diff --git a/ui/scss/_grid.scss b/ui/scss/_grid.scss deleted file mode 100644 index cdaa42133..000000000 --- a/ui/scss/_grid.scss +++ /dev/null @@ -1,87 +0,0 @@ -@import "global"; - -$gutter_fluid: 4; - -[class*="span"] { - min-height: 1px; - max-width: 100%; -} - -.span12 { width: 100%; } -.span11 { width: 91.666%; } -.span10 { width: 83.333%; } -.span9 { width: 75%; } -.span8 { width: 66.666%; } -.span7 { width: 58.333%; } -.span6 { width: 50%; } -.span5 { width: 41.666%; } -.span4 { width: 33.333%; } -.span3 { width: 25%; } -.span2 { width: 16.666%; } -.span1 { width: 8.333%; } - -.row-fluid { - width: 100%; - > [class*="span"] { - float: left; - width: 100%; - margin-left: 1% * $gutter_fluid; - &:first-child - { - margin-left: 0; - } - } - - $column_width: (100% - $gutter_fluid * 11) / 12; - - > .span12 { width: $column_width * 12 + $gutter_fluid * 11; } - > .span11 { width: $column_width * 11 + $gutter_fluid * 10; } - > .span10 { width: $column_width * 10 + $gutter_fluid * 9; } - > .span9 { width: $column_width * 9 + $gutter_fluid * 8; } - > .span8 { width: $column_width * 8 + $gutter_fluid * 7; } - > .span7 { width: $column_width * 7 + $gutter_fluid * 6; } - > .span6 { width: $column_width * 6 + $gutter_fluid * 5; } - > .span5 { width: $column_width * 5 + $gutter_fluid * 4; } - > .span4 { width: $column_width * 4 + $gutter_fluid * 3; } - > .span3 { width: $column_width * 3 + $gutter_fluid * 2; } - > .span2 { width: $column_width * 2 + $gutter_fluid * 1; } - > .span1 { width: $column_width; } -} - -.tile-fluid { - width: 100%; - > [class*="span"] { - float: left; - } -} - -.column-fluid { - @include display-flex(); - flex-wrap: wrap; - > [class*="span"] { - @include display-flex(); - @include flex(1 0 auto); - overflow: hidden; - justify-content: center; - } -} - -.row-fluid, .tile-fluid { - @include clearfix(); -} - -@media (max-width: $mobile-width-threshold) { - .row-fluid, .tile-fluid, .column-fluid { - width: 100%; - } - .pull-left, .pull-right - { - float: none; - } - [class*="span"] { - float: none !important; - width: 100% !important; - margin-left: 0 !important; - display: block !important; - } -} \ No newline at end of file diff --git a/ui/scss/_gui.scss b/ui/scss/_gui.scss index f875a6940..3d85f4b22 100644 --- a/ui/scss/_gui.scss +++ b/ui/scss/_gui.scss @@ -1,35 +1,51 @@ @import "global"; -@mixin text-link($color: $color-primary, $hover-opacity: 0.70) { +html +{ + height: 100%; + font-size: $font-size; +} +body +{ + font-family: 'Source Sans Pro', sans-serif; + line-height: $font-line-height; +} - .icon +#window +{ + min-height: 100vh; + background: $color-canvas; +} + +.badge +{ + background: $color-money; + display: inline-block; + padding: 2px; + color: white; + border-radius: 2px; +} +.credit-amount--indicator +{ + font-weight: bold; + color: $color-money; +} + +#main-content +{ + padding: $spacing-vertical; + margin-top: $height-header; + display: flex; + flex-direction: column; + main { + margin-left: auto; + margin-right: auto; + max-width: 100%; + } + main.main--single-column { - &:first-child { - padding-right: 5px; - } - &:last-child:not(:only-child) { - padding-left: 5px; - } + width: $width-page-constrained; } - - &:not(.no-underline) { - text-decoration: underline; - .icon { - text-decoration: none; - } - } - &:hover - { - opacity: $hover-opacity; - transition: opacity $transition-standard; - text-decoration: underline; - .icon { - text-decoration: none; - } - } - - color: $color; - cursor: pointer; } .icon-fixed-width { @@ -80,7 +96,10 @@ p } .truncated-text { - display: inline-block; + //display: inline-block; + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; } .busy-indicator @@ -126,10 +145,11 @@ p .sort-section { display: block; - margin-bottom: 5px; + margin-bottom: $spacing-vertical * 2/3; text-align: right; + line-height: 1; font-size: 0.85em; color: $color-help; } diff --git a/ui/scss/_icons.scss b/ui/scss/_icons.scss index 91b8255bb..441113e39 100644 --- a/ui/scss/_icons.scss +++ b/ui/scss/_icons.scss @@ -25,12 +25,6 @@ transform: translate(0, 0); } -.icon-mega -{ - font-size: 200px; - line-height: 1; -} - /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen readers do not read off random characters that represent icons */ .icon-glass:before { diff --git a/ui/scss/all.scss b/ui/scss/all.scss index b4c6611a6..7c87a5fbb 100644 --- a/ui/scss/all.scss +++ b/ui/scss/all.scss @@ -1,8 +1,6 @@ @import "_reset"; -@import "_grid"; @import "_icons"; @import "_mediaelement"; -@import "_canvas"; @import "_gui"; @import "component/_table"; @import "component/_button.scss"; @@ -10,6 +8,7 @@ @import "component/_file-actions.scss"; @import "component/_file-tile.scss"; @import "component/_form-field.scss"; +@import "component/_header.scss"; @import "component/_menu.scss"; @import "component/_tooltip.scss"; @import "component/_load-screen.scss"; diff --git a/ui/scss/component/_button.scss b/ui/scss/component/_button.scss index e3c5fe8e8..5c6fed22f 100644 --- a/ui/scss/component/_button.scss +++ b/ui/scss/component/_button.scss @@ -34,6 +34,11 @@ $button-focus-shift: 12%; { padding-left: 5px; } + .icon:only-child + { + padding-left: 0; + padding-right: 0; + } } .button-block { @@ -49,17 +54,17 @@ $button-focus-shift: 12%; $color-button-text: white; color: darken($color-button-text, $button-focus-shift * 0.5); background-color: $color-primary; - box-shadow: $default-box-shadow; + box-shadow: $box-shadow-layer; &:focus { color: $color-button-text; - //box-shadow: $focus-box-shadow; + //box-shadow: $box-shadow-focus; background-color: mix(black, $color-primary, $button-focus-shift) } } .button-alt { background-color: $color-bg-alt; - box-shadow: $default-box-shadow; + box-shadow: $box-shadow-layer; } .button-text @@ -76,3 +81,7 @@ $button-focus-shift: 12%; @include text-link(#aaa); font-size: 0.8em; } +.button--flat +{ + box-shadow: none !important; +} \ No newline at end of file diff --git a/ui/scss/component/_card.scss b/ui/scss/component/_card.scss index e019d7342..2e325d827 100644 --- a/ui/scss/component/_card.scss +++ b/ui/scss/component/_card.scss @@ -7,7 +7,7 @@ $padding-card-horizontal: $spacing-vertical * 2/3; margin-right: auto; max-width: $width-page-constrained; background: $color-bg; - box-shadow: $default-box-shadow; + box-shadow: $box-shadow-layer; border-radius: 2px; margin-bottom: $spacing-vertical * 2/3; overflow: auto; @@ -86,7 +86,7 @@ $card-link-scaling: 1.1; .card--link:hover { position: relative; z-index: 1; - box-shadow: $focus-box-shadow; + box-shadow: $box-shadow-focus; transform: scale($card-link-scaling); transform-origin: 50% 50%; overflow-x: visible; @@ -139,8 +139,12 @@ $height-card-small: $spacing-vertical * 15; overflow-x: auto; overflow-y: hidden; white-space: nowrap; + + /*hacky way to give space for hover */ padding-left: 20px; - margin-left: -20px; /*hacky way to give space for hover */ + margin-left: -20px; + padding-right: 20px; + margin-right: -20px; } .card-row__header { margin-bottom: $spacing-vertical / 3; diff --git a/ui/scss/component/_file-tile.scss b/ui/scss/component/_file-tile.scss index eb768bbf0..433abc746 100644 --- a/ui/scss/component/_file-tile.scss +++ b/ui/scss/component/_file-tile.scss @@ -1,37 +1,26 @@ @import "../global"; -$height-file-tile: $spacing-vertical * 8; +$height-file-tile: $spacing-vertical * 6; .file-tile__row { + overflow: hidden; height: $height-file-tile; .credit-amount { float: right; } - //Hack! Remove below! - .card__title-primary { - margin-top: $spacing-vertical * 2/3; + //also a hack + .card__media { + height: $height-file-tile; + max-width: $height-file-tile; + width: $height-file-tile; + margin-right: $spacing-vertical / 2; + float: left; + } + //basically everything here is a hack now + .file-tile__content { + padding-top: $spacing-vertical * 1/3; + margin-left: $height-file-tile + $spacing-vertical / 2; + } + .card__title-primary { + margin-top: 0; } -} - -.file-tile__thumbnail { - max-width: 100%; - max-height: $height-file-tile; - vertical-align: middle; - display: block; - margin-left: auto; - margin-right: auto; -} -.file-tile__thumbnail-container -{ - height: $height-file-tile; - @include absolute-center(); -} - -.file-tile__title { - font-weight: bold; -} - -.file-tile__description { - color: #444; - margin-top: 12px; - font-size: 0.9em; } \ No newline at end of file diff --git a/ui/scss/component/_header.scss b/ui/scss/component/_header.scss new file mode 100644 index 000000000..0071f01f9 --- /dev/null +++ b/ui/scss/component/_header.scss @@ -0,0 +1,94 @@ +@import "../global"; + +$color-header: #666; +$color-header-active: darken($color-header, 20%); + +#header +{ + color: $color-header; + background: #fff; + display: flex; + position: fixed; + box-shadow: $box-shadow-layer; + top: 0; + left: 0; + width: 100%; + z-index: 2; + padding: $spacing-vertical / 2; + box-sizing: border-box; +} +.header__item { + flex: 0 0 content; + padding-left: $spacing-vertical / 4; + padding-right: $spacing-vertical / 4; +} +.header__item--wunderbar { + flex-grow: 1; +} + +.wunderbar +{ + position: relative; + .icon { + position: absolute; + left: 10px; + top: $spacing-vertical / 2 - 4px; //hacked + } +} + +.wunderbar--active .icon-search { color: $color-primary; } + +.wunderbar__input { + background: rgba(255, 255, 255, 0.7); + width: 100%; + color: $color-header; + height: $spacing-vertical * 1.5; + line-height: $spacing-vertical * 1.5; + padding-left: 38px; + padding-right: 5px; + border: 1px solid $color-text-dark; + @include border-radius(2px); + border: 1px solid #ccc; + &:focus { + color: $color-header-active; + box-shadow: $box-shadow-focus; + border-color: $color-primary; + } +} + +nav.sub-header +{ + text-transform: uppercase; + padding: 0 0 $spacing-vertical; + &.sub-header--constrained { + max-width: $width-page-constrained; + margin-left: auto; + margin-right: auto; + } + > a + { + $sub-header-selected-underline-height: 2px; + display: inline-block; + margin: 0 15px; + padding: 0 5px; + line-height: $height-header - $spacing-vertical - $sub-header-selected-underline-height; + color: $color-header; + &:first-child + { + margin-left: 0; + } + &:last-child + { + margin-right: 0; + } + &.sub-header-selected + { + border-bottom: $sub-header-selected-underline-height solid $color-header-active; + color: $color-header-active; + } + &:hover + { + color: $color-header-active; + } + } +} \ No newline at end of file diff --git a/ui/scss/component/_menu.scss b/ui/scss/component/_menu.scss index e3b0566c4..d8e79be28 100644 --- a/ui/scss/component/_menu.scss +++ b/ui/scss/component/_menu.scss @@ -10,7 +10,7 @@ $border-radius-menu: 2px; position: absolute; white-space: nowrap; background-color: white; - box-shadow: $default-box-shadow; + box-shadow: $box-shadow-layer; border-radius: $border-radius-menu; padding-top: ($spacing-vertical / 5) 0px; z-index: 1; diff --git a/ui/scss/component/_modal.scss b/ui/scss/component/_modal.scss index 13284c7ff..05d5e8de1 100644 --- a/ui/scss/component/_modal.scss +++ b/ui/scss/component/_modal.scss @@ -29,7 +29,7 @@ overflow: auto; border-radius: 4px; padding: $spacing-vertical; - box-shadow: $default-box-shadow; + box-shadow: $box-shadow-layer; max-width: 400px; } diff --git a/ui/scss/component/_tooltip.scss b/ui/scss/component/_tooltip.scss index 9a6ccd7da..0be9b1db8 100644 --- a/ui/scss/component/_tooltip.scss +++ b/ui/scss/component/_tooltip.scss @@ -15,6 +15,7 @@ z-index: 1; left: 50%; margin-left: $tooltip-body-width * -1 / 2; + white-space: normal; box-sizing: border-box; padding: $spacing-vertical / 2; @@ -24,7 +25,7 @@ background-color: $color-bg; font-size: $font-size * 7/8; line-height: $font-line-height; - box-shadow: $default-box-shadow; + box-shadow: $box-shadow-layer; } .tooltip--header .tooltip__link {