From c60b1701ff539ff25124231e721f1ca09205e485 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Mon, 10 Jun 2019 14:50:32 -0400 Subject: [PATCH] discovery changes --- package.json | 7 +- src/platforms/electron/createWindow.js | 13 - src/ui/component/app/index.js | 8 +- src/ui/component/app/view.jsx | 84 ++--- src/ui/component/button/view.jsx | 2 +- src/ui/component/categoryList/index.js | 22 -- src/ui/component/categoryList/view.jsx | 316 ---------------- src/ui/component/channelAbout/view.jsx | 4 +- src/ui/component/channelContent/view.jsx | 9 +- src/ui/component/channelThumbnail/view.jsx | 11 +- src/ui/component/channelTile/index.js | 23 -- src/ui/component/channelTile/view.jsx | 89 ----- src/ui/component/common/credit-amount.jsx | 4 +- src/ui/component/emailCollection/index.js | 4 - src/ui/component/errorBoundary/view.jsx | 6 +- src/ui/component/fileCard/view.jsx | 154 -------- src/ui/component/fileList/index.js | 9 +- src/ui/component/fileList/view.jsx | 213 +++-------- .../{fileCard => fileListItem}/index.js | 17 +- src/ui/component/fileListItem/view.jsx | 132 +++++++ src/ui/component/fileListSearch/index.js | 16 - src/ui/component/fileListSearch/view.jsx | 44 --- src/ui/component/fileListTrending/index.js | 18 + src/ui/component/fileListTrending/view.jsx | 118 ++++++ src/ui/component/fileProperties/index.js | 18 + src/ui/component/fileProperties/view.jsx | 31 ++ src/ui/component/fileTags/index.js | 13 + src/ui/component/fileTags/view.jsx | 49 +++ src/ui/component/fileTile/index.js | 43 --- src/ui/component/fileTile/view.jsx | 216 ----------- src/ui/component/firstRun/index.js | 27 -- src/ui/component/firstRun/view.jsx | 131 ------- src/ui/component/header/view.jsx | 146 ++++---- .../navigationHistoryRecent/view.jsx | 2 +- src/ui/component/page/view.jsx | 10 +- src/ui/component/recommendedContent/view.jsx | 19 +- src/ui/component/rewardSummary/view.jsx | 8 +- src/ui/component/router/view.jsx | 9 +- src/ui/component/searchOptions/index.js | 21 +- src/ui/component/searchOptions/view.jsx | 35 +- src/ui/component/sideBar/index.js | 8 +- src/ui/component/sideBar/view.jsx | 117 ++---- src/ui/component/spinner/view.jsx | 2 +- src/ui/component/subscribeButton/view.jsx | 8 +- src/ui/component/subscribeSuggested/index.js | 13 - src/ui/component/subscribeSuggested/view.jsx | 38 -- src/ui/component/tag/index.js | 11 + src/ui/component/tag/view.jsx | 36 ++ src/ui/component/tags/index.js | 14 + src/ui/component/tags/view.jsx | 23 ++ src/ui/component/tagsSearch/index.js | 25 ++ src/ui/component/tagsSearch/view.jsx | 69 ++++ src/ui/component/tagsSelect/index.js | 25 ++ src/ui/component/tagsSelect/view.jsx | 61 +++ .../component/transactionListRecent/view.jsx | 2 +- src/ui/component/uriIndicator/view.jsx | 15 +- src/ui/component/userEmailNew/view.jsx | 1 - src/ui/component/walletBalance/view.jsx | 7 +- src/ui/constants/action_types.js | 3 - src/ui/constants/icons.js | 1 + src/ui/constants/pages.js | 3 +- src/ui/constants/settings.js | 1 - src/ui/modal/modal.jsx | 8 +- src/ui/modal/modalAffirmPurchase/view.jsx | 2 +- src/ui/page/account/view.jsx | 2 + src/ui/page/channel/view.jsx | 66 ++-- src/ui/page/discover/index.js | 20 +- src/ui/page/discover/view.jsx | 86 +---- src/ui/page/file/view.jsx | 53 ++- src/ui/page/fileListDownloaded/index.js | 11 +- src/ui/page/fileListDownloaded/view.jsx | 58 ++- src/ui/page/fileListPublished/index.js | 3 +- src/ui/page/fileListPublished/view.jsx | 60 +-- src/ui/page/search/index.js | 28 +- src/ui/page/search/view.jsx | 54 ++- src/ui/page/show/view.jsx | 2 +- src/ui/page/subscriptions/index.js | 28 +- .../page/subscriptions/internal/first-run.jsx | 51 --- .../internal/user-subscriptions.jsx | 124 ------- src/ui/page/subscriptions/view.jsx | 134 +++---- src/ui/page/tags/index.js | 17 + src/ui/page/tags/view.jsx | 28 ++ src/ui/page/tagsEdit/index.js | 17 + src/ui/page/tagsEdit/view.jsx | 18 + src/ui/reducers.js | 10 +- src/ui/redux/actions/app.js | 6 - src/ui/redux/actions/content.js | 9 +- src/ui/redux/actions/subscriptions.js | 174 ++++----- src/ui/redux/reducers/app.js | 6 - src/ui/redux/reducers/settings.js | 4 +- src/ui/redux/reducers/subscriptions.js | 8 - src/ui/redux/selectors/app.js | 18 - src/ui/redux/selectors/subscriptions.js | 12 +- src/ui/scss/all.scss | 5 +- src/ui/scss/component/_badge.scss | 17 + src/ui/scss/component/_banner.scss | 31 -- src/ui/scss/component/_button.scss | 59 ++- src/ui/scss/component/_card.scss | 82 ++-- src/ui/scss/component/_channel.scss | 30 +- src/ui/scss/component/_content.scss | 2 +- src/ui/scss/component/_expandable.scss | 12 +- src/ui/scss/component/_file-list.scss | 137 +++++++ src/ui/scss/component/_file-properties.scss | 13 + src/ui/scss/component/_file-render.scss | 8 +- src/ui/scss/component/_form-field.scss | 13 +- src/ui/scss/component/_form-row.scss | 10 +- src/ui/scss/component/_header.scss | 85 +++-- src/ui/scss/component/_icon.scss | 9 +- src/ui/scss/component/_item-list.scss | 12 +- src/ui/scss/component/_main.scss | 80 ++-- src/ui/scss/component/_markdown-editor.scss | 4 +- src/ui/scss/component/_markdown-preview.scss | 22 +- src/ui/scss/component/_media.scss | 351 ++---------------- src/ui/scss/component/_modal.scss | 49 +-- src/ui/scss/component/_navigation.scss | 115 ++---- src/ui/scss/component/_notice.scss | 2 +- src/ui/scss/component/_pagination.scss | 2 +- src/ui/scss/component/_placeholder.scss | 58 +-- src/ui/scss/component/_scrollbar.scss | 27 -- src/ui/scss/component/_search.scss | 51 +-- src/ui/scss/component/_snack-bar.scss | 7 +- src/ui/scss/component/_spinner.scss | 13 +- src/ui/scss/component/_splash.scss | 2 +- src/ui/scss/component/_table.scss | 10 +- src/ui/scss/component/_tags.scss | 94 +++++ src/ui/scss/component/_tooltip.scss | 2 +- src/ui/scss/component/_wunderbar.scss | 56 ++- src/ui/scss/component/_yrbl.scss | 6 +- src/ui/scss/component/tabs.scss | 11 +- src/ui/scss/init/_gui.scss | 5 +- src/ui/scss/init/_mixins.scss | 4 - src/ui/scss/init/_vars.scss | 37 +- src/ui/store.js | 5 +- src/ui/util/enhanced-layout.js | 30 +- src/ui/util/reorder-array.js | 10 + .../{shuffleArray.js => shuffle-array.js} | 0 src/ui/util/use-persisted-state.js | 23 ++ static/index.dev.html | 3 +- static/index.html | 1 + yarn.lock | 50 ++- 140 files changed, 2071 insertions(+), 3285 deletions(-) delete mode 100644 src/ui/component/categoryList/index.js delete mode 100644 src/ui/component/categoryList/view.jsx delete mode 100644 src/ui/component/channelTile/index.js delete mode 100644 src/ui/component/channelTile/view.jsx delete mode 100644 src/ui/component/fileCard/view.jsx rename src/ui/component/{fileCard => fileListItem}/index.js (53%) create mode 100644 src/ui/component/fileListItem/view.jsx delete mode 100644 src/ui/component/fileListSearch/index.js delete mode 100644 src/ui/component/fileListSearch/view.jsx create mode 100644 src/ui/component/fileListTrending/index.js create mode 100644 src/ui/component/fileListTrending/view.jsx create mode 100644 src/ui/component/fileProperties/index.js create mode 100644 src/ui/component/fileProperties/view.jsx create mode 100644 src/ui/component/fileTags/index.js create mode 100644 src/ui/component/fileTags/view.jsx delete mode 100644 src/ui/component/fileTile/index.js delete mode 100644 src/ui/component/fileTile/view.jsx delete mode 100644 src/ui/component/firstRun/index.js delete mode 100644 src/ui/component/firstRun/view.jsx delete mode 100644 src/ui/component/subscribeSuggested/index.js delete mode 100644 src/ui/component/subscribeSuggested/view.jsx create mode 100644 src/ui/component/tag/index.js create mode 100644 src/ui/component/tag/view.jsx create mode 100644 src/ui/component/tags/index.js create mode 100644 src/ui/component/tags/view.jsx create mode 100644 src/ui/component/tagsSearch/index.js create mode 100644 src/ui/component/tagsSearch/view.jsx create mode 100644 src/ui/component/tagsSelect/index.js create mode 100644 src/ui/component/tagsSelect/view.jsx delete mode 100644 src/ui/page/subscriptions/internal/first-run.jsx delete mode 100644 src/ui/page/subscriptions/internal/user-subscriptions.jsx create mode 100644 src/ui/page/tags/index.js create mode 100644 src/ui/page/tags/view.jsx create mode 100644 src/ui/page/tagsEdit/index.js create mode 100644 src/ui/page/tagsEdit/view.jsx delete mode 100644 src/ui/scss/component/_banner.scss create mode 100644 src/ui/scss/component/_file-list.scss create mode 100644 src/ui/scss/component/_file-properties.scss delete mode 100644 src/ui/scss/component/_scrollbar.scss create mode 100644 src/ui/scss/component/_tags.scss create mode 100644 src/ui/util/reorder-array.js rename src/ui/util/{shuffleArray.js => shuffle-array.js} (100%) create mode 100644 src/ui/util/use-persisted-state.js diff --git a/package.json b/package.json index 43448d283d6..e4fc4a72189 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@exponent/electron-cookies": "^2.0.0", "@hot-loader/react-dom": "16.8", "@lbry/color": "^1.0.2", - "@lbry/components": "^2.7.0", + "@lbry/components": "^2.7.2", "@reach/rect": "^0.2.1", "@reach/tabs": "^0.1.5", "@types/three": "^0.93.1", @@ -119,7 +119,7 @@ "jsmediatags": "^3.8.1", "json-loader": "^0.5.4", "lbry-format": "https://github.com/lbryio/lbry-format.git", - "lbry-redux": "lbryio/lbry-redux#02f6918238110726c0b3b4248c61a84ac0b969e3", + "lbry-redux": "lbryio/lbry-redux#7ed316ba48f16246e4d3b99467fa5c3ffdeffa52", "lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845", "lint-staged": "^7.0.2", "localforage": "^1.7.1", @@ -153,6 +153,7 @@ "react-router": "^5.0.0", "react-router-dom": "^5.0.0", "react-simplemde-editor": "^4.0.0", + "react-spring": "^8.0.20", "react-toggle": "^4.0.2", "redux": "^3.6.0", "redux-persist": "^4.8.0", @@ -191,7 +192,7 @@ "yarn": "^1.3" }, "lbrySettings": { - "lbrynetDaemonVersion": "0.37.2", + "lbrynetDaemonVersion": "0.38.0rc6", "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip", "lbrynetDaemonDir": "static/daemon", "lbrynetDaemonFileName": "lbrynet" diff --git a/src/platforms/electron/createWindow.js b/src/platforms/electron/createWindow.js index 90fd4df2921..532194cfb27 100644 --- a/src/platforms/electron/createWindow.js +++ b/src/platforms/electron/createWindow.js @@ -84,19 +84,6 @@ export default appState => { window.loadURL(rendererURL + deepLinkingURI); setupBarMenu(); - // Windows back/forward mouse navigation - window.on('app-command', (e, cmd) => { - switch (cmd) { - case 'browser-backward': - window.webContents.send('navigate-backward', null); - break; - case 'browser-forward': - window.webContents.send('navigate-forward', null); - break; - default: // Do nothing - } - }); - window.on('close', event => { if (!appState.isQuitting && !appState.autoUpdateAccepted) { event.preventDefault(); diff --git a/src/ui/component/app/index.js b/src/ui/component/app/index.js index 767e4a32d98..e0c6274dcc7 100644 --- a/src/ui/component/app/index.js +++ b/src/ui/component/app/index.js @@ -1,22 +1,20 @@ import { hot } from 'react-hot-loader/root'; import { connect } from 'react-redux'; import { doUpdateBlockHeight, doError } from 'lbry-redux'; -import { doToggleEnhancedLayout } from 'redux/actions/app'; -import { selectUser } from 'lbryinc'; +import { selectUser, doRewardList, doFetchRewardedContent } from 'lbryinc'; import { selectThemePath } from 'redux/selectors/settings'; -import { selectEnhancedLayout } from 'redux/selectors/app'; import App from './view'; const select = state => ({ user: selectUser(state), theme: selectThemePath(state), - enhancedLayout: selectEnhancedLayout(state), }); const perform = dispatch => ({ alertError: errorList => dispatch(doError(errorList)), updateBlockHeight: () => dispatch(doUpdateBlockHeight()), - toggleEnhancedLayout: () => dispatch(doToggleEnhancedLayout()), + fetchRewards: () => dispatch(doRewardList()), + fetchRewardedContent: () => dispatch(doFetchRewardedContent()), }); export default hot( diff --git a/src/ui/component/app/view.jsx b/src/ui/component/app/view.jsx index cd7ca8a7295..e6a4b70280f 100644 --- a/src/ui/component/app/view.jsx +++ b/src/ui/component/app/view.jsx @@ -1,85 +1,53 @@ // @flow -import React from 'react'; +import React, { useEffect, useRef } from 'react'; import Router from 'component/router/index'; import ModalRouter from 'modal/modalRouter'; import ReactModal from 'react-modal'; import SideBar from 'component/sideBar'; import Header from 'component/header'; import { openContextMenu } from 'util/context-menu'; -import EnhancedLayoutListener from 'util/enhanced-layout'; +// import useKonamiListener from 'util/enhanced-layout'; import Yrbl from 'component/yrbl'; -const TWO_POINT_FIVE_MINUTES = 1000 * 60 * 2.5; - type Props = { alertError: (string | {}) => void, pageTitle: ?string, theme: string, - updateBlockHeight: () => void, - toggleEnhancedLayout: () => void, - enhancedLayout: boolean, + fetchRewards: () => void, + fetchRewardedContent: () => void, }; -class App extends React.PureComponent { - componentWillMount() { - const { alertError, theme } = this.props; +function App(props: Props) { + const { theme, fetchRewards, fetchRewardedContent } = props; + const appRef = useRef(); + const isEnhancedLayout = useKonamiListener(); - // TODO: create type for this object - // it lives in jsonrpc.js - document.addEventListener('unhandledError', (event: any) => { - alertError(event.detail); - }); + useEffect(() => { + ReactModal.setAppElement(appRef.current); + fetchRewards(); + fetchRewardedContent(); + }, [fetchRewards, fetchRewardedContent]); + useEffect(() => { // $FlowFixMe document.documentElement.setAttribute('data-mode', theme); - } - - componentDidMount() { - const { updateBlockHeight, toggleEnhancedLayout } = this.props; - - ReactModal.setAppElement('#window'); // fuck this - - this.enhance = new EnhancedLayoutListener(() => toggleEnhancedLayout()); - - updateBlockHeight(); - setInterval(() => { - updateBlockHeight(); - }, TWO_POINT_FIVE_MINUTES); - } - - componentDidUpdate(prevProps: Props) { - const { theme: prevTheme } = prevProps; - const { theme } = this.props; + }, [theme]); - if (prevTheme !== theme) { - // $FlowFixMe - document.documentElement.setAttribute('data-mode', theme); - } - } + return ( +
openContextMenu(e)}> +
- componentWillUnmount() { - this.enhance = null; - } - - enhance: ?any; - - render() { - const { enhancedLayout } = this.props; - - return ( -
openContextMenu(e)}> -
- - -
+
+
+
- - - {enhancedLayout && }
- ); - } + + + {isEnhancedLayout && } +
+ ); } export default App; diff --git a/src/ui/component/button/view.jsx b/src/ui/component/button/view.jsx index 2f616d60315..4c62c3aab93 100644 --- a/src/ui/component/button/view.jsx +++ b/src/ui/component/button/view.jsx @@ -62,8 +62,8 @@ class Button extends React.PureComponent { 'button--primary': button === 'primary', 'button--secondary': button === 'secondary', 'button--alt': button === 'alt', - 'button--danger': button === 'danger', 'button--inverse': button === 'inverse', + 'button--close': button === 'close', 'button--disabled': disabled, 'button--link': button === 'link', 'button--constrict': constrict, diff --git a/src/ui/component/categoryList/index.js b/src/ui/component/categoryList/index.js deleted file mode 100644 index 45480912b0e..00000000000 --- a/src/ui/component/categoryList/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import { connect } from 'react-redux'; -import { doFetchClaimsByChannel } from 'redux/actions/content'; -import { makeSelectCategoryListUris } from 'redux/selectors/content'; -import { makeSelectFetchingChannelClaims, doResolveUris } from 'lbry-redux'; -import { selectShowNsfw } from 'redux/selectors/settings'; -import CategoryList from './view'; - -const select = (state, props) => ({ - urisInList: makeSelectCategoryListUris(props.uris, props.categoryLink)(state), - fetching: makeSelectFetchingChannelClaims(props.categoryLink)(state), - obscureNsfw: !selectShowNsfw(state), -}); - -const perform = dispatch => ({ - fetchChannel: channel => dispatch(doFetchClaimsByChannel(channel)), - resolveUris: uris => dispatch(doResolveUris(uris, true)), -}); - -export default connect( - select, - perform -)(CategoryList); diff --git a/src/ui/component/categoryList/view.jsx b/src/ui/component/categoryList/view.jsx deleted file mode 100644 index 95a5cf4714e..00000000000 --- a/src/ui/component/categoryList/view.jsx +++ /dev/null @@ -1,316 +0,0 @@ -// @flow -import * as ICONS from 'constants/icons'; -import React, { PureComponent, createRef } from 'react'; -import { normalizeURI, parseURI } from 'lbry-redux'; -import ToolTip from 'component/common/tooltip'; -import FileCard from 'component/fileCard'; -import Button from 'component/button'; -import SubscribeButton from 'component/subscribeButton'; -import throttle from 'util/throttle'; -import { formatLbryUriForWeb } from 'util/uri'; - -type Props = { - category: string, - categoryLink: ?string, - fetching: boolean, - obscureNsfw: boolean, - currentPageAttributes: { scrollY: number }, - fetchChannel: string => void, - urisInList: ?Array, - resolveUris: (Array) => void, - lazyLoad: boolean, // only fetch rows if they are on the screen -}; - -type State = { - canScrollNext: boolean, - canScrollPrevious: boolean, -}; - -class CategoryList extends PureComponent { - static defaultProps = { - categoryLink: undefined, - lazyLoad: false, - }; - - scrollWrapper: { current: null | HTMLUListElement }; - - constructor() { - super(); - - this.state = { - canScrollPrevious: false, - canScrollNext: true, - }; - - (this: any).handleScrollNext = this.handleScrollNext.bind(this); - (this: any).handleScrollPrevious = this.handleScrollPrevious.bind(this); - (this: any).handleArrowButtonsOnScroll = this.handleArrowButtonsOnScroll.bind(this); - // (this: any).handleResolveOnScroll = this.handleResolveOnScroll.bind(this); - - this.scrollWrapper = createRef(); - } - - componentDidMount() { - const { fetching, categoryLink, fetchChannel, resolveUris, urisInList, lazyLoad } = this.props; - if (!fetching && categoryLink && (!urisInList || !urisInList.length)) { - // Only fetch the channels claims if no urisInList are specifically passed in - // This allows setting a channel link and and passing in a custom list of urisInList (featured content usually works this way) - fetchChannel(categoryLink); - } - - const scrollWrapper = this.scrollWrapper.current; - if (scrollWrapper) { - scrollWrapper.addEventListener('scroll', throttle(this.handleArrowButtonsOnScroll, 500)); - - if (!urisInList) { - return; - } - - if (lazyLoad) { - if (window.innerHeight > scrollWrapper.offsetTop) { - resolveUris(urisInList); - } - } else { - resolveUris(urisInList); - } - } - } - - // The old lazy loading for home page relied on the navigation reducers copy of the scroll height - // Keeping it commented out for now to try and find a better way for better TTI on the homepage - // componentDidUpdate(prevProps: Props) { - // const {scrollY: previousScrollY} = prevProps.currentPageAttributes; - // const {scrollY} = this.props.currentPageAttributes; - - // if(scrollY > previousScrollY) { - // this.handleResolveOnScroll(); - // } - // } - - // handleResolveOnScroll() { - // const { - // urisInList, - // resolveUris, - // currentPageAttributes: {scrollY}, - // } = this.props; - - // const scrollWrapper = this.scrollWrapper.current; - // if(!scrollWrapper) { - // return; - // } - - // const shouldResolve = window.innerHeight > scrollWrapper.offsetTop - scrollY; - // if(shouldResolve && urisInList) { - // resolveUris(urisInList); - // } - // } - - handleArrowButtonsOnScroll() { - // Determine if the arrow buttons should be disabled - const scrollWrapper = this.scrollWrapper.current; - if (scrollWrapper) { - // firstElementChild and lastElementChild will always exist - // $FlowFixMe - const hasHiddenCardToLeft = !this.isCardVisible(scrollWrapper.firstElementChild); - // $FlowFixMe - const hasHiddenCardToRight = !this.isCardVisible(scrollWrapper.lastElementChild); - - this.setState({ - canScrollPrevious: hasHiddenCardToLeft, - canScrollNext: hasHiddenCardToRight, - }); - } - } - - handleScroll(scrollTarget: number) { - const scrollWrapper = this.scrollWrapper.current; - if (scrollWrapper) { - const currentScrollLeft = scrollWrapper.scrollLeft; - const direction = currentScrollLeft > scrollTarget ? 'left' : 'right'; - this.scrollCardsAnimated(scrollWrapper, scrollTarget, direction); - } - } - - scrollCardsAnimated = (scrollWrapper: HTMLUListElement, scrollTarget: number, direction: string) => { - let start; - const step = timestamp => { - if (!start) start = timestamp; - - const currentLeftVal = scrollWrapper.scrollLeft; - - let newTarget; - let shouldContinue; - let progress = currentLeftVal; - - if (direction === 'right') { - progress += timestamp - start; - newTarget = Math.min(progress, scrollTarget); - shouldContinue = newTarget < scrollTarget; - } else { - progress -= timestamp - start; - newTarget = Math.max(progress, scrollTarget); - shouldContinue = newTarget > scrollTarget; - } - - scrollWrapper.scrollLeft = newTarget; - - if (shouldContinue) { - window.requestAnimationFrame(step); - } - }; - - window.requestAnimationFrame(step); - }; - - // check if a card is fully visible horizontally - isCardVisible = (card: HTMLLIElement): boolean => { - if (!card) { - return false; - } - const scrollWrapper = this.scrollWrapper.current; - if (scrollWrapper) { - const rect = card.getBoundingClientRect(); - const isVisible = scrollWrapper.scrollLeft < card.offsetLeft && rect.left >= 0 && rect.right <= window.innerWidth; - return isVisible; - } - - return false; - }; - - handleScrollNext() { - const scrollWrapper = this.scrollWrapper.current; - if (!scrollWrapper) { - return; - } - - const cards = scrollWrapper.getElementsByTagName('li'); - - // Loop over items until we find one that is visible - // The card before that (starting from the end) is the new "first" card on the screen - - let previousCard: ?HTMLLIElement; - for (let i = cards.length - 1; i > 0; i -= 1) { - const currentCard: HTMLLIElement = cards[i]; - const currentCardVisible = this.isCardVisible(currentCard); - - if (currentCardVisible && previousCard) { - const scrollTarget = previousCard.offsetLeft; - this.handleScroll(scrollTarget - cards[0].offsetLeft); - break; - } - - previousCard = currentCard; - } - } - - handleScrollPrevious() { - const scrollWrapper = this.scrollWrapper.current; - if (!scrollWrapper) { - return; - } - - const cards = scrollWrapper.getElementsByTagName('li'); - - let hasFoundCard; - let numberOfCardsThatCanFit = 0; - - // loop starting at the end until we find a visible card - // then count to find how many cards can fit on the screen - for (let i = cards.length - 1; i >= 0; i -= 1) { - const currentCard = cards[i]; - const isCurrentCardVisible = this.isCardVisible(currentCard); - - if (isCurrentCardVisible) { - if (!hasFoundCard) { - hasFoundCard = true; - } - - numberOfCardsThatCanFit += 1; - } else if (hasFoundCard) { - // this card is off the screen to the left - // we know how many cards can fit on a screen - // find the new target and scroll - const firstCardOffsetLeft = cards[0].offsetLeft; - const cardIndexToScrollTo = i + 1 - numberOfCardsThatCanFit; - const newFirstCard = cards[cardIndexToScrollTo]; - - let scrollTarget; - if (newFirstCard) { - scrollTarget = newFirstCard.offsetLeft; - } else { - // more cards can fit on the screen than are currently hidden - // just scroll to the first card - scrollTarget = cards[0].offsetLeft; - } - - scrollTarget -= firstCardOffsetLeft; // to play nice with the margins - - this.handleScroll(scrollTarget); - break; - } - } - } - - render() { - const { category, categoryLink, urisInList, obscureNsfw, lazyLoad } = this.props; - const { canScrollNext, canScrollPrevious } = this.state; - const isCommunityTopBids = category.match(/^community/i); - const showScrollButtons = isCommunityTopBids ? !obscureNsfw : true; - - let channelLink; - if (categoryLink) { - channelLink = formatLbryUriForWeb(categoryLink); - } - - return ( -
-
-

- {categoryLink ? ( - -

- {showScrollButtons && ( - - )} -
- {obscureNsfw && isCommunityTopBids ? ( -

- {__( - 'The community top bids section is only visible if you allow mature content in the app. You can change your content viewing preferences' - )}{' '} -

- ); - } -} - -export default CategoryList; diff --git a/src/ui/component/channelAbout/view.jsx b/src/ui/component/channelAbout/view.jsx index 5965c896cd1..c7eb7882525 100644 --- a/src/ui/component/channelAbout/view.jsx +++ b/src/ui/component/channelAbout/view.jsx @@ -22,8 +22,8 @@ function ChannelContent(props: Props) { const showAbout = description || email || website; return ( -
- {!showAbout &&

{__('Nothing here yet')}

} +
+ {!showAbout &&

{__('Nothing here yet')}

} {showAbout && ( {description && ( diff --git a/src/ui/component/channelContent/view.jsx b/src/ui/component/channelContent/view.jsx index 3f129b45a75..a78865815a7 100644 --- a/src/ui/component/channelContent/view.jsx +++ b/src/ui/component/channelContent/view.jsx @@ -19,7 +19,6 @@ type Props = { function ChannelContent(props: Props) { const { uri, fetching, claimsInChannel, totalPages, channelIsMine, fetchClaims } = props; const hasContent = Boolean(claimsInChannel && claimsInChannel.length); - return ( {fetching && !hasContent && ( @@ -28,11 +27,15 @@ function ChannelContent(props: Props) {
)} - {!fetching && !hasContent &&

{__("This channel hasn't uploaded anything.")}

} + {!fetching && !hasContent && ( +
+

{__("This channel hasn't uploaded anything.")}

+
+ )} {!channelIsMine && } - {hasContent && } + {hasContent && claim.permanent_url)} />} fetchClaims(uri, page)} diff --git a/src/ui/component/channelThumbnail/view.jsx b/src/ui/component/channelThumbnail/view.jsx index d52df8292cb..fe648b00402 100644 --- a/src/ui/component/channelThumbnail/view.jsx +++ b/src/ui/component/channelThumbnail/view.jsx @@ -7,24 +7,25 @@ import Gerbil from './gerbil.png'; type Props = { thumbnail: ?string, uri: string, + className?: string, }; function ChannelThumbnail(props: Props) { - const { thumbnail, uri } = props; + const { thumbnail, uri, className } = props; // Generate a random color class based on the first letter of the channel name const { channelName } = parseURI(uri); const initializer = channelName.charCodeAt(0) - 65; // will be between 0 and 57 - const className = `channel-thumbnail__default--${initializer % 4}`; + const colorClassName = `channel-thumbnail__default--${initializer % 4}`; return (
{!thumbnail && } - {thumbnail && } + {thumbnail && }
); } diff --git a/src/ui/component/channelTile/index.js b/src/ui/component/channelTile/index.js deleted file mode 100644 index d395a9f85d3..00000000000 --- a/src/ui/component/channelTile/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import { connect } from 'react-redux'; -import { - doResolveUri, - makeSelectClaimForUri, - makeSelectIsUriResolving, - makeSelectTotalItemsForChannel, -} from 'lbry-redux'; -import ChannelTile from './view'; - -const select = (state, props) => ({ - claim: makeSelectClaimForUri(props.uri)(state), - isResolvingUri: makeSelectIsUriResolving(props.uri)(state), - totalItems: makeSelectTotalItemsForChannel(props.uri)(state), -}); - -const perform = dispatch => ({ - resolveUri: uri => dispatch(doResolveUri(uri)), -}); - -export default connect( - select, - perform -)(ChannelTile); diff --git a/src/ui/component/channelTile/view.jsx b/src/ui/component/channelTile/view.jsx deleted file mode 100644 index ef57bbbaac3..00000000000 --- a/src/ui/component/channelTile/view.jsx +++ /dev/null @@ -1,89 +0,0 @@ -// @flow -import * as React from 'react'; -import CardMedia from 'component/cardMedia'; -import TruncatedText from 'component/common/truncated-text'; -import classnames from 'classnames'; -import SubscribeButton from 'component/subscribeButton'; -import { withRouter } from 'react-router-dom'; -import { formatLbryUriForWeb } from 'util/uri'; - -type Props = { - uri: string, - isResolvingUri: boolean, - totalItems: number, - size: string, - claim: ?ChannelClaim, - resolveUri: string => void, - history: { push: string => void }, -}; - -class ChannelTile extends React.PureComponent { - static defaultProps = { - size: 'regular', - }; - - componentDidMount() { - const { uri, resolveUri } = this.props; - - resolveUri(uri); - } - - componentWillReceiveProps(nextProps: Props) { - const { uri, resolveUri } = this.props; - - if (nextProps.uri !== uri) { - resolveUri(uri); - } - } - - render() { - const { claim, isResolvingUri, totalItems, uri, size, history } = this.props; - - let channelName; - let subscriptionUri; - if (claim) { - channelName = claim.name; - subscriptionUri = claim.permanent_url; - } - - const onClick = () => history.push(formatLbryUriForWeb(uri)); - - return ( -
- -
- {isResolvingUri &&
{__('Loading...')}
} - {!isResolvingUri && ( - -
- -
-
- {totalItems > 0 && ( - - {totalItems} {totalItems === 1 ? 'publish' : 'publishes'} - - )} - {!isResolvingUri && !totalItems && This is an empty channel.} -
-
- )} - {subscriptionUri && ( -
- -
- )} -
-
- ); - } -} - -export default withRouter(ChannelTile); diff --git a/src/ui/component/common/credit-amount.jsx b/src/ui/component/common/credit-amount.jsx index 24b85595dfd..5af75e3538e 100644 --- a/src/ui/component/common/credit-amount.jsx +++ b/src/ui/component/common/credit-amount.jsx @@ -10,7 +10,6 @@ type Props = { showFullPrice: boolean, showPlus: boolean, isEstimate?: boolean, - large?: boolean, showLBC?: boolean, fee?: boolean, badge?: boolean, @@ -27,7 +26,7 @@ class CreditAmount extends React.PureComponent { }; render() { - const { amount, precision, showFullPrice, showFree, showPlus, large, isEstimate, fee, showLBC, badge } = this.props; + const { amount, precision, showFullPrice, showFree, showPlus, isEstimate, fee, showLBC, badge } = this.props; const minimumRenderableAmount = 10 ** (-1 * precision); const fullPrice = formatFullPrice(amount, 2); @@ -69,7 +68,6 @@ class CreditAmount extends React.PureComponent { badge, 'badge--cost': badge && amount > 0, 'badge--free': badge && isFree, - 'badge--large': large, })} > {amountText} diff --git a/src/ui/component/emailCollection/index.js b/src/ui/component/emailCollection/index.js index 0d4b65ca52b..e867618fc12 100644 --- a/src/ui/component/emailCollection/index.js +++ b/src/ui/component/emailCollection/index.js @@ -12,10 +12,6 @@ const select = state => ({ }); const perform = dispatch => () => ({ - completeFirstRun: () => { - dispatch(doSetClientSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED, true)); - dispatch(doSetClientSetting(SETTINGS.FIRST_RUN_COMPLETED, true)); - }, acknowledgeEmail: () => { dispatch(doSetClientSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED, true)); }, diff --git a/src/ui/component/errorBoundary/view.jsx b/src/ui/component/errorBoundary/view.jsx index fb7132a8b2d..6e6c8d0928d 100644 --- a/src/ui/component/errorBoundary/view.jsx +++ b/src/ui/component/errorBoundary/view.jsx @@ -53,9 +53,9 @@ class ErrorBoundary extends React.Component { log(message) { declare var app: { env: string }; - // if (app.env === 'production') { - Lbryio.call('event', 'desktop_error', { error_message: message }); - // } + if (app.env === 'production') { + Lbryio.call('event', 'desktop_error', { error_message: message }); + } } refresh() { diff --git a/src/ui/component/fileCard/view.jsx b/src/ui/component/fileCard/view.jsx deleted file mode 100644 index a2d4e88d7b0..00000000000 --- a/src/ui/component/fileCard/view.jsx +++ /dev/null @@ -1,154 +0,0 @@ -// @flow -import * as icons from 'constants/icons'; -import * as React from 'react'; -import { normalizeURI, convertToShareLink } from 'lbry-redux'; -import CardMedia from 'component/cardMedia'; -import TruncatedText from 'component/common/truncated-text'; -import Icon from 'component/common/icon'; -import UriIndicator from 'component/uriIndicator'; -import classnames from 'classnames'; -import FilePrice from 'component/filePrice'; -import { openCopyLinkMenu } from 'util/context-menu'; -import DateTime from 'component/dateTime'; -import { withRouter } from 'react-router-dom'; -import { formatLbryUriForWeb } from 'util/uri'; - -type Props = { - uri: string, - claim: ?StreamClaim, - fileInfo: ?{}, - metadata: ?StreamMetadata, - rewardedContentClaimIds: Array, - obscureNsfw: boolean, - claimIsMine: boolean, - pending?: boolean, - resolveUri: string => void, - isResolvingUri: boolean, - isSubscribed: boolean, - isNew: boolean, - placeholder: boolean, - preventResolve: boolean, - history: { push: string => void }, - thumbnail: string, - title: string, - nsfw: boolean, -}; - -class FileCard extends React.PureComponent { - static defaultProps = { - placeholder: false, - preventResolve: false, - }; - - componentDidMount() { - if (!this.props.preventResolve) { - this.resolve(this.props); - } - } - - componentDidUpdate() { - if (!this.props.preventResolve) { - this.resolve(this.props); - } - } - - resolve = (props: Props) => { - const { isResolvingUri, resolveUri, claim, uri, pending } = props; - - if (!pending && !isResolvingUri && claim === undefined && uri) { - resolveUri(uri); - } - }; - - render() { - const { - claim, - fileInfo, - rewardedContentClaimIds, - obscureNsfw, - claimIsMine, - pending, - isSubscribed, - isNew, - isResolvingUri, - placeholder, - history, - thumbnail, - title, - nsfw, - } = this.props; - - const abandoned = !isResolvingUri && !claim && !pending && !placeholder; - - if (abandoned) { - return null; - } - - if (!claim && (!pending || placeholder)) { - return ( -
  • -
    -
    -
    -
    -
    -
  • - ); - } - - // fix to use tags - one of many nsfw tags... - const shouldHide = !claimIsMine && !pending && obscureNsfw && nsfw; - if (shouldHide) { - return null; - } - - const uri = !pending ? normalizeURI(this.props.uri) : this.props.uri; - const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); - const handleContextMenu = event => { - event.preventDefault(); - event.stopPropagation(); - if (claim) { - openCopyLinkMenu(convertToShareLink(claim.permanent_url), event); - } - }; - - const onClick = e => { - e.stopPropagation(); - history.push(formatLbryUriForWeb(uri)); - }; - - return ( -
  • {}} - className={classnames('media-card', { - 'card--link': !pending, - 'media--pending': pending, - })} - onContextMenu={handleContextMenu} - > - -
    - -
    -
    - {pending ?
    Pending...
    : } -
    - -
    -
    -
    - - {isRewardContent && } - {isSubscribed && } - {claimIsMine && } - {!claimIsMine && fileInfo && } - {isNew && {__('NEW')}} -
    -
  • - ); - } -} - -export default withRouter(FileCard); diff --git a/src/ui/component/fileList/index.js b/src/ui/component/fileList/index.js index b6c075cf965..f73f0fcd624 100644 --- a/src/ui/component/fileList/index.js +++ b/src/ui/component/fileList/index.js @@ -1,14 +1,9 @@ import { connect } from 'react-redux'; -import { selectClaimsById, doSetFileListSort } from 'lbry-redux'; import FileList from './view'; -const select = state => ({ - claimsById: selectClaimsById(state), -}); +const select = state => ({}); -const perform = dispatch => ({ - setFileListSort: (page, value) => dispatch(doSetFileListSort(page, value)), -}); +const perform = dispatch => ({}); export default connect( select, diff --git a/src/ui/component/fileList/view.jsx b/src/ui/component/fileList/view.jsx index bbe1c770061..4643e831cf1 100644 --- a/src/ui/component/fileList/view.jsx +++ b/src/ui/component/fileList/view.jsx @@ -1,165 +1,70 @@ // @flow import * as React from 'react'; -import { buildURI, SORT_OPTIONS } from 'lbry-redux'; -import { FormField, Form } from 'component/common/form'; -import FileCard from 'component/fileCard'; +import classnames from 'classnames'; +import FileListItem from 'component/fileListItem'; +import Spinner from 'component/spinner'; +import { FormField } from 'component/common/form'; +import usePersistedState from 'util/use-persisted-state'; + +const SORT_NEW = 'new'; +const SORT_OLD = 'old'; type Props = { - hideFilter: boolean, - sortByHeight?: boolean, - claimsById: Array, - fileInfos: Array, - sortBy: string, - page?: string, - setFileListSort: (?string, string) => void, + uris: Array, + header: React.Node, + sort: React.Node, + injectedItem?: React.Node, + loading: boolean, + noHeader?: boolean, + slim?: string, + empty?: string, + // If using the default header, this is a unique ID needed to persist the state of the filter setting + persistedStorageKey?: string, }; -class FileList extends React.PureComponent { - static defaultProps = { - hideFilter: false, - sortBy: SORT_OPTIONS.DATE_NEW, - }; - - constructor(props: Props) { - super(props); - (this: any).handleSortChanged = this.handleSortChanged.bind(this); - - this.sortFunctions = { - [SORT_OPTIONS.DATE_NEW]: fileInfos => - this.props.sortByHeight - ? fileInfos.sort((fileInfo1, fileInfo2) => { - if (fileInfo1.confirmations < 1) { - return -1; - } else if (fileInfo2.confirmations < 1) { - return 1; - } - - const height1 = this.props.claimsById[fileInfo1.claim_id] - ? this.props.claimsById[fileInfo1.claim_id].height - : 0; - const height2 = this.props.claimsById[fileInfo2.claim_id] - ? this.props.claimsById[fileInfo2.claim_id].height - : 0; - - if (height1 !== height2) { - // flipped because heigher block height is newer - return height2 - height1; - } - - if (fileInfo1.absolute_channel_position && fileInfo2.absolute_channel_position) { - return fileInfo1.absolute_channel_position - fileInfo2.absolute_channel_position; - } - - return 0; - }) - : [...fileInfos].reverse(), - [SORT_OPTIONS.DATE_OLD]: fileInfos => - this.props.sortByHeight - ? fileInfos.slice().sort((fileInfo1, fileInfo2) => { - const height1 = this.props.claimsById[fileInfo1.claim_id] - ? this.props.claimsById[fileInfo1.claim_id].height - : 999999; - const height2 = this.props.claimsById[fileInfo2.claim_id] - ? this.props.claimsById[fileInfo2.claim_id].height - : 999999; - if (height1 < height2) { - return -1; - } else if (height1 > height2) { - return 1; - } - return 0; - }) - : fileInfos, - [SORT_OPTIONS.TITLE]: fileInfos => - fileInfos.slice().sort((fileInfo1, fileInfo2) => { - const getFileTitle = fileInfo => { - const { value, name, claim_name: claimName } = fileInfo; - if (value) { - return value.title || claimName; - } - - // Invalid claim - return ''; - }; - const title1 = getFileTitle(fileInfo1).toLowerCase(); - const title2 = getFileTitle(fileInfo2).toLowerCase(); - if (title1 < title2) { - return -1; - } else if (title1 > title2) { - return 1; - } - return 0; - }), - [SORT_OPTIONS.FILENAME]: fileInfos => - fileInfos.slice().sort(({ file_name: fileName1 }, { file_name: fileName2 }) => { - const fileName1Lower = fileName1.toLowerCase(); - const fileName2Lower = fileName2.toLowerCase(); - if (fileName1Lower < fileName2Lower) { - return -1; - } else if (fileName2Lower > fileName1Lower) { - return 1; - } - return 0; - }), - }; - } - - getChannelSignature = (fileInfo: { pending: boolean } & FileListItem) => { - if (fileInfo.pending) { - return undefined; - } +export default function FileList(props: Props) { + const { uris, header, sort, injectedItem, loading, persistedStorageKey, noHeader, slim, empty } = props; + const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey || 'file-list-global-sort', SORT_NEW); + const sortedUris = uris && currentSort === SORT_OLD ? uris.reverse() : uris; + const hasUris = uris && !!uris.length; - return fileInfo.channel_claim_id; - }; - - handleSortChanged(event: SyntheticInputEvent<*>) { - this.props.setFileListSort(this.props.page, event.target.value); + function handleSortChange() { + setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW); } - sortFunctions: {}; - - render() { - const { fileInfos, hideFilter, sortBy } = this.props; - - const content = []; - if (!fileInfos) { - return null; - } - - this.sortFunctions[sortBy](fileInfos).forEach(fileInfo => { - const { name: claimName, claim_name: claimNameDownloaded, claim_id: claimId, txid, nout, isNew } = fileInfo; - const uriParams = {}; - - // This is unfortunate - // https://github.com/lbryio/lbry/issues/1159 - const name = claimName || claimNameDownloaded; - uriParams.contentName = name; - uriParams.claimId = claimId; - const uri = buildURI(uriParams); - const outpoint = `${txid}:${nout}`; - - // See https://github.com/lbryio/lbry-desktop/issues/1327 for discussion around using outpoint as the key - content.push(); - }); - - return ( -
    - {!hideFilter && ( -
    - - - - + return ( +
    + {!noHeader && ( +
    + {header || ( + + + - - )} - -
    -
    {content}
    -
    -
    - ); - } + )} + {loading && } +
    {sort}
    +
    + )} + {hasUris && ( +
      + {sortedUris.map((uri, index) => ( + + + {index === 4 && injectedItem &&
    • {injectedItem}
    • } +
      + ))} +
    + )} + {!hasUris && !loading && ( +
    {empty ||

    {__('No results')}

    }
    + )} + + ); } - -export default FileList; diff --git a/src/ui/component/fileCard/index.js b/src/ui/component/fileListItem/index.js similarity index 53% rename from src/ui/component/fileCard/index.js rename to src/ui/component/fileListItem/index.js index 86cc6200db5..4b635223443 100644 --- a/src/ui/component/fileCard/index.js +++ b/src/ui/component/fileListItem/index.js @@ -2,8 +2,6 @@ import { connect } from 'react-redux'; import { doResolveUri, makeSelectClaimForUri, - makeSelectMetadataForUri, - makeSelectFileInfoForUri, makeSelectIsUriResolving, makeSelectClaimIsMine, makeSelectClaimIsPending, @@ -11,25 +9,15 @@ import { makeSelectTitleForUri, makeSelectClaimIsNsfw, } from 'lbry-redux'; -import { selectRewardContentClaimIds } from 'lbryinc'; -import { makeSelectContentPositionForUri } from 'redux/selectors/content'; import { selectShowNsfw } from 'redux/selectors/settings'; -import { makeSelectIsSubscribed, makeSelectIsNew } from 'redux/selectors/subscriptions'; -import { doClearContentHistoryUri } from 'redux/actions/content'; -import FileCard from './view'; +import FileListItem from './view'; const select = (state, props) => ({ pending: makeSelectClaimIsPending(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state), obscureNsfw: !selectShowNsfw(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state), - rewardedContentClaimIds: selectRewardContentClaimIds(state, props), - fileInfo: makeSelectFileInfoForUri(props.uri)(state), - metadata: makeSelectMetadataForUri(props.uri)(state), isResolvingUri: makeSelectIsUriResolving(props.uri)(state), - position: makeSelectContentPositionForUri(props.uri)(state), - isSubscribed: makeSelectIsSubscribed(props.uri)(state), - isNew: makeSelectIsNew(props.uri)(state), thumbnail: makeSelectThumbnailForUri(props.uri)(state), title: makeSelectTitleForUri(props.uri)(state), nsfw: makeSelectClaimIsNsfw(props.uri)(state), @@ -37,10 +25,9 @@ const select = (state, props) => ({ const perform = dispatch => ({ resolveUri: uri => dispatch(doResolveUri(uri)), - clearHistoryUri: uri => dispatch(doClearContentHistoryUri(uri)), }); export default connect( select, perform -)(FileCard); +)(FileListItem); diff --git a/src/ui/component/fileListItem/view.jsx b/src/ui/component/fileListItem/view.jsx new file mode 100644 index 00000000000..d66c968c4f6 --- /dev/null +++ b/src/ui/component/fileListItem/view.jsx @@ -0,0 +1,132 @@ +// @flow +import React, { useEffect } from 'react'; +import classnames from 'classnames'; +import { convertToShareLink } from 'lbry-redux'; +import { withRouter } from 'react-router-dom'; +import { openCopyLinkMenu } from 'util/context-menu'; +import { formatLbryUriForWeb } from 'util/uri'; +import CardMedia from 'component/cardMedia'; +import UriIndicator from 'component/uriIndicator'; +import TruncatedText from 'component/common/truncated-text'; +import DateTime from 'component/dateTime'; +import FileProperties from 'component/fileProperties'; +import FileTags from 'component/fileTags'; +import SubscribeButton from 'component/subscribeButton'; +import ChannelThumbnail from 'component/channelThumbnail'; + +type Props = { + uri: string, + claim: ?Claim, + obscureNsfw: boolean, + claimIsMine: boolean, + pending?: boolean, + resolveUri: string => void, + isResolvingUri: boolean, + preventResolve: boolean, + history: { push: string => void }, + thumbnail: string, + title: string, + nsfw: boolean, + large: boolean, + placeholder: boolean, + slim: boolean, +}; + +function FileListItem(props: Props) { + const { + obscureNsfw, + claimIsMine, + pending, + history, + uri, + isResolvingUri, + thumbnail, + title, + nsfw, + resolveUri, + claim, + large, + placeholder, + slim, + } = props; + + const haventFetched = claim === undefined; + const abandoned = !isResolvingUri && !claim; + const shouldHide = abandoned || (!claimIsMine && obscureNsfw && nsfw); + const isChannel = claim && claim.value_type === 'channel'; + // $FlowFixMe + const claimsInChannel = isChannel ? claim.meta.claims_in_channel : 0; + + function handleContextMenu(e) { + e.preventDefault(); + e.stopPropagation(); + // $FlowFixMe + openCopyLinkMenu(convertToShareLink(claim.permanent_url), e); + } + + function onClick(e) { + if ((isChannel || title) && !pending) { + history.push(formatLbryUriForWeb(uri)); + } + } + + useEffect(() => { + if (!isResolvingUri && haventFetched && uri) { + // resolveUri(uri); + } + }, [isResolvingUri, uri, resolveUri, haventFetched]); + + if (shouldHide) { + return null; + } + + if (placeholder && !claim) { + return ( +
  • +
    +
    +
    +
    +
    +
  • + ); + } + + return ( +
  • + {isChannel ? : } +
    +
    +
    + +
    + {!slim && ( +
    + {isChannel && } + +
    + )} +
    + +
    +
    + + {pending &&
    Pending...
    } +
    {isChannel ? `${claimsInChannel} ${__('publishes')}` : }
    +
    + + {!slim && } +
    +
    +
  • + ); +} + +export default withRouter(FileListItem); diff --git a/src/ui/component/fileListSearch/index.js b/src/ui/component/fileListSearch/index.js deleted file mode 100644 index 32b04d22032..00000000000 --- a/src/ui/component/fileListSearch/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import { connect } from 'react-redux'; -import { - makeSelectSearchUris, - selectIsSearching, - selectSearchDownloadUris, - makeSelectQueryWithOptions, -} from 'lbry-redux'; -import FileListSearch from './view'; - -const select = (state, props) => ({ - uris: makeSelectSearchUris(makeSelectQueryWithOptions()(state))(state), - downloadUris: selectSearchDownloadUris(props.query)(state), - isSearching: selectIsSearching(state), -}); - -export default connect(select)(FileListSearch); diff --git a/src/ui/component/fileListSearch/view.jsx b/src/ui/component/fileListSearch/view.jsx deleted file mode 100644 index 9f1974bc43e..00000000000 --- a/src/ui/component/fileListSearch/view.jsx +++ /dev/null @@ -1,44 +0,0 @@ -// @flow -import * as React from 'react'; -import { parseURI } from 'lbry-redux'; -import FileTile from 'component/fileTile'; -import ChannelTile from 'component/channelTile'; -import HiddenNsfwClaims from 'component/hiddenNsfwClaims'; - -const NoResults = () =>
    {__('No results')}
    ; - -type Props = { - query: string, - isSearching: boolean, - uris: ?Array, -}; - -class FileListSearch extends React.PureComponent { - render() { - const { uris, query, isSearching } = this.props; - return ( - query && ( - -
    -
    - - {!isSearching && uris && uris.length ? ( - uris.map(uri => - parseURI(uri).claimName[0] === '@' ? ( - - ) : ( - - ) - ) - ) : ( - - )} -
    -
    -
    - ) - ); - } -} - -export default FileListSearch; diff --git a/src/ui/component/fileListTrending/index.js b/src/ui/component/fileListTrending/index.js new file mode 100644 index 00000000000..d89995618e9 --- /dev/null +++ b/src/ui/component/fileListTrending/index.js @@ -0,0 +1,18 @@ +import { connect } from 'react-redux'; +import { selectFollowedTags, doFetchByTags, selectTrendingUris, selectFetchingTrending } from 'lbry-redux'; +import DiscoverPage from './view'; + +const select = state => ({ + followedTags: selectFollowedTags(state), + trending: selectTrendingUris(state), + loading: selectFetchingTrending(state), +}); + +const perform = { + doFetchByTags, +}; + +export default connect( + select, + perform +)(DiscoverPage); diff --git a/src/ui/component/fileListTrending/view.jsx b/src/ui/component/fileListTrending/view.jsx new file mode 100644 index 00000000000..e9b5f082fc4 --- /dev/null +++ b/src/ui/component/fileListTrending/view.jsx @@ -0,0 +1,118 @@ +// @flow +import React, { useEffect } from 'react'; +import { FormField } from 'component/common/form'; +import FileList from 'component/fileList'; +import moment from 'moment'; +import usePersistedState from 'util/use-persisted-state'; +const TRENDING_SORT_ME = 'me'; +const types = ['trending', 'top', 'new']; +const times = ['day', 'week', 'month', 'year', 'all']; + +type Props = { + trending: Array, + doFetchByTags: (number, {}) => void, + injectedItem: any, + tags: Array, + loading: boolean, + personal: boolean, +}; + +function FileListTrending(props: Props) { + const { doFetchByTags, trending, tags, loading, personal, injectedItem } = props; + const [personalSort, setPersonalSort] = usePersistedState('file-list-trending:personalSort', TRENDING_SORT_ME); + const [typeSort, setTypeSort] = usePersistedState('file-list-trending:typeSort', types[0]); + const [timeSort, setTimeSort] = usePersistedState('file-list-trending:timeSort', times[1]); + + const tagsString = tags.join(','); + useEffect(() => { + const options = {}; + const newTags = tagsString.split(','); + + if (personalSort === TRENDING_SORT_ME) { + options.any_tags = newTags; + } else if (typeSort === 'trending') { + options.order_by = ['trending_global', 'trending_mixed']; + } else if (typeSort === 'new') { + options.order_by = ['release_time']; + } else if (typeSort === 'top') { + options.order_by = ['effective_amount']; + if (timeSort !== 'all') { + const time = Math.floor( + moment() + .subtract(1, timeSort) + .unix() + ); + options.release_time = `>${time}`; + } + } + + doFetchByTags(20, options); + }, [personalSort, typeSort, timeSort, doFetchByTags, tagsString]); + + return ( + +

    + {typeSort.charAt(0).toUpperCase() + typeSort.slice(1)} {'For'} +

    + {!personal && tags && tags.length ? ( + tags.map(tag => ( + + {tag} + + )) + ) : ( + setPersonalSort(e.target.value)} + > + + + + )} + + } + sort={ + + setTypeSort(e.target.value)} + > + {types.map(type => ( + + ))} + + {typeSort === 'top' && ( + setTimeSort(e.target.value)} + > + {times.map(time => ( + + ))} + + )} + + } + /> + ); +} + +export default FileListTrending; diff --git a/src/ui/component/fileProperties/index.js b/src/ui/component/fileProperties/index.js new file mode 100644 index 00000000000..1d6180a04bf --- /dev/null +++ b/src/ui/component/fileProperties/index.js @@ -0,0 +1,18 @@ +import { connect } from 'react-redux'; +import { makeSelectFileInfoForUri, makeSelectClaimIsMine } from 'lbry-redux'; +import { selectRewardContentClaimIds } from 'lbryinc'; +import { makeSelectIsSubscribed, makeSelectIsNew } from 'redux/selectors/subscriptions'; +import FileProperties from './view'; + +const select = (state, props) => ({ + rewardedContentClaimIds: selectRewardContentClaimIds(state, props), + downloaded: !!makeSelectFileInfoForUri(props.uri)(state), + isSubscribed: makeSelectIsSubscribed(props.uri)(state), + isNew: makeSelectIsNew(props.uri)(state), + claimIsMine: makeSelectClaimIsMine(props.uri)(state), +}); + +export default connect( + select, + null +)(FileProperties); diff --git a/src/ui/component/fileProperties/view.jsx b/src/ui/component/fileProperties/view.jsx new file mode 100644 index 00000000000..bf8c1a4007b --- /dev/null +++ b/src/ui/component/fileProperties/view.jsx @@ -0,0 +1,31 @@ +// @flow +import * as icons from 'constants/icons'; +import * as React from 'react'; +import { parseURI } from 'lbry-redux'; +import Icon from 'component/common/icon'; +import FilePrice from 'component/filePrice'; + +type Props = { + uri: string, + downloaded: boolean, + claimIsMine: boolean, + isSubscribed: boolean, + isNew: boolean, + rewardedContentClaimIds: Array, +}; + +export default function FileProperties(props: Props) { + const { uri, downloaded, claimIsMine, rewardedContentClaimIds, isSubscribed, isNew } = props; + const { claimId } = parseURI(uri); + const isRewardContent = rewardedContentClaimIds.includes(claimId); + + return ( +
    + {isSubscribed && } + {!claimIsMine && downloaded && } + {isRewardContent && } + {isNew && {__('NEW')}} + +
    + ); +} diff --git a/src/ui/component/fileTags/index.js b/src/ui/component/fileTags/index.js new file mode 100644 index 00000000000..bc63f15d7b7 --- /dev/null +++ b/src/ui/component/fileTags/index.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux'; +import { makeSelectTagsForUri, selectFollowedTags } from 'lbry-redux'; +import FileTags from './view'; + +const select = (state, props) => ({ + tags: makeSelectTagsForUri(props.uri)(state), + followedTags: selectFollowedTags(state), +}); + +export default connect( + select, + null +)(FileTags); diff --git a/src/ui/component/fileTags/view.jsx b/src/ui/component/fileTags/view.jsx new file mode 100644 index 00000000000..969e3d1c008 --- /dev/null +++ b/src/ui/component/fileTags/view.jsx @@ -0,0 +1,49 @@ +// @flow +import * as React from 'react'; +import Button from 'component/button'; + +const MAX_TAGS = 4; + +type Props = { + tags: Array, + followedTags: Array, +}; + +export default function FileTags(props: Props) { + const { tags, followedTags } = props; + + let tagsToDisplay = []; + for (var i = 0; tagsToDisplay.length < MAX_TAGS - 2; i++) { + const tag = followedTags[i]; + if (!tag) { + break; + } + + if (tags.includes(tag.name)) { + tagsToDisplay.push(tag.name); + } + } + + const sortedTags = tags.sort((a, b) => a.localeCompare(b)); + + for (var i = 0; i < sortedTags.length; i++) { + const tag = sortedTags[i]; + if (!tag || tagsToDisplay.length === MAX_TAGS) { + break; + } + + if (!tagsToDisplay.includes(tag)) { + tagsToDisplay.push(tag); + } + } + + return ( +
    + {tagsToDisplay.map(tag => ( + + ))} +
    + ); +} diff --git a/src/ui/component/fileTile/index.js b/src/ui/component/fileTile/index.js deleted file mode 100644 index 63004d6d768..00000000000 --- a/src/ui/component/fileTile/index.js +++ /dev/null @@ -1,43 +0,0 @@ -import { connect } from 'react-redux'; -import { - doResolveUri, - makeSelectClaimForUri, - makeSelectMetadataForUri, - makeSelectFileInfoForUri, - makeSelectIsUriResolving, - makeSelectClaimIsMine, - makeSelectThumbnailForUri, - makeSelectTitleForUri, - makeSelectClaimIsNsfw, -} from 'lbry-redux'; -import { selectRewardContentClaimIds } from 'lbryinc'; -import { selectShowNsfw } from 'redux/selectors/settings'; -import { doClearPublish, doUpdatePublishForm } from 'redux/actions/publish'; -import { makeSelectIsSubscribed, makeSelectIsNew } from 'redux/selectors/subscriptions'; -import FileTile from './view'; - -const select = (state, props) => ({ - claim: makeSelectClaimForUri(props.uri)(state), - isDownloaded: !!makeSelectFileInfoForUri(props.uri)(state), - metadata: makeSelectMetadataForUri(props.uri)(state), - isResolvingUri: makeSelectIsUriResolving(props.uri)(state), - rewardedContentClaimIds: selectRewardContentClaimIds(state, props), - obscureNsfw: !selectShowNsfw(state), - claimIsMine: makeSelectClaimIsMine(props.uri)(state), - isSubscribed: makeSelectIsSubscribed(props.uri)(state), - isNew: makeSelectIsNew(props.uri)(state), - thumbnail: makeSelectThumbnailForUri(props.uri)(state), - title: makeSelectTitleForUri(props.uri)(state), - nsfw: makeSelectClaimIsNsfw(props.uri)(state), -}); - -const perform = dispatch => ({ - clearPublish: () => dispatch(doClearPublish()), - resolveUri: uri => dispatch(doResolveUri(uri)), - updatePublishForm: value => dispatch(doUpdatePublishForm(value)), -}); - -export default connect( - select, - perform -)(FileTile); diff --git a/src/ui/component/fileTile/view.jsx b/src/ui/component/fileTile/view.jsx deleted file mode 100644 index 4d2a0486d54..00000000000 --- a/src/ui/component/fileTile/view.jsx +++ /dev/null @@ -1,216 +0,0 @@ -// @flow -import * as ICONS from 'constants/icons'; -import React, { Fragment } from 'react'; -import { normalizeURI, parseURI } from 'lbry-redux'; -import CardMedia from 'component/cardMedia'; -import TruncatedText from 'component/common/truncated-text'; -import Icon from 'component/common/icon'; -import Button from 'component/button'; -import classnames from 'classnames'; -import FilePrice from 'component/filePrice'; -import UriIndicator from 'component/uriIndicator'; -import DateTime from 'component/dateTime'; -import Yrbl from 'component/yrbl'; -import { withRouter } from 'react-router-dom'; -import { formatLbryUriForWeb } from 'util/uri'; - -type Props = { - obscureNsfw: boolean, - claimIsMine: boolean, - isDownloaded: boolean, - uri: string, - isResolvingUri: boolean, - rewardedContentClaimIds: Array, - claim: ?StreamClaim, - metadata: ?StreamMetadata, - resolveUri: string => void, - clearPublish: () => void, - updatePublishForm: ({}) => void, - hideNoResult: boolean, // don't show the tile if there is no claim at this uri - displayHiddenMessage?: boolean, - size: string, - isSubscribed: boolean, - isNew: boolean, - history: { push: string => void }, - thumbnail: ?string, - title: ?string, - nsfw: boolean, -}; - -class FileTile extends React.PureComponent { - static defaultProps = { - size: 'regular', - }; - - componentDidMount() { - const { isResolvingUri, claim, uri, resolveUri } = this.props; - if (!isResolvingUri && !claim && uri) resolveUri(uri); - } - - componentDidUpdate() { - const { isResolvingUri, claim, uri, resolveUri } = this.props; - if (!isResolvingUri && claim === undefined && uri) resolveUri(uri); - } - - renderFileProperties() { - const { isSubscribed, isDownloaded, claim, uri, rewardedContentClaimIds, isNew, claimIsMine } = this.props; - const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); - - if (!isNew && !isSubscribed && !isRewardContent && !isDownloaded) { - return null; - } - - return ( - // TODO: turn this into it's own component and share it with FileCard - // The only issue is icon placement on the search page -
    - - {isNew && {__('NEW')}} - {isSubscribed && } - {isRewardContent && } - {!claimIsMine && isDownloaded && } - {claimIsMine && } -
    - ); - } - - render() { - const { - claim, - metadata, - isResolvingUri, - obscureNsfw, - claimIsMine, - clearPublish, - updatePublishForm, - hideNoResult, - displayHiddenMessage, - size, - history, - thumbnail, - title, - nsfw, - } = this.props; - - if (!claim && isResolvingUri) { - return ( -
    -
    -
    -
    -
    -
    -
    -
    - ); - } - - const shouldHide = !claimIsMine && obscureNsfw && nsfw; - if (shouldHide) { - return displayHiddenMessage ? ( - - {__('This file is hidden because it is marked NSFW. Update your')}{' '} -
    -
    -
    - -
    -
    - - - -
    -
    - -
    -

    {__('You Are Awesome!')}

    -
    -
    -

    {__("Check out some of the neat content below me. I'll see you around!")}

    -
    -
    -
    -
    -
    -
    - - ); - } -} diff --git a/src/ui/component/header/view.jsx b/src/ui/component/header/view.jsx index 4f8fa525cdd..45dedef07cd 100644 --- a/src/ui/component/header/view.jsx +++ b/src/ui/component/header/view.jsx @@ -4,110 +4,100 @@ import * as React from 'react'; import Button from 'component/button'; import LbcSymbol from 'component/common/lbc-symbol'; import WunderBar from 'component/wunderbar'; -import Icon from 'component/common/icon'; type Props = { autoUpdateDownloaded: boolean, balance: string, isUpgradeAvailable: boolean, - roundedBalance: string, - isBackDisabled: boolean, - isForwardDisabled: boolean, - back: () => void, - forward: () => void, + roundedBalance: number, downloadUpgradeRequested: any => void, }; const Header = (props: Props) => { - const { - autoUpdateDownloaded, - balance, - downloadUpgradeRequested, - isUpgradeAvailable, - roundedBalance, - back, - isBackDisabled, - forward, - isForwardDisabled, - } = props; + const { autoUpdateDownloaded, downloadUpgradeRequested, isUpgradeAvailable, roundedBalance } = props; const showUpgradeButton = autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable); return (
    -
    -
    + {/* @endif */} - {/* @endif */} - - + -
    -
    ); diff --git a/src/ui/component/navigationHistoryRecent/view.jsx b/src/ui/component/navigationHistoryRecent/view.jsx index cf2941fb16b..92e5c283125 100644 --- a/src/ui/component/navigationHistoryRecent/view.jsx +++ b/src/ui/component/navigationHistoryRecent/view.jsx @@ -23,7 +23,7 @@ export default function NavigationHistoryRecent(props: Props) { ))}
    -
    ) : null; diff --git a/src/ui/component/page/view.jsx b/src/ui/component/page/view.jsx index d26a858c5f6..78f0b692c97 100644 --- a/src/ui/component/page/view.jsx +++ b/src/ui/component/page/view.jsx @@ -9,7 +9,6 @@ const LOADER_TIMEOUT = 1000; type Props = { children: React.Node | Array, pageTitle: ?string, - notContained: ?boolean, // No max-width, but keep the padding loading: ?boolean, className: ?string, }; @@ -69,16 +68,11 @@ class Page extends React.PureComponent { loaderTimeout: ?TimeoutID; render() { - const { children, notContained, loading, className } = this.props; + const { children, loading, className } = this.props; const { showLoader } = this.state; return ( -
    +
    {!loading && children} {showLoader && (
    diff --git a/src/ui/component/recommendedContent/view.jsx b/src/ui/component/recommendedContent/view.jsx index 47186d69e99..cc3d9532483 100644 --- a/src/ui/component/recommendedContent/view.jsx +++ b/src/ui/component/recommendedContent/view.jsx @@ -1,6 +1,6 @@ // @flow import React from 'react'; -import FileTile from 'component/fileTile'; +import FileList from 'component/fileList'; type Props = { uri: string, @@ -51,15 +51,14 @@ export default class RecommendedContent extends React.PureComponent { const { recommendedContent, isSearching } = this.props; return ( -
    - Related - {recommendedContent && - recommendedContent.map(recommendedUri => ( - - ))} - {recommendedContent && !recommendedContent.length && !isSearching && ( -
    No related content found
    - )} +
    + Related} + empty={
    {__('No related content found')}
    } + />
    ); } diff --git a/src/ui/component/rewardSummary/view.jsx b/src/ui/component/rewardSummary/view.jsx index 72927063b0f..b9dce2952ca 100644 --- a/src/ui/component/rewardSummary/view.jsx +++ b/src/ui/component/rewardSummary/view.jsx @@ -43,7 +43,8 @@ class RewardSummary extends React.Component { {__('There are no rewards available at this time, please check back later')}. - ))} + ))}{' '} +
    - -

    - {__('Read our')} - {isChannel ? ( - - ) : ( - - )} + )} -

    - - -
    {__('These search results are provided by LBRY, Inc.')}
    +
    + } + sort={ + + {__('Find what you were looking for?')} +
    +
    {__('These search results are provided by LBRY, Inc.')}
    )} diff --git a/src/ui/page/show/view.jsx b/src/ui/page/show/view.jsx index cbab040334d..411d172db10 100644 --- a/src/ui/page/show/view.jsx +++ b/src/ui/page/show/view.jsx @@ -49,7 +49,7 @@ class ShowPage extends React.PureComponent { } innerContent = ( - + {isResolvingUri && } {!isResolvingUri && {__("There's nothing available at this location.")}} diff --git a/src/ui/page/subscriptions/index.js b/src/ui/page/subscriptions/index.js index ccd8836f043..b037522384a 100644 --- a/src/ui/page/subscriptions/index.js +++ b/src/ui/page/subscriptions/index.js @@ -1,45 +1,25 @@ import { connect } from 'react-redux'; -import * as settings from 'constants/settings'; import { selectSubscriptionClaims, selectSubscriptions, selectSubscriptionsBeingFetched, selectIsFetchingSubscriptions, - selectUnreadSubscriptions, - selectViewMode, - selectFirstRunCompleted, - selectshowSuggestedSubs, + selectSuggestedChannels, } from 'redux/selectors/subscriptions'; -import { - doFetchMySubscriptions, - doSetViewMode, - doFetchRecommendedSubscriptions, - doCompleteFirstRun, - doShowSuggestedSubs, -} from 'redux/actions/subscriptions'; -import { doSetClientSetting } from 'redux/actions/settings'; -import { makeSelectClientSetting } from 'redux/selectors/settings'; +import { doFetchMySubscriptions, doFetchRecommendedSubscriptions } from 'redux/actions/subscriptions'; import SubscriptionsPage from './view'; const select = state => ({ loading: selectIsFetchingSubscriptions(state) || Boolean(Object.keys(selectSubscriptionsBeingFetched(state)).length), subscribedChannels: selectSubscriptions(state), - autoDownload: makeSelectClientSetting(settings.AUTO_DOWNLOAD)(state), - allSubscriptions: selectSubscriptionClaims(state), - unreadSubscriptions: selectUnreadSubscriptions(state), - viewMode: selectViewMode(state), - firstRunCompleted: selectFirstRunCompleted(state), - showSuggestedSubs: selectshowSuggestedSubs(state), + subscriptionContent: selectSubscriptionClaims(state), + suggestedSubscriptions: selectSuggestedChannels(state), }); export default connect( select, { doFetchMySubscriptions, - doSetClientSetting, - doSetViewMode, doFetchRecommendedSubscriptions, - doCompleteFirstRun, - doShowSuggestedSubs, } )(SubscriptionsPage); diff --git a/src/ui/page/subscriptions/internal/first-run.jsx b/src/ui/page/subscriptions/internal/first-run.jsx deleted file mode 100644 index 130e1105074..00000000000 --- a/src/ui/page/subscriptions/internal/first-run.jsx +++ /dev/null @@ -1,51 +0,0 @@ -// @flow -import React from 'react'; -import Button from 'component/button'; -import SuggestedSubscriptions from 'component/subscribeSuggested'; -import Yrbl from 'component/yrbl'; - -type Props = { - showSuggested: boolean, - loadingSuggested: boolean, - numberOfSubscriptions: number, - onFinish: () => void, - doShowSuggestedSubs: () => void, -}; - -export default function SubscriptionsFirstRun(props: Props) { - const { showSuggested, loadingSuggested, numberOfSubscriptions, doShowSuggestedSubs, onFinish } = props; - - return ( -
    - 0 ? __('Woohoo!') : __('No subscriptions... yet.')} - subtitle={ - -

    - {showSuggested - ? __('I hear these channels are pretty good.') - : __("I'll tell you where the good channels are if you find me a wheel.")} -

    - {!showSuggested && ( -
    -
    - )} - {showSuggested && numberOfSubscriptions > 0 && ( -
    -
    - )} -
    - } - /> - {showSuggested && !loadingSuggested && } -
    - ); -} diff --git a/src/ui/page/subscriptions/internal/user-subscriptions.jsx b/src/ui/page/subscriptions/internal/user-subscriptions.jsx deleted file mode 100644 index 77c78b9046b..00000000000 --- a/src/ui/page/subscriptions/internal/user-subscriptions.jsx +++ /dev/null @@ -1,124 +0,0 @@ -// @flow -import { VIEW_ALL, VIEW_LATEST_FIRST } from 'constants/subscriptions'; -import React, { Fragment } from 'react'; -import Button from 'component/button'; -import HiddenNsfwClaims from 'component/hiddenNsfwClaims'; -import FileList from 'component/fileList'; -import { FormField } from 'component/common/form'; -import FileCard from 'component/fileCard'; -import { parseURI } from 'lbry-redux'; -import SuggestedSubscriptions from 'component/subscribeSuggested'; -import MarkAsRead from 'component/subscribeMarkAsRead'; -import Tooltip from 'component/common/tooltip'; -import Yrbl from 'component/yrbl'; -import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs'; - -type Props = { - viewMode: ViewMode, - doSetViewMode: ViewMode => void, - hasSubscriptions: boolean, - subscriptions: Array<{ uri: string, ...StreamClaim }>, - autoDownload: boolean, - onChangeAutoDownload: (SyntheticInputEvent<*>) => void, - unreadSubscriptions: Array<{ channel: string, uris: Array }>, -}; - -export default (props: Props) => { - const { - viewMode, - doSetViewMode, - hasSubscriptions, - subscriptions, - autoDownload, - onChangeAutoDownload, - unreadSubscriptions, - } = props; - - const index = viewMode === VIEW_ALL ? 0 : 1; - const onTabChange = index => (index === 0 ? doSetViewMode(VIEW_ALL) : doSetViewMode(VIEW_LATEST_FIRST)); - - return ( - - {hasSubscriptions && ( - - - {__('All Subscriptions')} - {__('Latest Only')} - - - - - - - { - if (name && claimId) { - arr.push(`lbry://${name}#${claimId}`); - } - return arr; - }, [])} - /> - } - > - -
    - {__('Your subscriptions')} - {unreadSubscriptions.length > 0 && } -
    - -
    - - - {unreadSubscriptions.length ? ( - unreadSubscriptions.map(({ channel, uris }) => { - const { claimName } = parseURI(channel); - return ( -
    -

    -

    - -
    -
      - {uris.map(uri => ( - - ))} -
    -
    -
    - ); - }) - ) : ( - - - - - )} -
    -
    -
    - )} - - {!hasSubscriptions && ( - - - - - )} -
    - ); -}; diff --git a/src/ui/page/subscriptions/view.jsx b/src/ui/page/subscriptions/view.jsx index a33475d24ac..57134548d78 100644 --- a/src/ui/page/subscriptions/view.jsx +++ b/src/ui/page/subscriptions/view.jsx @@ -1,105 +1,55 @@ // @flow -import * as SETTINGS from 'constants/settings'; -import React, { PureComponent } from 'react'; +import React, { useEffect, useState } from 'react'; import Page from 'component/page'; -import FirstRun from './internal/first-run'; -import UserSubscriptions from './internal/user-subscriptions'; +import FileList from 'component/fileList'; +import Button from 'component/button'; type Props = { subscribedChannels: Array, // The channels a user is subscribed to - unreadSubscriptions: Array<{ - channel: string, - uris: Array, - }>, - allSubscriptions: Array<{ uri: string, ...StreamClaim }>, + subscriptionContent: Array<{ uri: string, ...StreamClaim }>, + suggestedSubscriptions: Array<{ uri: string }>, loading: boolean, - autoDownload: boolean, - viewMode: ViewMode, - doSetViewMode: ViewMode => void, doFetchMySubscriptions: () => void, - doSetClientSetting: (string, boolean) => void, doFetchRecommendedSubscriptions: () => void, - loadingSuggested: boolean, - firstRunCompleted: boolean, - doCompleteFirstRun: () => void, - doShowSuggestedSubs: () => void, - showSuggestedSubs: boolean, }; -export default class SubscriptionsPage extends PureComponent { - constructor() { - super(); - - (this: any).onAutoDownloadChange = this.onAutoDownloadChange.bind(this); - } - - componentDidMount() { - const { - doFetchMySubscriptions, - doFetchRecommendedSubscriptions, - allSubscriptions, - firstRunCompleted, - doShowSuggestedSubs, - } = this.props; - +export default function SubscriptionsPage(props: Props) { + const { + subscriptionContent, + subscribedChannels, + doFetchMySubscriptions, + doFetchRecommendedSubscriptions, + suggestedSubscriptions, + loading, + } = props; + const hasSubscriptions = !!subscribedChannels.length; + const [showSuggested, setShowSuggested] = useState(!hasSubscriptions); + + useEffect(() => { doFetchMySubscriptions(); doFetchRecommendedSubscriptions(); - - // For channels that already have subscriptions, show the suggested subs right away - // This can probably be removed at a future date, it is just to make it so the "view your x subscriptions" button shows up right away - // Existing users will still go through the "first run" - if (!firstRunCompleted && allSubscriptions.length) { - doShowSuggestedSubs(); - } - } - - onAutoDownloadChange(event: SyntheticInputEvent<*>) { - this.props.doSetClientSetting(SETTINGS.AUTO_DOWNLOAD, event.target.checked); - } - - render() { - const { - subscribedChannels, - allSubscriptions, - loading, - autoDownload, - viewMode, - doSetViewMode, - loadingSuggested, - firstRunCompleted, - doCompleteFirstRun, - doShowSuggestedSubs, - showSuggestedSubs, - unreadSubscriptions, - } = this.props; - const numberOfSubscriptions = subscribedChannels && subscribedChannels.length; - - return ( - // Only pass in the loading prop if there are no subscriptions - // If there are any, let the page update in the background - // The loading prop removes children and shows a loading spinner - - {firstRunCompleted ? ( - 0} - subscriptions={allSubscriptions} - autoDownload={autoDownload} - onChangeAutoDownload={this.onAutoDownloadChange} - unreadSubscriptions={unreadSubscriptions} - loadingSuggested={loadingSuggested} - /> - ) : ( - - )} - - ); - } + }, [doFetchMySubscriptions, doFetchRecommendedSubscriptions]); + + return ( + +
    + setShowSuggested(!showSuggested)} + /> + } + header={

    {showSuggested ? __('You might like these channels') : __('Latest From Your Subscriptions')}

    } + uris={ + showSuggested + ? suggestedSubscriptions.map(sub => sub.uri) + : subscriptionContent.map(sub => sub.permanent_url) + } + /> +
    +
    + ); } diff --git a/src/ui/page/tags/index.js b/src/ui/page/tags/index.js new file mode 100644 index 00000000000..029080c7afd --- /dev/null +++ b/src/ui/page/tags/index.js @@ -0,0 +1,17 @@ +import { connect } from 'react-redux'; +import { selectFollowedTags, doFetchByTags, selectTrendingUris } from 'lbry-redux'; +import DiscoverPage from './view'; + +const select = state => ({ + followedTags: selectFollowedTags(state), + trending: selectTrendingUris(state), +}); + +const perform = { + doFetchByTags, +}; + +export default connect( + select, + perform +)(DiscoverPage); diff --git a/src/ui/page/tags/view.jsx b/src/ui/page/tags/view.jsx new file mode 100644 index 00000000000..3f118d14e13 --- /dev/null +++ b/src/ui/page/tags/view.jsx @@ -0,0 +1,28 @@ +// @flow +import React from 'react'; +import Page from 'component/page'; +import FileListTrending from 'component/fileListTrending'; + +type Props = { + location: { search: string }, +}; + +function TagsPage(props: Props) { + const { + location: { search }, + } = props; + + const urlParams = new URLSearchParams(search); + const tagsQuery = urlParams.get('t'); + const tags = tagsQuery.split(','); + + return ( + +
    + +
    +
    + ); +} + +export default TagsPage; diff --git a/src/ui/page/tagsEdit/index.js b/src/ui/page/tagsEdit/index.js new file mode 100644 index 00000000000..029080c7afd --- /dev/null +++ b/src/ui/page/tagsEdit/index.js @@ -0,0 +1,17 @@ +import { connect } from 'react-redux'; +import { selectFollowedTags, doFetchByTags, selectTrendingUris } from 'lbry-redux'; +import DiscoverPage from './view'; + +const select = state => ({ + followedTags: selectFollowedTags(state), + trending: selectTrendingUris(state), +}); + +const perform = { + doFetchByTags, +}; + +export default connect( + select, + perform +)(DiscoverPage); diff --git a/src/ui/page/tagsEdit/view.jsx b/src/ui/page/tagsEdit/view.jsx new file mode 100644 index 00000000000..99bd051de47 --- /dev/null +++ b/src/ui/page/tagsEdit/view.jsx @@ -0,0 +1,18 @@ +// @flow +import React from 'react'; +import Page from 'component/page'; +import TagsSelect from 'component/tagsSelect'; + +type Props = {}; + +function DiscoverPage(props: Props) { + return ( + +
    + +
    +
    + ); +} + +export default DiscoverPage; diff --git a/src/ui/reducers.js b/src/ui/reducers.js index 265d9ec8ea2..c9641f3a372 100644 --- a/src/ui/reducers.js +++ b/src/ui/reducers.js @@ -1,6 +1,13 @@ import { combineReducers } from 'redux'; import { connectRouter } from 'connected-react-router'; -import { claimsReducer, fileInfoReducer, searchReducer, walletReducer, notificationsReducer } from 'lbry-redux'; +import { + claimsReducer, + fileInfoReducer, + searchReducer, + walletReducer, + notificationsReducer, + tagsReducer, +} from 'lbry-redux'; import { userReducer, rewardsReducer, costInfoReducer, blacklistReducer, homepageReducer, statsReducer } from 'lbryinc'; import appReducer from 'redux/reducers/app'; import availabilityReducer from 'redux/reducers/availability'; @@ -27,6 +34,7 @@ export default history => settings: settingsReducer, stats: statsReducer, subscriptions: subscriptionsReducer, + tags: tagsReducer, user: userReducer, wallet: walletReducer, }); diff --git a/src/ui/redux/actions/app.js b/src/ui/redux/actions/app.js index 4be100b6405..7be2c2f732c 100644 --- a/src/ui/redux/actions/app.js +++ b/src/ui/redux/actions/app.js @@ -391,12 +391,6 @@ export function doConditionalAuthNavigate(newSession) { }; } -export function doToggleEnhancedLayout() { - return { - type: ACTIONS.ENNNHHHAAANNNCEEE, - }; -} - export function doToggleSearchExpanded() { return { type: ACTIONS.TOGGLE_SEARCH_EXPANDED, diff --git a/src/ui/redux/actions/content.js b/src/ui/redux/actions/content.js index aa977c7720e..bc4b840db39 100644 --- a/src/ui/redux/actions/content.js +++ b/src/ui/redux/actions/content.js @@ -21,7 +21,6 @@ import { selectBalance, makeSelectChannelForClaimUri, parseURI, - creditsToString, doError, } from 'lbry-redux'; import { makeSelectCostInfoForUri } from 'lbryinc'; @@ -293,13 +292,7 @@ export function doFetchClaimsByChannel(uri: string, page: number = 1, pageSize: data: { uri, page }, }); - const { claimName, claimId } = parseURI(uri); - let channelName = claimName; - if (claimId) { - channelName += `#${claimId}`; - } - - Lbry.claim_search({ channel_name: channelName, page, page_size: pageSize }).then(result => { + Lbry.claim_search({ channel: uri, is_controlling: true, page, page_size: pageSize }).then(result => { const { items: claimsInChannel, page: returnedPage } = result; if (claimsInChannel && claimsInChannel.length) { diff --git a/src/ui/redux/actions/subscriptions.js b/src/ui/redux/actions/subscriptions.js index 3c61b5832da..d49d8d317d5 100644 --- a/src/ui/redux/actions/subscriptions.js +++ b/src/ui/redux/actions/subscriptions.js @@ -7,7 +7,7 @@ import { Lbryio, rewards, doClaimRewardType } from 'lbryinc'; import { selectSubscriptions, selectUnreadByChannel } from 'redux/selectors/subscriptions'; import { makeSelectClientSetting } from 'redux/selectors/settings'; import { Lbry, buildURI, parseURI, doResolveUris } from 'lbry-redux'; -import { doPurchaseUri, doFetchClaimsByChannel } from 'redux/actions/content'; +import { doPurchaseUri } from 'redux/actions/content'; const CHECK_SUBSCRIPTIONS_INTERVAL = 15 * 60 * 1000; const SUBSCRIPTION_DOWNLOAD_LIMIT = 1; @@ -35,8 +35,7 @@ export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: GetSt Lbryio.call('subscription', 'list') .then(dbSubscriptions => { const storedSubscriptions = dbSubscriptions || []; - - // User has no subscriptions in db or redux + // // User has no subscriptions in db or redux if (!storedSubscriptions.length && (!reduxSubscriptions || !reduxSubscriptions.length)) { return []; } @@ -45,25 +44,12 @@ export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: GetSt // If something is in the db, but not in redux, add it to redux // If something is in redux, but not in the db, add it to the db if (storedSubscriptions.length !== reduxSubscriptions.length) { - const dbSubMap = {}; const reduxSubMap = {}; - const subsNotInDB = []; const subscriptionsToReturn = reduxSubscriptions.slice(); - storedSubscriptions.forEach(sub => { - dbSubMap[sub.claim_id] = 1; - }); - reduxSubscriptions.forEach(sub => { const { claimId } = parseURI(sub.uri); reduxSubMap[claimId] = 1; - - if (!dbSubMap[claimId]) { - subsNotInDB.push({ - claim_id: claimId, - channel_name: sub.channelName, - }); - } }); storedSubscriptions.forEach(sub => { @@ -73,13 +59,7 @@ export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: GetSt } }); - return Promise.all(subsNotInDB.map(payload => Lbryio.call('subscription', 'new', payload))) - .then(() => subscriptionsToReturn) - .catch( - () => - // let it fail, we will try again when the navigate to the subscriptions page - subscriptionsToReturn - ); + return subscriptionsToReturn; } // DB is already synced, just return the subscriptions in redux @@ -223,85 +203,85 @@ export const doCheckSubscription = (subscriptionUri: string, shouldNotify?: bool throw Error(`Trying to find new content for ${subscriptionUri} but it doesn't exist in your subscriptions`); } - const { claimId } = parseURI(subscriptionUri); - // We may be duplicating calls here. Can this logic be baked into doFetchClaimsByChannel? - Lbry.claim_search({ channel_id: claimId, page: 1, page_size: PAGE_SIZE }).then(claimListByChannel => { - const { items: claimsInChannel } = claimListByChannel; + Lbry.claim_search({ channel: subscriptionUri, is_controlling: true, page: 1, page_size: PAGE_SIZE }).then( + claimListByChannel => { + const { items: claimsInChannel } = claimListByChannel; - // may happen if subscribed to an abandoned channel or an empty channel - if (!claimsInChannel || !claimsInChannel.length) { - return; - } + // may happen if subscribed to an abandoned channel or an empty channel + if (!claimsInChannel || !claimsInChannel.length) { + return; + } - // Determine if the latest subscription currently saved is actually the latest subscription - const latestIndex = claimsInChannel.findIndex( - claim => `${claim.name}#${claim.claim_id}` === savedSubscription.latest - ); + // Determine if the latest subscription currently saved is actually the latest subscription + const latestIndex = claimsInChannel.findIndex( + claim => `${claim.name}#${claim.claim_id}` === savedSubscription.latest + ); - // If latest is -1, it is a newly subscribed channel or there have been 10+ claims published since last viewed - const latestIndexToNotify = latestIndex === -1 ? 10 : latestIndex; - - // If latest is 0, nothing has changed - // Do not download/notify about new content, it would download/notify 10 claims per channel - if (latestIndex !== 0 && savedSubscription.latest) { - let downloadCount = 0; - - const newUnread = []; - claimsInChannel.slice(0, latestIndexToNotify).forEach(claim => { - const uri = buildURI({ contentName: claim.name, claimId: claim.claim_id }, true); - const shouldDownload = - shouldAutoDownload && Boolean(downloadCount < SUBSCRIPTION_DOWNLOAD_LIMIT && !claim.value.fee); - - // Add the new content to the list of "un-read" subscriptions - if (shouldNotify) { - newUnread.push(uri); - } - - if (shouldDownload) { - downloadCount += 1; - dispatch(doPurchaseUri(uri, { cost: 0 }, true)); - } - }); + // If latest is -1, it is a newly subscribed channel or there have been 10+ claims published since last viewed + const latestIndexToNotify = latestIndex === -1 ? 10 : latestIndex; + + // If latest is 0, nothing has changed + // Do not download/notify about new content, it would download/notify 10 claims per channel + if (latestIndex !== 0 && savedSubscription.latest) { + let downloadCount = 0; + + const newUnread = []; + claimsInChannel.slice(0, latestIndexToNotify).forEach(claim => { + const uri = buildURI({ contentName: claim.name, claimId: claim.claim_id }, true); + const shouldDownload = + shouldAutoDownload && Boolean(downloadCount < SUBSCRIPTION_DOWNLOAD_LIMIT && !claim.value.fee); + // Add the new content to the list of "un-read" subscriptions + if (shouldNotify) { + newUnread.push(uri); + } + + if (shouldDownload) { + downloadCount += 1; + dispatch(doPurchaseUri(uri, { cost: 0 }, true)); + } + }); + + dispatch( + doUpdateUnreadSubscriptions( + subscriptionUri, + newUnread, + downloadCount > 0 ? NOTIFICATION_TYPES.DOWNLOADING : NOTIFICATION_TYPES.NOTIFY_ONLY + ) + ); + } + + // Set the latest piece of content for a channel + // This allows the app to know if there has been new content since it was last set dispatch( - doUpdateUnreadSubscriptions( - subscriptionUri, - newUnread, - downloadCount > 0 ? NOTIFICATION_TYPES.DOWNLOADING : NOTIFICATION_TYPES.NOTIFY_ONLY + setSubscriptionLatest( + { + channelName: claimsInChannel[0].channel_name, + uri: buildURI( + { + channelName: claimsInChannel[0].channel_name, + claimId: claimsInChannel[0].claim_id, + }, + false + ), + }, + buildURI({ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id }, false) ) ); - } - // Set the latest piece of content for a channel - // This allows the app to know if there has been new content since it was last set - dispatch( - setSubscriptionLatest( - { - channelName: claimsInChannel[0].channel_name, - uri: buildURI( - { - channelName: claimsInChannel[0].channel_name, - claimId: claimsInChannel[0].claim_id, - }, - false - ), + // calling FETCH_CHANNEL_CLAIMS_COMPLETED after not calling STARTED + // means it will delete a non-existant fetchingChannelClaims[uri] + dispatch({ + type: ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED, + data: { + uri: subscriptionUri, + claims: claimsInChannel || [], + page: 1, }, - buildURI({ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id }, false) - ) - ); - - // calling FETCH_CHANNEL_CLAIMS_COMPLETED after not calling STARTED - // means it will delete a non-existant fetchingChannelClaims[uri] - dispatch({ - type: ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED, - data: { - uri: subscriptionUri, - claims: claimsInChannel || [], - page: 1, - }, - }); - }); + }); + } + ); }; export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch, getState: GetState) => { @@ -394,13 +374,3 @@ export const doFetchRecommendedSubscriptions = () => (dispatch: Dispatch) => { }) ); }; - -export const doCompleteFirstRun = () => (dispatch: Dispatch) => - dispatch({ - type: ACTIONS.SUBSCRIPTION_FIRST_RUN_COMPLETED, - }); - -export const doShowSuggestedSubs = () => (dispatch: Dispatch) => - dispatch({ - type: ACTIONS.VIEW_SUGGESTED_SUBSCRIPTIONS, - }); diff --git a/src/ui/redux/reducers/app.js b/src/ui/redux/reducers/app.js index c9c32447f6d..d76e5be44a9 100644 --- a/src/ui/redux/reducers/app.js +++ b/src/ui/redux/reducers/app.js @@ -43,7 +43,6 @@ export type AppState = { isUpgradeAvailable: ?boolean, isUpgradeSkipped: ?boolean, hasClickedComment: boolean, - enhancedLayout: boolean, searchOptionsExpanded: boolean, }; @@ -228,11 +227,6 @@ reducers[ACTIONS.AUTHENTICATION_FAILURE] = state => modal: MODALS.AUTHENTICATION_FAILURE, }); -reducers[ACTIONS.ENNNHHHAAANNNCEEE] = state => - Object.assign({}, state, { - enhancedLayout: !state.enhancedLayout, - }); - reducers[ACTIONS.TOGGLE_SEARCH_EXPANDED] = state => Object.assign({}, state, { searchOptionsExpanded: !state.searchOptionsExpanded, diff --git a/src/ui/redux/reducers/settings.js b/src/ui/redux/reducers/settings.js index 0181f988101..bff9be8a047 100644 --- a/src/ui/redux/reducers/settings.js +++ b/src/ui/redux/reducers/settings.js @@ -19,11 +19,9 @@ const defaultState = { [SETTINGS.SHOW_UNAVAILABLE]: getLocalStorageSetting(SETTINGS.SHOW_UNAVAILABLE, true), [SETTINGS.NEW_USER_ACKNOWLEDGED]: getLocalStorageSetting(SETTINGS.NEW_USER_ACKNOWLEDGED, false), [SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED]: getLocalStorageSetting(SETTINGS.EMAIL_COLLECTION_ACKNOWLEDGED, false), - [SETTINGS.INVITE_ACKNOWLEDGED]: getLocalStorageSetting(SETTINGS.INVITE_ACKNOWLEDGED, false), - [SETTINGS.FIRST_RUN_COMPLETED]: getLocalStorageSetting(SETTINGS.FIRST_RUN_COMPLETED, false), [SETTINGS.CREDIT_REQUIRED_ACKNOWLEDGED]: false, // this needs to be re-acknowledged every run [SETTINGS.LANGUAGE]: getLocalStorageSetting(SETTINGS.LANGUAGE, 'en'), - [SETTINGS.THEME]: getLocalStorageSetting(SETTINGS.THEME, 'dark'), + [SETTINGS.THEME]: getLocalStorageSetting(SETTINGS.THEME, 'light'), [SETTINGS.THEMES]: getLocalStorageSetting(SETTINGS.THEMES, []), [SETTINGS.AUTOMATIC_DARK_MODE_ENABLED]: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false), [SETTINGS.AUTOPLAY]: getLocalStorageSetting(SETTINGS.AUTOPLAY, false), diff --git a/src/ui/redux/reducers/subscriptions.js b/src/ui/redux/reducers/subscriptions.js index f49aaa4a78c..e1efbf0eb84 100644 --- a/src/ui/redux/reducers/subscriptions.js +++ b/src/ui/redux/reducers/subscriptions.js @@ -134,14 +134,6 @@ export default handleActions( ...state, loadingSuggested: false, }), - [ACTIONS.SUBSCRIPTION_FIRST_RUN_COMPLETED]: (state: SubscriptionState): SubscriptionState => ({ - ...state, - firstRunCompleted: true, - }), - [ACTIONS.VIEW_SUGGESTED_SUBSCRIPTIONS]: (state: SubscriptionState): SubscriptionState => ({ - ...state, - showSuggestedSubs: true, - }), }, defaultState ); diff --git a/src/ui/redux/selectors/app.js b/src/ui/redux/selectors/app.js index 85e23eac21a..792b7fac4bb 100644 --- a/src/ui/redux/selectors/app.js +++ b/src/ui/redux/selectors/app.js @@ -1,9 +1,4 @@ -import * as SETTINGS from 'constants/settings'; -import * as PAGES from 'constants/pages'; -import * as ICONS from 'constants/icons'; import { createSelector } from 'reselect'; -import { selectCurrentPage, selectHistoryStack } from 'lbry-redux'; -import { makeSelectClientSetting } from 'redux/selectors/settings'; export const selectState = state => state.app || {}; @@ -129,20 +124,7 @@ export const selectModal = createSelector( } ); -export const selectEnhancedLayout = createSelector( - selectState, - state => state.enhancedLayout -); - export const selectSearchOptionsExpanded = createSelector( selectState, state => state.searchOptionsExpanded ); - -export const selectShouldShowInviteGuide = createSelector( - makeSelectClientSetting(SETTINGS.FIRST_RUN_COMPLETED), - makeSelectClientSetting(SETTINGS.INVITE_ACKNOWLEDGED), - (firstRunCompleted, inviteAcknowledged) => { - return firstRunCompleted ? !inviteAcknowledged : false; - } -); diff --git a/src/ui/redux/selectors/subscriptions.js b/src/ui/redux/selectors/subscriptions.js index 2c3569a04fc..c1152c667e7 100644 --- a/src/ui/redux/selectors/subscriptions.js +++ b/src/ui/redux/selectors/subscriptions.js @@ -9,7 +9,6 @@ import { parseURI, } from 'lbry-redux'; import { swapKeyAndValue } from 'util/swap-json'; -import { shuffleArray } from 'util/shuffleArray'; // Returns the entire subscriptions state const selectState = state => state.subscriptions || {}; @@ -86,13 +85,10 @@ export const selectSuggestedChannels = createSelector( } }); - return Object.keys(suggestedChannels) - .map(uri => ({ - uri, - label: suggestedChannels[uri], - })) - .sort(shuffleArray) - .slice(0, 5); + return Object.keys(suggestedChannels).map(uri => ({ + uri, + label: suggestedChannels[uri], + })); } ); diff --git a/src/ui/scss/all.scss b/src/ui/scss/all.scss index 5088d6a1b54..404f8dcf2ba 100644 --- a/src/ui/scss/all.scss +++ b/src/ui/scss/all.scss @@ -10,7 +10,6 @@ @import 'init/gui'; @import 'component/animation'; @import 'component/badge'; -@import 'component/banner'; @import 'component/button'; @import 'component/card'; @import 'component/channel'; @@ -19,6 +18,8 @@ @import 'component/dat-gui'; @import 'component/expandable'; @import 'component/file-download'; +@import 'component/file-list'; +@import 'component/file-properties'; @import 'component/file-render'; @import 'component/form-field'; @import 'component/header'; @@ -33,7 +34,6 @@ @import 'component/notice'; @import 'component/pagination'; @import 'component/placeholder'; -@import 'component/scrollbar'; @import 'component/search'; @import 'component/snack-bar'; @import 'component/spinner'; @@ -42,6 +42,7 @@ @import 'component/syntax-highlighter'; @import 'component/table'; @import 'component/tabs'; +@import 'component/tags'; @import 'component/time'; @import 'component/toggle'; @import 'component/tooltip'; diff --git a/src/ui/scss/component/_badge.scss b/src/ui/scss/component/_badge.scss index 23d70124e92..0435f56458e 100644 --- a/src/ui/scss/component/_badge.scss +++ b/src/ui/scss/component/_badge.scss @@ -1 +1,18 @@ @import '~@lbry/components/sass/badge/_index.scss'; + +.badge--tag { + @extend .badge; + background-color: lighten($lbry-teal-5, 55%); + color: darken($lbry-teal-5, 20%); + + [data-mode='dark'] & { + color: lighten($lbry-teal-5, 60%); + background-color: rgba($lbry-teal-5, 0.3); + } +} + +.badge--alert { + @extend .badge; + background-color: $lbry-red-2; + color: $lbry-white; +} diff --git a/src/ui/scss/component/_banner.scss b/src/ui/scss/component/_banner.scss deleted file mode 100644 index 7b71ce1734b..00000000000 --- a/src/ui/scss/component/_banner.scss +++ /dev/null @@ -1,31 +0,0 @@ -.banner { - display: flex; - overflow: hidden; - background-color: $lbry-black; - color: $lbry-white; -} - -.banner--first-run { - height: 310px; - padding-right: var(--spacing-vertical-medium); - - // Adjust this class inside other `.banner--xxx` styles for control over animation - .banner__item--static-for-animation { - height: 310px; - display: flex; - flex-direction: column; - justify-content: center; - } -} - -.banner__item { - &:not(:first-child) { - margin-left: var(--spacing-vertical-large); - } -} - -.banner__content { - display: flex; - align-items: center; - height: 100%; -} diff --git a/src/ui/scss/component/_button.scss b/src/ui/scss/component/_button.scss index 93c138a48c4..1c2cc57b8f4 100644 --- a/src/ui/scss/component/_button.scss +++ b/src/ui/scss/component/_button.scss @@ -3,16 +3,8 @@ .button { display: inline-block; - .button__content { - display: flex; - align-items: center; - height: 100%; - } - svg { stroke-width: 1.9; - width: 1.2rem; - height: 1.2rem; position: relative; color: $lbry-gray-5; @@ -23,12 +15,6 @@ height: 1.4rem; } } - - // Handle icons on the left or right side of the button label - svg + .button__label, - .button__label + svg { - margin-left: var(--spacing-vertical-miniscule); - } } .button--primary { @@ -56,16 +42,57 @@ box-sizing: border-box; } +.button--alt { + padding: 0; +} + .button--uri-indicator { max-width: 100%; height: 1.2em; vertical-align: text-top; - overflow: hidden; text-align: left; text-overflow: ellipsis; transition: color 0.2s; &:hover { - color: $lbry-teal-3; + color: $lbry-teal-5; + } +} + +.button--close { + position: absolute; + top: var(--spacing-miniscule); + right: var(--spacing-miniscule); + padding: 0.3rem; + transition: all var(--transition-duration) var(--transition-style); + + &:hover { + background-color: $lbry-red-3; + color: $lbry-white; + border-radius: var(--card-radius); } } + +.button--subscribe { + vertical-align: text-top; + align-items: flex-start; +} + +.button__label { + // white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + // display: flex; + // align-items: center; +} + +// Handle icons on the left or right side of the button label +svg + .button__label, +.button__label + svg { + margin-left: var(--spacing-miniscule); +} + +.button__content { + display: flex; + align-items: center; +} diff --git a/src/ui/scss/component/_card.scss b/src/ui/scss/component/_card.scss index ab177228cfd..c423f30f722 100644 --- a/src/ui/scss/component/_card.scss +++ b/src/ui/scss/component/_card.scss @@ -1,12 +1,13 @@ .card { background-color: $lbry-white; - margin-bottom: var(--spacing-vertical-xlarge); + margin-bottom: var(--spacing-xlarge); position: relative; border-radius: var(--card-radius); box-shadow: var(--card-box-shadow) $lbry-gray-1; + overflow: hidden; html[data-mode='dark'] & { - background-color: rgba($lbry-white, 0.1); + background-color: lighten($lbry-black, 5%); box-shadow: var(--card-box-shadow) darken($lbry-gray-1, 80%); } } @@ -21,10 +22,11 @@ } .card--section { - padding: var(--spacing-vertical-large); + position: relative; + padding: var(--spacing-large); .card__content:not(:last-of-type) { - margin-bottom: var(--spacing-vertical-large); + margin-bottom: var(--spacing-large); } } @@ -39,6 +41,10 @@ justify-content: space-between; } +.card--modal { + box-shadow: none; +} + // C A R D // A C T I O N S @@ -47,7 +53,7 @@ font-size: 1.15rem; > *:not(:last-child) { - margin-right: var(--spacing-vertical-medium); + margin-right: var(--spacing-medium); } } @@ -74,7 +80,7 @@ } .card__actions--top-space { - padding-top: var(--spacing-vertical-small); + padding-top: var(--spacing-small); } // C A R D @@ -84,13 +90,12 @@ font-size: 1.25rem; p:not(:last-child) { - margin-bottom: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); } +} - .badge { - bottom: -0.15rem; - position: relative; - } +.card__content--large { + font-size: 4rem; } // C A R D @@ -100,25 +105,17 @@ position: relative; &:not(.card__header--flat) { - margin-bottom: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); } } -// C A R D -// I N T E R N A L - -.card__internal-links { - top: 2rem; - right: 2rem; - position: absolute; -} - // C A R D // L I S T .card__list { display: grid; - grid-gap: var(--spacing-vertical-medium); + grid-gap: var(--spacing-medium); + margin-top: var(--spacing-large); // Depending on screen width, the amount of items in // each row change and are auto-sized @@ -135,31 +132,21 @@ grid-template-columns: repeat(auto-fill, minmax(calc(100% / 7), 1fr)); } - @media (min-width: 1051px) and (max-width: 1550px) { + @media (min-width: 1200px) and (max-width: 1550px) { grid-template-columns: repeat(auto-fill, minmax(calc(100% / 6), 1fr)); } - @media (min-width: 901px) and (max-width: 1050px) { - grid-template-columns: repeat(auto-fill, minmax(calc(100% / 5), 1fr)); - } - - @media (min-width: 751px) and (max-width: 900px) { - grid-template-columns: repeat(auto-fill, minmax(calc(100% / 4), 1fr)); - } - - @media (max-width: 750px) { - grid-template-columns: repeat(auto-fill, minmax(calc(100% / 3), 1fr)); - } + grid-template-columns: repeat(auto-fill, minmax(calc(100% / 5), 1fr)); } .card__list--rewards { column-count: 2; - column-gap: var(--spacing-vertical-medium); - margin-bottom: var(--spacing-vertical-large); + column-gap: var(--spacing-medium); + margin-bottom: var(--spacing-large); .card { display: inline-block; - margin: 0 0 var(--spacing-vertical-medium); + margin: 0 0 var(--spacing-medium); width: 100%; } } @@ -169,8 +156,7 @@ .card__message { border-left: 0.5rem solid; - padding: var(--spacing-vertical-medium) var(--spacing-vertical-medium) var(--spacing-vertical-medium) - var(--spacing-vertical-large); + padding: var(--spacing-medium) var(--spacing-medium) var(--spacing-medium) var(--spacing-large); &:not(&--error):not(&--failure):not(&--success) { background-color: rgba($lbry-teal-1, 0.1); @@ -198,22 +184,24 @@ .card__subtitle { @extend .help; - background-color: lighten($lbry-gray-1, 7%); - color: darken($lbry-gray-5, 30%); + color: darken($lbry-gray-5, 25%); font-size: 1.15rem; - margin-bottom: var(--spacing-vertical-small); + margin-bottom: var(--spacing-small); + flex: 1; p { - margin-bottom: var(--spacing-vertical-small); + margin-bottom: var(--spacing-small); } .badge { bottom: -0.12rem; position: relative; + margin-left: 0; } [data-mode='dark'] & { - background-color: darken($lbry-gray-5, 20%); + // TODO: dark + // background-color: darken($lbry-gray-5, 20%); } } @@ -223,10 +211,10 @@ .card__title { font-size: 2rem; font-weight: 600; - margin-bottom: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); + .card__content { - margin-top: var(--spacing-vertical-medium); + margin-top: var(--spacing-medium); } } @@ -235,7 +223,7 @@ align-items: center; & > *:not(:last-child) { - margin-right: var(--spacing-vertical-medium); + margin-right: var(--spacing-medium); } } diff --git a/src/ui/scss/component/_channel.scss b/src/ui/scss/component/_channel.scss index fcbadeaf10b..39aefb157cd 100644 --- a/src/ui/scss/component/_channel.scss +++ b/src/ui/scss/component/_channel.scss @@ -7,6 +7,8 @@ $metadata-z-index: 1; align-items: flex-end; box-sizing: content-box; color: $lbry-white; + border-top-left-radius: var(--card-radius); + border-top-right-radius: var(--card-radius); } .channel-cover__custom { @@ -24,13 +26,19 @@ $metadata-z-index: 1; } .channel-thumbnail { - position: absolute; display: flex; - left: var(--spacing-main-padding); - height: var(--channel-thumbnail-size); - width: var(--channel-thumbnail-size); + height: 5.3rem; + width: 5.4rem; background-size: cover; + margin-right: var(--spacing-medium); +} + +.channel__thumbnail--channel-page { + position: absolute; + height: var(--channel-thumbnail-width); + width: var(--channel-thumbnail-width); box-shadow: 0px 8px 40px -3px $lbry-black; + left: var(--spacing-medium); } .channel-thumbnail__custom { @@ -44,7 +52,7 @@ $metadata-z-index: 1; margin-left: auto; margin-right: auto; align-self: flex-end; - margin-bottom: -1px; + // margin-bottom: -1px; } .channel-thumbnail, @@ -70,14 +78,15 @@ $metadata-z-index: 1; z-index: $metadata-z-index; // Jump over the thumbnail photo because it is absolutely positioned // Then add normal page spacing, _then_ add the actual padding - margin-left: calc(var(--channel-thumbnail-size) + var(--spacing-main-padding)); - padding-left: var(--spacing-vertical-large); - padding-bottom: var(--spacing-vertical-medium); + padding-left: calc(var(--channel-thumbnail-width) + var(--spacing-large)); + // padding-left: var(--spacing-large); + padding-bottom: var(--spacing-medium); } .channel__title { font-size: 3rem; font-weight: 800; + margin-right: var(--spacing-large); } .channel__url { @@ -85,3 +94,8 @@ $metadata-z-index: 1; margin-top: -0.25rem; color: rgba($lbry-white, 0.75); } + +// TODO: rename +.channel__data { + min-height: 10rem; +} diff --git a/src/ui/scss/component/_content.scss b/src/ui/scss/component/_content.scss index a1a8ddc8085..b2c1f20869a 100644 --- a/src/ui/scss/component/_content.scss +++ b/src/ui/scss/component/_content.scss @@ -73,7 +73,7 @@ display: flex; flex-direction: column; justify-content: center; - padding: 0 var(--spacing-vertical-large); + padding: 0 var(--spacing-large); background-color: #000; } diff --git a/src/ui/scss/component/_expandable.scss b/src/ui/scss/component/_expandable.scss index 70eb9b01c38..ab82b45e25d 100644 --- a/src/ui/scss/component/_expandable.scss +++ b/src/ui/scss/component/_expandable.scss @@ -1,7 +1,7 @@ .expandable { border-bottom: 1px solid $lbry-gray-1; - margin-bottom: var(--spacing-vertical-medium); - padding-bottom: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); + padding-bottom: var(--spacing-medium); html[data-mode='dark'] & { border-color: rgba($lbry-gray-5, 0.2); @@ -14,7 +14,7 @@ .expandable--closed, .expandable--open { - margin-bottom: var(--spacing-vertical-small); + margin-bottom: var(--spacing-small); } .expandable--closed { @@ -29,11 +29,7 @@ left: 0; pointer-events: none; - background-image: linear-gradient( - to bottom, - transparent 0%, - mix($lbry-white, $lbry-gray-1, 70%) 90% - ); + background-image: linear-gradient(to bottom, transparent 0%, mix($lbry-white, $lbry-gray-1, 70%) 90%); content: ''; position: absolute; diff --git a/src/ui/scss/component/_file-list.scss b/src/ui/scss/component/_file-list.scss new file mode 100644 index 00000000000..bdc6a46b4f0 --- /dev/null +++ b/src/ui/scss/component/_file-list.scss @@ -0,0 +1,137 @@ +.file-list__header { + display: flex; + align-items: center; + min-height: 4rem; + padding: var(--spacing-medium); + color: $lbry-white; + border-top-left-radius: var(--card-radius); + border-top-right-radius: var(--card-radius); + + & > *:not(:last-child) { + margin-right: 0.5rem; + } + + fieldset-section { + margin-bottom: 0; + } +} + +.file-list__dropdown { + background-position: 95% center; + background-repeat: no-repeat; + background-size: 1.2rem; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23ffffff'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A"); + height: 2.5rem; + padding: 0 var(--spacing-medium); + padding-right: var(--spacing-large); + margin-bottom: 0; + border: 1px solid $lbry-white; + color: $lbry-white; + background-color: lighten($lbry-black, 10%); +} + +.file-list__header, +.file-list__dropdown { + background-color: lighten($lbry-black, 10%); + + [data-mode='dark'] & { + background-color: darken($lbry-black, 10%); + } +} + +.file-list__header-text { + display: flex; + align-items: center; +} + +.file-list__header-text, +.file-list__dropdown { + font-size: 1.3rem; +} + +.file-list__sort { + display: flex; + align-items: center; + margin-left: auto; + + & > * { + margin-left: var(--spacing-small); + } +} + +.file-list__item { + display: flex; + position: relative; + font-size: 1.3rem; + padding: var(--spacing-medium); + cursor: pointer; + overflow: hidden; + + &:hover { + background-color: darken($lbry-white, 5%); + + [data-mode='dark'] & { + background-color: lighten($lbry-black, 10%); + } + } + + .media__thumb { + width: var(--file-list-thumbnail-width); + flex-shrink: 0; + margin-right: var(--spacing-medium); + } + + .media__thumb--profile { + width: 6rem; + } +} + +.file-list__item--injected, +.file-list__item { + border-bottom: 1px solid rgba($lbry-teal-5, 0.1); +} + +.file-list__item--large { + @include mediaThumbHoverZoom; + font-size: 1.6rem; + border-bottom: 0; + padding: 0; + padding-bottom: var(--spacing-medium); + + &:hover { + background-color: transparent; + } + + .media__thumb { + width: 20rem; + } +} + +.file-list__item-metadata { + display: flex; + flex-direction: column; + width: 100%; +} + +.file-list__item-info { + align-items: flex-start; +} + +.file-list__item-info, +.file-list__item-properties { + display: flex; + justify-content: space-between; +} + +.file-list__item-properties { + align-items: flex-end; +} + +.file-list__item-title { + font-weight: 600; + margin-right: auto; +} + +.file-list__item-tags { + margin-left: 0; +} diff --git a/src/ui/scss/component/_file-properties.scss b/src/ui/scss/component/_file-properties.scss new file mode 100644 index 00000000000..49142fb89b1 --- /dev/null +++ b/src/ui/scss/component/_file-properties.scss @@ -0,0 +1,13 @@ +.file-properties { + display: flex; + position: relative; + align-items: center; + + & > *:not(:first-child) { + margin-left: var(--spacing-small); + } + + @media (max-width: 600px) { + display: none; + } +} diff --git a/src/ui/scss/component/_file-render.scss b/src/ui/scss/component/_file-render.scss index 78917de168c..dda85388bb3 100644 --- a/src/ui/scss/component/_file-render.scss +++ b/src/ui/scss/component/_file-render.scss @@ -52,7 +52,7 @@ .markdown-preview { height: 100%; overflow: auto; - padding: var(--spacing-vertical-large); + padding: var(--spacing-large); } } @@ -75,7 +75,7 @@ .CodeMirror .CodeMirror-lines { // is there really a .CodeMirror inside a .CodeMirror? - padding: var(--spacing-vertical-small) 0; + padding: var(--spacing-small) 0; } .CodeMirror-code { @@ -86,11 +86,11 @@ .CodeMirror-gutters { background-color: $lbry-gray-1; border-right: 1px solid $lbry-gray-2; - padding-right: var(--spacing-vertical-medium); + padding-right: var(--spacing-medium); } .CodeMirror-line { - padding-left: var(--spacing-vertical-medium); + padding-left: var(--spacing-medium); } .CodeMirror-linenumber { diff --git a/src/ui/scss/component/_form-field.scss b/src/ui/scss/component/_form-field.scss index c8ca791c303..02006ff250d 100644 --- a/src/ui/scss/component/_form-field.scss +++ b/src/ui/scss/component/_form-field.scss @@ -103,13 +103,10 @@ fieldset-group { } &.fieldgroup--paginate { - margin: var(--spacing-vertical-large) 0; - align-items: center; + padding-bottom: var(--spacing-large); + margin-top: var(--spacing-large); + align-items: flex-end; justify-content: center; - - .pagination { - margin-bottom: -1em; - } } } @@ -271,12 +268,12 @@ fieldset-section { label + .react-toggle, .react-toggle + label { - margin-left: var(--spacing-vertical-small); + margin-left: var(--spacing-small); } .form-field__help { @extend .help; - margin-top: var(--spacing-vertical-medium); + margin-top: var(--spacing-medium); } .form-field--price-amount { diff --git a/src/ui/scss/component/_form-row.scss b/src/ui/scss/component/_form-row.scss index ddac864aa47..47f18e9dec7 100644 --- a/src/ui/scss/component/_form-row.scss +++ b/src/ui/scss/component/_form-row.scss @@ -4,12 +4,12 @@ align-items: flex-end; &:not(.form-row--padded):not(:last-of-type) { - margin-bottom: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); } .form-field { &:not(:first-of-type) { - padding-left: var(--spacing-vertical-medium); + padding-left: var(--spacing-medium); } &.form-field--stretch { @@ -24,7 +24,7 @@ button + input, input + button { - margin-left: var(--spacing-vertical-medium); + margin-left: var(--spacing-medium); } } @@ -34,8 +34,8 @@ .form-row--padded { // Ignore the class name, margin allows these to collapse with other items - margin-top: var(--spacing-vertical-large); - margin-bottom: var(--spacing-vertical-large); + margin-top: var(--spacing-large); + margin-bottom: var(--spacing-large); } .form-row--right { diff --git a/src/ui/scss/component/_header.scss b/src/ui/scss/component/_header.scss index 863aea5ceb6..c697343c67d 100644 --- a/src/ui/scss/component/_header.scss +++ b/src/ui/scss/component/_header.scss @@ -1,39 +1,43 @@ .header { + z-index: 2; // Main content uses z-index: 1, this ensures it always scrolls under the header position: fixed; + top: 0; width: 100%; - height: var(--header-height); - display: flex; - justify-content: space-between; - z-index: 2; // Main content uses z-index: 1, this ensures it always scrolls under the header background-color: $lbry-white; border-bottom: 1px solid $lbry-gray-1; html[data-mode='dark'] & { - background-color: rgba($lbry-black, 0.9); background-color: mix($lbry-black, $lbry-gray-3, 90%); color: $lbry-white; border-bottom: none; } } +.header__contents { + width: 100%; + height: calc(var(--header-height) - 1px); + max-width: var(--page-max-width); + padding: 0 var(--spacing-medium); + display: flex; + justify-content: space-between; + margin: auto; +} + .header__navigation { display: flex; - // First navigation item is the top left wrapper - // This contains the Lbry logo (home link) and forward/back arrows on desktop - &:first-of-type { - @media (min-width: 601px) { - width: calc(var(--side-nav-width) - 1px); - } + &:last-of-type { + width: var(--side-nav-width); + } + + @media (max-width: 600px) { + display: none; } } .header__navigation-arrows { display: flex; - - .button__content { - justify-content: center; - } + margin: 0 var(--spacing-small); } .header__navigation-item { @@ -48,7 +52,11 @@ } &:hover { - background-color: $lbry-gray-1; + color: $lbry-teal-5; + + svg { + stroke: $lbry-teal-5; + } } &.header__navigation-item--active { @@ -56,15 +64,24 @@ height: 0.2em; bottom: 0; width: 100%; - background-color: $lbry-teal-3; + background-color: $lbry-teal-5; content: ''; position: absolute; + + html[data-mode='dark'] & { + background-color: $lbry-teal-3; + } } } + // TODO: dark html[data-mode='dark'] & { &:hover { - background-color: $lbry-gray-5; + color: $lbry-teal-3; + + svg { + stroke: $lbry-teal-3; + } } svg { @@ -74,9 +91,8 @@ } .header__navigation-item--back, -.header__navigation-item--forward, -.header__navigation-item--menu { - width: var(--header-height); +.header__navigation-item--forward { + width: 3rem; } .header__navigation-item--lbry { @@ -91,17 +107,16 @@ } .header__navigation-item--right-action { - .button__content { - padding: 0 var(--spacing-vertical-large); + &:first-of-type { + margin-right: auto; } -} -.header__navigation-item--right-action:not(:last-child), -.header__navigation-item--lbry { - border-right: 1px solid $lbry-gray-1; + &:not(:first-of-type) { + padding: 0 var(--spacing-medium); + } - html[data-mode='dark'] & { - border-right: 1px solid $lbry-gray-5; + &:last-of-type { + margin-right: 0; } } @@ -117,22 +132,10 @@ } } -.header__navigation-item--menu { - // Add this back once we have an actual menu for mobile - // @media (min-width: 601px) { - display: none; - // } -} - -// Hide links that will live in the menu bar @media (max-width: 600px) { .header__navigation-item--back, .header__navigation-item--forward, .header__navigation-item--right-action { display: none; } - - .header__navigation-item--lbry { - padding: var(--spacing-vertical-medium); - } } diff --git a/src/ui/scss/component/_icon.scss b/src/ui/scss/component/_icon.scss index a7f3acd1258..74bd2c7d0eb 100644 --- a/src/ui/scss/component/_icon.scss +++ b/src/ui/scss/component/_icon.scss @@ -1,10 +1,11 @@ // Not all icons are created equally... at least the react-feather ones aren't // Minor adjustments to ensure icons line up vertically -.icon--Heart { - top: -1px; +.icon--Flag, +.icon--Home { + top: -2px; } -.icon--Flag { - top: -2px; +.icon--Heart { + top: -1px; } diff --git a/src/ui/scss/component/_item-list.scss b/src/ui/scss/component/_item-list.scss index 5ed35d150ce..32eb24e3524 100644 --- a/src/ui/scss/component/_item-list.scss +++ b/src/ui/scss/component/_item-list.scss @@ -1,22 +1,22 @@ .item-list { background-color: $lbry-white; - margin-bottom: var(--spacing-vertical-large); - padding: var(--spacing-vertical-large); + margin-bottom: var(--spacing-large); + padding: var(--spacing-large); html[data-mode='dark'] & { background-color: rgba($lbry-white, 0.1); } .card__actions { - margin-top: var(--spacing-vertical-medium); - margin-left: var(--spacing-vertical-small); + margin-top: var(--spacing-medium); + margin-left: var(--spacing-small); } } .item-list__row { align-items: center; display: flex; - padding: var(--spacing-vertical-small); + padding: var(--spacing-small); fieldset-section { margin-bottom: 0; @@ -29,7 +29,7 @@ white-space: nowrap; &:not(:first-of-type) { - padding-left: var(--spacing-vertical-small); + padding-left: var(--spacing-small); } } diff --git a/src/ui/scss/component/_main.scss b/src/ui/scss/component/_main.scss index 9f38ce6e894..0c91d2726ec 100644 --- a/src/ui/scss/component/_main.scss +++ b/src/ui/scss/component/_main.scss @@ -1,58 +1,52 @@ .main-wrapper { position: absolute; - top: var(--header-height); - min-height: calc(100% - var(--header-height)); - width: 100%; + min-height: 100vh; + width: 100vw; + padding-top: var(--spacing-main-padding); + padding-left: var(--spacing-medium); background-color: mix($lbry-white, $lbry-gray-1, 70%); + display: flex; - html[data-mode='dark'] & { + [data-mode='dark'] & { background-color: $lbry-black; } - - @media (min-width: 600px) { - left: var(--side-nav-width); - width: calc(100% - var(--side-nav-width)); - } } -.main { +.main-wrapper-inner { display: flex; - flex-direction: column; - margin: auto; -} - -.main--contained { - max-width: 900px; - padding: var(--spacing-main-padding); -} - -.main--not-contained { - padding: var(--spacing-main-padding); + max-width: 1200px; + width: 100%; + margin-left: auto; + margin-right: auto; + margin-top: var(--spacing-main-padding); + position: relative; } -.main--no-padding { - padding: 0; -} +.main { + min-width: 0; + width: calc(100% - var(--side-nav-width) - var(--spacing-main-padding)); + position: relative; -.main--no-padding-top { - padding-top: 0; + @media (max-width: 600px) { + width: 100%; + margin-right: 0; + margin-left: 0; + } } .main--file-page { - padding: var(--spacing-main-padding); - max-width: var(--file-page-max-width); display: grid; - grid-gap: var(--spacing-vertical-large); + grid-gap: var(--spacing-large); grid-template-rows: auto 1fr; grid-template-columns: 1fr auto; + max-width: calc(100% - var(--side-nav-width) - var(--spacing-main-padding)); + grid-template-areas: 'content content' - 'info related'; + 'info related'; - @media (min-width: 1300px) { - grid-template-areas: - 'content related' - 'info related'; + .grid-area--content { + width: 100%; } .grid-area--content { @@ -63,6 +57,7 @@ } .grid-area--related { grid-area: related; + width: 40rem; } } @@ -70,20 +65,7 @@ align-items: center; display: flex; flex-direction: column; - margin-top: 100px; - margin-bottom: 100px; + padding-top: 100px; + padding-bottom: 100px; text-align: center; } - -// On pages that are not contained, they still might want to have items inside the page -// that extend the full width ex: cover photo -// But the components inside of those pages should be still have "page" padding -.main__item--extend-outside { - $main-width: calc(100vw - var(--side-nav-width)); - width: $main-width; - position: relative; - left: 50%; - right: 50%; - margin-left: calc(-50vw + (var(--side-nav-width) * 0.5)); - margin-right: calc(-50vw + (var(--side-nav-width) * 0.5)); -} diff --git a/src/ui/scss/component/_markdown-editor.scss b/src/ui/scss/component/_markdown-editor.scss index 43931720fef..8b7e0ef519b 100644 --- a/src/ui/scss/component/_markdown-editor.scss +++ b/src/ui/scss/component/_markdown-editor.scss @@ -124,7 +124,7 @@ .editor-statusbar { color: rgba($lbry-black, 0.5); font-size: 1rem; - padding: var(--spacing-vertical-medium) 0; // overriding styles from elsewhere + padding: var(--spacing-medium) 0; // overriding styles from elsewhere html[data-mode='dark'] & { color: inherit; @@ -132,7 +132,7 @@ } .form-field--SimpleMDE { - margin-top: var(--spacing-vertical-large); + margin-top: var(--spacing-large); // Overriding the lbry/components form styling .editor-toolbar { diff --git a/src/ui/scss/component/_markdown-preview.scss b/src/ui/scss/component/_markdown-preview.scss index f6167148742..9d6dc15531f 100644 --- a/src/ui/scss/component/_markdown-preview.scss +++ b/src/ui/scss/component/_markdown-preview.scss @@ -8,14 +8,14 @@ h6 { font-size: inherit; font-weight: 600; - margin-bottom: var(--spacing-vertical-medium); - padding-top: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); + padding-top: var(--spacing-medium); } // Paragraphs p { font-size: 1.15rem; - margin-bottom: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); // word-break: break-all; svg { @@ -35,7 +35,7 @@ // Tables table { margin-bottom: 1.2rem; - padding: var(--spacing-vertical-medium); + padding: var(--spacing-medium); tr { td, @@ -44,15 +44,15 @@ th:first-of-type, td:last-of-type, th:last-of-type { - padding: var(--spacing-vertical-medium); + padding: var(--spacing-medium); } } } // Image img { - margin-bottom: var(--spacing-vertical-medium); - padding-top: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); + padding-top: var(--spacing-medium); } // Horizontal Rule @@ -76,8 +76,8 @@ } code { - margin-bottom: var(--spacing-vertical-medium); - padding: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); + padding: var(--spacing-medium); background-color: $lbry-gray-1; color: $lbry-gray-5; @@ -93,7 +93,7 @@ // Lists ul, ol { - margin-bottom: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); > li { list-style-position: outside; @@ -105,7 +105,7 @@ } li { - margin-left: var(--spacing-vertical-large); + margin-left: var(--spacing-large); p { display: inline-block; diff --git a/src/ui/scss/component/_media.scss b/src/ui/scss/component/_media.scss index 6cb38251a20..b00389e8a6e 100644 --- a/src/ui/scss/component/_media.scss +++ b/src/ui/scss/component/_media.scss @@ -6,80 +6,7 @@ border-radius: var(--card-radius); .media__title { - margin-bottom: var(--spacing-vertical-small); - } - - .media__properties { - height: 1em; - } -} - -// M E D I A -// T I L E - -.media-tile { - @include mediaThumbHoverZoom; - display: flex; - font-size: 1.5rem; - position: relative; - - &:not(:last-of-type) { - margin-bottom: var(--spacing-vertical-large); - } - - .media__thumb { - flex-shrink: 0; - width: 20rem; - } - - .media__info { - margin-left: var(--spacing-vertical-medium); - } -} - -.media-tile--large { - font-size: 2rem; - - .media__thumb { - width: 25rem; - } - - .media__info { - margin-left: var(--spacing-vertical-large); - } - - .media__subtext { - font-size: 0.7em; - } -} - -.media-tile--small { - font-size: 1.2rem; - - &:not(:last-of-type) { - margin-bottom: var(--spacing-vertical-medium); - } - - .media__thumb { - width: 11em; - } - - .media__title { - margin-bottom: var(--spacing-vertical-small); - } - - .media__subtext:last-child { - margin-bottom: 0; - } - - .media__properties { - bottom: 0.5rem; - left: calc(-100% - -2rem); - position: absolute; - padding: 0 var(--spacing-vertical-small); - border-radius: 5px; - background-color: $lbry-white; - color: $lbry-black; + margin-bottom: var(--spacing-small); } } @@ -118,6 +45,13 @@ background-image: linear-gradient(to bottom right, $lbry-teal-3, $lbry-grape-5 100%); } +.media__thumb--profile { + // height: var(--channel-thumbnail-width); + width: 70px; + height: 70px; + background-size: cover; +} + // M E D I A // T I T L E @@ -132,7 +66,7 @@ display: inline; font-size: 2rem; line-height: 1.33; - margin-right: var(--spacing-vertical-small); + margin-right: var(--spacing-small); } .media__uri { @@ -140,6 +74,11 @@ padding-bottom: 5px; opacity: 0.6; user-select: all; + height: 2rem; + // position: absolute; + // top: 2rem; + // transform: translateY(-2rem); + // margin-bottom: -50px; } .media__insufficient-credits { @@ -152,8 +91,8 @@ .media__actions { display: flex; flex-wrap: wrap; - margin-top: var(--spacing-vertical-small); - margin-bottom: var(--spacing-vertical-small); + margin-top: var(--spacing-small); + margin-bottom: var(--spacing-small); } .media__actions--between { @@ -162,26 +101,26 @@ .media__action-group { > *:not(:last-child) { - margin-right: var(--spacing-vertical-medium); + margin-right: var(--spacing-medium); } } .media__action-group--large { display: flex; align-items: flex-end; - margin-top: var(--spacing-vertical-small); - margin-bottom: var(--spacing-vertical-small); + margin-top: var(--spacing-small); + margin-bottom: var(--spacing-small); > * { font-size: 1.15rem; &:not(:last-child) { - margin-right: var(--spacing-vertical-large); + margin-right: var(--spacing-large); } } &:not(:last-child) { - margin-right: var(--spacing-vertical-large); + margin-right: var(--spacing-large); } } @@ -189,7 +128,7 @@ // C O N T E N T .media__content--large { - padding-right: var(--spacing-vertical-large); + padding-right: var(--spacing-large); } // M E D I A @@ -202,7 +141,7 @@ font-size: 0.9em; &:not(:last-child) { - margin-bottom: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); } html[data-mode='dark'] & { @@ -251,7 +190,7 @@ .media__info--large { border-top: 1px solid $lbry-gray-1; - padding-top: var(--spacing-vertical-medium); + padding-top: var(--spacing-medium); html[data-mode='dark'] & { border-color: rgba($lbry-gray-5, 0.2); @@ -263,7 +202,7 @@ word-break: break-word; &:not(:last-of-type) { - margin-bottom: var(--spacing-vertical-large); + margin-bottom: var(--spacing-large); } &.media__info-text--small { @@ -278,7 +217,7 @@ .media__info-title { font-size: 1.25rem; font-weight: 500; - margin-bottom: var(--spacing-vertical-small); + margin-bottom: var(--spacing-small); } // M E D I A @@ -286,49 +225,11 @@ .media__message { font-size: 1.1rem; - padding: var(--spacing-vertical-medium); - margin: var(--spacing-vertical-medium) var(--spacing-vertical-large); + padding: var(--spacing-medium); + margin: var(--spacing-medium) var(--spacing-large); white-space: normal; } -// M E D I A -// P R O P E R T I E S - -.media__properties { - display: flex; - align-items: center; - - &:not(:empty) { - margin-top: var(--spacing-vertical-small); - } - - > *:not(:last-child) { - margin-right: var(--spacing-vertical-small); - } -} - -.media__properties--large { - display: inline-block; - vertical-align: top; - - &:not(:empty) { - height: 2.55rem; - margin-top: 0px; - - margin-bottom: var(--spacing-vertical-small); - padding-top: var(--spacing-vertical-small); - } - - .badge { - align-items: center; - position: relative; - - > *:not(:last-child) { - margin-right: var(--spacing-vertical-small); - } - } -} - // M E D I A // T E X T @@ -339,199 +240,3 @@ color: $lbry-white; } } - -// M E D I A -// G R O U P - -.media-group--list { - .media-card { - display: inline-block; - margin-bottom: var(--spacing-vertical-large); - vertical-align: top; - width: 100%; - } - - .media__thumb { - margin-bottom: var(--spacing-vertical-medium); - } -} - -.media-group--list-recommended { - border-left: 1px solid $lbry-gray-1; - min-height: 50vh; - overflow: hidden; - width: 30rem; - padding-left: var(--spacing-vertical-large); - - html[data-mode='dark'] & { - border-color: rgba($lbry-gray-5, 0.2); - } - - > span { - border-bottom: 1px solid $lbry-gray-1; - display: block; - font-size: 1.25rem; - font-weight: 500; - justify-content: space-between; - margin-bottom: var(--spacing-vertical-large); - padding-top: var(--spacing-vertical-small); - padding-bottom: var(--spacing-vertical-medium); - - html[data-mode='dark'] & { - border-color: rgba($lbry-gray-5, 0.2); - } - } -} - -.media-group--row { - padding-top: var(--spacing-vertical-large); - white-space: nowrap; - width: 100%; - - html[data-mode='dark'] & { - color: mix($lbry-white, $lbry-gray-3, 50%); - } - - &:first-of-type { - background-image: linear-gradient(to bottom right, $lbry-black, $lbry-cyan-5 80%); - color: $lbry-white; - - html[data-mode='dark'] & { - background-image: linear-gradient( - to bottom right, - transparent, - rgba(mix($lbry-blue-3, $lbry-black, 70%), 0.8) 100% - ); - } - .media-group__header-title { - background-image: linear-gradient(to right, $lbry-white 80%, transparent 100%); - } - - .channel-info__actions { - color: $lbry-white; - } - - .media__subtitle { - color: mix($lbry-cyan-5, $lbry-white, 20%); - } - } - - &:not(:first-of-type):not(:last-of-type) { - border-bottom: 1px solid rgba($lbry-gray-1, 0.7); - - html[data-mode='dark'] & { - border-color: rgba($lbry-gray-5, 0.2); - } - } - - .media-group__header { - flex-direction: row; - padding-left: var(--spacing-vertical-large); - } - - .media-group__header-title { - // color: transparent; // TODO: Add this back for lbryweb, it helps with long titles but was causing issues. A better solution is needed. - Sean - // !exist unprefixed, needs SVG for LBRY web - -webkit-background-clip: text; - background-image: linear-gradient(to right, $lbry-white 80%, transparent 100%); - - html[data-mode='dark'] & { - background-image: linear-gradient(to right, mix($lbry-white, $lbry-gray-3, 50%) 80%, transparent 100%); - } - } - - .media__thumb { - margin-bottom: var(--spacing-vertical-medium); - } -} - -// M E D I A G R O U P -// H E A D E R - -.media-group__header { - display: flex; - font-weight: 700; - justify-content: space-between; -} - -.media-group__header-navigation { - display: flex; - padding-right: var(--spacing-vertical-large); - - button:not(:last-of-type) { - margin-right: var(--spacing-vertical-medium); - } - - svg { - display: block; - } -} - -.media-group__header-title { - display: flex; - align-items: flex-end; - font-weight: 700; - font-size: 2rem; - padding-bottom: var(--spacing-vertical-small); - - & > *:not(:first-child) { - font-weight: normal; - margin-left: var(--spacing-vertical-medium); - } -} - -// M E D I A -// S C R O L L H O U S E - -.media-scrollhouse { - min-height: 200px; - padding-bottom: var(--spacing-vertical-large); - - // Show the scroll bar on hover - // `overlay` doesn't take up any vertical space - overflow-x: hidden; - overflow-y: hidden; - &:hover { - overflow-x: overlay; - } - - // The media queries in this block ensure that a row of - // content and its' child elements look good at certain breakpoints - - @media (min-width: 601px) { - padding-top: var(--spacing-vertical-small); - } - - @media (max-width: 600px) { - padding-top: var(--spacing-vertical-medium); - } - - // Handles the margin on the right side of the last item - // Overflow: hidden makes doesn't care if the margin is off the screen, just the content - // Using padding shrinks the last item - &::after { - content: ''; - display: inline-block; - width: var(--spacing-vertical-large); - height: 1px; - } - - .media-card { - cursor: pointer; - display: inline-block; - max-width: 250px; - min-width: 150px; - overflow-y: visible; - vertical-align: top; - - @media (min-width: 601px) { - width: calc((100% / 6) - 2.25rem); - margin-left: var(--spacing-vertical-large); - } - } -} - -.badge--alert { - background-color: $lbry-red-2; - color: $lbry-white; -} diff --git a/src/ui/scss/component/_modal.scss b/src/ui/scss/component/_modal.scss index c971123b07e..41b0b772dcc 100644 --- a/src/ui/scss/component/_modal.scss +++ b/src/ui/scss/component/_modal.scss @@ -5,7 +5,7 @@ right: 0; align-items: center; - background-color: rgba($lbry-white, 0.7); + background-color: rgba($lbry-black, 0.7); display: flex; justify-content: center; position: fixed; @@ -22,7 +22,7 @@ line-height: 1.55; max-width: var(--modal-width); overflow: auto; - padding: var(--spacing-vertical-large); + padding: var(--spacing-large); word-break: break-word; @media (min-width: 501px) { @@ -35,7 +35,7 @@ } .card__content:not(:last-child) { - margin-bottom: var(--spacing-vertical-large); + margin-bottom: var(--spacing-large); } .button.button--alt { @@ -47,50 +47,13 @@ } } -.modal--fullscreen { - top: 0; - left: 0; - bottom: 0; - right: 0; - - background-color: rgba($lbry-gray-1, 0.5); - padding: var(--spacing-vertical-large); - overflow-y: scroll; - position: absolute; - - .main { - // I will come back to these when I do media queries - // They should be variables - padding: 130px 80px 0 80px; - } -} - -// For slide down animation on the search modal -// Slide down isn"t possible without doing a lot of re-work to the modal component -.ReactModal__Overlay { - .modal--fullscreen { - height: 0; - transition: height var(--animation-duration) var(--animation-style); - } -} - -.ReactModal__Overlay--after-open { - .modal--fullscreen { - height: 100vh; - } -} - -.ReactModal__Overlay--before-close { - height: 0; -} - .modal__header { font-size: 2em; } .error-modal__content { display: flex; - padding: 0 var(--spacing-vertical-medium) var(--spacing-vertical-medium) var(--spacing-vertical-medium); + padding: 0 var(--spacing-medium) var(--spacing-medium) var(--spacing-medium); } .error-modal__warning-symbol { @@ -111,8 +74,8 @@ .error-modal__error-list { max-width: var(--modal-width); max-height: 400px; - margin-top: var(--spacing-vertical-small); - padding: var(--spacing-vertical-small); + margin-top: var(--spacing-small); + padding: var(--spacing-small); background-color: $lbry-red-1; border-left: 2px solid $lbry-red-5; diff --git a/src/ui/scss/component/_navigation.scss b/src/ui/scss/component/_navigation.scss index 1440578c9da..2335fa27ebe 100644 --- a/src/ui/scss/component/_navigation.scss +++ b/src/ui/scss/component/_navigation.scss @@ -1,126 +1,81 @@ +.navigation-wrapper { + width: var(--side-nav-width); + left: calc(100% - var(--side-nav-width)); + height: 100%; + position: absolute; +} + .navigation { position: fixed; - top: var(--header-height); - height: calc(100vh - var(--header-height)); - display: flex; - flex-direction: column; - overflow: visible; - background-color: $lbry-white; - border-right: 1px solid rgba($lbry-gray-1, 0.9); - padding-top: var(--spacing-vertical-large); - padding-right: var(--spacing-vertical-small); - font-size: 1.2rem; - z-index: 2; - - html[data-mode='dark'] & { - background-color: $lbry-black; - border-right: 1px solid $lbry-black; - } - - // The navigation does not need to be visible - // on smaller screen widths - - @media (min-width: 601px) { - width: var(--side-nav-width); - } + width: var(--side-nav-width); + font-size: 1.4rem; @media (max-width: 600px) { display: none; } - - &::before { - top: 0; - left: 0; - bottom: 0; - right: 0; - - // TODO: Make this gradient "to bottom" on mobile view - background-image: linear-gradient(to right, transparent, rgba(mix($lbry-blue-3, $lbry-gray-4, 15%), 0.2) 100%); - content: ''; - position: absolute; - - html[data-mode='dark'] & { - background-image: linear-gradient( - to bottom right, - transparent, - rgba(mix($lbry-blue-3, $lbry-gray-4, 15%), 0.2) 100% - ); - } - } } .navigation__links { position: relative; } -.navigation__links--bottom { - margin-top: auto; - margin-bottom: var(--spacing-vertical-large); +.navigation__links--small { + @extend .navigation__links; + font-size: 1.2rem; } .navigation__link { display: block; line-height: 1.75; - padding-left: var(--spacing-vertical-large); position: relative; text-align: left; transition: color 0.2s; + overflow: hidden; white-space: nowrap; - width: 100%; - color: $lbry-gray-5; + text-overflow: ellipsis; + color: lighten($lbry-black, 20%); - &::before { - top: 0; - left: 0; - width: 0; - background-color: $lbry-teal-3; - content: ''; - height: 100%; - position: absolute; - transition: width 0.2s; + &:hover { + color: $lbry-teal-4; + .icon { + color: $lbry-teal-4; + } } - &:not(.navigation__link--title):hover, &.navigation__link--active { - color: $lbry-black; - - html[data-mode='dark'] & { - color: $lbry-gray-1; - } - - &::before { - width: var(--tab-indicator-size); + color: $lbry-teal-5; + .icon { + color: $lbry-teal-4; } } - &.navigation__link--guide:not(:hover) { - color: rgba($lbry-black, 0.75); - - html[data-mode='dark'] & { - color: rgba($lbry-white, 0.75); + [data-mode='dark'] & { + color: $lbry-gray-1; + svg { + color: $lbry-gray-1; } - &::before { - animation: bounce 1.75s infinite; + &:hover, + &.navigation__link--active { + color: $lbry-teal-4; + .icon { + color: $lbry-teal-4; + } } } } .navigation__link--title { - margin-bottom: var(--spacing-vertical-small); - padding-top: var(--spacing-vertical-large); - + margin-top: var(--spacing-large); color: $lbry-gray-5; - cursor: default; font-size: 1.15rem; font-weight: 700; letter-spacing: 0.1rem; - text-transform: uppercase; } .navigation__link-items { font-size: 1.15rem; - padding-left: var(--spacing-vertical-large); + padding-left: var(--spacing-large); } .navigation__link-item { diff --git a/src/ui/scss/component/_notice.scss b/src/ui/scss/component/_notice.scss index c14b3b94a00..4291412958a 100644 --- a/src/ui/scss/component/_notice.scss +++ b/src/ui/scss/component/_notice.scss @@ -1,7 +1,7 @@ .notice { border: 1px solid; border-radius: 5px; - padding: var(--spacing-vertical-medium) var(--spacing-vertical-large); + padding: var(--spacing-medium) var(--spacing-large); text-shadow: 0 1px 0 rgba($lbry-white, 0.5); &:not(.notice--error) { diff --git a/src/ui/scss/component/_pagination.scss b/src/ui/scss/component/_pagination.scss index db9131a4cb8..4207849f196 100644 --- a/src/ui/scss/component/_pagination.scss +++ b/src/ui/scss/component/_pagination.scss @@ -3,7 +3,7 @@ align-items: center; + .form-field { - margin-left: var(--spacing-vertical-medium); + margin-left: var(--spacing-medium); } } diff --git a/src/ui/scss/component/_placeholder.scss b/src/ui/scss/component/_placeholder.scss index f5653060a00..02d6cca55b0 100644 --- a/src/ui/scss/component/_placeholder.scss +++ b/src/ui/scss/component/_placeholder.scss @@ -1,55 +1,23 @@ -.media-placeholder { - .placeholder { - @include placeholder; - } +.placeholder { + @include placeholder; } -.media-card.media-placeholder { - .media__title { - width: 100%; - } - - .media__thumb { - margin-bottom: var(--spacing-vertical-medium); - } - - .media__title { - height: 3.5rem; - } - - .media__channel { - width: 70%; - height: 0.6em; - margin-bottom: var(--spacing-vertical-small); - } - - .media__date { - height: 0.6em; - width: 30%; - margin-bottom: var(--spacing-vertical-medium); - } - - .media__properties { - height: 1.1em; - } +.placeholder__wrapper { + width: 100%; } -.media-tile--large.media-placeholder { - .media__title { - width: 60%; - height: 3rem; - } +.placeholder { + display: flex; - .media__channel { - width: 35%; - height: 1.75rem; - margin-top: var(--spacing-vertical-small); + &.file-list__item-title { + width: 100%; + height: 3rem; } - .media__subtitle { - margin-top: var(--spacing-vertical-large); + &.media__subtitle { + margin-top: var(--spacing-small); - width: 100%; - height: 10rem; + width: 30%; + height: 2em; } } diff --git a/src/ui/scss/component/_scrollbar.scss b/src/ui/scss/component/_scrollbar.scss deleted file mode 100644 index a554ae4b12e..00000000000 --- a/src/ui/scss/component/_scrollbar.scss +++ /dev/null @@ -1,27 +0,0 @@ -::-webkit-scrollbar { - width: 6px; - height: 5px; - - background-color: transparent; - overflow: auto; -} - -::-webkit-scrollbar-thumb { - background-color: $lbry-gray-3; - - html[data-mode='dark'] & { - background-color: $lbry-gray-5; - } -} - -::-webkit-scrollbar-thumb:active { - background-color: $lbry-teal-3; -} - -::-webkit-scrollbar-track { - background-color: transparent; - - html[data-mode='dark'] & { - background-color: $lbry-black; - } -} diff --git a/src/ui/scss/component/_search.scss b/src/ui/scss/component/_search.scss index 4aa9c500453..da797a31195 100644 --- a/src/ui/scss/component/_search.scss +++ b/src/ui/scss/component/_search.scss @@ -1,5 +1,5 @@ .search__header { - margin-bottom: var(--spacing-vertical-large); + margin-bottom: var(--spacing-large); .placeholder { background-color: rgba($lbry-white, 0.1); @@ -10,63 +10,24 @@ } } -.search__title { - font-size: 3em; - align-items: center; - cursor: default; - display: flex; - font-weight: 700; - line-height: 1.33; - margin-bottom: var(--spacing-vertical-large); - - > * { - margin-left: var(--spacing-vertical-small); - } -} - -.search__results-section { - margin-bottom: var(--spacing-vertical-large); - min-height: 200px; - position: relative; -} - -.search__options-wrapper { - font-size: 1.25em; - margin: var(--spacing-vertical-xlarge) 0; -} - .search__options { - margin-top: var(--spacing-vertical-large); + margin-top: var(--spacing-large); legend { &.search__legend--1 { - background-color: $lbry-teal-1; + background-color: $lbry-teal-4; } &.search__legend--2 { - background-color: $lbry-cyan-1; + background-color: $lbry-cyan-4; } &.search__legend--3 { - background-color: $lbry-pink-1; - } - - [data-mode='dark'] & { - &.search__legend--1 { - background-color: $lbry-teal-5; - } - - &.search__legend--2 { - background-color: $lbry-cyan-5; - } - - &.search__legend--3 { - background-color: $lbry-pink-5; - } + background-color: $lbry-pink-4; } } fieldset:not(:first-child) { - margin-top: var(--spacing-vertical-large); + margin-top: var(--spacing-large); } } diff --git a/src/ui/scss/component/_snack-bar.scss b/src/ui/scss/component/_snack-bar.scss index 3bdbd8c90e9..4d3e24136c9 100644 --- a/src/ui/scss/component/_snack-bar.scss +++ b/src/ui/scss/component/_snack-bar.scss @@ -5,8 +5,7 @@ background-color: $lbry-teal-4; border-radius: 0.5rem; color: $lbry-white; - padding: var(--spacing-vertical-small) var(--spacing-vertical-large) var(--spacing-vertical-small) - var(--spacing-vertical-medium); + padding: var(--spacing-small) var(--spacing-large) var(--spacing-small) var(--spacing-medium); position: fixed; transition: all var(--transition-duration) var(--transition-type); z-index: 10000; // hack to get it over react modal @@ -18,7 +17,7 @@ .snack-bar__action { display: inline-block; - margin: var(--spacing-vertical-small) 0; + margin: var(--spacing-small) 0; min-width: min-content; text-transform: uppercase; @@ -35,7 +34,7 @@ div { &:nth-of-type(1) { font-size: 1.5rem; - margin-right: var(--spacing-vertical-medium); + margin-right: var(--spacing-medium); } &:nth-of-type(2) { diff --git a/src/ui/scss/component/_spinner.scss b/src/ui/scss/component/_spinner.scss index f2a16408626..66717e3330d 100644 --- a/src/ui/scss/component/_spinner.scss +++ b/src/ui/scss/component/_spinner.scss @@ -3,7 +3,7 @@ height: 40px; font-size: 10px; - margin: var(--spacing-vertical-small); + margin: var(--spacing-small); text-align: center; .rect { @@ -45,22 +45,15 @@ } .spinner--small { - width: 40px; - height: 32px; - - font-size: 10px; - margin: var(--spacing-vertical-small) 0; - text-align: center; + height: 10px; .rect { width: 3px; - height: 100%; - margin: 0 2px; } } .spinner--splash { - margin-top: var(--spacing-vertical-large); + margin-top: var(--spacing-large); .rect { background-color: $lbry-white; diff --git a/src/ui/scss/component/_splash.scss b/src/ui/scss/component/_splash.scss index 8a581aa84a5..ccbed184381 100644 --- a/src/ui/scss/component/_splash.scss +++ b/src/ui/scss/component/_splash.scss @@ -10,7 +10,7 @@ } // .splash__actions { -// margin-top: var(--spacing-vertical-medium); +// margin-top: var(--spacing-medium); // align-items: center; // } diff --git a/src/ui/scss/component/_table.scss b/src/ui/scss/component/_table.scss index d97492ed4f8..07d8ca9f6e5 100644 --- a/src/ui/scss/component/_table.scss +++ b/src/ui/scss/component/_table.scss @@ -40,11 +40,9 @@ table, td:nth-of-type(3) { // Only add ellipsis to the links in the table // We still want to show the entire message if a TX includes one - .button__content { - @include constrict(10rem); - vertical-align: bottom; - display: inline-block; - } + @include constrict(10rem); + vertical-align: bottom; + display: inline-block; } } @@ -69,6 +67,6 @@ table, .table--invites { svg { margin-bottom: -2px; - margin-left: var(--spacing-vertical-small); + margin-left: var(--spacing-small); } } diff --git a/src/ui/scss/component/_tags.scss b/src/ui/scss/component/_tags.scss new file mode 100644 index 00000000000..23c9cc81705 --- /dev/null +++ b/src/ui/scss/component/_tags.scss @@ -0,0 +1,94 @@ +$main: $lbry-teal-5; + +.tags { + display: flex; + flex-wrap: wrap; + min-width: 0; + + .tag { + margin-top: var(--spacing-small); + margin-right: var(--spacing-small); + } +} + +.tags, +.tags--remove, +.tags--add { + .button__label { + display: flex; + align-items: center; + } +} + +.tags--remove { + @extend .tags; + margin-bottom: var(--spacing-large); + font-size: 18px; +} + +.tags--vertical { + @extend .tags; + flex-direction: column; + align-items: flex-start; +} + +.tags--selected { + @extend .tags; + margin: var(--spacing-large) 0; +} + +.tags__empty-message { + margin-top: var(--spacing-medium); +} + +.tag { + @extend .badge--tag; + user-select: none; + cursor: pointer; + display: flex; + align-items: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-transform: lowercase; + font-size: 0.7em; + max-width: 10rem; + + &:hover:not(:disabled) { + background-color: $main; + color: $lbry-white; + } + + &:active, + &:focus { + background-color: $main; + } +} + +.tag--remove { + @extend .tag; + font-size: 1em !important; + max-width: 20rem; +} + +.tag--add { + @extend .tag; + background-color: rgba($main, 0.05); + border: 1px solid rgba($main, 0.3); + border-radius: 1rem; + transition: all var(--animation-duration) var(--animation-style); + + &:hover { + border-radius: 3px; + } +} + +.tag__action-label { + border-left: 1px solid rgba($lbry-black, 0.1); + margin-left: 0.5rem; + padding-left: 0.5rem; + + html[data-mode='dark'] & { + border-color: rgba($lbry-white, 0.1); + } +} diff --git a/src/ui/scss/component/_tooltip.scss b/src/ui/scss/component/_tooltip.scss index 87651252a22..7ae09e53597 100644 --- a/src/ui/scss/component/_tooltip.scss +++ b/src/ui/scss/component/_tooltip.scss @@ -16,7 +16,7 @@ font-size: 1rem; color: $lbry-black; font-weight: 400; - padding: var(--spacing-vertical-miniscule); + padding: var(--spacing-miniscule); position: absolute; text-align: center; white-space: pre-wrap; diff --git a/src/ui/scss/component/_wunderbar.scss b/src/ui/scss/component/_wunderbar.scss index a4f633325c7..574a3b524ae 100644 --- a/src/ui/scss/component/_wunderbar.scss +++ b/src/ui/scss/component/_wunderbar.scss @@ -1,28 +1,24 @@ .wunderbar { min-width: 175px; height: 100%; - cursor: text; display: flex; + align-items: center; flex: 1; position: relative; z-index: 1; + margin-right: var(--spacing-large); - html[data-mode='dark'] & { - border-color: $lbry-gray-5; + @media (max-width: 600px) { + margin-right: 0; } > .icon { top: 0; - left: var(--spacing-vertical-small); - + left: var(--spacing-small); height: 100%; position: absolute; z-index: 1; - - html[data-mode='dark'] & { - color: $lbry-gray-5; - } } } @@ -38,31 +34,31 @@ .wunderbar__input { width: 100%; - height: 100%; - + height: var(--button-height); + border-radius: var(--button-radius); + background-color: $lbry-gray-1; align-items: center; - border-right: 1px solid $lbry-gray-1; - border-left: 1px solid $lbry-gray-1; - border-bottom: none; - border-top: none; + border: none; display: flex; justify-content: center; - padding-left: var(--spacing-vertical-large); - transition: background-color 0.2s; + padding-left: 2.5rem; + transition: all 0.2s; + + &:focus { + border-radius: var(--input-border-radius); + } + + &::placeholder { + color: $lbry-black; + opacity: 0.9; + } html[data-mode='dark'] & { - border-right: 1px solid; - border-left: 1px solid; color: $lbry-white; + background-color: darken($lbry-gray-5, 20%); - &:not(:focus) { - border-color: $lbry-gray-5; - } - - &:focus { - background-color: $lbry-gray-1; - border-color: $lbry-gray-1; - color: $lbry-black; + &::placeholder { + color: $lbry-gray-1; } } } @@ -85,7 +81,7 @@ display: flex; flex-direction: row; justify-items: flex-start; - padding: var(--spacing-vertical-small) var(--spacing-vertical-medium); + padding: var(--spacing-small) var(--spacing-medium); &:not(:first-of-type) { border-top: 1px solid transparent; @@ -98,7 +94,7 @@ .wunderbar__suggestion-label { overflow: hidden; - padding-left: var(--spacing-vertical-large); + padding-left: var(--spacing-large); text-overflow: ellipsis; white-space: nowrap; } @@ -107,7 +103,7 @@ font-size: 1rem; font-weight: 600; line-height: 0.1; // to vertically align because the font size is smaller - margin-left: var(--spacing-vertical-small); + margin-left: var(--spacing-small); opacity: 0.7; text-transform: uppercase; white-space: nowrap; diff --git a/src/ui/scss/component/_yrbl.scss b/src/ui/scss/component/_yrbl.scss index 6e9b1df4e40..c03f0640cb1 100644 --- a/src/ui/scss/component/_yrbl.scss +++ b/src/ui/scss/component/_yrbl.scss @@ -4,12 +4,12 @@ justify-content: center; vertical-align: middle; text-align: left; - margin-bottom: var(--spacing-vertical-xlarge); + margin-bottom: var(--spacing-xlarge); } .yrbl { height: 20rem; - margin-right: var(--spacing-vertical-large); + margin-right: var(--spacing-large); } .yrbl__content { @@ -20,7 +20,7 @@ align-self: center; height: 250px; width: auto; - margin: 0 var(--spacing-vertical-large); + margin: 0 var(--spacing-large); } .yrbl--search { diff --git a/src/ui/scss/component/tabs.scss b/src/ui/scss/component/tabs.scss index 5f83fbb348b..acbb8c3616d 100644 --- a/src/ui/scss/component/tabs.scss +++ b/src/ui/scss/component/tabs.scss @@ -7,7 +7,7 @@ align-items: center; background-color: $lbry-black; color: $lbry-white; - padding: var(--spacing-vertical-medium) var(--spacing-main-padding); + padding: var(--spacing-medium) var(--spacing-main-padding); & > *:not(.tab) { // If there is anything after the tabs, render it on the opposite side of the page @@ -20,11 +20,12 @@ } .tabs__list--channel-page { - padding-left: calc(var(--channel-thumbnail-size) + var(--spacing-main-padding) + var(--spacing-vertical-large)); + padding-left: calc(var(--channel-thumbnail-width) + var(--spacing-large)); + padding-right: var(--spacing-medium); } .tab { - margin-right: var(--spacing-vertical-large); + margin-right: var(--spacing-large); padding: 5px 0; font-weight: 700; font-size: var(--tab-header-size); @@ -56,7 +57,3 @@ height: var(--tab-indicator-size); background-color: $lbry-teal-3; } - -.tab__panel { - margin-top: var(--spacing-vertical-large); -} diff --git a/src/ui/scss/init/_gui.scss b/src/ui/scss/init/_gui.scss index af510b55623..2d7638d16de 100644 --- a/src/ui/scss/init/_gui.scss +++ b/src/ui/scss/init/_gui.scss @@ -25,6 +25,7 @@ body { overflow: hidden; html[data-mode='dark'] & { + background-color: $lbry-black; color: mix($lbry-white, $lbry-gray-3, 50%); } } @@ -132,8 +133,8 @@ code { color: $lbry-gray-5; display: block; padding: 1rem; - margin-top: var(--spacing-vertical-medium); - margin-bottom: var(--spacing-vertical-medium); + margin-top: var(--spacing-medium); + margin-bottom: var(--spacing-medium); border-radius: 5px; html[data-mode='dark'] & { diff --git a/src/ui/scss/init/_mixins.scss b/src/ui/scss/init/_mixins.scss index b68e28acc17..d70404d53a8 100644 --- a/src/ui/scss/init/_mixins.scss +++ b/src/ui/scss/init/_mixins.scss @@ -2,10 +2,6 @@ animation: pulse 2s infinite ease-in-out; background-color: $lbry-gray-2; border-radius: var(--card-radius); - - html[data-mode='dark'] & { - background-color: rgba($lbry-white, 0.1); - } } @mixin mediaThumbHoverZoom { diff --git a/src/ui/scss/init/_vars.scss b/src/ui/scss/init/_vars.scss index 1bdebafabab..eff778e53d0 100644 --- a/src/ui/scss/init/_vars.scss +++ b/src/ui/scss/init/_vars.scss @@ -7,20 +7,21 @@ $large-breakpoint: 1921px; :root { // Width & spacing - --side-nav-width: 180px; + --page-max-width: 1200px; + --mobile: 600px; + --side-nav-width: 170px; --font-size-subtext-multiple: 0.92; - --spacing-vertical-miniscule: calc(2rem / 5); - --spacing-vertical-tiny: calc(2rem / 4); - --spacing-vertical-small: calc(2rem / 3); - --spacing-vertical-medium: calc(2rem / 2); - --spacing-vertical-large: 2rem; - --spacing-vertical-xlarge: 3rem; - --spacing-main-padding: var(--spacing-vertical-xlarge); + --spacing-miniscule: calc(2rem / 5); + --spacing-tiny: calc(2rem / 4); + --spacing-small: calc(2rem / 3); + --spacing-medium: calc(2rem / 2); + --spacing-large: 2rem; + --spacing-xlarge: 3rem; + --spacing-main-padding: var(--spacing-xlarge); --file-page-max-width: 1787px; --file-max-height: 788px; --file-max-width: 1400px; --video-aspect-ratio: 56.25%; // 9 x 16 - --channel-thumbnail-size: 10rem; // Color --color-background: #270f34; @@ -45,27 +46,13 @@ $large-breakpoint: 1921px; --button-height: 2.6rem; // Header - --header-height: 3.5rem; - - // Header -> search - --search-modal-input-height: 70px; + --header-height: 5rem; // Card --card-radius: 5px; --card-max-width: 1000px; --card-box-shadow: 0px 0px 30px 2px; - // File - --file-tile-media-height: 125px; - --file-tile-media-width: calc(125px * (16 / 9)); - --file-tile-media-height-small: 60px; - --file-tile-media-width-small: calc(60px * (16 / 9)); - --file-tile-media-height-large: 200px; - --file-tile-media-width-large: calc(200px * (16 / 9)); - --file-page-min-width: 400px; - --recommended-content-width: 300px; - --recommended-content-width-medium: 400px; - // Modal --modal-width: 440px; @@ -77,4 +64,6 @@ $large-breakpoint: 1921px; --thumbnail-preview-height: 100px; --thumbnail-preview-width: 177px; --cover-photo-height: 300px; + --channel-thumbnail-width: 10rem; + --file-list-thumbnail-width: 10rem; } diff --git a/src/ui/store.js b/src/ui/store.js index 7ed7801e5fd..1825afe9bdc 100644 --- a/src/ui/store.js +++ b/src/ui/store.js @@ -5,7 +5,7 @@ import localForage from 'localforage'; import { createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; import { createHashHistory, createBrowserHistory } from 'history'; -import { connectRouter, routerMiddleware } from 'connected-react-router'; +import { routerMiddleware } from 'connected-react-router'; import createRootReducer from './reducers'; function isFunction(object) { @@ -86,6 +86,7 @@ const persistOptions = { 'subscriptions', 'app', 'search', + 'tags', ], // Order is important. Needs to be compressed last or other transforms can't // read the data @@ -99,7 +100,7 @@ const persistOptions = { searchFilter, compressor, ], - debounce: IS_WEB ? 5000 : 10000, + debounce: 5000, storage: localForage, }; diff --git a/src/ui/util/enhanced-layout.js b/src/ui/util/enhanced-layout.js index 20830684a64..08029c76bc0 100644 --- a/src/ui/util/enhanced-layout.js +++ b/src/ui/util/enhanced-layout.js @@ -1,3 +1,20 @@ +import { useEffect, useState } from 'react'; + +export default function useKonamiListener() { + const [isActive, setIsActive] = useState(false); + useEffect(() => { + let listener; + if (!listener) { + listener = new Konami(() => setIsActive(!isActive)); + } + return () => { + listener = null; + }; + }, [isActive]); + + return isActive; +} + /* eslint-disable */ /* * Konami-JS ~ @@ -9,7 +26,6 @@ * Licensed under the MIT License (http://opensource.org/licenses/MIT) * Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1+ and Android */ - var Konami = function(callback) { var konami = { addEvent: function(obj, type, fn, ref_obj) { @@ -137,16 +153,4 @@ var Konami = function(callback) { return konami; }; - -if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { - module.exports = Konami; -} else { - if (typeof define === 'function' && define.amd) { - define([], function() { - return Konami; - }); - } else { - window.Konami = Konami; - } -} /* eslint-enable */ diff --git a/src/ui/util/reorder-array.js b/src/ui/util/reorder-array.js new file mode 100644 index 00000000000..07a1ef8653f --- /dev/null +++ b/src/ui/util/reorder-array.js @@ -0,0 +1,10 @@ +// @flow +// https://github.com/lodash/lodash/issues/1701 + +export default function reorder(list: Array, startIndex: number, endIndex: number) { + const result = Array.from(list); + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + + return result; +} diff --git a/src/ui/util/shuffleArray.js b/src/ui/util/shuffle-array.js similarity index 100% rename from src/ui/util/shuffleArray.js rename to src/ui/util/shuffle-array.js diff --git a/src/ui/util/use-persisted-state.js b/src/ui/util/use-persisted-state.js new file mode 100644 index 00000000000..6e67db732ae --- /dev/null +++ b/src/ui/util/use-persisted-state.js @@ -0,0 +1,23 @@ +import { useState, useEffect } from 'react'; + +export default function usePersistedState(key, firstTimeDefault) { + // If no key is passed in, act as a normal `useState` + let defaultValue; + if (key) { + defaultValue = localStorage.getItem(key); + } + + if (!defaultValue) { + defaultValue = firstTimeDefault; + } + + const [value, setValue] = useState(defaultValue); + + useEffect(() => { + if (key) { + localStorage.setItem(key, value); + } + }, [key, value]); + + return [value, setValue]; +} diff --git a/static/index.dev.html b/static/index.dev.html index 88166d44b7f..79111bb34ca 100644 --- a/static/index.dev.html +++ b/static/index.dev.html @@ -2,7 +2,8 @@ - lbry.tv + LBRY Dev + diff --git a/static/index.html b/static/index.html index 4b01c9183f5..d61e7ba15a0 100644 --- a/static/index.html +++ b/static/index.html @@ -12,6 +12,7 @@ + diff --git a/yarn.lock b/yarn.lock index fc556c0eb47..201cbea2b34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -256,7 +256,7 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5": +"@babel/parser@^7.0.0", "@babel/parser@^7.4.0", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5": version "7.4.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872" integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew== @@ -762,14 +762,30 @@ pirates "^4.0.0" source-map-support "^0.5.9" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.3": - version "7.4.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12" - integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1": + version "7.4.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.3.tgz#79888e452034223ad9609187a0ad1fe0d2ad4bdc" + integrity sha512-9lsJwJLxDh/T3Q3SZszfWOTkk3pHbkmH+3KY+zwIDmsNlxsumuhS2TH3NIpktU4kNvfzy+k3eLT7aTJSPTo0OA== dependencies: regenerator-runtime "^0.13.2" -"@babel/template@^7.1.0", "@babel/template@^7.4.4": +"@babel/runtime@^7.4.3": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.4.tgz#dc2e34982eb236803aa27a07fea6857af1b9171d" + integrity sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg== + dependencies: + regenerator-runtime "^0.13.2" + +"@babel/template@^7.1.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.0.tgz#12474e9c077bae585c5d835a95c0b0b790c25c8b" + integrity sha512-SOWwxxClTTh5NdbbYZ0BmaBVzxzTh2tO/TeLTbF6MO6EzVhHTnff8CdBXx3mEtazFBoysmEM6GU/wF+SuSx4Fw== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.4.0" + "@babel/types" "^7.4.0" + +"@babel/template@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" integrity sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw== @@ -802,7 +818,7 @@ lodash "^4.2.0" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.4": +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0" integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ== @@ -846,10 +862,10 @@ resolved "https://registry.yarnpkg.com/@lbry/color/-/color-1.1.1.tgz#b80ec25fb515d436129332b20c767c5a7014ac09" integrity sha512-BdxqWmm84WYOd1ZsPruJiGr7WBEophVfJKg7oywFOAMb0h6ERe4Idx1ceweMSvCOyPlR5GAhil9Gvk70SBFdxQ== -"@lbry/components@^2.7.0": - version "2.7.1" - resolved "https://registry.yarnpkg.com/@lbry/components/-/components-2.7.1.tgz#883b5a452b47f9aa7ee4e594855c6b52283110bf" - integrity sha512-iDg1kDuY4L0pOiwbQUv8yC8EEO76xHgzeuZqfX2W/MWLHUZXsxmtKDbUBk+aHAeoLr93CETJYdCbeXQMbIhGpw== +"@lbry/components@^2.7.2": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@lbry/components/-/components-2.7.2.tgz#a941ced2adf78ab52026f5533e5f6b3454f862a9" + integrity sha512-TSBmxc4i2E3v7qGC7LgdcrUUcpiUYBA1KMVSW5vrpDJroNy3fnr3/niGJTudVQ4LQzlOXpt7bsRATFh8vRmXnQ== "@mapbox/hast-util-table-cell-style@^0.1.3": version "0.1.3" @@ -6572,9 +6588,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4: yargs "^13.2.2" zstd-codec "^0.1.1" -lbry-redux@lbryio/lbry-redux#02f6918238110726c0b3b4248c61a84ac0b969e3: +lbry-redux@lbryio/lbry-redux#7ed316ba48f16246e4d3b99467fa5c3ffdeffa52: version "0.0.1" - resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/02f6918238110726c0b3b4248c61a84ac0b969e3" + resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/7ed316ba48f16246e4d3b99467fa5c3ffdeffa52" dependencies: proxy-polyfill "0.1.6" reselect "^3.0.0" @@ -9496,6 +9512,14 @@ react-simplemde-editor@^4.0.0: "@types/codemirror" "^0.0.65" easymde "^2.6.0" +react-spring@^8.0.20: + version "8.0.20" + resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.20.tgz#e25967f6059364b09cf0339168d73014e87c9d17" + integrity sha512-40ZUQ5uI5YHsoQWLPchWNcEUh6zQ6qvcVDeTI2vW10ldoCN3PvDsII9wBH2xEbMl+BQvYmHzGdfLTQxPxJWGnQ== + dependencies: + "@babel/runtime" "^7.3.1" + prop-types "^15.5.8" + react-toggle@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.0.2.tgz#77f487860efb87fafd197672a2db8c885be1440f"