diff --git a/package.json b/package.json index 04b67d80b..cc23bd04e 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", @@ -60,7 +62,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 +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#02f6918238110726c0b3b4248c61a84ac0b969e3", + "lbry-redux": "lbryio/lbry-redux#6a447d0542d23b9a37e266f5f85d3bde5297a451", "lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845", "lint-staged": "^7.0.2", "localforage": "^1.7.1", @@ -153,6 +155,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 +194,7 @@ "yarn": "^1.3" }, "lbrySettings": { - "lbrynetDaemonVersion": "0.37.4", + "lbrynetDaemonVersion": "0.38.0rc7", "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..0949ecdfb 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, @@ -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/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..309664b70 100644 --- a/src/ui/component/channelContent/view.jsx +++ b/src/ui/component/channelContent/view.jsx @@ -1,6 +1,6 @@ // @flow import React, { Fragment } from 'react'; -import FileList from 'component/fileList'; +import ClaimList from 'component/claimList'; import HiddenNsfwClaims from 'component/hiddenNsfwClaims'; import { withRouter } from 'react-router-dom'; import Paginate from 'component/common/paginate'; @@ -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..df3c24142 100644 --- a/src/ui/component/channelThumbnail/view.jsx +++ b/src/ui/component/channelThumbnail/view.jsx @@ -7,20 +7,21 @@ 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 && } 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/claimList/index.js b/src/ui/component/claimList/index.js new file mode 100644 index 000000000..55ef525f8 --- /dev/null +++ b/src/ui/component/claimList/index.js @@ -0,0 +1,11 @@ +import { connect } from 'react-redux'; +import ClaimList from './view'; + +const select = state => ({}); + +const perform = dispatch => ({}); + +export default connect( + select, + perform +)(ClaimList); diff --git a/src/ui/component/claimList/view.jsx b/src/ui/component/claimList/view.jsx new file mode 100644 index 000000000..7ce272d1f --- /dev/null +++ b/src/ui/component/claimList/view.jsx @@ -0,0 +1,70 @@ +// @flow +import type { Node } from 'react'; +import React from 'react'; +import classnames from 'classnames'; +import ClaimListItem from 'component/claimListItem'; +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 = { + uris: Array, + header: Node | boolean, + headerAltControls: Node, + injectedItem?: Node, + loading: boolean, + type: string, + empty?: string, + meta?: Node, + // If using the default header, this is a unique ID needed to persist the state of the filter setting + persistedStorageKey?: string, +}; + +export default function ClaimList(props: Props) { + const { uris, headerAltControls, injectedItem, loading, persistedStorageKey, empty, meta, type, header } = props; + const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW); + const sortedUris = uris && currentSort === SORT_OLD ? uris.reverse() : uris; + const hasUris = uris && !!uris.length; + + function handleSortChange() { + setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW); + } + + return ( +
+ {header !== false && ( +
+ {header || ( + + + + + )} + {loading && } +
{headerAltControls}
+
+ )} + {meta &&
{meta}
} + {hasUris && ( +
    + {sortedUris.map((uri, index) => ( + + + {index === 4 && injectedItem &&
  • {injectedItem}
  • } +
    + ))} +
+ )} + {!hasUris && !loading &&

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

} +
+ ); +} diff --git a/src/ui/component/claimListDiscover/index.js b/src/ui/component/claimListDiscover/index.js new file mode 100644 index 000000000..8729ef22f --- /dev/null +++ b/src/ui/component/claimListDiscover/index.js @@ -0,0 +1,18 @@ +import { connect } from 'react-redux'; +import { doClaimSearch, selectLastClaimSearchUris, selectFetchingClaimSearch, doToggleTagFollow } from 'lbry-redux'; +import ClaimListDiscover from './view'; + +const select = state => ({ + uris: selectLastClaimSearchUris(state), + loading: selectFetchingClaimSearch(state), +}); + +const perform = { + doClaimSearch, + doToggleTagFollow, +}; + +export default connect( + select, + perform +)(ClaimListDiscover); diff --git a/src/ui/component/claimListDiscover/view.jsx b/src/ui/component/claimListDiscover/view.jsx new file mode 100644 index 000000000..40f0b7a20 --- /dev/null +++ b/src/ui/component/claimListDiscover/view.jsx @@ -0,0 +1,140 @@ +// @flow +import type { Node } from 'react'; +import React, { useEffect } from 'react'; +import moment from 'moment'; +import { FormField } from 'component/common/form'; +import ClaimList from 'component/claimList'; +import Tag from 'component/tag'; +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 SEARCH_SORT_YOU = 'you'; +const SEARCH_SORT_ALL = 'everyone'; +const TYPE_TRENDING = 'trending'; +const TYPE_TOP = 'top'; +const TYPE_NEW = 'new'; +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, + doClaimSearch: (number, {}) => void, + injectedItem: any, + tags: Array, + loading: boolean, + personal: boolean, + doToggleTagFollow: string => void, + meta?: Node, +}; + +function ClaimListDiscover(props: Props) { + const { doClaimSearch, uris, tags, loading, personal, injectedItem, meta } = props; + 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); + + const toCapitalCase = string => string.charAt(0).toUpperCase() + string.slice(1); + const tagsString = tags.join(','); + useEffect(() => { + const options = {}; + const newTags = tagsString.split(','); + + if ((newTags && !personal) || (newTags && personal && personalSort === SEARCH_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); + }, [personal, personalSort, typeSort, timeSort, doClaimSearch, tagsString]); + + const header = ( +

+ setTypeSort(e.target.value)} + > + {SEARCH_TYPES.map(type => ( + + ))} + + {__('For')} + {!personal && tags && tags.length ? ( + tags.map(tag => ) + ) : ( + setPersonalSort(e.target.value)} + > + {SEARCH_FILTER_TYPES.map(type => ( + + ))} + + )} +

+ ); + + const headerAltControls = ( + + {typeSort === 'top' && ( + setTimeSort(e.target.value)} + > + {SEARCH_TIMES.map(time => ( + + ))} + + )} + + ); + + return ( +
+ +
+ ); +} + +export default ClaimListDiscover; diff --git a/src/ui/component/fileCard/index.js b/src/ui/component/claimListItem/index.js similarity index 53% rename from src/ui/component/fileCard/index.js rename to src/ui/component/claimListItem/index.js index 86cc6200d..b69db1d6c 100644 --- a/src/ui/component/fileCard/index.js +++ b/src/ui/component/claimListItem/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 ClaimListItem 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); +)(ClaimListItem); diff --git a/src/ui/component/claimListItem/view.jsx b/src/ui/component/claimListItem/view.jsx new file mode 100644 index 000000000..bc2836ce6 --- /dev/null +++ b/src/ui/component/claimListItem/view.jsx @@ -0,0 +1,130 @@ +// @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 ClaimTags from 'component/claimTags'; +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, + placeholder: boolean, + type: string, +}; + +function ClaimListItem(props: Props) { + const { + obscureNsfw, + claimIsMine, + pending, + history, + uri, + isResolvingUri, + thumbnail, + title, + nsfw, + resolveUri, + claim, + placeholder, + type, + } = 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 ? : } +
    +
    +
    + +
    + {type !== 'small' && ( +
    + {isChannel && } + {!isChannel && } +
    + )} +
    + +
    +
    + + {pending &&
    Pending...
    } +
    {isChannel ? `${claimsInChannel} ${__('publishes')}` : }
    +
    + + +
    +
    +
  • + ); +} + +export default withRouter(ClaimListItem); diff --git a/src/ui/component/claimTags/index.js b/src/ui/component/claimTags/index.js new file mode 100644 index 000000000..0f498f2e3 --- /dev/null +++ b/src/ui/component/claimTags/index.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux'; +import { makeSelectTagsForUri, selectFollowedTags } from 'lbry-redux'; +import ClaimTags from './view'; + +const select = (state, props) => ({ + tags: makeSelectTagsForUri(props.uri)(state), + followedTags: selectFollowedTags(state), +}); + +export default connect( + select, + null +)(ClaimTags); diff --git a/src/ui/component/claimTags/view.jsx b/src/ui/component/claimTags/view.jsx new file mode 100644 index 000000000..04470cf7e --- /dev/null +++ b/src/ui/component/claimTags/view.jsx @@ -0,0 +1,52 @@ +// @flow +import * as React from 'react'; +import classnames from 'classnames'; +import Button from 'component/button'; + +const SLIM_TAGS = 2; +const NORMAL_TAGS = 4; +const LARGE_TAGS = 10; + +type Props = { + tags: Array, + followedTags: Array, + type: string, +}; + +export default function ClaimTags(props: Props) { + const { tags, followedTags, type } = props; + const numberOfTags = type === 'small' ? SLIM_TAGS : type === 'large' ? LARGE_TAGS : NORMAL_TAGS; + + let tagsToDisplay = []; + for (var i = 0; tagsToDisplay.length < numberOfTags - 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 === numberOfTags) { + break; + } + + if (!tagsToDisplay.includes(tag)) { + tagsToDisplay.push(tag); + } + } + + return ( +
    + {tagsToDisplay.map(tag => ( +
    + ); +} 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/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={
    -
    -
    - - -
    - - - -
    -
    - -
    -

    {__('You Are Awesome!')}

    -
    -
    -

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

    -
    -
    -
    -
    -
    - - - ); - } -} 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 4f8fa525c..afce53895 100644 --- a/src/ui/component/header/view.jsx +++ b/src/ui/component/header/view.jsx @@ -1,116 +1,124 @@ // @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, balance: string, isUpgradeAvailable: boolean, - roundedBalance: string, - isBackDisabled: boolean, - isForwardDisabled: boolean, - back: () => void, - forward: () => void, + roundedBalance: number, downloadUpgradeRequested: any => void, + history: { push: string => void }, + currentTheme: string, + automaticDarkModeEnabled: boolean, + setClientSetting: (string, boolean | string) => void, }; const Header = (props: Props) => { - const { - autoUpdateDownloaded, - balance, - downloadUpgradeRequested, - isUpgradeAvailable, - roundedBalance, - back, - isBackDisabled, - forward, - isForwardDisabled, - } = 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 (
    -
    -
    + {/* @endif */} - {/* @endif */} - - + -
    -
    ); }; -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={ + + ); + } else { + return null; } - - const inner = {name}; - - if (!channelLink) { - return inner; - } - - return ( - - ); } } diff --git a/src/ui/component/userEmail/index.js b/src/ui/component/userEmail/index.js new file mode 100644 index 000000000..4fdc00f92 --- /dev/null +++ b/src/ui/component/userEmail/index.js @@ -0,0 +1,18 @@ +import { connect } from 'react-redux'; +import { selectEmailToVerify, doUserResendVerificationEmail, doUserCheckEmailVerified, selectUser } from 'lbryinc'; +import UserEmailVerify from './view'; + +const select = state => ({ + email: selectEmailToVerify(state), + user: selectUser(state), +}); + +const perform = dispatch => ({ + resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)), + checkEmailVerified: () => dispatch(doUserCheckEmailVerified()), +}); + +export default connect( + select, + perform +)(UserEmailVerify); diff --git a/src/ui/component/userEmail/view.jsx b/src/ui/component/userEmail/view.jsx new file mode 100644 index 000000000..eaa413476 --- /dev/null +++ b/src/ui/component/userEmail/view.jsx @@ -0,0 +1,60 @@ +// @flow +import * as React from 'react'; +import Button from 'component/button'; +import { FormField } from 'component/common/form'; +import UserEmailNew from 'component/userEmailNew'; +import UserEmailVerify from 'component/userEmailVerify'; + +type Props = { + cancelButton: React.Node, + email: string, + resendVerificationEmail: string => void, + checkEmailVerified: () => void, + user: { + has_verified_email: boolean, + }, +}; + +function UserEmail(props: Props) { + const { email, user } = props; + let isVerified = false; + if (user) { + isVerified = user.has_verified_email; + } + + return ( +
    + {!email && } + {user && email && !isVerified && } + {email && isVerified && ( + +
    +

    {__('Email')}

    +

    + {email && isVerified && __('Your email has been successfully verified')} + {!email && __('')}. +

    +
    + + {isVerified && ( + } + /> + )} +

    + {`${__( + 'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards.' + )} `} +

    +
    + )} +
    + ); +} + +export default UserEmail; diff --git a/src/ui/component/userEmailVerify/view.jsx b/src/ui/component/userEmailVerify/view.jsx index 2c6cc9fec..d196844f6 100644 --- a/src/ui/component/userEmailVerify/view.jsx +++ b/src/ui/component/userEmailVerify/view.jsx @@ -56,7 +56,7 @@ class UserEmailVerify extends React.PureComponent {
    -

    +

    {__('An email was sent to')} {email}.{' '} {__('Follow the link and you will be good to go. This will update automatically.')}

    diff --git a/src/ui/component/walletAddress/view.jsx b/src/ui/component/walletAddress/view.jsx index 5ec6a418c..7c5083f7c 100644 --- a/src/ui/component/walletAddress/view.jsx +++ b/src/ui/component/walletAddress/view.jsx @@ -1,5 +1,4 @@ // @flow -import * as icons from 'constants/icons'; import React from 'react'; import Button from 'component/button'; import CopyableText from 'component/copyableText'; @@ -27,7 +26,7 @@ class WalletAddress extends React.PureComponent { (this: any).toggleQR = this.toggleQR.bind(this); } - componentWillMount() { + componentDidMount() { const { checkAddressIsMine, receiveAddress, getNewAddress } = this.props; if (!receiveAddress) { getNewAddress(); @@ -62,9 +61,8 @@ class WalletAddress extends React.PureComponent {
    ); } 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..c28c545ce 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 ClaimListDiscover from 'component/claimListDiscover'; +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..a7e5879ac 100644 --- a/src/ui/page/file/view.jsx +++ b/src/ui/page/file/view.jsx @@ -19,6 +19,7 @@ import FileDownloadLink from 'component/fileDownloadLink'; import classnames from 'classnames'; import getMediaType from 'util/get-media-type'; import RecommendedContent from 'component/recommendedContent'; +import ClaimTags from 'component/claimTags'; type Props = { claim: StreamClaim, @@ -67,6 +68,8 @@ class FilePage extends React.Component { (this: any).viewerContainer = React.createRef(); } + viewerContainer: { current: React.ElementRef }; + componentDidMount() { const { uri, @@ -108,15 +111,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 +151,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 +183,12 @@ class FilePage extends React.Component { const insufficientCredits = !claimIsMine && costInfo && costInfo.cost > balance; return ( - -
    -
    diff --git a/src/ui/page/fileListDownloaded/index.js b/src/ui/page/fileListDownloaded/index.js index a1396c231..653f666a0 100644 --- a/src/ui/page/fileListDownloaded/index.js +++ b/src/ui/page/fileListDownloaded/index.js @@ -1,17 +1,10 @@ import { connect } from 'react-redux'; -import { - selectFileInfosDownloaded, - selectMyClaimsWithoutChannels, - selectIsFetchingFileList, - selectFileListDownloadedSort, -} from 'lbry-redux'; +import { selectDownloadedUris, selectIsFetchingFileList } from 'lbry-redux'; import FileListDownloaded from './view'; const select = state => ({ - fileInfos: selectFileInfosDownloaded(state), + downloadedUris: selectDownloadedUris(state), fetching: selectIsFetchingFileList(state), - claims: selectMyClaimsWithoutChannels(state), - sortBy: selectFileListDownloadedSort(state), }); export default connect( diff --git a/src/ui/page/fileListDownloaded/view.jsx b/src/ui/page/fileListDownloaded/view.jsx index cbafaf894..ad48d5bcf 100644 --- a/src/ui/page/fileListDownloaded/view.jsx +++ b/src/ui/page/fileListDownloaded/view.jsx @@ -1,45 +1,42 @@ // @flow import React from 'react'; import Button from 'component/button'; -import FileList from 'component/fileList'; -import Page from 'component/page'; -import { PAGES } from 'lbry-redux'; +import ClaimList from 'component/claimList'; type Props = { fetching: boolean, - fileInfos: {}, - sortBy: string, + downloadedUris: Array, }; -class FileListDownloaded extends React.PureComponent { - render() { - const { fetching, fileInfos, sortBy } = this.props; - const hasDownloads = fileInfos && Object.values(fileInfos).length > 0; +function FileListDownloaded(props: Props) { + const { fetching, downloadedUris } = props; + const hasDownloads = !!downloadedUris.length; - return ( - // Removed the wapper to try combining this page with UserHistory - // This should eventually move into /components if we want to keep it this way - - {hasDownloads ? ( - - ) : ( -
    -
    -
    -

    {__("You haven't downloaded anything from LBRY yet.")}

    -
    + return ( + // Removed the wapper to try combining this page with UserHistory + // This should eventually move into /components if we want to keep it this way + + {hasDownloads ? ( +
    + +
    + ) : ( +
    +
    +
    +

    {__("You haven't downloaded anything from LBRY yet.")}

    +
    -
    -
    -
    +
    +
    +
    -
    -
    - )} -
    - ); - } +
    + +
    + )} + + ); } export default FileListDownloaded; diff --git a/src/ui/page/fileListPublished/index.js b/src/ui/page/fileListPublished/index.js index cde0c9af5..a1bfceff6 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, 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), - sortBy: selectFileListPublishedSort(state), }); const perform = dispatch => ({ diff --git a/src/ui/page/fileListPublished/view.jsx b/src/ui/page/fileListPublished/view.jsx index e72971574..bb98f9c58 100644 --- a/src/ui/page/fileListPublished/view.jsx +++ b/src/ui/page/fileListPublished/view.jsx @@ -1,47 +1,45 @@ // @flow -import React from 'react'; +import React, { useEffect } from 'react'; import Button from 'component/button'; -import FileList from 'component/fileList'; +import ClaimList from 'component/claimList'; import Page from 'component/page'; -import { PAGES } from 'lbry-redux'; type Props = { - claims: Array, + uris: Array, checkPendingPublishes: () => void, fetching: boolean, - sortBy: string, }; -class FileListPublished extends React.PureComponent { - componentDidMount() { - const { checkPendingPublishes } = this.props; +function FileListPublished(props: Props) { + const { checkPendingPublishes, fetching, uris } = 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 ( + + {uris && uris.length ? ( +
    + +
    + ) : ( +
    +
    +
    +

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

    +
    -
    -
    -
    +
    +
    +
    -
    -
    - )} -
    - ); - } +
    + + + )} +
    + ); } export default FileListPublished; diff --git a/src/ui/page/help/view.jsx b/src/ui/page/help/view.jsx index e71079928..924120d34 100644 --- a/src/ui/page/help/view.jsx +++ b/src/ui/page/help/view.jsx @@ -130,7 +130,7 @@ class HelpPage extends React.PureComponent {
    -
    @@ -147,7 +147,7 @@ class HelpPage extends React.PureComponent {
    -
    @@ -167,7 +167,7 @@ class HelpPage extends React.PureComponent { navigate="/$/report" label={__('Submit a Bug Report/Feature Request')} icon={icons.REPORT} - button="primary" + button="inverse" /> @@ -188,8 +188,8 @@ class HelpPage extends React.PureComponent {
    -
    @@ -198,8 +198,8 @@ class HelpPage extends React.PureComponent { {/* @endif */} -
    -
    +
    +

    {__('About')}

    {this.state.upgradeAvailable !== null && this.state.upgradeAvailable ? ( diff --git a/src/ui/page/invite/view.jsx b/src/ui/page/invite/view.jsx index a4929a9cc..ae5c832dc 100644 --- a/src/ui/page/invite/view.jsx +++ b/src/ui/page/invite/view.jsx @@ -3,7 +3,6 @@ import React from 'react'; import BusyIndicator from 'component/common/busy-indicator'; import InviteNew from 'component/inviteNew'; import InviteList from 'component/inviteList'; -import Page from 'component/page'; type Props = { isPending: boolean, @@ -27,7 +26,7 @@ class InvitePage extends React.PureComponent { const { isPending, isFailed } = this.props; return ( - +
    {isPending && } {!isPending && isFailed && {__('Failed to retrieve invite status.')}} {!isPending && !isFailed && ( @@ -36,7 +35,7 @@ class InvitePage extends React.PureComponent { )} - +
    ); } } diff --git a/src/ui/page/rewards/view.jsx b/src/ui/page/rewards/view.jsx index 81d461e5a..9001a2781 100644 --- a/src/ui/page/rewards/view.jsx +++ b/src/ui/page/rewards/view.jsx @@ -129,7 +129,7 @@ class RewardsPage extends PureComponent {

    -
    {this.renderCustomRewardCode()}
    +
    {this.renderCustomRewardCode()}
    ); } @@ -138,7 +138,7 @@ class RewardsPage extends PureComponent { return (
    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..45967d79d 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 ClaimListItem from 'component/claimListItem'; +import ClaimList from 'component/claimList'; 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/sendCredits/index.js b/src/ui/page/sendCredits/index.js deleted file mode 100644 index 0d19a7d34..000000000 --- a/src/ui/page/sendCredits/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import SendReceivePage from './view'; - -export default SendReceivePage; diff --git a/src/ui/page/sendCredits/view.jsx b/src/ui/page/sendCredits/view.jsx deleted file mode 100644 index 8e6017929..000000000 --- a/src/ui/page/sendCredits/view.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import classnames from 'classnames'; -import WalletSend from 'component/walletSend'; -import WalletAddress from 'component/walletAddress'; -import Page from 'component/page'; -import UnsupportedOnWeb from 'component/common/unsupported-on-web'; - -const SendReceivePage = () => ( - - {IS_WEB && } -
    - - -
    -
    -); - -export default SendReceivePage; 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..676b3626d 100644 --- a/src/ui/page/subscriptions/index.js +++ b/src/ui/page/subscriptions/index.js @@ -1,45 +1,26 @@ 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 { selectLastClaimSearchUris, doClaimSearch } from 'lbry-redux'; 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), + suggestedSubscriptions: selectSuggestedChannels(state), + uris: selectLastClaimSearchUris(state), }); export default connect( select, { doFetchMySubscriptions, - doSetClientSetting, - doSetViewMode, doFetchRecommendedSubscriptions, - doCompleteFirstRun, - doShowSuggestedSubs, + doClaimSearch, } )(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..28774a65d 100644 --- a/src/ui/page/subscriptions/view.jsx +++ b/src/ui/page/subscriptions/view.jsx @@ -1,105 +1,80 @@ // @flow -import * as SETTINGS from 'constants/settings'; -import React, { PureComponent } from 'react'; +import * as PAGES from 'constants/pages'; +import React, { useEffect } from 'react'; import Page from 'component/page'; -import FirstRun from './internal/first-run'; -import UserSubscriptions from './internal/user-subscriptions'; +import ClaimList from 'component/claimList'; +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 }>, + subscribedChannels: Array<{ uri: string }>, // The channels a user is subscribed to + 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, + location: { search: string }, + history: { push: string => void }, + doClaimSearch: (number, {}) => void, + uris: Array, }; -export default class SubscriptionsPage extends PureComponent { - constructor() { - super(); +export default function SubscriptionsPage(props: Props) { + const { + subscribedChannels, + doFetchMySubscriptions, + doFetchRecommendedSubscriptions, + suggestedSubscriptions, + loading, + location, + history, + doClaimSearch, + uris, + } = props; - (this: any).onAutoDownloadChange = this.onAutoDownloadChange.bind(this); + const hasSubscriptions = !!subscribedChannels.length; + const { search } = location; + const urlParams = new URLSearchParams(search); + const viewingSuggestedSubs = urlParams.get('view'); + + function onClick() { + let url = `/$/${PAGES.SUBSCRIPTIONS}`; + if (!viewingSuggestedSubs) { + url += '?view=discover'; + } + + history.push(url); } - componentDidMount() { - const { - doFetchMySubscriptions, - doFetchRecommendedSubscriptions, - allSubscriptions, - firstRunCompleted, - doShowSuggestedSubs, - } = this.props; - + 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(); - } - } + const idString = subscribedChannels.map(channel => channel.uri.split('#')[1]).join(','); + useEffect(() => { + const ids = idString.split(','); + const options = { + channel_ids: ids, + }; - onAutoDownloadChange(event: SyntheticInputEvent<*>) { - this.props.doSetClientSetting(SETTINGS.AUTO_DOWNLOAD, event.target.checked); - } + doClaimSearch(20, options); + }, [idString, doClaimSearch]); - 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 ( + +
    + {viewingSuggestedSubs ? __('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..5bf5a2ec7 --- /dev/null +++ b/src/ui/page/tags/index.js @@ -0,0 +1,14 @@ +import { connect } from 'react-redux'; +import { selectFollowedTags, doToggleTagFollow } from 'lbry-redux'; +import Tags from './view'; + +const select = state => ({ + followedTags: selectFollowedTags(state), +}); + +export default connect( + select, + { + doToggleTagFollow, + } +)(Tags); diff --git a/src/ui/page/tags/view.jsx b/src/ui/page/tags/view.jsx new file mode 100644 index 000000000..e56be12bc --- /dev/null +++ b/src/ui/page/tags/view.jsx @@ -0,0 +1,45 @@ +// @flow +import React from 'react'; +import Page from 'component/page'; +import ClaimListDiscover from 'component/claimListDiscover'; +import Button from 'component/button'; + +type Props = { + location: { search: string }, + followedTags: Array, + doToggleTagFollow: string => void, +}; + +function TagsPage(props: Props) { + const { + location: { search }, + followedTags, + doToggleTagFollow, + } = props; + + const urlParams = new URLSearchParams(search); + const tagsQuery = urlParams.get('t') || ''; + const tags = tagsQuery.split(','); + // Eventually allow more than one tag on this page + // Restricting to one to make follow/unfollow simpler + const tag = tags[0]; + + const isFollowing = followedTags.map(({ name }) => name).includes(tag); + + return ( + + doToggleTagFollow(tag)} + label={isFollowing ? __('Unfollow this tag') : __('Follow this tag')} + /> + } + /> + + ); +} + +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/page/transactionHistory/index.js b/src/ui/page/transactionHistory/index.js index 92006a6c5..9a8d2b1fb 100644 --- a/src/ui/page/transactionHistory/index.js +++ b/src/ui/page/transactionHistory/index.js @@ -1,14 +1,8 @@ import { connect } from 'react-redux'; -import { - doFetchTransactions, - selectTransactionItems, - selectIsFetchingTransactions, - doFetchClaimListMine, -} from 'lbry-redux'; +import { doFetchTransactions, selectTransactionItems, doFetchClaimListMine } from 'lbry-redux'; import TransactionHistoryPage from './view'; const select = state => ({ - fetchingTransactions: selectIsFetchingTransactions(state), transactions: selectTransactionItems(state), }); diff --git a/src/ui/page/transactionHistory/view.jsx b/src/ui/page/transactionHistory/view.jsx index 712646afe..5dfec660a 100644 --- a/src/ui/page/transactionHistory/view.jsx +++ b/src/ui/page/transactionHistory/view.jsx @@ -1,10 +1,8 @@ // @flow import React from 'react'; import classnames from 'classnames'; -import BusyIndicator from 'component/common/busy-indicator'; import TransactionList from 'component/transactionList'; import Page from 'component/page'; -import RefreshTransactionButton from 'component/transactionRefreshButton'; import UnsupportedOnWeb from 'component/common/unsupported-on-web'; type Props = { @@ -23,36 +21,17 @@ class TransactionHistoryPage extends React.PureComponent { } render() { - const { fetchingTransactions, transactions } = this.props; + const { transactions } = this.props; return ( {IS_WEB && }
    -
    -

    - {__('Transaction History')} - -

    -
    - {fetchingTransactions && !transactions.length ? ( -
    - -
    - ) : ( - '' - )} - {transactions && transactions.length ? ( -
    - -
    - ) : ( -
    {__("Looks like you don't have any transactions")}
    - )} +
    ); diff --git a/src/ui/page/userHistory/view.jsx b/src/ui/page/userHistory/view.jsx index 61b92fefd..bdf85c340 100644 --- a/src/ui/page/userHistory/view.jsx +++ b/src/ui/page/userHistory/view.jsx @@ -1,7 +1,6 @@ // @flow import React from 'react'; import Page from 'component/page'; -import UserHistory from 'component/navigationHistoryRecent'; import DownloadList from 'page/fileListDownloaded'; type Props = {}; @@ -10,7 +9,6 @@ class UserHistoryPage extends React.PureComponent { render() { return ( - ); diff --git a/src/ui/page/wallet/index.js b/src/ui/page/wallet/index.js new file mode 100644 index 000000000..6d797c4ab --- /dev/null +++ b/src/ui/page/wallet/index.js @@ -0,0 +1,11 @@ +import { connect } from 'react-redux'; +import Wallet from './view'; + +const select = state => ({}); + +const perform = dispatch => ({}); + +export default connect( + select, + perform +)(Wallet); diff --git a/src/ui/page/wallet/view.jsx b/src/ui/page/wallet/view.jsx new file mode 100644 index 000000000..c93b3a2c9 --- /dev/null +++ b/src/ui/page/wallet/view.jsx @@ -0,0 +1,17 @@ +import React from 'react'; +import WalletBalance from 'component/walletBalance'; +import WalletSend from 'component/walletSend'; +import WalletAddress from 'component/walletAddress'; +import TransactionListRecent from 'component/transactionListRecent'; +import Page from 'component/page'; + +const WalletPage = () => ( + + + + + + +); + +export default WalletPage; diff --git a/src/ui/reducers.js b/src/ui/reducers.js index 265d9ec8e..bb89aa2c5 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, + tagsReducerBuilder, +} from 'lbry-redux'; import { userReducer, rewardsReducer, costInfoReducer, blacklistReducer, homepageReducer, statsReducer } from 'lbryinc'; import appReducer from 'redux/reducers/app'; import availabilityReducer from 'redux/reducers/availability'; @@ -8,6 +15,17 @@ import contentReducer from 'redux/reducers/content'; import settingsReducer from 'redux/reducers/settings'; import subscriptionsReducer from 'redux/reducers/subscriptions'; import publishReducer from 'redux/reducers/publish'; +import { defaultKnownTags, defaultFollowedTags } from 'constants/tags'; + +function getDefaultKnownTags() { + return defaultFollowedTags.concat(defaultKnownTags).reduce( + (tagsMap, tag) => ({ + ...tagsMap, + [tag]: { name: tag }, + }), + {} + ); +} export default history => combineReducers({ @@ -27,6 +45,7 @@ export default history => settings: settingsReducer, stats: statsReducer, subscriptions: subscriptionsReducer, + tags: tagsReducerBuilder({ followedTags: defaultFollowedTags, knownTags: getDefaultKnownTags() }), 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..f971fb322 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,13 @@ 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, + 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 3c61b5832..8347de765 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,10 +203,14 @@ 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 => { + 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 @@ -394,13 +378,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..e4fb71b31 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 || {}; @@ -17,7 +16,7 @@ const selectState = state => state.subscriptions || {}; // Returns the list of channel uris a user is subscribed to export const selectSubscriptions = createSelector( selectState, - state => state.subscriptions + state => state.subscriptions && state.subscriptions.sort((a, b) => a.channelName.localeCompare(b.channelName)) ); // Fetching list of users 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..b14ebd8d8 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,21 +18,22 @@ @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'; -@import 'component/icon'; @import 'component/item-list'; @import 'component/main'; @import 'component/markdown-editor'; @import 'component/markdown-preview'; @import 'component/media'; +@import 'component/menu-button'; @import 'component/modal'; @import 'component/navigation'; @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..874ee2822 100644 --- a/src/ui/scss/component/_badge.scss +++ b/src/ui/scss/component/_badge.scss @@ -1 +1,21 @@ @import '~@lbry/components/sass/badge/_index.scss'; + +.badge--tag { + @extend .badge; + background-color: lighten($lbry-teal-5, 55%); + color: darken($lbry-teal-5, 20%); + svg { + stroke: $lbry-teal-5; + } + + [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..99fc00438 100644 --- a/src/ui/scss/component/_button.scss +++ b/src/ui/scss/component/_button.scss @@ -2,17 +2,10 @@ .button { display: inline-block; - - .button__content { - display: flex; - align-items: center; - height: 100%; - } + font-weight: 400; svg { stroke-width: 1.9; - width: 1.2rem; - height: 1.2rem; position: relative; color: $lbry-gray-5; @@ -23,15 +16,11 @@ 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 { +// Fix this in lbry/components +.button--primary:not(:hover) { + background-color: $lbry-teal-4; svg { color: white; } @@ -42,10 +31,12 @@ height: 5rem; width: 5rem; border-radius: 2.5rem; + &:not(:hover) { + background-color: $lbry-teal-4; + } } .button--primary, -.button--alt, .button--inverse { height: var(--button-height); line-height: var(--button-height); @@ -56,16 +47,68 @@ box-sizing: border-box; } +.button--inverse { + border-color: $lbry-teal-4; + + &:hover { + color: $lbry-white; + background-color: $lbry-teal-4; + .icon { + stroke: $lbry-white; + } + } +} + +.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-black; + color: $lbry-white; + border-radius: var(--card-radius); + } +} + +.button--subscribe { + vertical-align: text-top; + align-items: flex-start; +} + +.button__content { + display: flex; + align-items: center; + min-width: 0; +} + +.button__label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +// Handle icons on the left or right side of the button label +svg + .button__label, +.button__label + svg { + margin-left: var(--spacing-miniscule); +} diff --git a/src/ui/scss/component/_card.scss b/src/ui/scss/component/_card.scss index ab177228c..65b523b87 100644 --- a/src/ui/scss/component/_card.scss +++ b/src/ui/scss/component/_card.scss @@ -1,12 +1,14 @@ .card { background-color: $lbry-white; - margin-bottom: var(--spacing-vertical-xlarge); + margin-bottom: var(--spacing-large); position: relative; border-radius: var(--card-radius); box-shadow: var(--card-box-shadow) $lbry-gray-1; + overflow: hidden; + font-size: 1.25rem; 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 +23,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,15 +42,27 @@ justify-content: space-between; } +.card--reward-total { + background-repeat: no-repeat; + background-size: cover; + // justify-content: space-between; + color: $lbry-white; +} + +.card--modal { + box-shadow: none; +} + // C A R D // A C T I O N S .card__actions { display: flex; + align-items: center; font-size: 1.15rem; > *:not(:last-child) { - margin-right: var(--spacing-vertical-medium); + margin-right: var(--spacing-medium); } } @@ -74,23 +89,26 @@ } .card__actions--top-space { - padding-top: var(--spacing-vertical-small); + padding-top: var(--spacing-small); +} + +.card__actions--table { + padding: var(--spacing-medium); } // C A R D // C O N T E N T .card__content { - 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: 3rem; + line-height: 1.5; + font-weight: 700; } // C A R D @@ -100,67 +118,21 @@ 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); - - // Depending on screen width, the amount of items in - // each row change and are auto-sized - - // @media (min-width: 2001px) { - // grid-template-columns: repeat(auto-fill, minmax(calc(100% / 10), 1fr)); - // } - - // @media (min-width: 1801px) and (max-width: 2000px) { - // grid-template-columns: repeat(auto-fill, minmax(calc(100% / 8), 1fr)); - // } - - @media (min-width: 1551px) { - grid-template-columns: repeat(auto-fill, minmax(calc(100% / 7), 1fr)); - } - - @media (min-width: 1051px) 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)); - } -} - -.card__list--rewards { column-count: 2; - column-gap: var(--spacing-vertical-medium); - margin-bottom: var(--spacing-vertical-large); + column-gap: var(--spacing-medium); + display: block; .card { display: inline-block; - margin: 0 0 var(--spacing-vertical-medium); - width: 100%; + margin: 0 0 var(--spacing-medium); } } @@ -169,8 +141,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,18 +169,20 @@ .card__subtitle { @extend .help; - background-color: lighten($lbry-gray-1, 7%); - color: darken($lbry-gray-5, 30%); + color: darken($lbry-gray-5, 25%); + background-color: lighten($lbry-gray-1, 5%); 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'] & { @@ -223,10 +196,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 +208,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..c98657cf4 100644 --- a/src/ui/scss/component/_content.scss +++ b/src/ui/scss/component/_content.scss @@ -23,7 +23,7 @@ .button--view, .button--play { - background-color: $lbry-green-3; + background-color: $lbry-teal-2; } } } @@ -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; } @@ -104,6 +104,7 @@ } .content__view--container { + background-color: black; width: 100%; height: 100%; top: 0; 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..ded90bb46 --- /dev/null +++ b/src/ui/scss/component/_file-list.scss @@ -0,0 +1,154 @@ +.file-list__header { + display: flex; + align-items: center; + height: 4.5rem; + padding: var(--spacing-medium); + font-size: 1rem; // Ensures select & header text have same font-size + 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; + } + + // Normal link buttons are too dark on the black file list background + .button--link { + color: $lbry-teal-3; + + &:hover { + color: $lbry-teal-1; + } + } +} + +.file-list__header--small { + height: 3rem; + font-size: 1em; +} + +.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; + font-size: 1.4em; + + & > * { + 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); + } +} + +.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; +} + +.file-list__meta { + padding: var(--spacing-medium); + background-color: lighten($lbry-teal-5, 55%); +} diff --git a/src/ui/scss/component/_file-properties.scss b/src/ui/scss/component/_file-properties.scss new file mode 100644 index 000000000..c1821ff89 --- /dev/null +++ b/src/ui/scss/component/_file-properties.scss @@ -0,0 +1,23 @@ +.file-properties { + display: flex; + position: relative; + align-items: center; + + & > *:not(:last-child) { + margin-right: var(--spacing-small); + } + + @media (max-width: 600px) { + display: none; + } +} + +.file-properties--large { + flex-wrap: wrap; + font-size: 18px; + margin: var(--spacing-small) 0; + + & > * { + margin-top: var(--spacing-small); + } +} 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..9d4c38db5 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; - } } } @@ -179,15 +176,15 @@ fieldset-group { form { [type='button'], [type='submit'] { - &.button--primary { + &.button--inverse { &:not(:hover) { - background-color: $lbry-teal-5; - border-color: $lbry-teal-5; + background-color: transparent; + border-color: $lbry-black; + color: $lbry-black; } &:hover { - background-color: $lbry-teal-3; - border-color: $lbry-teal-3; + background-color: $lbry-teal-4; } } } @@ -206,6 +203,7 @@ fieldset-section { // input-submit is connected to a button // The input height needs to match the button height to lineup correctly // Other inputs are fine since they are on their own and are used under different circumstances + input[type='email'], input[type='text'] { height: var(--button-height); @@ -221,6 +219,7 @@ fieldset-section { border-bottom-left-radius: 0; border-top-right-radius: var(--input-border-radius); border-bottom-right-radius: var(--input-border-radius); + border-color: $lbry-black; } } @@ -271,12 +270,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..7e6472b3b 100644 --- a/src/ui/scss/component/_header.scss +++ b/src/ui/scss/component/_header.scss @@ -1,39 +1,45 @@ .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; - + box-shadow: var(--card-box-shadow) $lbry-gray-1; + padding-left: var(--spacing-large); + padding-right: var(--spacing-large); 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; + box-shadow: var(--card-box-shadow) $lbry-black; } } +.header__contents { + width: 100%; + height: calc(var(--header-height) - 1px); + max-width: var(--page-max-width); + display: flex; + justify-content: space-between; + margin: auto; +} + .header__navigation { display: flex; + justify-content: space-between; - // First navigation item is the top left wrapper - // This contains the Lbry logo (home link) and forward/back arrows on desktop - &:first-of-type { - @media (min-width: 601px) { - width: calc(var(--side-nav-width) - 1px); + &:last-of-type { + width: calc(var(--side-nav-width) + var(--spacing-medium)); + @media (max-width: 600px) { + display: none; } } } .header__navigation-arrows { display: flex; - - .button__content { - justify-content: center; - } + margin-right: var(--spacing-small); } .header__navigation-item { @@ -44,11 +50,15 @@ border-radius: 0; svg { - stroke: $lbry-black; + stroke: $lbry-gray-5; } &:hover { - background-color: $lbry-gray-1; + color: $lbry-teal-5; + + svg { + stroke: $lbry-teal-5; + } } &.header__navigation-item--active { @@ -56,15 +66,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,15 +93,15 @@ } .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 { flex: 1; font-weight: 800; font-size: 1.2rem; + margin-right: var(--spacing-medium); .lbry-icon { height: 2rem; @@ -91,48 +110,22 @@ } .header__navigation-item--right-action { - .button__content { - padding: 0 var(--spacing-vertical-large); - } -} - -.header__navigation-item--right-action:not(:last-child), -.header__navigation-item--lbry { - border-right: 1px solid $lbry-gray-1; - - html[data-mode='dark'] & { - border-right: 1px solid $lbry-gray-5; - } + align-self: flex-end; + margin-left: auto; + padding: 0 var(--spacing-small); } .header__navigation-item--upgrade { - background-color: $lbry-teal-5; - color: $lbry-white; + color: $lbry-teal-5; svg { - stroke: $lbry-white; - } - - &:hover { - background-color: $lbry-teal-4; + stroke: $lbry-teal-5; } } -.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 deleted file mode 100644 index a7f3acd12..000000000 --- a/src/ui/scss/component/_icon.scss +++ /dev/null @@ -1,10 +0,0 @@ -// Not all icons are created equally... at least the react-feather ones aren't -// Minor adjustments to ensure icons line up vertically - -.icon--Heart { - top: -1px; -} - -.icon--Flag { - 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..89d5c205d 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(--header-height); + padding-left: var(--spacing-large); + padding-right: var(--spacing-large); 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-large); + 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-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,20 @@ } .grid-area--related { grid-area: related; + min-width: 30rem; + max-width: 35rem; + } + + @media (max-width: 600px) { + grid-template-areas: + 'content' + 'info' + 'related'; + + .grid-area--related { + grid-area: related; + width: auto; + } } } @@ -70,20 +78,19 @@ 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)); +.main__status { + @extend .help; + display: flex; + justify-content: space-between; + background-color: $lbry-teal-4; + color: $lbry-white; + + svg { + stroke: $lbry-white; + } } 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..324e00af9 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; @@ -85,15 +85,19 @@ font-family: Consolas, 'Lucida Console', 'Source Sans', monospace; } - a { - color: $lbry-blue-1; + a, + button { display: inline-block; + + .button__label { + white-space: normal; + } } // Lists ul, ol { - margin-bottom: var(--spacing-vertical-medium); + margin-bottom: var(--spacing-medium); > li { list-style-position: outside; @@ -105,7 +109,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..32b310665 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); } } @@ -132,14 +59,19 @@ display: inline; font-size: 2rem; line-height: 1.33; - margin-right: var(--spacing-vertical-small); + margin-right: var(--spacing-small); +} + +.media__uri-wrapper { + display: flex; + justify-content: space-between; + margin-bottom: var(--spacing-small); } .media__uri { font-size: 1.1rem; - padding-bottom: 5px; - opacity: 0.6; - user-select: all; + min-width: 0; + margin-right: var(--spacing-small); } .media__insufficient-credits { @@ -152,8 +84,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 +94,25 @@ .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 +120,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 +133,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 +182,7 @@ .media__info--large { border-top: 1px solid $lbry-gray-1; - padding-top: var(--spacing-vertical-medium); + margin-top: var(--spacing-medium); html[data-mode='dark'] & { border-color: rgba($lbry-gray-5, 0.2); @@ -263,7 +194,7 @@ word-break: break-word; &:not(:last-of-type) { - margin-bottom: var(--spacing-vertical-large); + margin-bottom: var(--spacing-large); } &.media__info-text--small { @@ -273,12 +204,17 @@ &.media__info-text--center { text-align: center; } + + .button__label { + text-align: left; + white-space: normal; + } } .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 +222,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 +237,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..10f8928f6 100644 --- a/src/ui/scss/component/_navigation.scss +++ b/src/ui/scss/component/_navigation.scss @@ -1,126 +1,85 @@ +.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; + margin-top: var(--spacing-small); } .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%); + margin-top: var(--spacing-miniscule); - &::before { - top: 0; - left: 0; - width: 0; - background-color: $lbry-teal-3; - content: ''; - height: 100%; - position: absolute; - transition: width 0.2s; + .icon { + margin-right: var(--spacing-small); } - &:not(.navigation__link--title):hover, - &.navigation__link--active { - color: $lbry-black; + &:hover { + color: $lbry-teal-4; + .icon { + color: $lbry-teal-4; + } + } - html[data-mode='dark'] & { + &.navigation__link--active { + color: $lbry-teal-5; + .icon { + color: $lbry-teal-4; + } + } + + [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 { @@ -157,3 +116,7 @@ } } } + +.navigation__link--indented { + padding-left: 2rem; +} 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..38c2405d8 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,16 @@ } .spinner--small { - width: 40px; - height: 32px; - - font-size: 10px; - margin: var(--spacing-vertical-small) 0; - text-align: center; + height: 10px; + display: inline-block; .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..df0dbab62 100644 --- a/src/ui/scss/component/_table.scss +++ b/src/ui/scss/component/_table.scss @@ -2,6 +2,8 @@ table, .table { + margin-bottom: var(--spacing-small); + [data-mode='dark'] & { background-color: transparent; @@ -9,6 +11,23 @@ table, border-bottom: 2px solid $lbry-white; } } + + th, + td { + padding-left: var(--spacing-large); + } +} + +td { + overflow: hidden; +} + +.table__header { + margin: var(--spacing-large); + + & + .table__header { + margin-top: 0; + } } .table__item--actionable { @@ -17,9 +36,13 @@ table, } } +.table__item-label { + font-size: 0.9em; + font-weight: 300; +} + .table--help { td:nth-of-type(1) { - font-weight: 600; min-width: 130px; } @@ -31,16 +54,11 @@ 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 - .button__content { + a, + button { @include constrict(10rem); vertical-align: bottom; display: inline-block; @@ -69,6 +87,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..91e04d49d --- /dev/null +++ b/src/ui/scss/component/_tags.scss @@ -0,0 +1,79 @@ +$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--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; + min-width: 0; + + &:hover { + background-color: $lbry-teal-4; + color: $lbry-white; + svg { + stroke: $lbry-white; + } + } + + &:active { + background-color: $main; + } +} + +.tag--remove { + @extend .tag; + max-width: 20rem; +} + +.tag--add { + background-color: lighten($lbry-teal-5, 60%); +} + +.tag__action-label { + margin-left: 0.5rem; + padding-left: 0.5rem; + padding-top: 0.7rem; + + 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..f58e10173 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: calc(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,35 @@ .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; - display: flex; - justify-content: center; - padding-left: var(--spacing-vertical-large); - transition: background-color 0.2s; + border: none; + min-width: 0; + padding-right: var(--spacing-small); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + 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 +85,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 +98,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 +107,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/menu-button.scss b/src/ui/scss/component/menu-button.scss new file mode 100644 index 000000000..f3613f74a --- /dev/null +++ b/src/ui/scss/component/menu-button.scss @@ -0,0 +1,97 @@ +// Extends reach-ui menu button base stylesheet +/* Used to detect in JavaScript if apps have loaded styles or not. */ +:root { + --reach-menu-button: 1; +} + +[data-reach-menu] { + font-family: sans-serif; + display: block; + position: absolute; +} + +[data-reach-menu-list] { + display: block; + white-space: nowrap; + outline: none; + font-size: 1.2rem; + background-color: $lbry-white; + box-shadow: 0px 10px 30px 2px $lbry-gray-2; + border: 1px solid $lbry-gray-1; + border-top: none; + + [data-mode='dark'] & { + background-color: lighten($lbry-black, 10%); + color: $lbry-white; + box-shadow: 0 10px 30px 2px $lbry-black; + border: 1px solid $lbry-gray-5; + border-top: none; + } +} + +[data-reach-menu-item] { + display: block; +} + +[data-reach-menu-item] { + cursor: pointer; + display: block; + color: inherit; + font: inherit; + text-decoration: initial; +} + +[data-reach-menu-item][data-selected] { + background: lighten($lbry-teal-5, 55%); + color: darken($lbry-teal-5, 15%); + outline: none; + + &:active { + background-color: $lbry-teal-4; + color: $lbry-white; + .icon { + stroke: $lbry-white; + } + } + + [data-mode='dark'] & { + background-color: $lbry-teal-5; + color: $lbry-white; + + &:hover, + &:focus { + .icon { + stroke: $lbry-white; + } + } + } +} + +.menu__title { + padding: var(--spacing-large) 0; + padding-left: var(--spacing-medium); + padding-right: 0; + + span { + margin-left: var(--spacing-small); + } +} + +.menu__link { + display: flex; + align-items: center; + padding: var(--spacing-medium); +} + +.menu__title, +.menu__link { + font-size: 1.3rem; + color: lighten($lbry-black, 20%); + + .icon { + margin-right: var(--spacing-small); + margin-bottom: 0.2rem; + + stroke: $lbry-gray-5; + } +} 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..9e55bd7ac 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%); } } @@ -53,10 +54,10 @@ code { justify-content: space-between; > * { - flex-basis: 0; flex-grow: 1; + flex-basis: 0; - &:not(:last-of-type) { + &:first-child { margin-right: 1.5rem; } } @@ -129,11 +130,11 @@ code { .help { font-size: 1rem; background-color: rgba($lbry-blue-1, 0.1); - color: $lbry-gray-5; + color: darken($lbry-gray-5, 15%); 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..bc7fb44aa 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: 6rem; // 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/format-credits.js b/src/ui/util/format-credits.js index 339ea3c42..0f36c8b82 100644 --- a/src/ui/util/format-credits.js +++ b/src/ui/util/format-credits.js @@ -5,8 +5,11 @@ export function formatCredits(amount, precision = 1) { } export function formatFullPrice(amount, precision = 1) { - let formated = ''; + if (!amount) { + return 0; + } + let formated = ''; const quantity = amount.toString().split('.'); const fraction = quantity[1]; 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/src/ui/util/use-tween.js b/src/ui/util/use-tween.js new file mode 100644 index 000000000..724db2f03 --- /dev/null +++ b/src/ui/util/use-tween.js @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react'; + +const getProgress = (elapsed, duration) => Math.min(elapsed / duration, 1); +const easeOut = progress => Math.pow(progress - 1, 5) + 1; + +export default function useTween(duration, onRest) { + const [value, setValue] = useState(0); + + useEffect(() => { + let start = performance.now(); + let elapsed = 0; + let frame; + + const tick = now => { + elapsed = now - start; + const progress = getProgress(elapsed, duration); + setValue(easeOut(progress)); + if (progress < 1) { + frame = requestAnimationFrame(tick); + } else { + onRest && onRest(); + } + }; + + frame = requestAnimationFrame(tick); + + return () => cancelAnimationFrame(frame); + }, [duration, onRest]); + + return value; +} 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/static/locales/en.json b/static/locales/en.json index d00f01883..240e27e31 100644 --- a/static/locales/en.json +++ b/static/locales/en.json @@ -4,80 +4,192 @@ "Cancel": "Cancel", "Show More...": "Show More...", "Show Less": "Show Less", - "Starting up": "Starting up", - "Connecting": "Connecting", "LBRY": "LBRY", "Navigate back": "Navigate back", "Navigate forward": "Navigate forward", - "Menu": "Menu", - "Your wallet": "Your wallet", - "Publish content": "Publish content", + "Account": "Account", + "Overview": "Overview", + "Wallet": "Wallet", "Publish": "Publish", - "Discover": "Discover", + "Settings": "Settings", + "Help": "Help", + "Make This Your Own": "Make This Your Own", + "For": "For", + "No results": "No results", + "Home": "Home", "Subscriptions": "Subscriptions", "Publishes": "Publishes", "Library": "Library", - "Overview": "Overview", - "Invite": "Invite", - "Rewards": "Rewards", - "Send & Recieve": "Send & Recieve", - "Transactions": "Transactions", - "Settings": "Settings", - "Help": "Help", - "Failed to load landing content.": "Failed to load landing content.", - "Hi There": "Hi There", - "Using LBRY is like dating a centaur. Totally normal up top, and": "Using LBRY is like dating a centaur. Totally normal up top, and", - "way different": "way different", - "underneath.": "underneath.", - "Up top, LBRY is similar to popular media sites.": "Up top, LBRY is similar to popular media sites.", - "Below, LBRY is controlled by users -- you -- via blockchain and decentralization.": "Below, LBRY is controlled by users -- you -- via blockchain and decentralization.", - "I'm In": "I'm In", - "You Are Awesome!": "You Are Awesome!", - "Check out some of the neat content below me. I'll see you around!": "Check out some of the neat content below me. I'll see you around!", - "Lets Get Started": "Lets Get Started", - "Not Now": "Not Now", + "Following": "Following", + "The tags you follow will change what's trending for you.": "The tags you follow will change what's trending for you.", + "Tags": "Tags", + "Search for more tags": "Search for more tags", + "Publish content": "Publish content", + "Unfollow this tag": "Unfollow this tag", + "Follow this tag": "Follow this tag", + "Published on": "Published on", + "Send a tip": "Send a tip", + "Share": "Share", + "Play": "Play", "Subscribe": "Subscribe", - "Unsubscribe": "Unsubscribe", + "Report content": "Report content", + "Content-Type": "Content-Type", + "Languages": "Languages", + "License": "License", + "Want to comment?": "Want to comment?", + "More": "More", + "FREE": "FREE", + "Related": "Related", + "No related content found": "No related content found", + "Download": "Download", + "Content": "Content", + "What are you publishing?": "What are you publishing?", + "Read our": "Read our", + "FAQ": "FAQ", + "to learn more.": "to learn more.", + "Title": "Title", + "Titular Title": "Titular Title", + "Description": "Description", + "Description of your content": "Description of your content", + "Thumbnail": "Thumbnail", + "Upload your thumbnail (.png/.jpg/.jpeg/.gif) to": "Upload your thumbnail (.png/.jpg/.jpeg/.gif) to", + "spee.ch": "spee.ch", + "Recommended size: 800x450 (16:9)": "Recommended size: 800x450 (16:9)", + "Price": "Price", + "How much will this content cost?": "How much will this content cost?", + "Free": "Free", + "Choose price": "Choose price", + "Anonymous or under a channel?": "Anonymous or under a channel?", + "This is a username or handle that your content can be found under.": "This is a username or handle that your content can be found under.", + "Ex. @Marvel, @TheBeatles, @BooksByJoe": "Ex. @Marvel, @TheBeatles, @BooksByJoe", + "Where can people find this content?": "Where can people find this content?", + "The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).": "The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).", + "Learn more": "Learn more", + "Name": "Name", + "Deposit (LBC)": "Deposit (LBC)", + "Mature audiences only": "Mature audiences only", + "Language": "Language", + "English": "English", + "Chinese": "Chinese", + "French": "French", + "German": "German", + "Japanese": "Japanese", + "Russian": "Russian", + "Spanish": "Spanish", + "Indonesian": "Indonesian", + "Italian": "Italian", + "Dutch": "Dutch", + "Turkish": "Turkish", + "Polish": "Polish", + "Malay": "Malay", + "By continuing, you accept the": "By continuing, you accept the", + "LBRY Terms of Service": "LBRY Terms of Service", + "Choose File": "Choose File", + "No File Chosen": "No File Chosen", + "Choose Thumbnail": "Choose Thumbnail", + "Enter a thumbnail URL": "Enter a thumbnail URL", + "Anonymous": "Anonymous", + "New channel...": "New channel...", + "You already have a claim at": "You already have a claim at", + "Publishing will update your existing claim.": "Publishing will update your existing claim.", + "Any amount will give you the winning bid.": "Any amount will give you the winning bid.", + "This LBC remains yours and the deposit can be undone at any time.": "This LBC remains yours and the deposit can be undone at any time.", + "License (Optional)": "License (Optional)", + "None": "None", + "Public Domain": "Public Domain", + "Copyrighted...": "Copyrighted...", + "Other...": "Other...", + "Email": "Email", + "Your email has been successfully verified": "Your email has been successfully verified", + "Your Email": "Your Email", + "Change": "Change", + "This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards.": "This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards.", + "Rewards": "Rewards", + "You have": "You have", + "in unclaimed rewards": "in unclaimed rewards", + "Claim Rewards": "Claim Rewards", + "LBC": "LBC", + "Earned From Rewards": "Earned From Rewards", "Invite a Friend": "Invite a Friend", "When your friends start using LBRY, the network gets stronger!": "When your friends start using LBRY, the network gets stronger!", "Or share this link with your friends": "Or share this link with your friends", "Earn": "Earn", "rewards": "rewards", "for inviting your friends.": "for inviting your friends.", - "Read our": "Read our", - "FAQ": "FAQ", "to learn more about referrals": "to learn more about referrals", - "Woah, you have a lot of friends! You've claimed the maximum amount of referral rewards. Check back soon to see if more are available!.": "Woah, you have a lot of friends! You've claimed the maximum amount of referral rewards. Check back soon to see if more are available!.", - "Invite History": "Invite History", - "Claim Your 150 LBC Invite Reward": "Claim Your 150 LBC Invite Reward", - "Invitee Email": "Invitee Email", - "Invite Status": "Invite Status", - "Reward": "Reward", - "Not Accepted": "Not Accepted", - "Unclaimable": "Unclaimable", - "Accepted": "Accepted", - "Claimed": "Claimed", - "Claimable": "Claimable", + "Power To The People": "Power To The People", + "LBRY is powered by the users. More users, more power… and with great power comes great responsibility.": "LBRY is powered by the users. More users, more power… and with great power comes great responsibility.", "Checking your invite status": "Checking your invite status", + "You have ...": "You have ...", + "You have no rewards available, please check": "You have no rewards available, please check", + "Don't Miss Out": "Don't Miss Out", + "We'll let you know about LBRY updates, security issues, and great new content.": "We'll let you know about LBRY updates, security issues, and great new content.", + "Your email address will never be sold and you can unsubscribe at any time.": "Your email address will never be sold and you can unsubscribe at any time.", + "View Rewards": "View Rewards", + "Latest From Your Subscriptions": "Latest From Your Subscriptions", + "Find New Channels": "Find New Channels", + "Discover New Channels": "Discover New Channels", + "View Your Subscriptions": "View Your Subscriptions", + "publishes": "publishes", + "About": "About", + "Share Channel": "Share Channel", + "This channel hasn't uploaded anything.": "This channel hasn't uploaded anything.", + "Go to page:": "Go to page:", + "Nothing here yet": "Nothing here yet", + "Enter a URL for your thumbnail.": "Enter a URL for your thumbnail.", + "Thumbnail Preview": "Thumbnail Preview", + "Use thumbnail upload tool": "Use thumbnail upload tool", + "Create a URL for this content. Simpler names are easier to find and remember.": "Create a URL for this content. Simpler names are easier to find and remember.", + "Subscribed": "Subscribed", + "Open file": "Open file", + "Delete this file": "Delete this file", + "Delete": "Delete", + "Downloaded to": "Downloaded to", + "You have...": "You have...", "Balance": "Balance", "You currently have": "You currently have", - "LBC": "LBC", - "You have": "You have", - "in unclaimed rewards": "in unclaimed rewards", - "Claim Rewards": "Claim Rewards", - "to learn more about LBRY Rewards": "to learn more about LBRY Rewards", "Recent Transactions": "Recent Transactions", - "No transactions... yet.": "No transactions... yet.", - "Refresh": "Refresh", - "Loading transactions": "Loading transactions", "Looks like you don't have any recent transactions.": "Looks like you don't have any recent transactions.", "Full History": "Full History", + "Refresh": "Refresh", + "Send Credits": "Send Credits", + "Send LBC to your friends or favorite creators": "Send LBC to your friends or favorite creators", "Amount": "Amount", + "Recipient address": "Recipient address", + "Send": "Send", + "Receive Credits": "Receive Credits", + "Use this wallet address to receive credits sent by another user (or yourself).": "Use this wallet address to receive credits sent by another user (or yourself).", + "Address copied.": "Address copied.", + "Get New Address": "Get New Address", + "Show QR code": "Show QR code", + "You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.": "You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.", "Type": "Type", "Details": "Details", "Transaction": "Transaction", "Date": "Date", - "Unlock Tip": "Unlock Tip", + "Abandon Claim": "Abandon Claim", + "fee": "fee", + "Find New Tags To Follow": "Find New Tags To Follow", + "Aw shucks!": "Aw shucks!", + "There was an error. It's been reported and will be fixed": "There was an error. It's been reported and will be fixed", + "Try": "Try", + "refreshing the app": "refreshing the app", + "to fix it": "to fix it", + "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", @@ -104,178 +216,40 @@ "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.", - "Learn more": "Learn more", "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", - "Price": "Price", "Currency": "Currency", "LBRY Credits (LBC)": "LBRY Credits (LBC)", "US Dollars": "US Dollars", - "Blockchain Sync": "Blockchain Sync", - "Catching up with the blockchain": "Catching up with the blockchain", - "Uh oh. Sean must have messed something up. Try refreshing to fix it.": "Uh oh. Sean must have messed something up. Try refreshing to fix it.", - "If you still have issues, your anti-virus software or firewall may be preventing startup.": "If you still have issues, your anti-virus software or firewall may be preventing startup.", - "Reach out to hello@lbry.com for help, or check out": "Reach out to hello@lbry.com for help, or check out", - "Account": "Account", - "Make This Your Own": "Make This Your Own", - "You are already following a couple tags, try searching for a new one.": "You are already following a couple tags, try searching for a new one.", - "Tags": "Tags", - "Home": "Home", - "Aw shucks!": "Aw shucks!", - "There was an error. It's been reported and will be fixed": "There was an error. It's been reported and will be fixed", - "Try": "Try", - "refreshing the app": "refreshing the app", - "to fix it": "to fix it", - "NEW": "NEW", - "The publisher has chosen to charge LBC to view this content. Your balance is currently to low to view it.": "The publisher has chosen to charge LBC to view this content. Your balance is currently to low to view it.", - "Checkout": "Checkout", - "the rewards page": "the rewards page", - "or send more LBC to your wallet.": "or send more LBC to your wallet.", - "Published on": "Published on", - "Send a tip": "Send a tip", - "Share": "Share", - "Play": "Play", - "Report content": "Report content", - "Content-Type": "Content-Type", - "Languages": "Languages", - "License": "License", - "Want to comment?": "Want to comment?", - "More": "More", - "Download": "Download", - "Content": "Content", - "What are you publishing?": "What are you publishing?", - "to learn more.": "to learn more.", - "Title": "Title", - "Titular Title": "Titular Title", - "Description": "Description", - "Description of your content": "Description of your content", - "Thumbnail": "Thumbnail", - "Enter a URL for your thumbnail.": "Enter a URL for your thumbnail.", - "How much will this content cost?": "How much will this content cost?", - "Free": "Free", - "Choose price": "Choose price", - "Anonymous or under a channel?": "Anonymous or under a channel?", - "This is a username or handle that your content can be found under.": "This is a username or handle that your content can be found under.", - "Ex. @Marvel, @TheBeatles, @BooksByJoe": "Ex. @Marvel, @TheBeatles, @BooksByJoe", - "Where can people find this content?": "Where can people find this content?", - "The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).": "The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).", - "Name": "Name", - "Deposit (LBC)": "Deposit (LBC)", - "Mature audiences only": "Mature audiences only", - "Language": "Language", - "English": "English", - "Chinese": "Chinese", - "French": "French", - "German": "German", - "Japanese": "Japanese", - "Russian": "Russian", - "Spanish": "Spanish", - "Indonesian": "Indonesian", - "Italian": "Italian", - "Dutch": "Dutch", - "Turkish": "Turkish", - "Polish": "Polish", - "Malay": "Malay", - "By continuing, you accept the": "By continuing, you accept the", - "LBRY Terms of Service": "LBRY Terms of Service", - "Choose File": "Choose File", - "No File Chosen": "No File Chosen", - "Thumbnail Preview": "Thumbnail Preview", - "Use thumbnail upload tool": "Use thumbnail upload tool", - "Anonymous": "Anonymous", - "New channel...": "New channel...", - "Create a URL for this content. Simpler names are easier to find and remember.": "Create a URL for this content. Simpler names are easier to find and remember.", - "Any amount will give you the winning bid.": "Any amount will give you the winning bid.", - "This LBC remains yours and the deposit can be undone at any time.": "This LBC remains yours and the deposit can be undone at any time.", - "License (Optional)": "License (Optional)", - "None": "None", - "Public Domain": "Public Domain", - "Copyrighted...": "Copyrighted...", - "Other...": "Other...", - "Upload your thumbnail (.png/.jpg/.jpeg/.gif) to": "Upload your thumbnail (.png/.jpg/.jpeg/.gif) to", - "spee.ch": "spee.ch", - "Recommended size: 800x450 (16:9)": "Recommended size: 800x450 (16:9)", - "Choose Thumbnail": "Choose Thumbnail", - "Enter a thumbnail URL": "Enter a thumbnail URL", - "Receive Credits": "Receive Credits", - "Use this wallet address to receive credits sent by another user (or yourself).": "Use this wallet address to receive credits sent by another user (or yourself).", - "Address copied.": "Address copied.", - "Get New Address": "Get New Address", - "Show QR code": "Show QR code", - "You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.": "You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.", - "Send Credits": "Send Credits", - "Send LBC to your friends or favorite creators": "Send LBC to your friends or favorite creators", - "Recipient address": "Recipient address", - "Send": "Send", - "No transactions.": "No transactions.", - "FREE": "FREE", - "Search": "Search", - "Invalid character %s in name: %s.": "Invalid character %s in name: %s.", - "View file": "View file", "There's nothing available at this location.": "There's nothing available at this location.", "Loading decentralized data...": "Loading decentralized data...", - "See All Visited Links": "See All Visited Links", - "Open file": "Open file", - "Delete this file": "Delete this file", - "Delete": "Delete", - "Downloaded to": "Downloaded to", - "Drop to remove": "Drop to remove", - "About": "About", - "Share Channel": "Share Channel", - "Newest First": "Newest First", - "Oldest First": "Oldest First", - "Go to page:": "Go to page:", - "Nothing here yet": "Nothing here yet", - "Claim sequence must be a number.": "Claim sequence must be a number.", - "Requesting stream...": "Requesting stream...", - "Connecting...": "Connecting...", - "Confirm Purchase": "Confirm Purchase", - "This will purchase": "This will purchase", - "for": "for", - "credits": "credits", - "Downloading stream... not long left now!": "Downloading stream... not long left now!", - "Downloading: ": "Downloading: ", - "% complete": "% complete", - "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.", - "files": "files", - "hidden due to your": "hidden due to your", - "content viewing preferences": "content viewing preferences", + "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", - "Find New Channels": "Find New Channels", - "Latest From Your Subscriptions": "Latest From Your Subscriptions", - "publishes": "publishes", - "No modifier provided after separator %s.": "No modifier provided after separator %s.", - "View Your Subscriptions": "View Your Subscriptions", - "You might like these channels": "You might like these channels", - "This channel hasn't uploaded anything.": "This channel hasn't uploaded anything.", - "Web link": "Web link", - "Facebook": "Facebook", - "": "", - "Twitter": "Twitter", - "View on Spee.ch": "View on Spee.ch", - "LBRY App link": "LBRY App link", - "Done": "Done", - "fee": "fee", + "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": "View", - "No results": "No results", - "Following": "Following", - "Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.": "Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.", - "Failed to load settings.": "Failed to load settings.", - "Multi-language support is brand new. Switching your language may have unintended consequences. Email ": "Multi-language support is brand new. Switching your language may have unintended consequences. Email ", - "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." + "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 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 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..d787eca78 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" @@ -893,22 +909,41 @@ resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf" integrity sha512-dEVG+ITnvqKGa4v040tP+n8LOKOqr94qjLva7bE5pnfm2KHJwsKz69J4KMxgWLznbpBJzy8vQfCayEk3vLZnZQ== -"@reach/auto-id@^0.2.0": +"@reach/auto-id@0.2.0", "@reach/auto-id@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.2.0.tgz#97f9e48fe736aa5c6f4f32cf73c1f19d005f8550" integrity sha512-lVK/svL2HuQdp7jgvlrLkFsUx50Az9chAhxpiPwBqcS83I2pVWvXp98FOcSCCJCV++l115QmzHhFd+ycw1zLBg== -"@reach/component-component@^0.1.3": +"@reach/component-component@0.1.3", "@reach/component-component@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@reach/component-component/-/component-component-0.1.3.tgz#5d156319572dc38995b246f81878bc2577c517e5" integrity sha512-a1USH7L3bEfDdPN4iNZGvMEFuBfkdG+QNybeyDv8RloVFgZYRoM+KGXyy2KOfEnTUM8QWDRSROwaL3+ts5Angg== +"@reach/menu-button@^0.1.18": + version "0.1.18" + resolved "https://registry.yarnpkg.com/@reach/menu-button/-/menu-button-0.1.18.tgz#cb9e3bf1c2a2bdb5d618697b87ad353dfbca123e" + integrity sha512-MGdN8SWaQ0u0xj6KWUnK9fFc1VBg8NhNnFhEd2sp3D56XJcr08HU5GeVq/MFf5C97mxRnIPIKGnMPPeGAn9OFA== + dependencies: + "@reach/component-component" "0.1.3" + "@reach/portal" "^0.2.1" + "@reach/rect" "0.2.1" + "@reach/utils" "^0.2.3" + "@reach/window-size" "^0.1.4" + warning "^4.0.2" + "@reach/observe-rect@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.0.3.tgz#2ea3dcc369ab22bd9f050a92ea319321356a61e8" integrity sha1-LqPcw2mrIr2fBQqS6jGTITVqYeg= -"@reach/rect@^0.2.1": +"@reach/portal@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.2.1.tgz#07720b999e0063a9e179c14dbdc60fd991cfc9fa" + integrity sha512-pUQ0EtCcYm4ormEjJmdk4uzZCxOpaRHB8FDKJXy6q6GqRqQwZ4lAT1f2Tvw0DAmULmyZTpe1/heXY27Tdnct+Q== + dependencies: + "@reach/component-component" "^0.1.3" + +"@reach/rect@0.2.1", "@reach/rect@^0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.2.1.tgz#7343020174c90e2290b844d17c03fd9c78e6b601" integrity sha512-aZ9RsNHDMQ3zETonikqu9/85iXxj+LPqZ9Gr9UAncj3AufYmGeWG3XG6b37B+7ORH+mkhVpLU2ZlIWxmOe9Cqg== @@ -925,11 +960,40 @@ "@reach/utils" "^0.2.2" warning "^4.0.2" +"@reach/tooltip@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.2.1.tgz#70a80d6defedee53cedf5480cd3d37dfb20020d0" + integrity sha512-3O7oXoymNkEAolHN9WbuspY7mA3zIOrTaibmYkKFtGT6FgyBrAQyOQn1ZhBuSza6RjSIkEtFRpbDEKK1UJEI6A== + dependencies: + "@reach/auto-id" "0.2.0" + "@reach/portal" "^0.2.1" + "@reach/rect" "^0.2.1" + "@reach/utils" "^0.2.3" + "@reach/visually-hidden" "^0.1.4" + prop-types "^15.7.2" + "@reach/utils@^0.2.2": version "0.2.2" resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.2.tgz#c3a05ae9fd1f921988ae8a89b5a0d28d1a2b92df" integrity sha512-jYeIi46AA5jh2gfdXD/nInUYfeLp3girRafiajP7AVHF6B4hpYAzUSx/ZH4xmPyf5alut5rml2DHxrv+X+Xu+A== +"@reach/utils@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.3.tgz#820f6a6af4301b4c5065cfc04bb89e6a3d1d723f" + integrity sha512-zM9rA8jDchr05giMhL95dPeYkK67cBQnIhCVrOKKqgWGsv+2GE/HZqeptvU4zqs0BvIqsThwov+YxVNVh5csTQ== + +"@reach/visually-hidden@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.1.4.tgz#0dc4ecedf523004337214187db70a46183bd945b" + integrity sha512-QHbzXjflSlCvDd6vJwdwx16mSB+vUCCQMiU/wK/CgVNPibtpEiIbisyxkpZc55DyDFNUIqP91rSUsNae+ogGDQ== + +"@reach/window-size@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@reach/window-size/-/window-size-0.1.4.tgz#3257b646548f61c2708a661a683620fbe0a706cb" + integrity sha512-JZshEuGsLvi6fUIJ7Unx12yNeM5SmqWjber2MLr9tfwf1hpNv73EiPBOIJyV0DjW7GXzjcOEvwnqysm59s2s/A== + dependencies: + "@reach/component-component" "^0.1.3" + "@samverschueren/stream-to-observable@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" @@ -6572,9 +6636,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#6a447d0542d23b9a37e266f5f85d3bde5297a451: 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/6a447d0542d23b9a37e266f5f85d3bde5297a451" dependencies: proxy-polyfill "0.1.6" reselect "^3.0.0" @@ -9496,6 +9560,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"