From 60543562aaee86903dcb34e212e33b6c7f95cf5f Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Tue, 11 Jun 2019 14:10:58 -0400 Subject: [PATCH 01/11] tags --- package.json | 7 +- src/platforms/electron/createWindow.js | 13 - src/platforms/web/electron.js | 8 +- src/platforms/web/stubs.js | 1 - src/ui/component/app/index.js | 8 +- src/ui/component/app/view.jsx | 80 ++-- 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/fileCard/view.jsx | 154 -------- src/ui/component/fileList/index.js | 9 +- src/ui/component/fileList/view.jsx | 213 +++-------- src/ui/component/fileListDiscover/index.js | 18 + src/ui/component/fileListDiscover/view.jsx | 139 +++++++ .../{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/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 | 158 ++++---- .../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 | 123 +++--- 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 | 35 ++ 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 | 63 ++++ .../component/transactionListRecent/view.jsx | 2 +- src/ui/component/uriIndicator/view.jsx | 51 +-- src/ui/component/userEmailNew/view.jsx | 1 - src/ui/component/walletBalance/view.jsx | 6 +- 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 | 9 +- src/ui/modal/modalAffirmPurchase/view.jsx | 2 +- .../modal/modalAutoGenerateThumbnail/view.jsx | 2 - src/ui/page/account/view.jsx | 2 + src/ui/page/channel/view.jsx | 64 ++-- src/ui/page/discover/index.js | 17 +- src/ui/page/discover/view.jsx | 86 +---- src/ui/page/file/view.jsx | 39 +- src/ui/page/fileListDownloaded/index.js | 11 +- src/ui/page/fileListDownloaded/view.jsx | 57 ++- 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 | 130 ++----- src/ui/page/tags/index.js | 14 + src/ui/page/tags/view.jsx | 28 ++ src/ui/page/tagsEdit/index.js | 14 + 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 | 166 ++++----- src/ui/redux/reducers/app.js | 13 - src/ui/redux/reducers/settings.js | 4 +- src/ui/redux/reducers/subscriptions.js | 8 - src/ui/redux/selectors/app.js | 15 - 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 | 88 ++--- src/ui/scss/component/_icon.scss | 9 +- src/ui/scss/component/_item-list.scss | 12 +- src/ui/scss/component/_main.scss | 82 ++-- 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 | 117 ++---- src/ui/scss/component/_notice.scss | 2 +- src/ui/scss/component/_pagination.scss | 2 +- src/ui/scss/component/_placeholder.scss | 60 +-- 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 +- .../{shuffleArray.js => shuffle-array.js} | 0 src/ui/util/use-persisted-state.js | 23 ++ static/index.dev.html | 3 +- static/index.html | 1 + webpack.web.config.js | 3 +- yarn.lock | 50 ++- 140 files changed, 2059 insertions(+), 3305 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 create mode 100644 src/ui/component/fileListDiscover/index.js create mode 100644 src/ui/component/fileListDiscover/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/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/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 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 04b67d80b..24ca3ab60 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#08ed1be3905896452536c92f17997bcde4533aea", "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.4", + "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 90fd4df29..532194cfb 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/platforms/web/electron.js b/src/platforms/web/electron.js index 7988d4971..4c64809ee 100644 --- a/src/platforms/web/electron.js +++ b/src/platforms/web/electron.js @@ -1,11 +1,7 @@ export const clipboard = () => { - throw 'Fix me!'; + throw new Error('Fix me!'); }; export const ipcRenderer = () => { - throw 'Fix me!'; -}; - -export const remote = () => { - throw 'Fix me!'; + throw new Error('Fix me!'); }; diff --git a/src/platforms/web/stubs.js b/src/platforms/web/stubs.js index fc2a22f96..2de68b7ac 100644 --- a/src/platforms/web/stubs.js +++ b/src/platforms/web/stubs.js @@ -1,7 +1,6 @@ const callable = () => { throw Error('Need to fix this stub'); }; -const returningCallable = value => () => value; export const remote = { dialog: { diff --git a/src/ui/component/app/index.js b/src/ui/component/app/index.js index 767e4a32d..e0c6274dc 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 e9b8a9b71..c59f0054e 100644 --- a/src/ui/component/app/view.jsx +++ b/src/ui/component/app/view.jsx @@ -1,82 +1,54 @@ // @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, language: string, theme: string, - updateBlockHeight: () => void, - toggleEnhancedLayout: () => void, - enhancedLayout: boolean, + fetchRewards: () => void, + fetchRewardedContent: () => void, }; -class App extends React.PureComponent { - componentDidMount() { - const { updateBlockHeight, toggleEnhancedLayout, 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); + }, [theme]); - ReactModal.setAppElement('#window'); // fuck this + return ( +
openContextMenu(e)}> +
- this.enhance = new EnhancedLayoutListener(() => toggleEnhancedLayout()); - - updateBlockHeight(); - setInterval(() => { - updateBlockHeight(); - }, TWO_POINT_FIVE_MINUTES); - } - - componentDidUpdate(prevProps: Props) { - const { theme: prevTheme } = prevProps; - const { theme } = this.props; - - if (prevTheme !== theme) { - // $FlowFixMe - document.documentElement.setAttribute('data-mode', theme); - } - } - - 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 2f616d603..4c62c3aab 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 45480912b..000000000 --- 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 95a5cf471..000000000 --- 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 5965c896c..c7eb78825 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 3f129b45a..a78865815 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 d52df8292..fe648b004 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 d395a9f85..000000000 --- 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 ef57bbbaa..000000000 --- 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 24b85595d..5af75e353 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 0d4b65ca5..e867618fc 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/fileCard/view.jsx b/src/ui/component/fileCard/view.jsx deleted file mode 100644 index a2d4e88d7..000000000 --- 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 b6c075cf9..f73f0fcd6 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 bbe1c7700..c358807d2 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, + headerAltControls: 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, - }; +export default function FileList(props: Props) { + const { uris, header, headerAltControls, 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; - 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; - }), - }; + function handleSortChange() { + setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW); } - getChannelSignature = (fileInfo: { pending: boolean } & FileListItem) => { - if (fileInfo.pending) { - return undefined; - } - - return fileInfo.channel_claim_id; - }; - - handleSortChanged(event: SyntheticInputEvent<*>) { - this.props.setFileListSort(this.props.page, event.target.value); - } - - 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 && } +
    {headerAltControls}
    +
    + )} + {hasUris && ( +
      + {sortedUris.map((uri, index) => ( + + + {index === 4 && injectedItem &&
    • {injectedItem}
    • } +
      + ))} +
    + )} + {!hasUris && !loading && ( +
    {empty ||

    {__('No results')}

    }
    + )} + + ); } - -export default FileList; diff --git a/src/ui/component/fileListDiscover/index.js b/src/ui/component/fileListDiscover/index.js new file mode 100644 index 000000000..d84f83cfc --- /dev/null +++ b/src/ui/component/fileListDiscover/index.js @@ -0,0 +1,18 @@ +import { connect } from 'react-redux'; +import { selectFollowedTags, doClaimSearch, selectLastClaimSearchUris, selectFetchingClaimSearch } from 'lbry-redux'; +import FileListDiscover from './view'; + +const select = state => ({ + followedTags: selectFollowedTags(state), + uris: selectLastClaimSearchUris(state), + loading: selectFetchingClaimSearch(state), +}); + +const perform = { + doClaimSearch, +}; + +export default connect( + select, + perform +)(FileListDiscover); diff --git a/src/ui/component/fileListDiscover/view.jsx b/src/ui/component/fileListDiscover/view.jsx new file mode 100644 index 000000000..b203ba69b --- /dev/null +++ b/src/ui/component/fileListDiscover/view.jsx @@ -0,0 +1,139 @@ +// @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 TIME_DAY = 'day'; +const TIME_WEEK = 'week'; +const TIME_MONTH = 'month'; +const TIME_YEAR = 'year'; +const TIME_ALL = 'all'; +const TRENDING_SORT_YOU = 'you'; +const TRENDING_SORT_ALL = 'everyone'; +const TYPE_TRENDING = 'trending'; +const TYPE_TOP = 'top'; +const TYPE_NEW = 'new'; +const TRENDING_FILTER_TYPES = [TRENDING_SORT_YOU, TRENDING_SORT_ALL]; +const TRENDING_TYPES = ['trending', 'top', 'new']; +const TRENDING_TIMES = [TIME_DAY, TIME_WEEK, TIME_MONTH, TIME_YEAR, TIME_ALL]; + +type Props = { + uris: Array, + doClaimSearch: (number, {}) => void, + injectedItem: any, + tags: Array, + loading: boolean, + personal: boolean, +}; + +function FileListDiscover(props: Props) { + const { doClaimSearch, uris, tags, loading, personal, injectedItem } = props; + const [personalSort, setPersonalSort] = usePersistedState('file-list-trending:personalSort', TRENDING_SORT_YOU); + const [typeSort, setTypeSort] = usePersistedState('file-list-trending:typeSort', TYPE_TRENDING); + const [timeSort, setTimeSort] = usePersistedState('file-list-trending:timeSort', TIME_WEEK); + + const toCapitalCase = string => string.charAt(0).toUpperCase() + string.slice(1); + const tagsString = tags.join(','); + useEffect(() => { + const options = {}; + const newTags = tagsString.split(','); + + if (personalSort === TRENDING_SORT_YOU) { + options.any_tags = newTags; + } + + if (typeSort === TYPE_TRENDING) { + options.order_by = ['trending_global', 'trending_mixed']; + } else if (typeSort === TYPE_NEW) { + options.order_by = ['release_time']; + } else if (typeSort === TYPE_TOP) { + options.order_by = ['effective_amount']; + if (timeSort !== TIME_ALL) { + const time = Math.floor( + moment() + .subtract(1, timeSort) + .unix() + ); + options.release_time = `>${time}`; + } + } + + doClaimSearch(20, options); + }, [personalSort, typeSort, timeSort, doClaimSearch, tagsString]); + + const header = ( + +

    + {toCapitalCase(typeSort)} {'For'} +

    + {!personal && tags && tags.length ? ( + tags.map(tag => ( + + {tag} + + )) + ) : ( + setPersonalSort(e.target.value)} + > + {TRENDING_FILTER_TYPES.map(type => ( + + ))} + + )} +
    + ); + + const headerAltControls = ( + + setTypeSort(e.target.value)} + > + {TRENDING_TYPES.map(type => ( + + ))} + + {typeSort === 'top' && ( + setTimeSort(e.target.value)} + > + {TRENDING_TIMES.map(time => ( + + ))} + + )} + + ); + + return ( + + ); +} + +export default FileListDiscover; 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 86cc6200d..4b6352234 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 000000000..3aff7f9e7 --- /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'; + const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0; + + function handleContextMenu(e) { + e.preventDefault(); + e.stopPropagation(); + if (claim) { + 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 32b04d220..000000000 --- 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 9f1974bc4..000000000 --- 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/fileProperties/index.js b/src/ui/component/fileProperties/index.js new file mode 100644 index 000000000..1d6180a04 --- /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 000000000..bf8c1a400 --- /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 000000000..bc63f15d7 --- /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 000000000..969e3d1c0 --- /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 63004d6d7..000000000 --- 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 4d2a0486d..000000000 --- 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 4f8fa525c..45dedef07 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 cf2941fb1..92e5c2831 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 d26a858c5..78f0b692c 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 47186d69e..cc3d95324 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 72927063b..b9dce2952 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')} + ); + } else { + return null; } - - const inner = {name}; - - if (!channelLink) { - return inner; - } - - return ( - - ); } } diff --git a/src/ui/component/userEmailNew/view.jsx b/src/ui/component/userEmailNew/view.jsx index 99cda26e9..57976e85b 100644 --- a/src/ui/component/userEmailNew/view.jsx +++ b/src/ui/component/userEmailNew/view.jsx @@ -72,7 +72,6 @@ class UserEmailNew extends React.PureComponent { />

    {cancelButton}
    -

    {__('Your email address will never be sold and you can unsubscribe at any time.')}

    ); } diff --git a/src/ui/component/walletBalance/view.jsx b/src/ui/component/walletBalance/view.jsx index f01213fd8..7411959ca 100644 --- a/src/ui/component/walletBalance/view.jsx +++ b/src/ui/component/walletBalance/view.jsx @@ -19,7 +19,11 @@ const WalletBalance = (props: Props) => {

    {__('You currently have')}

    - {(balance || balance === 0) && } + {(balance || balance === 0) && ( + + + + )}
    ); diff --git a/src/ui/constants/action_types.js b/src/ui/constants/action_types.js index 379b37b50..993f8c2cd 100644 --- a/src/ui/constants/action_types.js +++ b/src/ui/constants/action_types.js @@ -18,7 +18,6 @@ export const SHOW_MODAL = 'SHOW_MODAL'; export const HIDE_MODAL = 'HIDE_MODAL'; export const CHANGE_MODALS_ALLOWED = 'CHANGE_MODALS_ALLOWED'; export const TOGGLE_SEARCH_EXPANDED = 'TOGGLE_SEARCH_EXPANDED'; -export const ENNNHHHAAANNNCEEE = 'ENNNHHHAAANNNCEEE'; // Navigation export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH'; @@ -193,8 +192,6 @@ export const SET_VIEW_MODE = 'SET_VIEW_MODE'; export const GET_SUGGESTED_SUBSCRIPTIONS_START = 'GET_SUGGESTED_SUBSCRIPTIONS_START'; export const GET_SUGGESTED_SUBSCRIPTIONS_SUCCESS = 'GET_SUGGESTED_SUBSCRIPTIONS_SUCCESS'; export const GET_SUGGESTED_SUBSCRIPTIONS_FAIL = 'GET_SUGGESTED_SUBSCRIPTIONS_FAIL'; -export const SUBSCRIPTION_FIRST_RUN_COMPLETED = 'SUBSCRIPTION_FIRST_RUN_COMPLETED'; -export const VIEW_SUGGESTED_SUBSCRIPTIONS = 'VIEW_SUGGESTED_SUBSCRIPTIONS'; // Publishing export const CLEAR_PUBLISH = 'CLEAR_PUBLISH'; diff --git a/src/ui/constants/icons.js b/src/ui/constants/icons.js index febeb855e..c591b840c 100644 --- a/src/ui/constants/icons.js +++ b/src/ui/constants/icons.js @@ -13,6 +13,7 @@ export const DOWNLOAD = 'Download'; export const UPLOAD = 'UploadCloud'; export const PUBLISHED = 'Cloud'; export const CLOSE = 'X'; +export const ADD = 'Plus'; export const EDIT = 'Edit3'; export const DELETE = 'Trash'; export const REPORT = 'Flag'; diff --git a/src/ui/constants/pages.js b/src/ui/constants/pages.js index 693783258..06c119859 100644 --- a/src/ui/constants/pages.js +++ b/src/ui/constants/pages.js @@ -4,7 +4,7 @@ export const CHANNEL = 'channel'; export const DISCOVER = 'discover'; export const DOWNLOADED = 'downloaded'; export const HELP = 'help'; -export const HISTORY = 'history'; +export const LIBRARY = 'library'; export const INVITE = 'invite'; export const PUBLISH = 'publish'; export const PUBLISHED = 'published'; @@ -18,3 +18,4 @@ export const ACCOUNT = 'account'; export const SUBSCRIPTIONS = 'subscriptions'; export const SEARCH = 'search'; export const TRANSACTIONS = 'transactions'; +export const TAGS = 'tags'; diff --git a/src/ui/constants/settings.js b/src/ui/constants/settings.js index a00c97a9b..a29721171 100644 --- a/src/ui/constants/settings.js +++ b/src/ui/constants/settings.js @@ -3,7 +3,6 @@ export const CREDIT_REQUIRED_ACKNOWLEDGED = 'credit_required_acknowledged'; export const NEW_USER_ACKNOWLEDGED = 'welcome_acknowledged'; export const EMAIL_COLLECTION_ACKNOWLEDGED = 'email_collection_acknowledged'; -export const FIRST_RUN_COMPLETED = 'first_run_completed'; export const INVITE_ACKNOWLEDGED = 'invite_acknowledged'; export const LANGUAGE = 'language'; export const SHOW_NSFW = 'showNsfw'; diff --git a/src/ui/modal/modal.jsx b/src/ui/modal/modal.jsx index 20536b1ad..02c3333c1 100644 --- a/src/ui/modal/modal.jsx +++ b/src/ui/modal/modal.jsx @@ -3,7 +3,6 @@ import * as React from 'react'; import ReactModal from 'react-modal'; import Button from 'component/button'; -import app from 'app'; import classnames from 'classnames'; type ModalProps = { @@ -20,7 +19,6 @@ type ModalProps = { extraContent?: React.Node, expandButtonLabel?: string, hideButtonLabel?: string, - fullScreen: boolean, title?: string | React.Node, }; @@ -32,7 +30,6 @@ export class Modal extends React.PureComponent { abortButtonLabel: __('Cancel'), confirmButtonDisabled: false, abortButtonDisabled: false, - fullScreen: false, }; render() { @@ -45,7 +42,6 @@ export class Modal extends React.PureComponent { abortButtonLabel, abortButtonDisabled, onAborted, - fullScreen, className, title, ...modalProps @@ -54,10 +50,7 @@ export class Modal extends React.PureComponent { {title && ( diff --git a/src/ui/modal/modalAffirmPurchase/view.jsx b/src/ui/modal/modalAffirmPurchase/view.jsx index 1f7083e1a..fe0a6217c 100644 --- a/src/ui/modal/modalAffirmPurchase/view.jsx +++ b/src/ui/modal/modalAffirmPurchase/view.jsx @@ -40,7 +40,7 @@ class ModalAffirmPurchase extends React.PureComponent { onAborted={cancelPurchase} >
    -

    +

    {__('This will purchase')} {title ? `"${title}"` : uri} {__('for')}{' '} diff --git a/src/ui/modal/modalAutoGenerateThumbnail/view.jsx b/src/ui/modal/modalAutoGenerateThumbnail/view.jsx index 0bff3d631..b8e847177 100644 --- a/src/ui/modal/modalAutoGenerateThumbnail/view.jsx +++ b/src/ui/modal/modalAutoGenerateThumbnail/view.jsx @@ -50,8 +50,6 @@ function ModalAutoGenerateThumbnail(props: Props) { return; } - console.log('resized'); - const fixedWidth = 450; const videoWidth = player.videoWidth; const videoHeight = player.videoHeight; diff --git a/src/ui/page/account/view.jsx b/src/ui/page/account/view.jsx index 0212333d4..4e6a3b8a0 100644 --- a/src/ui/page/account/view.jsx +++ b/src/ui/page/account/view.jsx @@ -16,6 +16,8 @@ const WalletPage = () => ( + + diff --git a/src/ui/page/channel/view.jsx b/src/ui/page/channel/view.jsx index 8cc4ca472..a70dac2c8 100644 --- a/src/ui/page/channel/view.jsx +++ b/src/ui/page/channel/view.jsx @@ -49,42 +49,44 @@ function ChannelPage(props: Props) { }; return ( - -

    - {cover && } + +
    +
    + {cover && } -
    - +
    + -
    -

    {title || channelName}

    -

    - {claimName} - {claimId && `#${claimId}`} -

    +
    +

    {title || channelName}

    +

    + {claimName} + {claimId && `#${claimId}`} +

    +
    -
    -
    +
    - - - {__('Content')} - {__('About')} -
    - - -
    -
    + + + {__('Content')} + {__('About')} +
    + + +
    +
    - - - - - - - - -
    + + + + + + + + +
    + ); } diff --git a/src/ui/page/discover/index.js b/src/ui/page/discover/index.js index 3b59c6e4a..b946452dd 100644 --- a/src/ui/page/discover/index.js +++ b/src/ui/page/discover/index.js @@ -1,23 +1,12 @@ import { connect } from 'react-redux'; -import { - doFetchRewardedContent, - doRewardList, - selectFeaturedUris, - doFetchFeaturedUris, - selectFetchingFeaturedUris, -} from 'lbryinc'; +import { selectFollowedTags } from 'lbry-redux'; import DiscoverPage from './view'; const select = state => ({ - featuredUris: selectFeaturedUris(state), - fetchingFeaturedUris: selectFetchingFeaturedUris(state), + followedTags: selectFollowedTags(state), }); -const perform = dispatch => ({ - fetchFeaturedUris: () => dispatch(doFetchFeaturedUris()), - fetchRewardedContent: () => dispatch(doFetchRewardedContent()), - fetchRewards: () => dispatch(doRewardList()), -}); +const perform = {}; export default connect( select, diff --git a/src/ui/page/discover/view.jsx b/src/ui/page/discover/view.jsx index f9ecc8648..85747a63a 100644 --- a/src/ui/page/discover/view.jsx +++ b/src/ui/page/discover/view.jsx @@ -1,82 +1,24 @@ // @flow import React from 'react'; +import FileListDiscover from 'component/fileListDiscover'; +import TagsSelect from 'component/tagsSelect'; import Page from 'component/page'; -import CategoryList from 'component/categoryList'; -import FirstRun from 'component/firstRun'; type Props = { - fetchFeaturedUris: () => void, - fetchRewardedContent: () => void, - fetchRewards: () => void, - fetchingFeaturedUris: boolean, - featuredUris: {}, + followedTags: Array, }; -class DiscoverPage extends React.PureComponent { - constructor() { - super(); - this.continousFetch = undefined; - } - - componentDidMount() { - const { fetchFeaturedUris, fetchRewardedContent, fetchRewards } = this.props; - fetchFeaturedUris(); - fetchRewardedContent(); - - this.continousFetch = setInterval(() => { - fetchFeaturedUris(); - fetchRewardedContent(); - fetchRewards(); - }, 1000 * 60 * 60); - } - - componentWillUnmount() { - this.clearContinuousFetch(); - } - - getCategoryLinkPartByCategory(category: string) { - const channelName = category.substr(category.indexOf('@')); - if (!channelName.includes('#')) { - return null; - } - return channelName; - } - - trimClaimIdFromCategory(category: string) { - return category.split('#')[0]; - } - - continousFetch: ?IntervalID; - - clearContinuousFetch() { - if (this.continousFetch) { - clearInterval(this.continousFetch); - this.continousFetch = null; - } - } - - render() { - const { featuredUris, fetchingFeaturedUris } = this.props; - const hasContent = typeof featuredUris === 'object' && Object.keys(featuredUris).length; - const failedToLoad = !fetchingFeaturedUris && !hasContent; - - return ( - - - {hasContent && - Object.keys(featuredUris).map(category => ( - - ))} - {failedToLoad &&
    {__('Failed to load landing content.')}
    } -
    - ); - } +function DiscoverPage(props: Props) { + const { followedTags } = props; + return ( + + tag.name)} + injectedItem={} + /> + + ); } export default DiscoverPage; diff --git a/src/ui/page/file/view.jsx b/src/ui/page/file/view.jsx index 9bc270c08..3a7c9febe 100644 --- a/src/ui/page/file/view.jsx +++ b/src/ui/page/file/view.jsx @@ -108,15 +108,15 @@ class FilePage extends React.Component { fetchViewCount(claim.claim_id); } + if (prevProps.uri !== uri) { + setViewed(uri); + } + // @if TARGET='app' if (fileInfo === undefined) { fetchFileInfo(uri); } // @endif - - if (prevProps.uri !== uri) { - setViewed(uri); - } } removeFromSubscriptionNotifications() { @@ -148,7 +148,8 @@ class FilePage extends React.Component { } = this.props; // File info - const { channel_name: channelName } = claim; + const { signing_channel: signingChannel } = claim; + const channelName = signingChannel && signingChannel.name; const { PLAYABLE_MEDIA_TYPES, PREVIEW_MEDIA_TYPES } = FilePage; const isRewardContent = (rewardedContentClaimIds || []).includes(claim.claim_id); const shouldObscureThumbnail = obscureNsfw && nsfw; @@ -179,23 +180,12 @@ class FilePage extends React.Component { const insufficientCredits = !claimIsMine && costInfo && costInfo.cost > balance; return ( - +
    -
    ))} +
    - - )} - - ); - } + + + + )} + + ); } export default FileListDownloaded; diff --git a/src/ui/page/fileListPublished/index.js b/src/ui/page/fileListPublished/index.js index cde0c9af5..4664d9932 100644 --- a/src/ui/page/fileListPublished/index.js +++ b/src/ui/page/fileListPublished/index.js @@ -1,12 +1,11 @@ import { connect } from 'react-redux'; -import { selectIsFetchingClaimListMine, selectFileListPublishedSort, selectMyClaimsWithoutChannels } from 'lbry-redux'; +import { selectIsFetchingClaimListMine, selectMyClaimsWithoutChannels } from 'lbry-redux'; import { doCheckPendingPublishes } from 'redux/actions/publish'; import FileListPublished from './view'; const select = state => ({ claims: selectMyClaimsWithoutChannels(state), fetching: selectIsFetchingClaimListMine(state), - sortBy: selectFileListPublishedSort(state), }); const perform = dispatch => ({ diff --git a/src/ui/page/fileListPublished/view.jsx b/src/ui/page/fileListPublished/view.jsx index e72971574..0a3302f30 100644 --- a/src/ui/page/fileListPublished/view.jsx +++ b/src/ui/page/fileListPublished/view.jsx @@ -1,47 +1,49 @@ // @flow -import React from 'react'; +import React, { useEffect } from 'react'; import Button from 'component/button'; import FileList from 'component/fileList'; import Page from 'component/page'; -import { PAGES } from 'lbry-redux'; type Props = { claims: Array, checkPendingPublishes: () => void, fetching: boolean, - sortBy: string, }; -class FileListPublished extends React.PureComponent { - componentDidMount() { - const { checkPendingPublishes } = this.props; +function FileListPublished(props: Props) { + const { checkPendingPublishes, fetching, claims } = props; + + useEffect(() => { checkPendingPublishes(); - } + }, [checkPendingPublishes]); - render() { - const { fetching, claims, sortBy } = this.props; - return ( - - {claims && claims.length ? ( - - ) : ( -
    -
    -
    -

    {__("It looks like you haven't published anything to LBRY yet.")}

    -
    + return ( + + {claims && claims.length ? ( +
    + `lbry://${info.name}#${info.claim_id}`)} + /> +
    + ) : ( +
    +
    +
    +

    {__("It looks like you haven't published anything to LBRY yet.")}

    +
    -
    -
    -
    +
    +
    +
    -
    -
    - )} -
    - ); - } +
    + + + )} +
    + ); } export default FileListPublished; diff --git a/src/ui/page/search/index.js b/src/ui/page/search/index.js index 400eb1cf1..1abe41438 100644 --- a/src/ui/page/search/index.js +++ b/src/ui/page/search/index.js @@ -1,14 +1,34 @@ import { connect } from 'react-redux'; -import { doSearch, selectIsSearching } from 'lbry-redux'; +import { doSearch, selectIsSearching, makeSelectSearchUris, makeSelectQueryWithOptions, doToast } from 'lbry-redux'; +import analytics from 'analytics'; import SearchPage from './view'; const select = state => ({ isSearching: selectIsSearching(state), + uris: makeSelectSearchUris(makeSelectQueryWithOptions()(state))(state), }); -const perform = { - doSearch, -}; +const perform = dispatch => ({ + doSearch: query => doSearch(query), + onFeedbackPositive: query => { + analytics.apiSearchFeedback(query, 1); + dispatch( + doToast({ + message: __('Thanks for the feedback! You help make the app better for everyone.'), + }) + ); + }, + onFeedbackNegative: query => { + analytics.apiSearchFeedback(query, 0); + dispatch( + doToast({ + message: __( + 'Thanks for the feedback. Mark has been notified and is currently walking over to his computer to work on this.' + ), + }) + ); + }, +}); export default connect( select, diff --git a/src/ui/page/search/view.jsx b/src/ui/page/search/view.jsx index 081a6ddfa..07c68016c 100644 --- a/src/ui/page/search/view.jsx +++ b/src/ui/page/search/view.jsx @@ -1,18 +1,27 @@ // @flow +import * as ICONS from 'constants/icons'; import React, { useEffect, Fragment } from 'react'; -import { isURIValid, normalizeURI, parseURI } from 'lbry-redux'; -import FileTile from 'component/fileTile'; -import ChannelTile from 'component/channelTile'; -import FileListSearch from 'component/fileListSearch'; +import { isURIValid, normalizeURI } from 'lbry-redux'; +import FileListItem from 'component/fileListItem'; +import FileList from 'component/fileList'; import Page from 'component/page'; import SearchOptions from 'component/searchOptions'; import Button from 'component/button'; -type Props = { doSearch: string => void, location: UrlLocation }; +type Props = { + doSearch: string => void, + location: UrlLocation, + uris: Array, + onFeedbackNegative: string => void, + onFeedbackPositive: string => void, +}; export default function SearchPage(props: Props) { const { doSearch, + uris, + onFeedbackPositive, + onFeedbackNegative, location: { search }, } = props; const urlParams = new URLSearchParams(search); @@ -20,10 +29,8 @@ export default function SearchPage(props: Props) { const isValid = isURIValid(urlQuery); let uri; - let isChannel; if (isValid) { uri = normalizeURI(urlQuery); - ({ isChannel } = parseURI(uri)); } useEffect(() => { @@ -42,19 +49,34 @@ export default function SearchPage(props: Props) { - {isChannel ? ( - - ) : ( - - )} + )} -
    - - -
    {__('These search results are provided by LBRY, Inc.')}
    +
    + } + headerAltControls={ + + {__('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 cbab04033..411d172db 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 ccd8836f0..b03752238 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 130e11050..000000000 --- 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 77c78b904..000000000 --- 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 a33475d24..2828e9d5b 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(); + }, [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} - /> - ) : ( - - )} - - ); - } + return ( + +
    + {showSuggested ? __('Discover New Channels') : __('Latest From Your Subscriptions')}} + headerAltControls={ +
    +
    + ); } diff --git a/src/ui/page/tags/index.js b/src/ui/page/tags/index.js new file mode 100644 index 000000000..dd4d22847 --- /dev/null +++ b/src/ui/page/tags/index.js @@ -0,0 +1,14 @@ +import { connect } from 'react-redux'; +import { selectFollowedTags } from 'lbry-redux'; +import Tags from './view'; + +const select = state => ({ + followedTags: selectFollowedTags(state), +}); + +const perform = {}; + +export default connect( + select, + perform +)(Tags); diff --git a/src/ui/page/tags/view.jsx b/src/ui/page/tags/view.jsx new file mode 100644 index 000000000..931c1f485 --- /dev/null +++ b/src/ui/page/tags/view.jsx @@ -0,0 +1,28 @@ +// @flow +import React from 'react'; +import Page from 'component/page'; +import FileListDiscover from 'component/fileListDiscover'; + +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 000000000..7e9153c8e --- /dev/null +++ b/src/ui/page/tagsEdit/index.js @@ -0,0 +1,14 @@ +import { connect } from 'react-redux'; +import { selectFollowedTags } from 'lbry-redux'; +import TagsEdit from './view'; + +const select = state => ({ + followedTags: selectFollowedTags(state), +}); + +const perform = {}; + +export default connect( + select, + perform +)(TagsEdit); diff --git a/src/ui/page/tagsEdit/view.jsx b/src/ui/page/tagsEdit/view.jsx new file mode 100644 index 000000000..99bd051de --- /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 265d9ec8e..c9641f3a3 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 4be100b64..7be2c2f73 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 aa977c772..bc4b840db 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 3c61b5832..d49d8d317 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 -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; + // 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); + 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); - } + // 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 (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 c9c32447f..b6b073b06 100644 --- a/src/ui/redux/reducers/app.js +++ b/src/ui/redux/reducers/app.js @@ -2,14 +2,7 @@ import * as ACTIONS from 'constants/action_types'; import * as MODALS from 'constants/modal_types'; -// @if TARGET='app' -// $FlowFixMe import { remote } from 'electron'; -// @endif -// @if TARGET='web' -// $FlowFixMe -import { remote } from 'web/stubs'; -// @endif // @if TARGET='app' const win = remote.BrowserWindow.getFocusedWindow(); @@ -43,7 +36,6 @@ export type AppState = { isUpgradeAvailable: ?boolean, isUpgradeSkipped: ?boolean, hasClickedComment: boolean, - enhancedLayout: boolean, searchOptionsExpanded: boolean, }; @@ -228,11 +220,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 befe2ea30..6d05600d0 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 f49aaa4a7..e1efbf0eb 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 ab21b0b26..276c0496f 100644 --- a/src/ui/redux/selectors/app.js +++ b/src/ui/redux/selectors/app.js @@ -1,6 +1,4 @@ -import * as SETTINGS from 'constants/settings'; import { createSelector } from 'reselect'; -import { makeSelectClientSetting } from 'redux/selectors/settings'; export const selectState = state => state.app || {}; @@ -121,20 +119,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 2c3569a04..c1152c667 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 5088d6a1b..404f8dcf2 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 23d70124e..0435f5645 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 7b71ce173..000000000 --- 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 93c138a48..1c2cc57b8 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 ab177228c..c423f30f7 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 fcbadeaf1..39aefb157 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 7a2880fbe..470fde947 100644 --- a/src/ui/scss/component/_content.scss +++ b/src/ui/scss/component/_content.scss @@ -78,7 +78,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 70eb9b01c..ab82b45e2 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 000000000..01687aabd --- /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__alt-controls { + 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 000000000..49142fb89 --- /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 78917de16..dda85388b 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 c8ca791c3..02006ff25 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 ddac864aa..47f18e9de 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 863aea5ce..95d5e228b 100644 --- a/src/ui/scss/component/_header.scss +++ b/src/ui/scss/component/_header.scss @@ -1,39 +1,44 @@ .header { - position: fixed; - 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 + position: fixed; + top: 0; + width: 100%; 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-left: 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 { + padding-left: var(--spacing-small); + 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 +53,11 @@ } &:hover { - background-color: $lbry-gray-1; + color: $lbry-teal-5; + + svg { + stroke: $lbry-teal-5; + } } &.header__navigation-item--active { @@ -56,15 +65,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 +92,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 +108,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 +133,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 a7f3acd12..74bd2c7d0 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--Flag, +.icon--Home { + top: -2px; +} + .icon--Heart { top: -1px; } - -.icon--Flag { - top: -2px; -} diff --git a/src/ui/scss/component/_item-list.scss b/src/ui/scss/component/_item-list.scss index 5ed35d150..32eb24e35 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 9f38ce6e8..0c91d2726 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-wrapper-inner { + display: flex; + max-width: 1200px; + width: 100%; + margin-left: auto; + margin-right: auto; + margin-top: var(--spacing-main-padding); + position: relative; } .main { - display: flex; - flex-direction: column; - margin: auto; -} + min-width: 0; + width: calc(100% - var(--side-nav-width) - var(--spacing-main-padding)); + position: relative; -.main--contained { - max-width: 900px; - padding: var(--spacing-main-padding); -} - -.main--not-contained { - padding: var(--spacing-main-padding); -} - -.main--no-padding { - padding: 0; -} - -.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 43931720f..8b7e0ef51 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 f61671487..9d6dc1553 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 6cb38251a..b00389e8a 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 c971123b0..41b0b772d 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 1440578c9..2335fa27e 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; + color: $lbry-teal-5; + .icon { + color: $lbry-teal-4; + } + } - html[data-mode='dark'] & { + [data-mode='dark'] & { + color: $lbry-gray-1; + svg { color: $lbry-gray-1; } - &::before { - width: var(--tab-indicator-size); - } - } - - &.navigation__link--guide:not(:hover) { - color: rgba($lbry-black, 0.75); - - html[data-mode='dark'] & { - color: rgba($lbry-white, 0.75); - } - - &::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 c14b3b94a..429141295 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 db9131a4c..4207849f1 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 f5653060a..02d6cca55 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 { +.placeholder__wrapper { + width: 100%; +} + +.placeholder { + display: flex; + + &.file-list__item-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; - } -} - -.media-tile--large.media-placeholder { - .media__title { - width: 60%; height: 3rem; } - .media__channel { - width: 35%; - height: 1.75rem; - margin-top: var(--spacing-vertical-small); - } + &.media__subtitle { + margin-top: var(--spacing-small); - .media__subtitle { - margin-top: var(--spacing-vertical-large); - - 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 a554ae4b1..000000000 --- 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 4aa9c5004..da797a311 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 3bdbd8c90..4d3e24136 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 f2a164086..66717e333 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 8a581aa84..ccbed1843 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 d97492ed4..07d8ca9f6 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 000000000..23c9cc817 --- /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 87651252a..7ae09e535 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 a4f633325..574a3b524 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 6e9b1df4e..c03f0640c 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 5f83fbb34..acbb8c361 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 af510b556..2d7638d16 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 b68e28acc..d70404d53 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 1bdebafab..eff778e53 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 7ed7801e5..1825afe9b 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 20830684a..08029c76b 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/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 000000000..6e67db732 --- /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 88166d44b..79111bb34 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 4b01c9183..d61e7ba15 100644 --- a/static/index.html +++ b/static/index.html @@ -12,6 +12,7 @@ + diff --git a/webpack.web.config.js b/webpack.web.config.js index b0e89c0a7..4bf7bd1d5 100644 --- a/webpack.web.config.js +++ b/webpack.web.config.js @@ -38,8 +38,9 @@ const webConfig = { }, resolve: { modules: [path.resolve(__dirname, 'src/platforms/')], + alias: { - electron: path.resolve(__dirname, 'src/platforms/web/electron'), + electron: path.resolve(__dirname, 'src/platforms/web/stubs'), }, }, plugins: [ diff --git a/yarn.lock b/yarn.lock index fc556c0eb..cfedf93d5 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#08ed1be3905896452536c92f17997bcde4533aea: 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/08ed1be3905896452536c92f17997bcde4533aea" 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" -- 2.45.2 From 7baa73aff9a2ffc636bf3562399b9c95982a799b Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Tue, 11 Jun 2019 14:15:36 -0400 Subject: [PATCH 02/11] fix: claim search params --- src/ui/redux/actions/content.js | 8 +- src/ui/redux/actions/subscriptions.js | 136 +++++++++++++------------- 2 files changed, 77 insertions(+), 67 deletions(-) diff --git a/src/ui/redux/actions/content.js b/src/ui/redux/actions/content.js index bc4b840db..f971fb322 100644 --- a/src/ui/redux/actions/content.js +++ b/src/ui/redux/actions/content.js @@ -292,7 +292,13 @@ export function doFetchClaimsByChannel(uri: string, page: number = 1, pageSize: data: { uri, page }, }); - Lbry.claim_search({ channel: uri, is_controlling: true, page, page_size: pageSize }).then(result => { + Lbry.claim_search({ + channel: uri, + page, + page_size: pageSize, + valid_channel_signatures: true, + order_by: ['release_time'], + }).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 d49d8d317..8347de765 100644 --- a/src/ui/redux/actions/subscriptions.js +++ b/src/ui/redux/actions/subscriptions.js @@ -204,84 +204,88 @@ export const doCheckSubscription = (subscriptionUri: string, shouldNotify?: bool } // We may be duplicating calls here. Can this logic be baked into doFetchClaimsByChannel? - Lbry.claim_search({ channel: subscriptionUri, is_controlling: true, page: 1, page_size: PAGE_SIZE }).then( - claimListByChannel => { - const { items: claimsInChannel } = claimListByChannel; + Lbry.claim_search({ + channel: subscriptionUri, + valid_channel_signatures: true, + order_by: ['release_time'], + 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 -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; + // 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); + 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); - } + // 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 (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( - 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) + doUpdateUnreadSubscriptions( + subscriptionUri, + newUnread, + downloadCount > 0 ? NOTIFICATION_TYPES.DOWNLOADING : NOTIFICATION_TYPES.NOTIFY_ONLY ) ); - - // 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, - }, - }); } - ); + + // 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 + ), + }, + 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) => { -- 2.45.2 From 510307de015583be455814021f309bb8cfc98fa8 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Tue, 11 Jun 2019 14:22:10 -0400 Subject: [PATCH 03/11] update lbry-redux --- package.json | 2 +- src/ui/component/fileList/view.jsx | 4 +--- yarn.lock | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 24ca3ab60..4f263d675 100644 --- a/package.json +++ b/package.json @@ -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#08ed1be3905896452536c92f17997bcde4533aea", + "lbry-redux": "lbryio/lbry-redux#fceb9747e10f6f6622e34580baf12c0521616407", "lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845", "lint-staged": "^7.0.2", "localforage": "^1.7.1", diff --git a/src/ui/component/fileList/view.jsx b/src/ui/component/fileList/view.jsx index c358807d2..2676396f9 100644 --- a/src/ui/component/fileList/view.jsx +++ b/src/ui/component/fileList/view.jsx @@ -62,9 +62,7 @@ export default function FileList(props: Props) { ))} )} - {!hasUris && !loading && ( -
    {empty ||

    {__('No results')}

    }
    - )} + {!hasUris && !loading &&

    {empty || __('No results')}

    } ); } diff --git a/yarn.lock b/yarn.lock index cfedf93d5..69867f00a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6588,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#08ed1be3905896452536c92f17997bcde4533aea: +lbry-redux@lbryio/lbry-redux#fceb9747e10f6f6622e34580baf12c0521616407: version "0.0.1" - resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/08ed1be3905896452536c92f17997bcde4533aea" + resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/fceb9747e10f6f6622e34580baf12c0521616407" dependencies: proxy-polyfill "0.1.6" reselect "^3.0.0" -- 2.45.2 From 6e59e5b1b35767bf8a38365bd680a4eb872e114e Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Tue, 11 Jun 2019 14:38:08 -0400 Subject: [PATCH 04/11] don't persist by default for FileList --- src/ui/component/fileList/view.jsx | 2 +- src/ui/page/fileListPublished/index.js | 4 ++-- src/ui/page/fileListPublished/view.jsx | 14 +++++--------- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/ui/component/fileList/view.jsx b/src/ui/component/fileList/view.jsx index 2676396f9..6310f171a 100644 --- a/src/ui/component/fileList/view.jsx +++ b/src/ui/component/fileList/view.jsx @@ -24,7 +24,7 @@ type Props = { export default function FileList(props: Props) { const { uris, header, headerAltControls, injectedItem, loading, persistedStorageKey, noHeader, slim, empty } = props; - const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey || 'file-list-global-sort', SORT_NEW); + const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW); const sortedUris = uris && currentSort === SORT_OLD ? uris.reverse() : uris; const hasUris = uris && !!uris.length; diff --git a/src/ui/page/fileListPublished/index.js b/src/ui/page/fileListPublished/index.js index 4664d9932..a1bfceff6 100644 --- a/src/ui/page/fileListPublished/index.js +++ b/src/ui/page/fileListPublished/index.js @@ -1,10 +1,10 @@ import { connect } from 'react-redux'; -import { selectIsFetchingClaimListMine, selectMyClaimsWithoutChannels } from 'lbry-redux'; +import { selectIsFetchingClaimListMine, selectMyClaimUrisWithoutChannels } from 'lbry-redux'; import { doCheckPendingPublishes } from 'redux/actions/publish'; import FileListPublished from './view'; const select = state => ({ - claims: selectMyClaimsWithoutChannels(state), + uris: selectMyClaimUrisWithoutChannels(state), fetching: selectIsFetchingClaimListMine(state), }); diff --git a/src/ui/page/fileListPublished/view.jsx b/src/ui/page/fileListPublished/view.jsx index 0a3302f30..30ddc7f62 100644 --- a/src/ui/page/fileListPublished/view.jsx +++ b/src/ui/page/fileListPublished/view.jsx @@ -5,27 +5,23 @@ import FileList from 'component/fileList'; import Page from 'component/page'; type Props = { - claims: Array, + uris: Array, checkPendingPublishes: () => void, fetching: boolean, }; function FileListPublished(props: Props) { - const { checkPendingPublishes, fetching, claims } = props; + const { checkPendingPublishes, fetching, uris } = props; useEffect(() => { checkPendingPublishes(); }, [checkPendingPublishes]); return ( - - {claims && claims.length ? ( + + {uris && uris.length ? (
    - `lbry://${info.name}#${info.claim_id}`)} - /> +
    ) : (
    -- 2.45.2 From ef2fb47b3952377dacac2774e26c055c8ad58d79 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Tue, 11 Jun 2019 14:56:23 -0400 Subject: [PATCH 05/11] fix: tag params on tags page --- package.json | 2 +- src/ui/component/fileListDiscover/view.jsx | 22 +++++++++++----------- src/ui/scss/component/_channel.scss | 2 ++ yarn.lock | 4 ++-- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 4f263d675..6fed1a788 100644 --- a/package.json +++ b/package.json @@ -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#fceb9747e10f6f6622e34580baf12c0521616407", + "lbry-redux": "lbryio/lbry-redux#291324d03f694c4fefa6967aa7be02d9245596a8", "lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845", "lint-staged": "^7.0.2", "localforage": "^1.7.1", diff --git a/src/ui/component/fileListDiscover/view.jsx b/src/ui/component/fileListDiscover/view.jsx index b203ba69b..e09dc2162 100644 --- a/src/ui/component/fileListDiscover/view.jsx +++ b/src/ui/component/fileListDiscover/view.jsx @@ -10,14 +10,14 @@ const TIME_WEEK = 'week'; const TIME_MONTH = 'month'; const TIME_YEAR = 'year'; const TIME_ALL = 'all'; -const TRENDING_SORT_YOU = 'you'; -const TRENDING_SORT_ALL = 'everyone'; +const SEARCH_SORT_YOU = 'you'; +const SEARCH_SORT_ALL = 'everyone'; const TYPE_TRENDING = 'trending'; const TYPE_TOP = 'top'; const TYPE_NEW = 'new'; -const TRENDING_FILTER_TYPES = [TRENDING_SORT_YOU, TRENDING_SORT_ALL]; -const TRENDING_TYPES = ['trending', 'top', 'new']; -const TRENDING_TIMES = [TIME_DAY, TIME_WEEK, TIME_MONTH, TIME_YEAR, TIME_ALL]; +const SEARCH_FILTER_TYPES = [SEARCH_SORT_YOU, SEARCH_SORT_ALL]; +const SEARCH_TYPES = ['trending', 'top', 'new']; +const SEARCH_TIMES = [TIME_DAY, TIME_WEEK, TIME_MONTH, TIME_YEAR, TIME_ALL]; type Props = { uris: Array, @@ -30,7 +30,7 @@ type Props = { function FileListDiscover(props: Props) { const { doClaimSearch, uris, tags, loading, personal, injectedItem } = props; - const [personalSort, setPersonalSort] = usePersistedState('file-list-trending:personalSort', TRENDING_SORT_YOU); + const [personalSort, setPersonalSort] = usePersistedState('file-list-trending:personalSort', SEARCH_SORT_YOU); const [typeSort, setTypeSort] = usePersistedState('file-list-trending:typeSort', TYPE_TRENDING); const [timeSort, setTimeSort] = usePersistedState('file-list-trending:timeSort', TIME_WEEK); @@ -40,7 +40,7 @@ function FileListDiscover(props: Props) { const options = {}; const newTags = tagsString.split(','); - if (personalSort === TRENDING_SORT_YOU) { + if ((tags && !personal) || (tags && personal && personalSort === SEARCH_SORT_YOU)) { options.any_tags = newTags; } @@ -82,7 +82,7 @@ function FileListDiscover(props: Props) { value={personalSort} onChange={e => setPersonalSort(e.target.value)} > - {TRENDING_FILTER_TYPES.map(type => ( + {SEARCH_FILTER_TYPES.map(type => ( @@ -101,7 +101,7 @@ function FileListDiscover(props: Props) { value={typeSort} onChange={e => setTypeSort(e.target.value)} > - {TRENDING_TYPES.map(type => ( + {SEARCH_TYPES.map(type => ( @@ -115,7 +115,7 @@ function FileListDiscover(props: Props) { value={timeSort} onChange={e => setTimeSort(e.target.value)} > - {TRENDING_TIMES.map(time => ( + {SEARCH_TIMES.map(time => ( @@ -129,7 +129,7 @@ function FileListDiscover(props: Props) { diff --git a/src/ui/scss/component/_channel.scss b/src/ui/scss/component/_channel.scss index 39aefb157..2f5e7f5c0 100644 --- a/src/ui/scss/component/_channel.scss +++ b/src/ui/scss/component/_channel.scss @@ -38,6 +38,8 @@ $metadata-z-index: 1; height: var(--channel-thumbnail-width); width: var(--channel-thumbnail-width); box-shadow: 0px 8px 40px -3px $lbry-black; + // left: var(--spacing-medium); + // left: 0; left: var(--spacing-medium); } diff --git a/yarn.lock b/yarn.lock index 69867f00a..4763077c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6588,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#fceb9747e10f6f6622e34580baf12c0521616407: +lbry-redux@lbryio/lbry-redux#291324d03f694c4fefa6967aa7be02d9245596a8: version "0.0.1" - resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/fceb9747e10f6f6622e34580baf12c0521616407" + resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/291324d03f694c4fefa6967aa7be02d9245596a8" dependencies: proxy-polyfill "0.1.6" reselect "^3.0.0" -- 2.45.2 From b668a619860ab127d3ba19948b9fa7e2ba21bba9 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Tue, 11 Jun 2019 14:59:48 -0400 Subject: [PATCH 06/11] fix: channel profile aligment --- src/ui/component/channelThumbnail/view.jsx | 2 +- src/ui/scss/component/_channel.scss | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ui/component/channelThumbnail/view.jsx b/src/ui/component/channelThumbnail/view.jsx index fe648b004..df3c24142 100644 --- a/src/ui/component/channelThumbnail/view.jsx +++ b/src/ui/component/channelThumbnail/view.jsx @@ -25,7 +25,7 @@ function ChannelThumbnail(props: Props) { })} > {!thumbnail && } - {thumbnail && } + {thumbnail && }
    ); } diff --git a/src/ui/scss/component/_channel.scss b/src/ui/scss/component/_channel.scss index 2f5e7f5c0..39aefb157 100644 --- a/src/ui/scss/component/_channel.scss +++ b/src/ui/scss/component/_channel.scss @@ -38,8 +38,6 @@ $metadata-z-index: 1; height: var(--channel-thumbnail-width); width: var(--channel-thumbnail-width); box-shadow: 0px 8px 40px -3px $lbry-black; - // left: var(--spacing-medium); - // left: 0; left: var(--spacing-medium); } -- 2.45.2 From 4f044af317660bcf74aeb9acae5b14a5acefb5de Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Tue, 11 Jun 2019 15:02:13 -0400 Subject: [PATCH 07/11] fix: lbry icon spacing on web --- src/ui/scss/component/_header.scss | 3 ++- src/ui/scss/component/_wunderbar.scss | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ui/scss/component/_header.scss b/src/ui/scss/component/_header.scss index 95d5e228b..6eddd693f 100644 --- a/src/ui/scss/component/_header.scss +++ b/src/ui/scss/component/_header.scss @@ -38,7 +38,7 @@ .header__navigation-arrows { display: flex; - margin: 0 var(--spacing-small); + margin-right: var(--spacing-small); } .header__navigation-item { @@ -100,6 +100,7 @@ flex: 1; font-weight: 800; font-size: 1.2rem; + margin-right: var(--spacing-medium); .lbry-icon { height: 2rem; diff --git a/src/ui/scss/component/_wunderbar.scss b/src/ui/scss/component/_wunderbar.scss index 574a3b524..4f4a2a289 100644 --- a/src/ui/scss/component/_wunderbar.scss +++ b/src/ui/scss/component/_wunderbar.scss @@ -7,7 +7,7 @@ flex: 1; position: relative; z-index: 1; - margin-right: var(--spacing-large); + margin-right: var(--spacing-xlarge); @media (max-width: 600px) { margin-right: 0; -- 2.45.2 From 15378594cec7bad23e06ae7ea037da6b34f1beef Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Tue, 11 Jun 2019 15:35:43 -0400 Subject: [PATCH 08/11] fix: table style --- src/ui/scss/component/_table.scss | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/ui/scss/component/_table.scss b/src/ui/scss/component/_table.scss index 07d8ca9f6..f822edadb 100644 --- a/src/ui/scss/component/_table.scss +++ b/src/ui/scss/component/_table.scss @@ -11,15 +11,30 @@ table, } } +td { + max-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +tr { + padding: 0 var(--spacing-small); +} + .table__item--actionable { .button svg { top: 3px; } } +.table__item-label { + font-size: 0.9em; + font-weight: 300; +} + .table--help { td:nth-of-type(1) { - font-weight: 600; min-width: 130px; } @@ -30,20 +45,6 @@ table, .table--transactions { table-layout: fixed; - - td:nth-of-type(1) { - // TX amounts - font-size: 0.9em; - font-weight: 600; - } - - 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 - @include constrict(10rem); - vertical-align: bottom; - display: inline-block; - } } .table--rewards { -- 2.45.2 From dae603339b64a9ab8eec743fd097b0241908b329 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Mon, 17 Jun 2019 16:32:38 -0400 Subject: [PATCH 09/11] moar discovery --- package.json | 4 +- src/ui/component/button/view.jsx | 1 + src/ui/component/copyableText/view.jsx | 2 +- src/ui/component/fileList/view.jsx | 26 +- src/ui/component/fileListDiscover/index.js | 4 +- src/ui/component/fileListDiscover/view.jsx | 69 ++-- src/ui/component/fileListItem/view.jsx | 4 +- src/ui/component/fileProperties/view.jsx | 3 +- src/ui/component/fileTags/view.jsx | 20 +- src/ui/component/header/index.js | 16 +- src/ui/component/header/view.jsx | 104 +++--- src/ui/component/inviteNew/view.jsx | 4 +- src/ui/component/page/index.js | 16 +- src/ui/component/page/view.jsx | 95 ++---- src/ui/component/recommendedContent/view.jsx | 2 +- src/ui/component/rewardLink/view.jsx | 2 +- src/ui/component/rewardListClaimed/view.jsx | 4 +- src/ui/component/rewardSummary/index.js | 3 +- src/ui/component/rewardSummary/view.jsx | 48 +-- src/ui/component/rewardTile/view.jsx | 4 +- src/ui/component/rewardTotal/index.js | 25 ++ .../rewardTotal/total-background.png | Bin 0 -> 47186 bytes src/ui/component/rewardTotal/view.jsx | 25 ++ src/ui/component/router/view.jsx | 5 +- src/ui/component/sideBar/index.js | 3 +- src/ui/component/sideBar/view.jsx | 43 +-- src/ui/component/subscribeButton/view.jsx | 4 +- src/ui/component/tag/view.jsx | 17 +- src/ui/component/tagsSelect/view.jsx | 1 + src/ui/component/transactionList/index.js | 2 + .../internal/transaction-list-item.jsx | 4 +- src/ui/component/transactionList/view.jsx | 46 ++- .../component/transactionListRecent/index.js | 10 +- .../component/transactionListRecent/view.jsx | 49 +-- src/ui/component/userEmail/index.js | 18 ++ src/ui/component/userEmail/view.jsx | 60 ++++ src/ui/component/userEmailNew/view.jsx | 1 + src/ui/component/userEmailVerify/view.jsx | 2 +- src/ui/component/walletAddress/view.jsx | 6 +- src/ui/component/walletBalance/view.jsx | 8 +- src/ui/component/walletSend/view.jsx | 4 +- src/ui/constants/icons.js | 5 + src/ui/constants/pages.js | 1 + src/ui/constants/tags.js | 14 + src/ui/page/account/index.js | 10 +- src/ui/page/account/view.jsx | 18 +- src/ui/page/auth/index.js | 11 +- src/ui/page/auth/view.jsx | 49 +-- src/ui/page/discover/view.jsx | 2 +- src/ui/page/file/view.jsx | 61 ++-- src/ui/page/help/view.jsx | 14 +- src/ui/page/invite/view.jsx | 5 +- src/ui/page/rewards/view.jsx | 4 +- src/ui/page/sendCredits/index.js | 3 - src/ui/page/sendCredits/view.jsx | 18 -- src/ui/page/subscriptions/index.js | 5 +- src/ui/page/subscriptions/view.jsx | 53 ++- src/ui/page/tags/index.js | 8 +- src/ui/page/tags/view.jsx | 23 +- src/ui/page/transactionHistory/index.js | 8 +- src/ui/page/transactionHistory/view.jsx | 27 +- src/ui/page/userHistory/view.jsx | 2 - src/ui/page/wallet/index.js | 11 + src/ui/page/wallet/view.jsx | 17 + src/ui/reducers.js | 15 +- src/ui/redux/selectors/subscriptions.js | 2 +- src/ui/scss/all.scss | 2 +- src/ui/scss/component/_badge.scss | 3 + src/ui/scss/component/_button.scss | 38 ++- src/ui/scss/component/_card.scss | 55 ++-- src/ui/scss/component/_content.scss | 3 +- src/ui/scss/component/_file-list.scss | 27 +- src/ui/scss/component/_file-properties.scss | 14 +- src/ui/scss/component/_form-field.scss | 12 +- src/ui/scss/component/_header.scss | 42 +-- src/ui/scss/component/_icon.scss | 11 - src/ui/scss/component/_main.scss | 35 +- src/ui/scss/component/_markdown-preview.scss | 8 +- src/ui/scss/component/_media.scss | 31 +- src/ui/scss/component/_navigation.scss | 9 +- src/ui/scss/component/_spinner.scss | 1 + src/ui/scss/component/_table.scss | 29 +- src/ui/scss/component/_tags.scss | 33 +- src/ui/scss/component/_wunderbar.scss | 10 +- src/ui/scss/component/menu-button.scss | 96 ++++++ src/ui/scss/init/_gui.scss | 6 +- src/ui/scss/init/_vars.scss | 2 +- src/ui/util/format-credits.js | 5 +- src/ui/util/use-tween.js | 31 ++ static/locales/en.json | 302 ++++++------------ yarn.lock | 58 +++- 91 files changed, 1123 insertions(+), 890 deletions(-) create mode 100644 src/ui/component/rewardTotal/index.js create mode 100644 src/ui/component/rewardTotal/total-background.png create mode 100644 src/ui/component/rewardTotal/view.jsx create mode 100644 src/ui/component/userEmail/index.js create mode 100644 src/ui/component/userEmail/view.jsx create mode 100644 src/ui/constants/tags.js delete mode 100644 src/ui/page/sendCredits/index.js delete mode 100644 src/ui/page/sendCredits/view.jsx create mode 100644 src/ui/page/wallet/index.js create mode 100644 src/ui/page/wallet/view.jsx delete mode 100644 src/ui/scss/component/_icon.scss create mode 100644 src/ui/scss/component/menu-button.scss create mode 100644 src/ui/util/use-tween.js diff --git a/package.json b/package.json index 6fed1a788..ec02d7d8e 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "postinstall": "electron-builder install-app-deps && node ./build/downloadDaemon.js" }, "dependencies": { + "@reach/menu-button": "^0.1.18", + "@reach/tooltip": "^0.2.1", "electron-dl": "^1.11.0", "electron-log": "^2.2.12", "electron-updater": "^4.0.6", @@ -119,7 +121,7 @@ "jsmediatags": "^3.8.1", "json-loader": "^0.5.4", "lbry-format": "https://github.com/lbryio/lbry-format.git", - "lbry-redux": "lbryio/lbry-redux#291324d03f694c4fefa6967aa7be02d9245596a8", + "lbry-redux": "lbryio/lbry-redux#6a447d0542d23b9a37e266f5f85d3bde5297a451", "lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845", "lint-staged": "^7.0.2", "localforage": "^1.7.1", diff --git a/src/ui/component/button/view.jsx b/src/ui/component/button/view.jsx index 4c62c3aab..0949ecdfb 100644 --- a/src/ui/component/button/view.jsx +++ b/src/ui/component/button/view.jsx @@ -105,6 +105,7 @@ class Button extends React.PureComponent { exact to={path} title={title} + disabled={disabled} onClick={e => { e.stopPropagation(); if (onClick) { diff --git a/src/ui/component/copyableText/view.jsx b/src/ui/component/copyableText/view.jsx index 1ac15adce..5a292719c 100644 --- a/src/ui/component/copyableText/view.jsx +++ b/src/ui/component/copyableText/view.jsx @@ -44,7 +44,7 @@ export default class CopyableText extends React.PureComponent { onFocus={this.onFocus} inputButton={
    @@ -122,7 +122,7 @@ function FileListItem(props: Props) {
    {isChannel ? `${claimsInChannel} ${__('publishes')}` : }
    - {!slim && } + diff --git a/src/ui/component/fileProperties/view.jsx b/src/ui/component/fileProperties/view.jsx index bf8c1a400..1086a81c3 100644 --- a/src/ui/component/fileProperties/view.jsx +++ b/src/ui/component/fileProperties/view.jsx @@ -15,7 +15,7 @@ type Props = { }; export default function FileProperties(props: Props) { - const { uri, downloaded, claimIsMine, rewardedContentClaimIds, isSubscribed, isNew } = props; + const { uri, downloaded, claimIsMine, rewardedContentClaimIds, isSubscribed } = props; const { claimId } = parseURI(uri); const isRewardContent = rewardedContentClaimIds.includes(claimId); @@ -24,7 +24,6 @@ export default function FileProperties(props: Props) { {isSubscribed && } {!claimIsMine && downloaded && } {isRewardContent && } - {isNew && {__('NEW')}} ); diff --git a/src/ui/component/fileTags/view.jsx b/src/ui/component/fileTags/view.jsx index 969e3d1c0..b9cb6ef56 100644 --- a/src/ui/component/fileTags/view.jsx +++ b/src/ui/component/fileTags/view.jsx @@ -1,19 +1,25 @@ // @flow import * as React from 'react'; +import classnames from 'classnames'; import Button from 'component/button'; -const MAX_TAGS = 4; +const SLIM_TAGS = 2; +const NORMAL_TAGS = 4; +const LARGE_TAGS = 10; type Props = { tags: Array, followedTags: Array, + large: boolean, + slim: boolean, }; export default function FileTags(props: Props) { - const { tags, followedTags } = props; + const { tags, followedTags, large, slim } = props; + const numberOfTags = slim ? SLIM_TAGS : large ? LARGE_TAGS : NORMAL_TAGS; let tagsToDisplay = []; - for (var i = 0; tagsToDisplay.length < MAX_TAGS - 2; i++) { + for (var i = 0; tagsToDisplay.length < numberOfTags - 2; i++) { const tag = followedTags[i]; if (!tag) { break; @@ -28,7 +34,7 @@ export default function FileTags(props: Props) { for (var i = 0; i < sortedTags.length; i++) { const tag = sortedTags[i]; - if (!tag || tagsToDisplay.length === MAX_TAGS) { + if (!tag || tagsToDisplay.length === numberOfTags) { break; } @@ -38,11 +44,9 @@ export default function FileTags(props: Props) { } return ( -
    +
    {tagsToDisplay.map(tag => ( - +
    ); diff --git a/src/ui/component/header/index.js b/src/ui/component/header/index.js index cb969e585..391bb7226 100644 --- a/src/ui/component/header/index.js +++ b/src/ui/component/header/index.js @@ -1,21 +1,21 @@ +import * as SETTINGS from 'constants/settings'; import { connect } from 'react-redux'; -import { selectBalance, SETTINGS } from 'lbry-redux'; +import { selectBalance, SETTINGS as LBRY_REDUX_SETTINGS } from 'lbry-redux'; import { formatCredits } from 'util/format-credits'; -import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app'; -import { doDownloadUpgradeRequested } from 'redux/actions/app'; -import Header from './view'; +import { doSetClientSetting } from 'redux/actions/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings'; +import Header from './view'; const select = state => ({ - autoUpdateDownloaded: selectAutoUpdateDownloaded(state), balance: selectBalance(state), - language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), // trigger redraw on language change - isUpgradeAvailable: selectIsUpgradeAvailable(state), + language: makeSelectClientSetting(LBRY_REDUX_SETTINGS.LANGUAGE)(state), // trigger redraw on language change roundedBalance: formatCredits(selectBalance(state) || 0, 2), + currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state), + automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state), }); const perform = dispatch => ({ - downloadUpgradeRequested: () => dispatch(doDownloadUpgradeRequested()), + setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)), }); export default connect( diff --git a/src/ui/component/header/view.jsx b/src/ui/component/header/view.jsx index 45dedef07..afce53895 100644 --- a/src/ui/component/header/view.jsx +++ b/src/ui/component/header/view.jsx @@ -1,9 +1,13 @@ // @flow import * as ICONS from 'constants/icons'; +import * as SETTINGS from 'constants/settings'; import * as React from 'react'; +import { withRouter } from 'react-router'; import Button from 'component/button'; import LbcSymbol from 'component/common/lbc-symbol'; import WunderBar from 'component/wunderbar'; +import Icon from 'component/common/icon'; +import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button'; type Props = { autoUpdateDownloaded: boolean, @@ -11,16 +15,29 @@ type Props = { isUpgradeAvailable: boolean, roundedBalance: number, downloadUpgradeRequested: any => void, + history: { push: string => void }, + currentTheme: string, + automaticDarkModeEnabled: boolean, + setClientSetting: (string, boolean | string) => void, }; const Header = (props: Props) => { - const { autoUpdateDownloaded, downloadUpgradeRequested, isUpgradeAvailable, roundedBalance } = props; + const { roundedBalance, history, setClientSetting, currentTheme, automaticDarkModeEnabled } = props; - const showUpgradeButton = autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable); + function handleThemeToggle() { + if (automaticDarkModeEnabled) { + setClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false); + } + + if (currentTheme === 'dark') { + setClientSetting(SETTINGS.THEME, 'light'); + } else { + setClientSetting(SETTINGS.THEME, 'dark'); + } + } return (
    -
    ); }; -export default Header; +export default withRouter(Header); diff --git a/src/ui/component/inviteNew/view.jsx b/src/ui/component/inviteNew/view.jsx index 0b1631d2d..7d8a38d95 100644 --- a/src/ui/component/inviteNew/view.jsx +++ b/src/ui/component/inviteNew/view.jsx @@ -1,7 +1,7 @@ // @flow import React from 'react'; import Button from 'component/button'; -import { Form, FormField, Submit } from 'component/common/form'; +import { Form, FormField } from 'component/common/form'; import CopyableText from 'component/copyableText'; type FormProps = { @@ -48,7 +48,7 @@ class FormInviteNew extends React.PureComponent { name="email" value={this.state.email} error={errorMessage} - inputButton={} + inputButton={
    + )} + {/* @endif */} + {children} +
    + ); } export default Page; diff --git a/src/ui/component/recommendedContent/view.jsx b/src/ui/component/recommendedContent/view.jsx index cc3d95324..d37987fd8 100644 --- a/src/ui/component/recommendedContent/view.jsx +++ b/src/ui/component/recommendedContent/view.jsx @@ -56,7 +56,7 @@ export default class RecommendedContent extends React.PureComponent { slim loading={isSearching} uris={recommendedContent} - header={Related} + header={{__('Related')}} empty={
    {__('No related content found')}
    } /> diff --git a/src/ui/component/rewardLink/view.jsx b/src/ui/component/rewardLink/view.jsx index d2e0bdfd3..0f1750422 100644 --- a/src/ui/component/rewardLink/view.jsx +++ b/src/ui/component/rewardLink/view.jsx @@ -18,7 +18,7 @@ const RewardLink = (props: Props) => { const { reward, claimReward, label, isPending, button } = props; return !reward ? null : ( + - + )}
    - } headerAltControls={ diff --git a/src/ui/page/subscriptions/view.jsx b/src/ui/page/subscriptions/view.jsx index d259d5800..28774a65d 100644 --- a/src/ui/page/subscriptions/view.jsx +++ b/src/ui/page/subscriptions/view.jsx @@ -2,7 +2,7 @@ import * as PAGES from 'constants/pages'; import React, { useEffect } from 'react'; import Page from 'component/page'; -import FileList from 'component/fileList'; +import ClaimList from 'component/claimList'; import Button from 'component/button'; type Props = { @@ -62,7 +62,7 @@ export default function SubscriptionsPage(props: Props) { return (
    - {viewingSuggestedSubs ? __('Discover New Channels') : __('Latest From Your Subscriptions')}} headerAltControls={ diff --git a/src/ui/page/tags/view.jsx b/src/ui/page/tags/view.jsx index 148641244..e56be12bc 100644 --- a/src/ui/page/tags/view.jsx +++ b/src/ui/page/tags/view.jsx @@ -1,7 +1,7 @@ // @flow import React from 'react'; import Page from 'component/page'; -import FileListDiscover from 'component/fileListDiscover'; +import ClaimListDiscover from 'component/claimListDiscover'; import Button from 'component/button'; type Props = { @@ -28,7 +28,7 @@ function TagsPage(props: Props) { return ( - state.subscriptions || {}; // Returns the list of channel uris a user is subscribed to export const selectSubscriptions = createSelector( selectState, - state => state.subscriptions.sort((a, b) => a.channelName.localeCompare(b.channelName)) + state => state.subscriptions && state.subscriptions.sort((a, b) => a.channelName.localeCompare(b.channelName)) ); // Fetching list of users subscriptions diff --git a/src/ui/scss/component/_file-list.scss b/src/ui/scss/component/_file-list.scss index 19a78b506..ded90bb46 100644 --- a/src/ui/scss/component/_file-list.scss +++ b/src/ui/scss/component/_file-list.scss @@ -26,7 +26,7 @@ } } -.file-list__header--slim { +.file-list__header--small { height: 3rem; font-size: 1em; } diff --git a/src/ui/scss/component/_navigation.scss b/src/ui/scss/component/_navigation.scss index 059708973..10f8928f6 100644 --- a/src/ui/scss/component/_navigation.scss +++ b/src/ui/scss/component/_navigation.scss @@ -8,7 +8,6 @@ .navigation { width: var(--side-nav-width); font-size: 1.4rem; - position: fixed; @media (max-width: 600px) { display: none; @@ -36,6 +35,10 @@ color: lighten($lbry-black, 20%); margin-top: var(--spacing-miniscule); + .icon { + margin-right: var(--spacing-small); + } + &:hover { color: $lbry-teal-4; .icon { diff --git a/src/ui/scss/component/menu-button.scss b/src/ui/scss/component/menu-button.scss index 6e35834ac..f3613f74a 100644 --- a/src/ui/scss/component/menu-button.scss +++ b/src/ui/scss/component/menu-button.scss @@ -73,7 +73,7 @@ padding-right: 0; span { - margin-left: var(--spacing-tiny); + margin-left: var(--spacing-small); } } @@ -85,7 +85,8 @@ .menu__title, .menu__link { - font-size: 1.2rem; + font-size: 1.3rem; + color: lighten($lbry-black, 20%); .icon { margin-right: var(--spacing-small); diff --git a/static/locales/en.json b/static/locales/en.json index f1400a65e..48e468eef 100644 --- a/static/locales/en.json +++ b/static/locales/en.json @@ -175,5 +175,77 @@ "Try": "Try", "refreshing the app": "refreshing the app", "to fix it": "to fix it", - "Search": "Search" + "Search": "Search", + "Starting up": "Starting up", + "Connecting": "Connecting", + "It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.": "It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.", + "Newest First": "Newest First", + "Oldest First": "Oldest First", + "Contact": "Contact", + "Site": "Site", + "Send a tip to": "Send a tip to", + "This will appear as a tip for \"Why I Quit YouTube\".": "This will appear as a tip for \"Why I Quit YouTube\".", + "You sent 10 LBC as a tip, Mahalo!": "You sent 10 LBC as a tip, Mahalo!", + "History": "History", + "/wallet": "/wallet", + "Pending": "Pending", + "You have %s in unclaimed rewards.": "You have %s in unclaimed rewards.", + "Download Directory": "Download Directory", + "LBRY downloads will be saved here.": "LBRY downloads will be saved here.", + "Max Purchase Price": "Max Purchase Price", + "No Limit": "No Limit", + "Choose limit": "Choose limit", + "This will prevent you from purchasing any content over a certain cost, as a safety measure.": "This will prevent you from purchasing any content over a certain cost, as a safety measure.", + "Purchase Confirmations": "Purchase Confirmations", + "Always confirm before purchasing content": "Always confirm before purchasing content", + "Only confirm purchases over a certain price": "Only confirm purchases over a certain price", + "When this option is chosen, LBRY won't ask you to confirm downloads below your chosen price.": "When this option is chosen, LBRY won't ask you to confirm downloads below your chosen price.", + "Content Settings": "Content Settings", + "Show NSFW content": "Show NSFW content", + "NSFW content may include nudity, intense sexuality, profanity, or other adult content. By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. ": "NSFW content may include nudity, intense sexuality, profanity, or other adult content. By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. ", + "Notifications": "Notifications", + "Show Desktop Notifications": "Show Desktop Notifications", + "Get notified when a publish is confirmed, or when new content is available to watch.": "Get notified when a publish is confirmed, or when new content is available to watch.", + "Share Diagnostic Data": "Share Diagnostic Data", + "Help make LBRY better by contributing analytics and diagnostic data about my usage.": "Help make LBRY better by contributing analytics and diagnostic data about my usage.", + "You will be ineligible to earn rewards while diagnostics are not being shared.": "You will be ineligible to earn rewards while diagnostics are not being shared.", + "Appearance": "Appearance", + "Theme": "Theme", + "Automatic dark mode (9pm to 8am)": "Automatic dark mode (9pm to 8am)", + "Wallet Security": "Wallet Security", + "Encrypt my wallet with a custom password.": "Encrypt my wallet with a custom password.", + "Secure your local wallet data with a custom password.": "Secure your local wallet data with a custom password.", + "Lost passwords cannot be recovered.": "Lost passwords cannot be recovered.", + "Experimental Settings": "Experimental Settings", + "Automatically download new content from my subscriptions": "Automatically download new content from my subscriptions", + "The latest file from each of your subscriptions will be downloaded for quick access as soon as it's published.": "The latest file from each of your subscriptions will be downloaded for quick access as soon as it's published.", + "Autoplay media files": "Autoplay media files", + "Autoplay video and audio files when navigating to a file, as well as the next related item when a file finishes playing.": "Autoplay video and audio files when navigating to a file, as well as the next related item when a file finishes playing.", + "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences.": "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences.", + "Application Cache": "Application Cache", + "This will clear the application cache. Your wallet will not be affected.": "This will clear the application cache. Your wallet will not be affected.", + "Clear Cache": "Clear Cache", + "Choose Directory": "Choose Directory", + "Currency": "Currency", + "LBRY Credits (LBC)": "LBRY Credits (LBC)", + "US Dollars": "US Dollars", + "There's nothing available at this location.": "There's nothing available at this location.", + "Loading decentralized data...": "Loading decentralized data...", + "Confirm File Remove": "Confirm File Remove", + "Remove": "Remove", + "Are you sure you'd like to remove": "Are you sure you'd like to remove", + "from the LBRY app?": "from the LBRY app?", + "Also delete this file from my computer": "Also delete this file from my computer", + "Less": "Less", + "Warning!": "Warning!", + "Confirm External Resource": "Confirm External Resource", + "Continue": "Continue", + "This file has been shared with you by other people.": "This file has been shared with you by other people.", + "LBRY Inc is not responsible for its content, click continue to proceed at your own risk.": "LBRY Inc is not responsible for its content, click continue to proceed at your own risk.", + "Find what you were looking for?": "Find what you were looking for?", + "Yes": "Yes", + "No": "No", + "These search results are provided by LBRY, Inc.": "These search results are provided by LBRY, Inc.", + "FILTER": "FILTER", + "View file": "View file" } \ No newline at end of file diff --git a/static/locales/pl.json b/static/locales/pl.json index d00674e60..617233669 100644 --- a/static/locales/pl.json +++ b/static/locales/pl.json @@ -283,5 +283,8 @@ "Open file": "Otwórz plik", "NEW": "NEW", "Failed to load settings.": "Failed to load settings.", - "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences.": "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences." + "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences.": "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences.", + "Wallet": "Wallet", + "Home": "Home", + "Following": "Following" } \ No newline at end of file -- 2.45.2 From fe87b3bcc72e246b7f6a881ca07213dca399aaa8 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Wed, 19 Jun 2019 11:32:54 -0400 Subject: [PATCH 11/11] update sdk --- package.json | 2 +- static/locales/en.json | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ec02d7d8e..cc23bd04e 100644 --- a/package.json +++ b/package.json @@ -194,7 +194,7 @@ "yarn": "^1.3" }, "lbrySettings": { - "lbrynetDaemonVersion": "0.38.0rc6", + "lbrynetDaemonVersion": "0.38.0rc7", "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip", "lbrynetDaemonDir": "static/daemon", "lbrynetDaemonFileName": "lbrynet" diff --git a/static/locales/en.json b/static/locales/en.json index 48e468eef..240e27e31 100644 --- a/static/locales/en.json +++ b/static/locales/en.json @@ -247,5 +247,9 @@ "No": "No", "These search results are provided by LBRY, Inc.": "These search results are provided by LBRY, Inc.", "FILTER": "FILTER", - "View file": "View file" + "View file": "View file", + "Waiting for blob.": "Waiting for blob.", + "Waiting for metadata.": "Waiting for metadata.", + "Sorry, looks like we can't play this file.": "Sorry, looks like we can't play this file.", + "Sorry, looks like we can't preview this file.": "Sorry, looks like we can't preview this file." } \ No newline at end of file -- 2.45.2