diff --git a/.eslintrc.json b/.eslintrc.json index 6a8430a1c..7f85a5aea 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -23,6 +23,7 @@ "WEBPACK_PORT": true }, "rules": { + "brace-style": 0, "comma-dangle": ["error", "always-multiline"], "handle-callback-err": 0, "indent": 0, diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c90d4a6c..cdfbebc7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.34.0] - [Unreleased] + +### Fixed + +- Lots of speed improvements + +### Added + +- New app design for better content discovery ([2477](https://github.com/lbryio/lbry-desktop/pull/2477)) +- New loading page ([2491](https://github.com/lbryio/lbry-desktop/pull/2491)) +- Comments ([2510](https://github.com/lbryio/lbry-desktop/pull/2510)) + ## [0.33.1] - [2019-06-12] ### Fixed diff --git a/package.json b/package.json index 8eb59eb26..e5a785903 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.33.1", + "version": "0.34.0-rc.3", "description": "A browser for the LBRY network, a digital marketplace controlled by its users.", "keywords": [ "lbry" @@ -63,7 +63,7 @@ "@exponent/electron-cookies": "^2.0.0", "@hot-loader/react-dom": "16.8", "@lbry/color": "^1.0.2", - "@lbry/components": "^2.7.2", + "@lbry/components": "^2.7.4", "@reach/rect": "^0.2.1", "@reach/tabs": "^0.1.5", "@reach/tooltip": "^0.2.1", @@ -123,7 +123,7 @@ "jsmediatags": "^3.8.1", "json-loader": "^0.5.4", "lbry-format": "https://github.com/lbryio/lbry-format.git", - "lbry-redux": "lbryio/lbry-redux#b3bf3f6d53410ff1c5415b51ca425341e364959f", + "lbry-redux": "lbryio/lbry-redux#2930ad82a90ca91f6caf3761597ef9a67da7db66", "lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845", "lint-staged": "^7.0.2", "localforage": "^1.7.1", @@ -198,7 +198,7 @@ "yarn": "^1.3" }, "lbrySettings": { - "lbrynetDaemonVersion": "0.38.0rc10", + "lbrynetDaemonVersion": "0.38.0", "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip", "lbrynetDaemonDir": "static/daemon", "lbrynetDaemonFileName": "lbrynet" diff --git a/src/ui/component/app/index.js b/src/ui/component/app/index.js index e0c6274dc..8e1482f5e 100644 --- a/src/ui/component/app/index.js +++ b/src/ui/component/app/index.js @@ -1,6 +1,6 @@ import { hot } from 'react-hot-loader/root'; import { connect } from 'react-redux'; -import { doUpdateBlockHeight, doError } from 'lbry-redux'; +import { doUpdateBlockHeight, doError, doFetchTransactions } from 'lbry-redux'; import { selectUser, doRewardList, doFetchRewardedContent } from 'lbryinc'; import { selectThemePath } from 'redux/selectors/settings'; import App from './view'; @@ -15,6 +15,7 @@ const perform = dispatch => ({ updateBlockHeight: () => dispatch(doUpdateBlockHeight()), fetchRewards: () => dispatch(doRewardList()), fetchRewardedContent: () => dispatch(doFetchRewardedContent()), + fetchTransactions: () => dispatch(doFetchTransactions()), }); export default hot( diff --git a/src/ui/component/app/view.jsx b/src/ui/component/app/view.jsx index c59f0054e..0c82d2233 100644 --- a/src/ui/component/app/view.jsx +++ b/src/ui/component/app/view.jsx @@ -9,6 +9,8 @@ import { openContextMenu } from 'util/context-menu'; import useKonamiListener from 'util/enhanced-layout'; import Yrbl from 'component/yrbl'; +export const MAIN_WRAPPER_CLASS = 'main-wrapper'; + type Props = { alertError: (string | {}) => void, pageTitle: ?string, @@ -16,18 +18,23 @@ type Props = { theme: string, fetchRewards: () => void, fetchRewardedContent: () => void, + fetchTransactions: () => void, }; function App(props: Props) { - const { theme, fetchRewards, fetchRewardedContent } = props; + const { theme, fetchRewards, fetchRewardedContent, fetchTransactions } = props; const appRef = useRef(); const isEnhancedLayout = useKonamiListener(); useEffect(() => { ReactModal.setAppElement(appRef.current); - fetchRewards(); fetchRewardedContent(); - }, [fetchRewards, fetchRewardedContent]); + + // @if TARGET='app' + fetchRewards(); + fetchTransactions(); + // @endif + }, [fetchRewards, fetchRewardedContent, fetchTransactions]); useEffect(() => { // $FlowFixMe @@ -38,7 +45,7 @@ function App(props: Props) {
openContextMenu(e)}>
-
+
diff --git a/src/ui/component/claimList/view.jsx b/src/ui/component/claimList/view.jsx index c8f742e4f..46f1877cd 100644 --- a/src/ui/component/claimList/view.jsx +++ b/src/ui/component/claimList/view.jsx @@ -1,6 +1,7 @@ // @flow +import { MAIN_WRAPPER_CLASS } from 'component/app/view'; import type { Node } from 'react'; -import React from 'react'; +import React, { useEffect } from 'react'; import classnames from 'classnames'; import ClaimPreview from 'component/claimPreview'; import Spinner from 'component/spinner'; @@ -18,13 +19,29 @@ type Props = { loading: boolean, type: string, empty?: string, - meta?: Node, + defaultSort?: boolean, + onScrollBottom?: any => void, + page?: number, + pageSize?: number, // 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 { + uris, + headerAltControls, + injectedItem, + loading, + persistedStorageKey, + empty, + defaultSort, + type, + header, + onScrollBottom, + page, + pageSize, + } = props; const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW); const hasUris = uris && !!uris.length; const sortedUris = (hasUris && (currentSort === SORT_NEW ? uris : uris.slice().reverse())) || []; @@ -33,33 +50,62 @@ export default function ClaimList(props: Props) { setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW); } + const urisLength = uris && uris.length; + useEffect(() => { + function handleScroll(e) { + if (pageSize && onScrollBottom) { + const x = document.querySelector(`.${MAIN_WRAPPER_CLASS}`); + + if (x && window.scrollY + window.innerHeight >= x.offsetHeight) { + if (!loading && urisLength >= pageSize) { + onScrollBottom(); + } + } + } + } + + if (onScrollBottom) { + window.addEventListener('scroll', handleScroll); + + return () => { + window.removeEventListener('scroll', handleScroll); + }; + } + }, [loading, onScrollBottom, urisLength]); + return ( -
+
{header !== false && (
- {header || ( - - - - - )} + {header} {loading && } -
{headerAltControls}
+
+ {headerAltControls} + {defaultSort && ( + + + + + )} +
)} - {meta &&
{meta}
} {hasUris && (
    {sortedUris.map((uri, index) => ( - - {index === 4 && injectedItem &&
  • {injectedItem}
  • } + + {index === 4 && injectedItem &&
  • {injectedItem}
  • }
    ))}
diff --git a/src/ui/component/claimListDiscover/index.js b/src/ui/component/claimListDiscover/index.js index 8729ef22f..a781cfbb2 100644 --- a/src/ui/component/claimListDiscover/index.js +++ b/src/ui/component/claimListDiscover/index.js @@ -1,10 +1,12 @@ import { connect } from 'react-redux'; import { doClaimSearch, selectLastClaimSearchUris, selectFetchingClaimSearch, doToggleTagFollow } from 'lbry-redux'; +import { selectSubscriptions } from 'redux/selectors/subscriptions'; import ClaimListDiscover from './view'; const select = state => ({ uris: selectLastClaimSearchUris(state), loading: selectFetchingClaimSearch(state), + subscribedChannels: selectSubscriptions(state), }); const perform = { diff --git a/src/ui/component/claimListDiscover/view.jsx b/src/ui/component/claimListDiscover/view.jsx index 331b4389a..9a34e40a9 100644 --- a/src/ui/component/claimListDiscover/view.jsx +++ b/src/ui/component/claimListDiscover/view.jsx @@ -1,12 +1,14 @@ // @flow import type { Node } from 'react'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import moment from 'moment'; +import usePersistedState from 'util/use-persisted-state'; import { FormField } from 'component/common/form'; import ClaimList from 'component/claimList'; import Tag from 'component/tag'; -import usePersistedState from 'util/use-persisted-state'; +import ClaimPreview from 'component/claimPreview'; +const PAGE_SIZE = 20; const TIME_DAY = 'day'; const TIME_WEEK = 'week'; const TIME_MONTH = 'month'; @@ -14,15 +16,18 @@ const TIME_YEAR = 'year'; const TIME_ALL = 'all'; const SEARCH_SORT_YOU = 'you'; const SEARCH_SORT_ALL = 'everyone'; +const SEARCH_SORT_CHANNELS = 'channels'; + const TYPE_TRENDING = 'trending'; const TYPE_TOP = 'top'; const TYPE_NEW = 'new'; -const SEARCH_FILTER_TYPES = [SEARCH_SORT_YOU, SEARCH_SORT_ALL]; +const SEARCH_FILTER_TYPES = [SEARCH_SORT_YOU, SEARCH_SORT_CHANNELS, 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, + subscribedChannels: Array, doClaimSearch: (number, {}) => void, injectedItem: any, tags: Array, @@ -33,19 +38,30 @@ type Props = { }; 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 { doClaimSearch, uris, tags, loading, personal, injectedItem, meta, subscribedChannels } = props; + const [personalSort, setPersonalSort] = usePersistedState('claim-list-discover:personalSort', SEARCH_SORT_YOU); + const [typeSort, setTypeSort] = usePersistedState('claim-list-discover:typeSort', TYPE_TRENDING); + const [timeSort, setTimeSort] = usePersistedState('claim-list-discover:timeSort', TIME_WEEK); + const [page, setPage] = useState(1); const toCapitalCase = string => string.charAt(0).toUpperCase() + string.slice(1); const tagsString = tags.join(','); + const channelsIdString = subscribedChannels.map(channel => channel.uri.split('#')[1]).join(','); useEffect(() => { - const options = {}; + const options: { + page_size: number, + any_tags?: Array, + order_by?: Array, + channel_ids?: Array, + release_time?: string, + } = { page_size: PAGE_SIZE, page }; const newTags = tagsString.split(','); + const newChannelIds = channelsIdString.split(','); if ((newTags && !personal) || (newTags && personal && personalSort === SEARCH_SORT_YOU)) { options.any_tags = newTags; + } else if (personalSort === SEARCH_SORT_CHANNELS) { + options.channel_ids = newChannelIds; } if (typeSort === TYPE_TRENDING) { @@ -65,7 +81,15 @@ function ClaimListDiscover(props: Props) { } doClaimSearch(20, options); - }, [personal, personalSort, typeSort, timeSort, doClaimSearch, tagsString]); + }, [personal, personalSort, typeSort, timeSort, doClaimSearch, page, tagsString, channelsIdString]); + + function getLabel(type) { + if (type === SEARCH_SORT_ALL) { + return __('Everyone'); + } + + return type === SEARCH_SORT_YOU ? __('Tags You Follow') : __('Channels You Follow'); + } const header = (

@@ -91,20 +115,18 @@ function ClaimListDiscover(props: Props) { name="trending_overview" className="claim-list__dropdown" value={personalSort} - onChange={e => setPersonalSort(e.target.value)} + onChange={e => { + setPage(1); + setPersonalSort(e.target.value); + }} > {SEARCH_FILTER_TYPES.map(type => ( ))} )} -

- ); - - const headerAltControls = ( - {typeSort === 'top' && ( {SEARCH_TIMES.map(time => ( ))} )} - + ); return (
setPage(page + 1)} + page={page} + pageSize={PAGE_SIZE} /> + + {loading && page > 1 && new Array(PAGE_SIZE).fill(1).map((x, i) => )}
); } diff --git a/src/ui/component/claimListItem/index.js b/src/ui/component/claimListItem/index.js deleted file mode 100644 index 6d08b46e0..000000000 --- a/src/ui/component/claimListItem/index.js +++ /dev/null @@ -1,35 +0,0 @@ -import { connect } from 'react-redux'; -import { - doResolveUri, - makeSelectClaimForUri, - makeSelectIsUriResolving, - makeSelectClaimIsMine, - makeSelectClaimIsPending, - makeSelectThumbnailForUri, - makeSelectTitleForUri, - makeSelectClaimIsNsfw, -} from 'lbry-redux'; -import { selectBlackListedOutpoints } from 'lbryinc'; -import { selectShowNsfw } from 'redux/selectors/settings'; -import ClaimPreview from './view'; - -const select = (state, props) => ({ - pending: makeSelectClaimIsPending(props.uri)(state), - claim: makeSelectClaimForUri(props.uri)(state), - obscureNsfw: !selectShowNsfw(state), - claimIsMine: makeSelectClaimIsMine(props.uri)(state), - isResolvingUri: makeSelectIsUriResolving(props.uri)(state), - thumbnail: makeSelectThumbnailForUri(props.uri)(state), - title: makeSelectTitleForUri(props.uri)(state), - nsfw: makeSelectClaimIsNsfw(props.uri)(state), - blackListedOutpoints: selectBlackListedOutpoints(state), -}); - -const perform = dispatch => ({ - resolveUri: uri => dispatch(doResolveUri(uri)), -}); - -export default connect( - select, - perform -)(ClaimPreview); diff --git a/src/ui/component/claimPreview/view.jsx b/src/ui/component/claimPreview/view.jsx index e5b7ac57c..839d79f27 100644 --- a/src/ui/component/claimPreview/view.jsx +++ b/src/ui/component/claimPreview/view.jsx @@ -1,7 +1,7 @@ // @flow import React, { useEffect } from 'react'; import classnames from 'classnames'; -import { convertToShareLink } from 'lbry-redux'; +import { parseURI, convertToShareLink } from 'lbry-redux'; import { withRouter } from 'react-router-dom'; import { openCopyLinkMenu } from 'util/context-menu'; import { formatLbryUriForWeb } from 'util/uri'; @@ -53,8 +53,8 @@ function ClaimPreview(props: Props) { blackListedOutpoints, } = props; const haventFetched = claim === undefined; - const abandoned = !isResolvingUri && !claim; - const isChannel = claim && claim.value_type === 'channel'; + const abandoned = !isResolvingUri && !claim && !placeholder; + const { isChannel } = parseURI(uri); const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0; let shouldHide = abandoned || (!claimIsMine && obscureNsfw && nsfw); @@ -92,12 +92,12 @@ function ClaimPreview(props: Props) { return null; } - if (placeholder && !claim) { + if (placeholder || isResolvingUri) { return ( -
  • +
  • -
    +
  • @@ -109,15 +109,15 @@ function ClaimPreview(props: Props) { role="link" onClick={pending ? undefined : onClick} onContextMenu={handleContextMenu} - className={classnames('claim-list__item', { - 'claim-list__item--large': type === 'large', + className={classnames('claim-preview', { + 'claim-preview--large': type === 'large', 'claim-list__pending': pending, })} > {isChannel ? : } -
    -
    -
    +
    +
    +
    {type !== 'small' && ( @@ -128,7 +128,7 @@ function ClaimPreview(props: Props) { )}
    -
    +
    {pending &&
    Pending...
    } diff --git a/src/ui/component/claimTags/view.jsx b/src/ui/component/claimTags/view.jsx index 04470cf7e..8a64e8750 100644 --- a/src/ui/component/claimTags/view.jsx +++ b/src/ui/component/claimTags/view.jsx @@ -3,7 +3,7 @@ import * as React from 'react'; import classnames from 'classnames'; import Button from 'component/button'; -const SLIM_TAGS = 2; +const SLIM_TAGS = 1; const NORMAL_TAGS = 4; const LARGE_TAGS = 10; diff --git a/src/ui/component/common/icon-custom.jsx b/src/ui/component/common/icon-custom.jsx index a1c696832..19a42c81e 100644 --- a/src/ui/component/common/icon-custom.jsx +++ b/src/ui/component/common/icon-custom.jsx @@ -40,6 +40,12 @@ export const icons = { ), + [ICONS.FEATURED]: buildIcon( + + + + + ), [ICONS.ARROW_LEFT]: buildIcon( @@ -62,7 +68,7 @@ export const icons = { ), - [ICONS.UPLOAD]: buildIcon( + [ICONS.PUBLISH]: buildIcon( ), - [ICONS.PUBLISHED]: buildIcon( - - - - ), - [ICONS.SUBSCRIPTION]: buildIcon( + [ICONS.SUBSCRIBE]: buildIcon( ), + [ICONS.UNSUBSCRIBE]: buildIcon( + + ), [ICONS.SETTINGS]: buildIcon( @@ -103,12 +103,8 @@ export const icons = { [ICONS.OVERVIEW]: buildIcon(), [ICONS.WALLET]: buildIcon( - - - - - - + + ), [ICONS.LIBRARY]: buildIcon(), @@ -210,4 +206,38 @@ export const icons = { [ICONS.CHAT]: buildIcon( ), + [ICONS.YES]: buildIcon( + + ), + [ICONS.NO]: buildIcon( + + ), + [ICONS.UP]: buildIcon(), + [ICONS.DOWN]: buildIcon(), + [ICONS.FULLSCREEN]: buildIcon( + + ), + [ICONS.FILE]: buildIcon( + + + + + ), + [ICONS.CHANNEL]: buildIcon( + + + + + ), + [ICONS.TWITTER]: buildIcon( + + ), + [ICONS.FACEBOOK]: buildIcon(), + [ICONS.WEB]: buildIcon( + + + + + + ), }; diff --git a/src/ui/component/common/icon.jsx b/src/ui/component/common/icon.jsx index 9a2076d51..480bc47f8 100644 --- a/src/ui/component/common/icon.jsx +++ b/src/ui/component/common/icon.jsx @@ -26,7 +26,7 @@ class IconComponent extends React.PureComponent { return __('Featured content. Earn rewards for watching.'); case ICONS.DOWNLOAD: return __('This file is downloaded.'); - case ICONS.SUBSCRIPTION: + case ICONS.SUBSCRIBE: return __('You are subscribed to this channel.'); case ICONS.SETTINGS: return __('Your settings.'); diff --git a/src/ui/component/fileActions/view.jsx b/src/ui/component/fileActions/view.jsx index 6a83b5526..30f4338d6 100644 --- a/src/ui/component/fileActions/view.jsx +++ b/src/ui/component/fileActions/view.jsx @@ -1,5 +1,5 @@ // @flow -import type { Node } from 'react'; +import type { ElementRef } from 'react'; import * as MODALS from 'constants/modal_types'; import * as ICONS from 'constants/icons'; import React from 'react'; @@ -17,7 +17,7 @@ type Props = { openModal: (id: string, { uri: string }) => void, claimIsMine: boolean, fileInfo: FileInfo, - viewerContainer: ?{ current: Node }, + viewerContainer: { current: ElementRef }, showFullscreen: boolean, }; diff --git a/src/ui/component/fileDetails/view.jsx b/src/ui/component/fileDetails/view.jsx index 38e4a46e0..fcf4d89ee 100644 --- a/src/ui/component/fileDetails/view.jsx +++ b/src/ui/component/fileDetails/view.jsx @@ -42,7 +42,6 @@ class FileDetails extends PureComponent { {description && ( -
    About
    diff --git a/src/ui/component/fileProperties/view.jsx b/src/ui/component/fileProperties/view.jsx index b3e3e41eb..e8b1635d0 100644 --- a/src/ui/component/fileProperties/view.jsx +++ b/src/ui/component/fileProperties/view.jsx @@ -4,6 +4,7 @@ import * as React from 'react'; import { parseURI } from 'lbry-redux'; import Icon from 'component/common/icon'; import FilePrice from 'component/filePrice'; +import VideoDuration from 'component/videoDuration'; type Props = { uri: string, @@ -21,10 +22,11 @@ export default function FileProperties(props: Props) { return (
    - {isSubscribed && } + {isSubscribed && } {!claimIsMine && downloaded && } {isRewardContent && } +
    ); } diff --git a/src/ui/component/fileRender/view.jsx b/src/ui/component/fileRender/view.jsx index 94c6adc4f..d2e40ba77 100644 --- a/src/ui/component/fileRender/view.jsx +++ b/src/ui/component/fileRender/view.jsx @@ -1,12 +1,18 @@ // @flow import { remote } from 'electron'; -import React from 'react'; +import React, { Suspense } from 'react'; import LoadingScreen from 'component/common/loading-screen'; import VideoViewer from 'component/viewers/videoViewer'; // Audio player on hold until the current player is dropped // This component is half working // const AudioViewer = React.lazy<*>(() => +// import( +// /* webpackChunkName: "audioViewer" */ +// 'component/viewers/audioViewer' +// ) +// ); +// const AudioViewer = React.lazy<*>(() => // import(/* webpackChunkName: "audioViewer" */ // 'component/viewers/audioViewer') // ); diff --git a/src/ui/component/fileViewer/internal/player.jsx b/src/ui/component/fileViewer/internal/player.jsx index 020d44097..3bc1c3eae 100644 --- a/src/ui/component/fileViewer/internal/player.jsx +++ b/src/ui/component/fileViewer/internal/player.jsx @@ -1,4 +1,5 @@ // @flow +import type { ElementRef } from 'react'; import '@babel/polyfill'; import * as React from 'react'; @@ -29,7 +30,7 @@ type Props = { onFinishCb: ?() => void, savePosition: number => void, changeVolume: number => void, - viewerContainer: React.Ref, + viewerContainer: { current: ElementRef }, searchBarFocused: boolean, }; @@ -114,7 +115,9 @@ class MediaPlayer extends React.PureComponent { componentWillUnmount() { const mediaElement = this.mediaContainer.current.children[0]; - document.removeEventListener('keydown', this.handleKeyDown); + // Temorarily removing for comments the keydown handler needs to know + // if a user is typing + // document.removeEventListener('keydown', this.handleKeyDown); if (mediaElement) { mediaElement.removeEventListener('click', this.togglePlay); @@ -128,11 +131,11 @@ class MediaPlayer extends React.PureComponent { if (!searchBarFocused) { // Handle fullscreen shortcut key (f) if (event.keyCode === F_KEYCODE) { - this.toggleFullscreen(); + // this.toggleFullscreen(); } // Handle toggle play // @if TARGET='app' - this.togglePlay(event); + // this.togglePlay(event); // @endif } }; @@ -263,9 +266,6 @@ class MediaPlayer extends React.PureComponent { this.renderFile(); } // @endif - - // Fullscreen event for web and app - document.addEventListener('keydown', this.handleKeyDown); } // @if TARGET='app' diff --git a/src/ui/component/fileViewer/view.jsx b/src/ui/component/fileViewer/view.jsx index bb2402668..500dd9f92 100644 --- a/src/ui/component/fileViewer/view.jsx +++ b/src/ui/component/fileViewer/view.jsx @@ -1,4 +1,5 @@ // @flow +import type { ElementRef } from 'react'; import * as PAGES from 'constants/pages'; import React, { Suspense } from 'react'; import classnames from 'classnames'; @@ -52,7 +53,7 @@ type Props = { nsfw: boolean, thumbnail: ?string, isPlayableType: boolean, - viewerContainer: React.Ref, + viewerContainer: { current: ElementRef }, }; class FileViewer extends React.PureComponent { @@ -126,7 +127,7 @@ class FileViewer extends React.PureComponent { } this.props.cancelPlay(); - window.removeEventListener('keydown', this.handleKeyDown); + // window.removeEventListener('keydown', this.handleKeyDown); } handleKeyDown(event: SyntheticKeyboardEvent<*>) { diff --git a/src/ui/component/header/view.jsx b/src/ui/component/header/view.jsx index 0ebf7b337..cff8b627d 100644 --- a/src/ui/component/header/view.jsx +++ b/src/ui/component/header/view.jsx @@ -91,11 +91,12 @@ const Header = (props: Props) => { {__('Wallet')} history.push(`/$/publish`)}> - + {__('Publish')} + diff --git a/src/ui/component/publishName/index.js b/src/ui/component/publishName/index.js index 7512cddac..ef6ca56da 100644 --- a/src/ui/component/publishName/index.js +++ b/src/ui/component/publishName/index.js @@ -15,6 +15,7 @@ const select = state => ({ channel: makeSelectPublishFormValue('channel')(state), bid: makeSelectPublishFormValue('bid')(state), uri: makeSelectPublishFormValue('uri')(state), + bid: makeSelectPublishFormValue('bid')(state), isStillEditing: selectIsStillEditing(state), isResolvingUri: selectIsResolvingPublishUris(state), amountNeededForTakeover: selectTakeOverAmount(state), diff --git a/src/ui/component/publishName/view.jsx b/src/ui/component/publishName/view.jsx index e53aff865..ee03f8f0a 100644 --- a/src/ui/component/publishName/view.jsx +++ b/src/ui/component/publishName/view.jsx @@ -10,7 +10,7 @@ type Props = { name: string, channel: string, uri: string, - bid: string, + bid: number, balance: number, isStillEditing: boolean, myClaimForUri: ?StreamClaim, @@ -27,7 +27,7 @@ function PublishText(props: Props) { uri, isStillEditing, myClaimForUri, - bid: bidString, + bid, isResolvingUri, amountNeededForTakeover, prepareEdit, @@ -37,7 +37,6 @@ function PublishText(props: Props) { const [nameError, setNameError] = useState(undefined); const [bidError, setBidError] = useState(undefined); const previousBidAmount = myClaimForUri && Number(myClaimForUri.amount); - const bid = Number(bidString); function editExistingClaim() { if (myClaimForUri) { diff --git a/src/ui/component/publishPrice/index.js b/src/ui/component/publishPrice/index.js index ffaa82b58..255f02dc1 100644 --- a/src/ui/component/publishPrice/index.js +++ b/src/ui/component/publishPrice/index.js @@ -1,4 +1,5 @@ import { connect } from 'react-redux'; +import { doUpdatePublishForm } from 'redux/actions/publish'; import { makeSelectPublishFormValue } from 'redux/selectors/publish'; import PublishPage from './view'; @@ -7,7 +8,11 @@ const select = state => ({ fee: makeSelectPublishFormValue('fee')(state), }); +const perform = dispatch => ({ + updatePublishForm: values => dispatch(doUpdatePublishForm(values)), +}); + export default connect( select, - null + perform )(PublishPage); diff --git a/src/ui/component/rewardTotal/view.jsx b/src/ui/component/rewardTotal/view.jsx index 9f5c77e80..1b49194a2 100644 --- a/src/ui/component/rewardTotal/view.jsx +++ b/src/ui/component/rewardTotal/view.jsx @@ -10,7 +10,8 @@ type Props = { function RewardTotal(props: Props) { const { rewards } = props; const rewardTotal = rewards.reduce((acc, val) => acc + val.reward_amount, 0); - const total = useTween(rewardTotal * 25); + const modifier = rewardTotal > 500 ? 1 : 15; // used to tweak the reward count speed + const total = useTween(rewardTotal * modifier); const integer = Math.round(total * rewardTotal); return ( diff --git a/src/ui/component/router/view.jsx b/src/ui/component/router/view.jsx index b7d6de79e..a708ea1fd 100644 --- a/src/ui/component/router/view.jsx +++ b/src/ui/component/router/view.jsx @@ -16,7 +16,7 @@ import AuthPage from 'page/auth'; import InvitePage from 'page/invite'; import SubscriptionsPage from 'page/subscriptions'; import SearchPage from 'page/search'; -import UserHistoryPage from 'page/userHistory'; +import LibraryPage from 'page/library'; import WalletPage from 'page/wallet'; import NavigationHistory from 'page/navigationHistory'; import TagsPage from 'page/tags'; @@ -24,6 +24,7 @@ import FollowingPage from 'page/following'; const Scroll = withRouter(function ScrollWrapper(props) { const { pathname } = props.location; + useEffect(() => { // Auto scroll to the top of a window for new pages // The browser will handle scrolling if it needs to, but @@ -51,12 +52,12 @@ export default function AppRouter() { - + - + {/* Below need to go at the end to make sure we don't match any of our pages first */} diff --git a/src/ui/component/searchOptions/view.jsx b/src/ui/component/searchOptions/view.jsx index 2848f6b7c..76dd883d8 100644 --- a/src/ui/component/searchOptions/view.jsx +++ b/src/ui/component/searchOptions/view.jsx @@ -26,7 +26,7 @@ const SearchOptions = (props: Props) => {
    - {linkText && linkTarget &&
    ); } diff --git a/src/ui/component/socialShare/view.jsx b/src/ui/component/socialShare/view.jsx index 8ba09bf9c..0e265c3ae 100644 --- a/src/ui/component/socialShare/view.jsx +++ b/src/ui/component/socialShare/view.jsx @@ -3,10 +3,9 @@ import * as ICONS from 'constants/icons'; import React from 'react'; import Button from 'component/button'; import CopyableText from 'component/copyableText'; -import Tooltip from 'component/common/tooltip'; type Props = { - claim: StreamClaim, + claim: Claim, onDone: () => void, speechShareable: boolean, isChannel: boolean, @@ -27,21 +26,15 @@ class SocialShare extends React.PureComponent { render() { const { claim, isChannel } = this.props; - const { claim_id: claimId, name: claimName, channel_name: channelName } = claim; + const { claim_id: claimId, name: claimName } = claim; const { speechShareable, onDone } = this.props; - const channelClaimId = claim.signing_channel && claim.signing_channel.claim_id; + const signingChannel = claim.signing_channel; + const channelClaimId = signingChannel && signingChannel.claim_id; + const channelName = signingChannel && signingChannel.name; - const getSpeechUri = (): string => { - if (isChannel) { - // For channel claims, the channel name (@something) is in `claim.name` - return `${claimName}:${claimId}`; - } else { - // If it's for a regular claim, check if it has an associated channel - return channelName && channelClaimId - ? `${channelName}:${channelClaimId}/${claimName}` - : `${claimId}/${claimName}`; - } + const getLbryTvUri = (): string => { + return `${claimName}/${claimId}`; }; const getLbryUri = (): string => { @@ -56,69 +49,57 @@ class SocialShare extends React.PureComponent { } }; - const speechPrefix = 'https://spee.ch/'; + const lbryTvPrefix = 'https://beta.lbry.tv/'; const lbryPrefix = 'https://open.lbry.com/'; const lbryUri = getLbryUri(); - const speechUri = getSpeechUri(); + const lbryTvUri = getLbryTvUri(); const encodedLbryURL: string = `${lbryPrefix}${encodeURIComponent(lbryUri)}`; const lbryURL: string = `${lbryPrefix}${getLbryUri()}`; + const encodedLbryTvUrl = `${lbryTvPrefix}${encodeURIComponent(lbryTvUri)}`; + const lbryTvUrl = `${lbryTvPrefix}${lbryTvUri}`; - const encodedSpeechURL = `${speechPrefix}${encodeURIComponent(speechUri)}`; - const speechURL = `${speechPrefix}${speechUri}`; + const shareOnFb = __('Share on Facebook'); + const shareOnTwitter = __('Share On Twitter'); return ( {speechShareable && (
    - - + +
    - -
    )}
    - +
    - -
    diff --git a/src/ui/component/subscribeButton/view.jsx b/src/ui/component/subscribeButton/view.jsx index 776b1de7b..e9c0b266d 100644 --- a/src/ui/component/subscribeButton/view.jsx +++ b/src/ui/component/subscribeButton/view.jsx @@ -1,7 +1,7 @@ // @flow import * as MODALS from 'constants/modal_types'; import * as ICONS from 'constants/icons'; -import React from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { parseURI } from 'lbry-redux'; import Button from 'component/button'; @@ -32,18 +32,36 @@ export default function SubscribeButton(props: Props) { showSnackBarOnSubscribe, doToast, } = props; - + const buttonRef = useRef(); + const [isHovering, setIsHovering] = useState(false); + const { claimName } = parseURI(uri); const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe; const subscriptionLabel = isSubscribed ? __('Following') : __('Follow'); + const unfollowOverride = isSubscribed && isHovering && __('Unfollow'); - const { claimName } = parseURI(uri); + useEffect(() => { + function handleHover() { + setIsHovering(!isHovering); + } + + const button = buttonRef.current; + if (button) { + button.addEventListener('mouseover', handleHover); + button.addEventListener('mouseleave', handleHover); + return () => { + button.removeEventListener('mouseover', handleHover); + button.removeEventListener('mouseleave', handleHover); + }; + } + }, [buttonRef, isHovering]); return (
    ) : (
    diff --git a/src/ui/page/following/view.jsx b/src/ui/page/following/view.jsx index 9edcff43f..ae86b84b9 100644 --- a/src/ui/page/following/view.jsx +++ b/src/ui/page/following/view.jsx @@ -5,26 +5,22 @@ import TagsSelect from 'component/tagsSelect'; import ClaimList from 'component/claimList'; type Props = { - subscribedChannels: Array<{ uri: string }>, + subscribedChannels: Array, }; -function DiscoverPage(props: Props) { +function FollowingEditPage(props: Props) { const { subscribedChannels } = props; - + const channelUris = subscribedChannels.map(({ uri }) => uri); return (
    - +
    - {__('Channels You Are Following')}} - empty={__("You aren't following any channels.")} - uris={subscribedChannels.map(({ uri }) => uri)} - /> + {__('Channels You Follow')}} uris={channelUris} />
    ); } -export default DiscoverPage; +export default FollowingEditPage; diff --git a/src/ui/page/library/index.js b/src/ui/page/library/index.js new file mode 100644 index 000000000..abb82e5be --- /dev/null +++ b/src/ui/page/library/index.js @@ -0,0 +1,3 @@ +import LibraryPage from './view'; + +export default LibraryPage; diff --git a/src/ui/page/library/view.jsx b/src/ui/page/library/view.jsx new file mode 100644 index 000000000..71bb0bfac --- /dev/null +++ b/src/ui/page/library/view.jsx @@ -0,0 +1,14 @@ +// @flow +import React from 'react'; +import Page from 'component/page'; +import DownloadList from 'page/fileListDownloaded'; + +function LibraryPage() { + return ( + + + + ); +} + +export default LibraryPage; diff --git a/src/ui/page/subscriptions/view.jsx b/src/ui/page/subscriptions/view.jsx index 193098d70..9fdcbe5b8 100644 --- a/src/ui/page/subscriptions/view.jsx +++ b/src/ui/page/subscriptions/view.jsx @@ -1,8 +1,9 @@ // @flow import * as PAGES from 'constants/pages'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import Page from 'component/page'; import ClaimList from 'component/claimList'; +import ClaimPreview from 'component/claimPreview'; import Button from 'component/button'; type Props = { @@ -29,7 +30,7 @@ export default function SubscriptionsPage(props: Props) { doClaimSearch, uris, } = props; - + const [page, setPage] = useState(1); const hasSubscriptions = !!subscribedChannels.length; const { search } = location; const urlParams = new URLSearchParams(search); @@ -53,28 +54,33 @@ export default function SubscriptionsPage(props: Props) { useEffect(() => { const ids = idString.split(','); const options = { + page, channel_ids: ids, order_by: ['release_time'], }; doClaimSearch(20, options); - }, [idString, doClaimSearch]); + }, [idString, doClaimSearch, page]); return (
    {viewingSuggestedSubs ? __('Discover New Channels') : __('Latest From Your Subscriptions')}} + header={ +

    {viewingSuggestedSubs ? __('Discover New Channels') : __("Latest From Who You're Following")}

    + } headerAltControls={
    ); diff --git a/src/ui/page/tags/view.jsx b/src/ui/page/tags/view.jsx index e56be12bc..ee912a72c 100644 --- a/src/ui/page/tags/view.jsx +++ b/src/ui/page/tags/view.jsx @@ -32,9 +32,9 @@ function TagsPage(props: Props) { tags={tags} meta={