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/src/ui/component/channelContent/view.jsx b/src/ui/component/channelContent/view.jsx index 7f0e5f27b..309664b70 100644 --- a/src/ui/component/channelContent/view.jsx +++ b/src/ui/component/channelContent/view.jsx @@ -35,7 +35,7 @@ function ChannelContent(props: Props) { {!channelIsMine && } - {hasContent && claim.permanent_url).reverse()} />} + {hasContent && claim.permanent_url)} />} fetchClaims(uri, page)} diff --git a/src/ui/component/claimList/view.jsx b/src/ui/component/claimList/view.jsx index 31bf67930..9b7014405 100644 --- a/src/ui/component/claimList/view.jsx +++ b/src/ui/component/claimList/view.jsx @@ -19,7 +19,7 @@ type Props = { loading: boolean, type: string, empty?: string, - meta?: Node, + defaultSort?: boolean, onScrollBottom?: any => void, // If using the default header, this is a unique ID needed to persist the state of the filter setting persistedStorageKey?: string, @@ -33,7 +33,7 @@ export default function ClaimList(props: Props) { loading, persistedStorageKey, empty, - meta, + defaultSort, type, header, onScrollBottom, @@ -46,7 +46,21 @@ export default function ClaimList(props: Props) { setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW); } + const urisLength = uris && uris.length; useEffect(() => { + function handleScroll(e) { + if (onScrollBottom) { + const x = document.querySelector(`.${MAIN_WRAPPER_CLASS}`); + + if (x && window.scrollY + window.innerHeight >= x.offsetHeight) { + // fix this + if (!loading && urisLength > 19) { + onScrollBottom(); + } + } + } + } + if (onScrollBottom) { window.addEventListener('scroll', handleScroll); @@ -54,42 +68,35 @@ export default function ClaimList(props: Props) { window.removeEventListener('scroll', handleScroll); }; } - }, [loading, handleScroll]); - - function handleScroll(e) { - if (onScrollBottom) { - const x = document.querySelector(`.${MAIN_WRAPPER_CLASS}`); - - if (x && window.scrollY + window.innerHeight >= x.offsetHeight) { - // fix this - if (!loading && uris.length > 19) { - onScrollBottom(); - } - } - } - } + }, [loading, onScrollBottom, urisLength]); return ( -
+
{header !== false && (
- {header || ( - - - - - )} + {header} {loading && } -
{headerAltControls}
+
+ {headerAltControls} + {defaultSort && ( + + + + + )} +
)} - {meta &&
{meta}
} {hasUris && (
    {sortedUris.map((uri, index) => ( diff --git a/src/ui/component/claimListDiscover/view.jsx b/src/ui/component/claimListDiscover/view.jsx index 309b49c2a..18f05d376 100644 --- a/src/ui/component/claimListDiscover/view.jsx +++ b/src/ui/component/claimListDiscover/view.jsx @@ -116,6 +116,7 @@ function ClaimListDiscover(props: Props) { const headerAltControls = ( + {meta} {typeSort === 'top' && ( ), - [ICONS.UPLOAD]: buildIcon( + [ICONS.PUBLISH]: buildIcon( ), - [ICONS.PUBLISHED]: buildIcon( - - - - ), - [ICONS.SUBSCRIPTION]: buildIcon( + [ICONS.SUBSCRIBE]: buildIcon( ), + [ICONS.UNSUBSCRIBE]: buildIcon( + + ), [ICONS.SETTINGS]: buildIcon( @@ -109,12 +103,8 @@ export const icons = { [ICONS.OVERVIEW]: buildIcon(), [ICONS.WALLET]: buildIcon( - - - - - - + + ), [ICONS.LIBRARY]: buildIcon(), @@ -224,4 +214,30 @@ export const icons = { ), [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/fileProperties/view.jsx b/src/ui/component/fileProperties/view.jsx index 15783e8a3..e8b1635d0 100644 --- a/src/ui/component/fileProperties/view.jsx +++ b/src/ui/component/fileProperties/view.jsx @@ -22,7 +22,7 @@ export default function FileProperties(props: Props) { return (
    - {isSubscribed && } + {isSubscribed && } {!claimIsMine && downloaded && } {isRewardContent && } 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/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) => {
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/discover/view.jsx b/src/ui/page/discover/view.jsx index 9b7dff33e..04e16ce8d 100644 --- a/src/ui/page/discover/view.jsx +++ b/src/ui/page/discover/view.jsx @@ -10,6 +10,7 @@ type Props = { function DiscoverPage(props: Props) { const { followedTags } = props; + return ( {hasDownloads ? (
- + {__('Your Library')}} + defaultSort + persistedStorageKey="claim-list-downloaded" + uris={downloadedUris} + loading={fetching} + />
) : (
diff --git a/src/ui/page/fileListPublished/view.jsx b/src/ui/page/fileListPublished/view.jsx index bb98f9c58..0c79f6aac 100644 --- a/src/ui/page/fileListPublished/view.jsx +++ b/src/ui/page/fileListPublished/view.jsx @@ -21,7 +21,14 @@ function FileListPublished(props: Props) { {uris && uris.length ? (
- + {__('Your Publishes')}} + loading={fetching} + persistedStorageKey="claim-list-published" + uris={uris} + defaultSort + headerAltControls={
) : (
diff --git a/src/ui/page/following/view.jsx b/src/ui/page/following/view.jsx index 31e53c3bc..ae86b84b9 100644 --- a/src/ui/page/following/view.jsx +++ b/src/ui/page/following/view.jsx @@ -14,10 +14,10 @@ function FollowingEditPage(props: Props) { return (
- +
- + {__('Channels You Follow')}} uris={channelUris} />
); 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 d7f399c64..9fdcbe5b8 100644 --- a/src/ui/page/subscriptions/view.jsx +++ b/src/ui/page/subscriptions/view.jsx @@ -3,6 +3,7 @@ import * as PAGES from 'constants/pages'; 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 = { @@ -79,6 +80,7 @@ export default function SubscriptionsPage(props: Props) { uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris} onScrollBottom={() => console.log('scroll bottom') || setPage(page + 1)} /> + {loading && page > 1 && new Array(20).fill(1).map((x, i) => )}
); 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={