diff --git a/CHANGELOG.md b/CHANGELOG.md index 9676163f0..8270fb16f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Allow typing of encryption password without clicking entry box ([#1977](https://github.com/lbryio/lbry-desktop/pull/1977)) * Focus on search bar with {cmd,ctrl} + "l" ([#2003](https://github.com/lbryio/lbry-desktop/pull/2003)) * Add support for clickable channel names on explore page headings ([#2023](https://github.com/lbryio/lbry-desktop/pull/2023)) + * Content loading placeholder styles on FileCard/FileTile ([#2022](https://github.com/lbryio/lbry-desktop/pull/2022)) + ### Changed * Make tooltip smarter ([#1979](https://github.com/lbryio/lbry-desktop/pull/1979)) * Change channel pages to have 48 items instead of 10 ([#2002](https://github.com/lbryio/lbry-desktop/pull/2002)) diff --git a/src/renderer/component/common/tooltip.jsx b/src/renderer/component/common/tooltip.jsx index c54318098..181c792c7 100644 --- a/src/renderer/component/common/tooltip.jsx +++ b/src/renderer/component/common/tooltip.jsx @@ -11,7 +11,11 @@ type Props = { onComponent?: boolean, // extra padding to account for button/form field size }; -class ToolTip extends React.PureComponent { +type State = { + direction: string, +}; + +class ToolTip extends React.PureComponent { static defaultProps = { direction: 'bottom', }; @@ -34,6 +38,9 @@ class ToolTip extends React.PureComponent { // Get parent-container const viewport = document.getElementById('content'); + if (!viewport) { + throw Error('Document must contain parent div with #content'); + } const visibility = { top: rect.top >= 0, diff --git a/src/renderer/component/dateTime/view.jsx b/src/renderer/component/dateTime/view.jsx index 83f7fa755..7049b223e 100644 --- a/src/renderer/component/dateTime/view.jsx +++ b/src/renderer/component/dateTime/view.jsx @@ -1,6 +1,15 @@ +// @flow import React from 'react'; +import moment from 'moment'; -class DateTime extends React.PureComponent { +type Props = { + date?: number, + timeAgo?: boolean, + formatOptions: {}, + show?: string, +}; + +class DateTime extends React.PureComponent { static SHOW_DATE = 'date'; static SHOW_TIME = 'time'; static SHOW_BOTH = 'both'; @@ -29,18 +38,24 @@ class DateTime extends React.PureComponent { } render() { - const { date, formatOptions } = this.props; + const { date, formatOptions, timeAgo } = this.props; const show = this.props.show || DateTime.SHOW_BOTH; const locale = app.i18n.getLocale(); + // If !date, it's currently being fetched + + if (timeAgo) { + return date ? {moment(date).from(moment())} : ; + } + return ( {date && - (show == DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) && + (show === DateTime.SHOW_BOTH || show === DateTime.SHOW_DATE) && date.toLocaleDateString([locale, 'en-US'], formatOptions)} - {show == DateTime.SHOW_BOTH && ' '} + {show === DateTime.SHOW_BOTH && ' '} {date && - (show == DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) && + (show === DateTime.SHOW_BOTH || show === DateTime.SHOW_TIME) && date.toLocaleTimeString()} {!date && '...'} diff --git a/src/renderer/component/fileCard/index.js b/src/renderer/component/fileCard/index.js index 95a7f9a67..b61ae8bb1 100644 --- a/src/renderer/component/fileCard/index.js +++ b/src/renderer/component/fileCard/index.js @@ -14,6 +14,8 @@ import { } from 'redux/selectors/content'; import { selectShowNsfw } from 'redux/selectors/settings'; import { selectPendingPublish } from 'redux/selectors/publish'; +import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; +import { doClearContentHistoryUri } from 'redux/actions/content'; import FileCard from './view'; const select = (state, props) => { @@ -36,6 +38,7 @@ const select = (state, props) => { ...fileCardInfo, pending: !!pendingPublish, position: makeSelectContentPositionForUri(props.uri)(state), + isSubscribed: makeSelectIsSubscribed(props.uri)(state), }; }; diff --git a/src/renderer/component/fileCard/view.jsx b/src/renderer/component/fileCard/view.jsx index 1d058d4ad..bb2df3780 100644 --- a/src/renderer/component/fileCard/view.jsx +++ b/src/renderer/component/fileCard/view.jsx @@ -10,6 +10,7 @@ import * as icons from 'constants/icons'; import classnames from 'classnames'; import FilePrice from 'component/filePrice'; import { openCopyLinkMenu } from 'util/contextMenu'; +import DateTime from 'component/dateTime'; type Props = { uri: string, @@ -24,15 +25,11 @@ type Props = { /* eslint-disable react/no-unused-prop-types */ resolveUri: string => void, isResolvingUri: boolean, - showPrice: boolean, /* eslint-enable react/no-unused-prop-types */ + isSubscribed: boolean, }; class FileCard extends React.PureComponent { - static defaultProps = { - showPrice: true, - }; - componentWillMount() { this.resolve(this.props); } @@ -59,9 +56,20 @@ class FileCard extends React.PureComponent { obscureNsfw, claimIsMine, pending, - showPrice, + isSubscribed, } = this.props; + if (!claim && !pending) { + return ( +
+
+
+
+
+
+ ); + } + const shouldHide = !claimIsMine && !pending && obscureNsfw && metadata && metadata.nsfw; if (shouldHide) { return null; @@ -71,6 +79,7 @@ class FileCard extends React.PureComponent { const title = metadata && metadata.title ? metadata.title : uri; const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null; const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); + const height = claim && claim.height; const handleContextMenu = event => { event.preventDefault(); event.stopPropagation(); @@ -97,10 +106,15 @@ class FileCard extends React.PureComponent {
{pending ?
Pending...
: }
-
- {showPrice && } - {isRewardContent && } - {fileInfo && } +
+ + +
+ + {isRewardContent && } + {isSubscribed && } + {fileInfo && } +
); diff --git a/src/renderer/component/fileTile/index.js b/src/renderer/component/fileTile/index.js index e5e242291..2b10e1356 100644 --- a/src/renderer/component/fileTile/index.js +++ b/src/renderer/component/fileTile/index.js @@ -11,6 +11,7 @@ import { selectShowNsfw } from 'redux/selectors/settings'; import { doNavigate } from 'redux/actions/navigation'; import { doClearPublish, doUpdatePublishForm } from 'redux/actions/publish'; import { selectRewardContentClaimIds } from 'redux/selectors/content'; +import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; import FileTile from './view'; const select = (state, props) => ({ @@ -21,6 +22,7 @@ const select = (state, props) => ({ rewardedContentClaimIds: selectRewardContentClaimIds(state, props), obscureNsfw: !selectShowNsfw(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state), + isSubscribed: makeSelectIsSubscribed(props.uri)(state), }); const perform = dispatch => ({ diff --git a/src/renderer/component/fileTile/view.jsx b/src/renderer/component/fileTile/view.jsx index 1793c331c..c23804b8e 100644 --- a/src/renderer/component/fileTile/view.jsx +++ b/src/renderer/component/fileTile/view.jsx @@ -10,10 +10,9 @@ import Button from 'component/button'; import classnames from 'classnames'; import FilePrice from 'component/filePrice'; import UriIndicator from 'component/uriIndicator'; +import DateTime from 'component/dateTime'; type Props = { - showUri: boolean, - showLocal: boolean, obscureNsfw: boolean, claimIsMine: boolean, isDownloaded: boolean, @@ -30,12 +29,11 @@ type Props = { displayHiddenMessage?: boolean, displayDescription?: boolean, size: string, + isSubscribed: boolean, }; class FileTile extends React.PureComponent { static defaultProps = { - showUri: false, - showLocal: false, displayDescription: true, size: 'regular', }; @@ -50,18 +48,28 @@ class FileTile extends React.PureComponent { if (!isResolvingUri && claim === undefined && uri) resolveUri(uri); } + renderFileProperties() { + const { isSubscribed, isDownloaded, claim, uri, rewardedContentClaimIds, size } = this.props; + const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); + + return ( +
+ + {isSubscribed && } + {isRewardContent && } + {isDownloaded && } +
+ ); + } + render() { const { claim, metadata, isResolvingUri, navigate, - rewardedContentClaimIds, - showUri, obscureNsfw, claimIsMine, - showLocal, - isDownloaded, clearPublish, updatePublishForm, hideNoResult, @@ -70,6 +78,24 @@ class FileTile extends React.PureComponent { size, } = this.props; + if (!claim && isResolvingUri) { + return ( +
+
+
+
+
+
+
+
+ ); + } + const shouldHide = !claimIsMine && obscureNsfw && metadata && metadata.nsfw; if (shouldHide) { return displayHiddenMessage ? ( @@ -87,12 +113,12 @@ class FileTile extends React.PureComponent { const title = isClaimed && metadata && metadata.title ? metadata.title : parseURI(uri).contentName; const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null; - const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); const onClick = () => navigate('/show', { uri }); + let height; let name; if (claim) { - ({ name } = claim); + ({ name, height } = claim); } return !name && hideNoResult ? null : ( @@ -108,43 +134,42 @@ class FileTile extends React.PureComponent { >
- {isResolvingUri &&
{__('Loading...')}
} - {!isResolvingUri && ( +
+ {(title || name) && ( + + )} +
+
+ +
+
+ + {size !== 'large' && this.renderFileProperties()} +
+ {displayDescription && ( +
+ +
+ )} + {size === 'large' && this.renderFileProperties()} + {!name && ( -
- -
-
- {showUri ? uri : } -
-
- - {isRewardContent && } - {showLocal && isDownloaded && } -
- {displayDescription && ( -
- -
- )} - {!name && ( - - {__('This location is unused.')}{' '} -
diff --git a/src/renderer/component/recommendedContent/view.jsx b/src/renderer/component/recommendedContent/view.jsx index 902229c47..4a12aecb3 100644 --- a/src/renderer/component/recommendedContent/view.jsx +++ b/src/renderer/component/recommendedContent/view.jsx @@ -2,7 +2,6 @@ import React from 'react'; import FileTile from 'component/fileTile'; import type { Claim } from 'types/claim'; -import Spinner from 'component/spinner'; type Props = { uri: string, @@ -66,7 +65,6 @@ export default class RecommendedContent extends React.PureComponent { {recommendedContent && !recommendedContent.length && !isSearching &&
No related content found
} - {isSearching && } ); } diff --git a/src/renderer/component/userHistory/index.js b/src/renderer/component/userHistory/index.js index 1b77955b1..01618f6be 100644 --- a/src/renderer/component/userHistory/index.js +++ b/src/renderer/component/userHistory/index.js @@ -18,7 +18,6 @@ const select = state => { const perform = dispatch => ({ navigate: (path, params) => dispatch(doNavigate(path, params)), clearHistoryUri: uri => dispatch(doClearContentHistoryUri(uri)), - }); export default connect( diff --git a/src/renderer/page/file/view.jsx b/src/renderer/page/file/view.jsx index 147a2313a..db0d2adba 100644 --- a/src/renderer/page/file/view.jsx +++ b/src/renderer/page/file/view.jsx @@ -43,9 +43,13 @@ type Props = { fetchFileInfo: string => void, fetchCostInfo: string => void, prepareEdit: ({}, string) => void, + setViewed: string => void, + autoplay: boolean, + setClientSetting: (string, string | boolean | number) => void, + /* eslint-disable react/no-unused-prop-types */ checkSubscription: (uri: string) => void, subscriptions: Array, - setViewed: string => void, + /* eslint-enable react/no-unused-prop-types */ }; class FilePage extends React.Component { @@ -183,7 +187,7 @@ class FilePage extends React.Component { ))}
-
+

{title}

{isRewardContent && ( @@ -220,14 +224,12 @@ class FilePage extends React.Component { onClick={() => openModal({ id: MODALS.SEND_TIP }, { uri })} /> )} - {speechShareable && ( -
diff --git a/src/renderer/redux/selectors/subscriptions.js b/src/renderer/redux/selectors/subscriptions.js index 12c35ae74..1931c1671 100644 --- a/src/renderer/redux/selectors/subscriptions.js +++ b/src/renderer/redux/selectors/subscriptions.js @@ -3,6 +3,7 @@ import { selectAllClaimsByChannel, selectClaimsById, selectAllFetchingChannelClaims, + makeSelectClaimForUri, } from 'lbry-redux'; // get the entire subscriptions state @@ -71,3 +72,13 @@ export const selectSubscriptionsBeingFetched = createSelector( return fetchingSubscriptionMap; } ); + +export const makeSelectIsSubscribed = uri => + createSelector(selectSubscriptions, makeSelectClaimForUri(uri), (subscriptions, claim) => { + if (!claim || !claim.channel_name) { + return false; + } + + const channelUri = `${claim.channel_name}#${claim.value.publisherSignature.certificateId}`; + return subscriptions.some(sub => sub.uri === channelUri); + }); diff --git a/src/renderer/scss/all.scss b/src/renderer/scss/all.scss index 30a4dc9f5..370cebd3f 100644 --- a/src/renderer/scss/all.scss +++ b/src/renderer/scss/all.scss @@ -32,3 +32,4 @@ @import 'component/_item-list.scss'; @import 'component/_time.scss'; @import 'component/_icon.scss'; +@import 'component/_placeholder.scss'; diff --git a/src/renderer/scss/component/_card.scss b/src/renderer/scss/component/_card.scss index 61b3093d8..69ef64ba6 100644 --- a/src/renderer/scss/component/_card.scss +++ b/src/renderer/scss/component/_card.scss @@ -88,20 +88,16 @@ // FileCard is slightly different where the title is only slightly bigger than the subtitle // Slightly bigger than 2 lines for consistent channel placement font-size: 1.1em; - height: 4em; + height: 3.3em; @media only screen and (min-width: $large-breakpoint) { font-size: 1.3em; } } -.card__title__space-between { +.card--space-between { display: flex; justify-content: space-between; - - .icon { - margin: 0 $spacing-vertical * 1/3; - } } .card__title-identity-icons { @@ -114,6 +110,7 @@ color: var(--card-text-color); font-size: 1em; line-height: 1em; + padding-top: $spacing-vertical * 1/3; @media (min-width: $large-breakpoint) { font-size: 1.2em; @@ -124,12 +121,13 @@ font-size: 1.1em; &:not(:first-of-type) { - margin-top: $spacing-vertical * 3/2; + padding-top: $spacing-vertical * 3/2; } } .card__subtext { font-size: 0.85em; + padding-top: $spacing-vertical * 1/3; } .card__meta { @@ -142,8 +140,11 @@ .card__file-properties { display: flex; align-items: center; - padding-top: $spacing-vertical * 1/3; color: var(--card-text-color); + + .icon + .icon { + margin-left: $spacing-vertical * 1/3; + } } // .card-media__internal__links should always be inside a card diff --git a/src/renderer/scss/component/_file-list.scss b/src/renderer/scss/component/_file-list.scss index 666046341..e62b4df5c 100644 --- a/src/renderer/scss/component/_file-list.scss +++ b/src/renderer/scss/component/_file-list.scss @@ -45,6 +45,7 @@ } .file-tile__title { + font-size: 1.1em; } .file-tile--small { @@ -65,6 +66,7 @@ .file-tile__info { display: flex; + flex: 1; flex-direction: column; margin-left: $spacing-vertical * 2/3; max-width: 500px; diff --git a/src/renderer/scss/component/_placeholder.scss b/src/renderer/scss/component/_placeholder.scss new file mode 100644 index 000000000..e66418137 --- /dev/null +++ b/src/renderer/scss/component/_placeholder.scss @@ -0,0 +1,40 @@ +@keyframes pulse { + 0% { + opacity: 1; + } + 50% { + opacity: 0.7; + } + 100% { + opacity: 1; + } +} + +.card--placeholder { + animation: pulse 2s infinite ease-in-out; + background: var(--color-placeholder); +} + +// Individual items we need a placeholder for +// FileCard +.placeholder__title { + margin-top: $spacing-vertical * 1/3; + height: 3em; +} + +.placeholder__channel { + margin-top: $spacing-vertical * 1/3; + height: 1em; + width: 70%; +} + +.placeholder__date { + height: 1em; + margin-top: $spacing-vertical * 1/3; + width: 50%; +} + +// FileTile +.placeholder__title--tile { + height: 3em; +} diff --git a/yarn.lock b/yarn.lock index 9298e7f8c..ff1003b87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5662,9 +5662,9 @@ lbry-redux@lbryio/lbry-redux: proxy-polyfill "0.1.6" reselect "^3.0.0" -lbry-redux@lbryio/lbry-redux#c079b108c3bc4ba2b4fb85fb112b52cfc040c301: +lbry-redux@lbryio/lbry-redux#4ee6c376e5f2c3e3e96d199a56970e2621a84af1: version "0.0.1" - resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/c079b108c3bc4ba2b4fb85fb112b52cfc040c301" + resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/4ee6c376e5f2c3e3e96d199a56970e2621a84af1" dependencies: proxy-polyfill "0.1.6" reselect "^3.0.0"