first version of infinite scroll
This commit is contained in:
parent
68a18556bf
commit
2ca254a573
33 changed files with 236 additions and 110 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "LBRY",
|
||||
"version": "0.33.1",
|
||||
"version": "0.33.2",
|
||||
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
||||
"keywords": [
|
||||
"lbry"
|
||||
|
@ -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#141593500693a93db74c62ef5a9fe67b43896603",
|
||||
"lbry-redux": "lbryio/lbry-redux#2930ad82a90ca91f6caf3761597ef9a67da7db66",
|
||||
"lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) {
|
|||
<div ref={appRef} onContextMenu={e => openContextMenu(e)}>
|
||||
<Header />
|
||||
|
||||
<div className="main-wrapper">
|
||||
<div className={MAIN_WRAPPER_CLASS}>
|
||||
<div className="main-wrapper-inner">
|
||||
<Router />
|
||||
<SideBar />
|
||||
|
|
|
@ -35,7 +35,7 @@ function ChannelContent(props: Props) {
|
|||
|
||||
{!channelIsMine && <HiddenNsfwClaims className="card__content help" uri={uri} />}
|
||||
|
||||
{hasContent && <ClaimList header={false} uris={claimsInChannel.map(claim => claim.permanent_url)} />}
|
||||
{hasContent && <ClaimList header={false} uris={claimsInChannel.map(claim => claim.permanent_url).reverse()} />}
|
||||
|
||||
<Paginate
|
||||
onPageChange={page => fetchClaims(uri, page)}
|
||||
|
|
|
@ -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';
|
||||
|
@ -19,14 +20,25 @@ type Props = {
|
|||
type: string,
|
||||
empty?: string,
|
||||
meta?: Node,
|
||||
onScrollBottom?: any => void,
|
||||
// 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,
|
||||
meta,
|
||||
type,
|
||||
header,
|
||||
onScrollBottom,
|
||||
} = props;
|
||||
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
||||
const sortedUris = uris && currentSort === SORT_NEW ? uris.slice().reverse() : uris;
|
||||
const hasUris = uris && !!uris.length;
|
||||
const sortedUris = (hasUris && (currentSort === SORT_NEW ? uris : uris.slice().reverse())) || [];
|
||||
|
||||
|
@ -34,8 +46,31 @@ export default function ClaimList(props: Props) {
|
|||
setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (onScrollBottom) {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
|
||||
return () => {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={classnames('file-list')}>
|
||||
<section className={classnames('claim-list')}>
|
||||
{header !== false && (
|
||||
<div className={classnames('claim-list__header', { 'claim-list__header--small': type === 'small' })}>
|
||||
{header || (
|
||||
|
@ -60,7 +95,7 @@ export default function ClaimList(props: Props) {
|
|||
{sortedUris.map((uri, index) => (
|
||||
<React.Fragment key={uri}>
|
||||
<ClaimPreview uri={uri} type={type} />
|
||||
{index === 4 && injectedItem && <li className="claim-list__item--injected">{injectedItem}</li>}
|
||||
{index === 4 && injectedItem && <li className="claim-preview--injected">{injectedItem}</li>}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
@ -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';
|
||||
|
@ -37,11 +39,17 @@ function ClaimListDiscover(props: 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 [page, setPage] = useState(1);
|
||||
|
||||
const toCapitalCase = string => string.charAt(0).toUpperCase() + string.slice(1);
|
||||
const tagsString = tags.join(',');
|
||||
useEffect(() => {
|
||||
const options = {};
|
||||
const options: {
|
||||
page_size: number,
|
||||
any_tags?: Array<string>,
|
||||
order_by?: Array<string>,
|
||||
release_time?: string,
|
||||
} = { page_size: PAGE_SIZE, page };
|
||||
const newTags = tagsString.split(',');
|
||||
|
||||
if ((newTags && !personal) || (newTags && personal && personalSort === SEARCH_SORT_YOU)) {
|
||||
|
@ -65,7 +73,7 @@ function ClaimListDiscover(props: Props) {
|
|||
}
|
||||
|
||||
doClaimSearch(20, options);
|
||||
}, [personal, personalSort, typeSort, timeSort, doClaimSearch, tagsString]);
|
||||
}, [personal, personalSort, typeSort, timeSort, doClaimSearch, page, tagsString]);
|
||||
|
||||
const header = (
|
||||
<h1 className="card__title--flex">
|
||||
|
@ -91,7 +99,10 @@ 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 => (
|
||||
<option key={type} value={type}>
|
||||
|
@ -132,7 +143,10 @@ function ClaimListDiscover(props: Props) {
|
|||
injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem}
|
||||
header={header}
|
||||
headerAltControls={headerAltControls}
|
||||
onScrollBottom={() => setPage(page + 1)}
|
||||
/>
|
||||
|
||||
{loading && page > 1 && new Array(PAGE_SIZE).fill(1).map((x, i) => <ClaimPreview key={i} placeholder />)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -94,10 +94,10 @@ function ClaimPreview(props: Props) {
|
|||
|
||||
if (placeholder && !claim) {
|
||||
return (
|
||||
<li className="claim-list__item" disabled>
|
||||
<li className="claim-preview" disabled>
|
||||
<div className="placeholder media__thumb" />
|
||||
<div className="placeholder__wrapper">
|
||||
<div className="placeholder claim-list__item-title" />
|
||||
<div className="placeholder claim-preview-title" />
|
||||
<div className="placeholder media__subtitle" />
|
||||
</div>
|
||||
</li>
|
||||
|
@ -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 ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />}
|
||||
<div className="claim-list__item-metadata">
|
||||
<div className="claim-list__item-info">
|
||||
<div className="claim-list__item-title">
|
||||
<div className="claim-preview-metadata">
|
||||
<div className="claim-preview-info">
|
||||
<div className="claim-preview-title">
|
||||
<TruncatedText text={title || (claim && claim.name)} lines={1} />
|
||||
</div>
|
||||
{type !== 'small' && (
|
||||
|
@ -128,7 +128,7 @@ function ClaimPreview(props: Props) {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="claim-list__item-properties">
|
||||
<div className="claim-preview-properties">
|
||||
<div className="media__subtitle">
|
||||
<UriIndicator uri={uri} link />
|
||||
{pending && <div>Pending...</div>}
|
||||
|
|
|
@ -40,6 +40,12 @@ export const icons = {
|
|||
<path d="M294.3,150.9l2-12.6l-12.2-2.1l0.8-4.9l17.1,2.9l-2.8,17.5L294.3,150.9L294.3,150.9z" />
|
||||
</svg>
|
||||
),
|
||||
[ICONS.FEATURED]: buildIcon(
|
||||
<g fill="none" fillRule="evenodd" strokeLinecap="round">
|
||||
<circle cx="12" cy="8" r="7" />
|
||||
<polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.ARROW_LEFT]: buildIcon(
|
||||
<g fill="none" fillRule="evenodd" strokeLinecap="round">
|
||||
<path d="M4, 12 L21, 12" />
|
||||
|
@ -210,4 +216,12 @@ export const icons = {
|
|||
[ICONS.CHAT]: buildIcon(
|
||||
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
|
||||
),
|
||||
[ICONS.YES]: buildIcon(
|
||||
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" />
|
||||
),
|
||||
[ICONS.NO]: buildIcon(
|
||||
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" />
|
||||
),
|
||||
[ICONS.UP]: buildIcon(<polyline points="18 15 12 9 6 15" />),
|
||||
[ICONS.DOWN]: buildIcon(<polyline points="6 9 12 15 18 9" />),
|
||||
};
|
||||
|
|
|
@ -42,7 +42,6 @@ class FileDetails extends PureComponent<Props> {
|
|||
<Expandable>
|
||||
{description && (
|
||||
<Fragment>
|
||||
<div className="media__info-title">About</div>
|
||||
<div className="media__info-text">
|
||||
<MarkdownPreview content={description} promptLinks />
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectFileInfoForUri, makeSelectClaimIsMine } from 'lbry-redux';
|
||||
import { makeSelectFileInfoForUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { selectRewardContentClaimIds } from 'lbryinc';
|
||||
import { makeSelectIsSubscribed, makeSelectIsNew } from 'redux/selectors/subscriptions';
|
||||
import FileProperties from './view';
|
||||
|
@ -10,6 +10,7 @@ const select = (state, props) => ({
|
|||
isSubscribed: makeSelectIsSubscribed(props.uri)(state),
|
||||
isNew: makeSelectIsNew(props.uri)(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -7,6 +7,7 @@ import FilePrice from 'component/filePrice';
|
|||
|
||||
type Props = {
|
||||
uri: string,
|
||||
claim: ?StreamClaim,
|
||||
downloaded: boolean,
|
||||
claimIsMine: boolean,
|
||||
isSubscribed: boolean,
|
||||
|
@ -15,16 +16,32 @@ type Props = {
|
|||
};
|
||||
|
||||
export default function FileProperties(props: Props) {
|
||||
const { uri, downloaded, claimIsMine, rewardedContentClaimIds, isSubscribed } = props;
|
||||
const { claim, uri, downloaded, claimIsMine, rewardedContentClaimIds, isSubscribed } = props;
|
||||
const { claimId } = parseURI(uri);
|
||||
const isRewardContent = rewardedContentClaimIds.includes(claimId);
|
||||
|
||||
const video = claim && claim.value && claim.value.video;
|
||||
let duration;
|
||||
if (video && video.duration) {
|
||||
// $FlowFixMe
|
||||
let date = new Date(null);
|
||||
date.setSeconds(video.duration);
|
||||
let timeString = date.toISOString().substr(11, 8);
|
||||
|
||||
if (timeString.startsWith('00:')) {
|
||||
timeString = timeString.substr(3);
|
||||
}
|
||||
|
||||
duration = timeString;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="file-properties">
|
||||
{isSubscribed && <Icon tooltip icon={icons.SUBSCRIPTION} />}
|
||||
{!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />}
|
||||
{isRewardContent && <Icon tooltip icon={icons.FEATURED} />}
|
||||
<FilePrice hideFree uri={uri} />
|
||||
{duration && <span className="media__subtitle">{duration}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
// );
|
||||
|
|
|
@ -56,7 +56,7 @@ export default function TagSelect(props: Props) {
|
|||
if (onSelect) {
|
||||
onSelect(tag);
|
||||
} else {
|
||||
doToggleTagFollow(tag);
|
||||
doToggleTagFollow(tag.name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,10 @@ class TransactionListRecent extends React.PureComponent<Props> {
|
|||
componentDidMount() {
|
||||
const { fetchMyClaims, fetchTransactions } = this.props;
|
||||
|
||||
// @if TARGET='app'
|
||||
fetchMyClaims();
|
||||
fetchTransactions();
|
||||
// @endif
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -4,6 +4,7 @@ import Button from 'component/button';
|
|||
import { FormField } from 'component/common/form';
|
||||
import UserEmailNew from 'component/userEmailNew';
|
||||
import UserEmailVerify from 'component/userEmailVerify';
|
||||
import cookie from 'cookie';
|
||||
|
||||
type Props = {
|
||||
cancelButton: React.Node,
|
||||
|
@ -22,6 +23,15 @@ function UserEmail(props: Props) {
|
|||
isVerified = user.has_verified_email;
|
||||
}
|
||||
|
||||
const buttonsProps = IS_WEB
|
||||
? {
|
||||
onClick: () => {
|
||||
document.cookie = cookie.serialize('auth_token', '');
|
||||
window.location.reload();
|
||||
},
|
||||
}
|
||||
: { href: 'https://lbry.com/faq/how-to-change-email' };
|
||||
|
||||
return (
|
||||
<section className="card card--section">
|
||||
{!email && <UserEmailNew />}
|
||||
|
@ -43,9 +53,7 @@ function UserEmail(props: Props) {
|
|||
readOnly
|
||||
label={__('Your Email')}
|
||||
value={email}
|
||||
inputButton={
|
||||
<Button button="inverse" label={__('Change')} href="https://lbry.com/faq/how-to-change-email" />
|
||||
}
|
||||
inputButton={<Button button="inverse" label={__('Change')} {...buttonsProps} />}
|
||||
/>
|
||||
)}
|
||||
<p className="help">
|
||||
|
|
|
@ -15,9 +15,8 @@ export const SEND = 'send';
|
|||
export const SETTINGS = 'settings';
|
||||
export const SHOW = 'show';
|
||||
export const ACCOUNT = 'account';
|
||||
export const SUBSCRIPTIONS = 'subscriptions';
|
||||
export const FOLLOWING = 'following';
|
||||
export const SEARCH = 'search';
|
||||
export const TRANSACTIONS = 'transactions';
|
||||
export const TAGS = 'tags';
|
||||
export const WALLET = 'wallet';
|
||||
export const FOLLOWING = 'following';
|
||||
|
|
|
@ -9,6 +9,8 @@ import InvitePage from 'page/invite';
|
|||
|
||||
const WalletPage = () => (
|
||||
<Page>
|
||||
<UserEmail />
|
||||
|
||||
{IS_WEB && <UnsupportedOnWeb />}
|
||||
<div className={classnames({ 'card--disabled': IS_WEB })}>
|
||||
<div className="columns">
|
||||
|
|
|
@ -15,7 +15,7 @@ function DiscoverPage(props: Props) {
|
|||
<ClaimListDiscover
|
||||
personal
|
||||
tags={followedTags.map(tag => tag.name)}
|
||||
injectedItem={<TagsSelect showClose title={__('Make This Your Own')} />}
|
||||
injectedItem={<TagsSelect showClose title={__('Customize Your Homepage')} />}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -184,6 +184,21 @@ class FilePage extends React.Component<Props> {
|
|||
|
||||
const insufficientCredits = !claimIsMine && costInfo && costInfo.cost > balance;
|
||||
|
||||
const video = claim && claim.value && claim.value.video;
|
||||
let duration;
|
||||
if (video && video.duration) {
|
||||
// $FlowFixMe
|
||||
let date = new Date(null);
|
||||
date.setSeconds(video.duration);
|
||||
let timeString = date.toISOString().substr(11, 8);
|
||||
|
||||
if (timeString.startsWith('00:')) {
|
||||
timeString = timeString.substr(3);
|
||||
}
|
||||
|
||||
duration = timeString;
|
||||
}
|
||||
|
||||
return (
|
||||
<Page className="main--file-page">
|
||||
<div className="grid-area--content card">
|
||||
|
@ -222,22 +237,11 @@ class FilePage extends React.Component<Props> {
|
|||
|
||||
<div className="grid-area--info media__content media__content--large">
|
||||
<h1 className="media__title media__title--large">{title}</h1>
|
||||
|
||||
<div className="media__actions media__actions--between">
|
||||
<div className="media__subtext media__subtext--large">
|
||||
<div className="media__subtitle__channel">
|
||||
<UriIndicator uri={uri} link />
|
||||
</div>
|
||||
{__('Published on')} <DateTime uri={uri} show={DateTime.SHOW_DATE} />
|
||||
<div className="media__subtext media__subtext--large">
|
||||
<div className="media__subtitle__channel">
|
||||
<UriIndicator uri={uri} link />
|
||||
</div>
|
||||
|
||||
{claimIsMine && (
|
||||
<p>
|
||||
{viewCount} {viewCount !== 1 ? __('Views') : __('View')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="media__actions media__actions--between">
|
||||
<div className="media__action-group--large">
|
||||
{claimIsMine && (
|
||||
|
@ -282,6 +286,25 @@ class FilePage extends React.Component<Props> {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="media__actions media__actions--between"
|
||||
style={{ marginTop: '1rem', paddingTop: '1rem', borderTop: '1px solid #ddd' }}
|
||||
>
|
||||
<div className="media__subtext media__subtext--large">
|
||||
<DateTime uri={uri} show={DateTime.SHOW_DATE} />
|
||||
</div>
|
||||
|
||||
<div className="media__subtext media__subtext--large">
|
||||
{video && <p className="media__info-text">{duration}</p>}
|
||||
|
||||
{claimIsMine && (
|
||||
<p>
|
||||
{viewCount} {viewCount !== 1 ? __('Views') : __('View')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="media__info--large">
|
||||
<ClaimTags uri={uri} type="large" />
|
||||
</div>
|
||||
|
|
|
@ -5,26 +5,22 @@ import TagsSelect from 'component/tagsSelect';
|
|||
import ClaimList from 'component/claimList';
|
||||
|
||||
type Props = {
|
||||
subscribedChannels: Array<{ uri: string }>,
|
||||
subscribedChannels: Array<Subscription>,
|
||||
};
|
||||
|
||||
function DiscoverPage(props: Props) {
|
||||
function FollowingEditPage(props: Props) {
|
||||
const { subscribedChannels } = props;
|
||||
|
||||
const channelUris = subscribedChannels.map(({ uri }) => uri);
|
||||
return (
|
||||
<Page>
|
||||
<div className="card">
|
||||
<TagsSelect showClose={false} title={__('Find New Tags To Follow')} />
|
||||
</div>
|
||||
<div className="card">
|
||||
<ClaimList
|
||||
header={<h1>{__('Channels You Are Following')}</h1>}
|
||||
empty={__("You aren't following any channels.")}
|
||||
uris={subscribedChannels.map(({ uri }) => uri)}
|
||||
/>
|
||||
<ClaimList uris={channelUris} />
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default DiscoverPage;
|
||||
export default FollowingEditPage;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @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 Button from 'component/button';
|
||||
|
@ -29,7 +29,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,27 +53,31 @@ 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 (
|
||||
<Page>
|
||||
<div className="card">
|
||||
<ClaimList
|
||||
loading={loading}
|
||||
header={<h1>{viewingSuggestedSubs ? __('Discover New Channels') : __('Latest From Your Subscriptions')}</h1>}
|
||||
header={
|
||||
<h1>{viewingSuggestedSubs ? __('Discover New Channels') : __("Latest From Who You're Following")}</h1>
|
||||
}
|
||||
headerAltControls={
|
||||
<Button
|
||||
button="link"
|
||||
label={viewingSuggestedSubs ? hasSubscriptions && __('Following') : __('Find New Channels')}
|
||||
label={viewingSuggestedSubs ? hasSubscriptions && __('View Your Feed') : __('Find New Channels')}
|
||||
onClick={() => onClick()}
|
||||
/>
|
||||
}
|
||||
uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris}
|
||||
onScrollBottom={() => console.log('scroll bottom') || setPage(page + 1)}
|
||||
/>
|
||||
</div>
|
||||
</Page>
|
||||
|
|
|
@ -4,13 +4,17 @@ import WalletSend from 'component/walletSend';
|
|||
import WalletAddress from 'component/walletAddress';
|
||||
import TransactionListRecent from 'component/transactionListRecent';
|
||||
import Page from 'component/page';
|
||||
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
|
||||
|
||||
const WalletPage = () => (
|
||||
<Page>
|
||||
<WalletBalance />
|
||||
<TransactionListRecent />
|
||||
<WalletSend />
|
||||
<WalletAddress />
|
||||
{IS_WEB && <UnsupportedOnWeb />}
|
||||
<div className={IS_WEB && 'card--disabled'}>
|
||||
<WalletBalance />
|
||||
<TransactionListRecent />
|
||||
<WalletSend />
|
||||
<WalletAddress />
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
|
||||
|
|
|
@ -13,13 +13,13 @@
|
|||
@import 'component/button';
|
||||
@import 'component/card';
|
||||
@import 'component/channel';
|
||||
@import 'component/claim-list';
|
||||
@import 'component/comments';
|
||||
@import 'component/content';
|
||||
@import 'component/credit';
|
||||
@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';
|
||||
|
|
|
@ -16,7 +16,7 @@ $metadata-z-index: 1;
|
|||
align-self: flex-start;
|
||||
position: absolute;
|
||||
object-fit: cover;
|
||||
filter: brightness(60%);
|
||||
filter: brightness(50%);
|
||||
}
|
||||
|
||||
.channel-cover,
|
||||
|
@ -27,8 +27,8 @@ $metadata-z-index: 1;
|
|||
|
||||
.channel-thumbnail {
|
||||
display: flex;
|
||||
height: 5.3rem;
|
||||
width: 5.4rem;
|
||||
height: 5rem;
|
||||
width: 6rem;
|
||||
background-size: cover;
|
||||
margin-right: var(--spacing-medium);
|
||||
}
|
||||
|
@ -52,7 +52,6 @@ $metadata-z-index: 1;
|
|||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
align-self: flex-end;
|
||||
// margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.channel-thumbnail,
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
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;
|
||||
font-size: 1.3rem;
|
||||
padding: 0 var(--spacing-medium);
|
||||
padding-right: var(--spacing-large);
|
||||
margin-bottom: 0;
|
||||
|
@ -60,16 +61,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.claim-list__header-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.claim-list__header-text,
|
||||
.claim-list__dropdown {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.claim-list__alt-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -81,7 +72,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.claim-list__item {
|
||||
.claim-preview {
|
||||
display: flex;
|
||||
position: relative;
|
||||
font-size: 1.3rem;
|
||||
|
@ -104,12 +95,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.claim-list__item--injected,
|
||||
.claim-list__item + .claim-list__item {
|
||||
.claim-preview--injected,
|
||||
.claim-preview {
|
||||
border-top: 1px solid rgba($lbry-teal-5, 0.1);
|
||||
}
|
||||
|
||||
.claim-list__item--large {
|
||||
.claim-preview--large {
|
||||
@include mediaThumbHoverZoom;
|
||||
font-size: 1.6rem;
|
||||
border-bottom: 0;
|
||||
|
@ -138,32 +129,32 @@
|
|||
}
|
||||
}
|
||||
|
||||
.claim-list__item-metadata {
|
||||
.claim-preview-metadata {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.claim-list__item-info {
|
||||
.claim-preview-info {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.claim-list__item-info,
|
||||
.claim-list__item-properties {
|
||||
.claim-preview-info,
|
||||
.claim-preview-properties {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.claim-list__item-properties {
|
||||
.claim-preview-properties {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.claim-list__item-title {
|
||||
.claim-preview-title {
|
||||
font-weight: 600;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.claim-list__item-tags {
|
||||
.claim-preview-tags {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
|
@ -3,6 +3,14 @@
|
|||
position: relative;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
stroke: rgba($lbry-black, 0.5);
|
||||
|
||||
html[data-mode='dark'] & {
|
||||
stroke: rgba($lbry-white, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
& > *:not(:last-child) {
|
||||
margin-right: var(--spacing-small);
|
||||
}
|
||||
|
|
|
@ -132,10 +132,6 @@
|
|||
color: rgba($lbry-black, 0.8);
|
||||
font-size: 0.9em;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: var(--spacing-medium);
|
||||
}
|
||||
|
||||
html[data-mode='dark'] & {
|
||||
color: rgba($lbry-white, 0.7);
|
||||
}
|
||||
|
@ -150,7 +146,7 @@
|
|||
|
||||
.media__subtitle {
|
||||
font-size: 0.8em;
|
||||
color: rgba($lbry-black, 0.8);
|
||||
color: rgba($lbry-black, 0.6);
|
||||
|
||||
[data-mode='dark'] & {
|
||||
color: rgba($lbry-white, 0.8);
|
||||
|
@ -167,6 +163,7 @@
|
|||
|
||||
.media__subtitle__channel {
|
||||
font-weight: 600;
|
||||
margin: var(--spacing-small) 0;
|
||||
}
|
||||
|
||||
// M E D I A
|
||||
|
@ -181,7 +178,7 @@
|
|||
}
|
||||
|
||||
.media__info--large {
|
||||
border-top: 1px solid $lbry-gray-1;
|
||||
// border-top: 1px solid $lbry-gray-1;
|
||||
margin-top: var(--spacing-medium);
|
||||
|
||||
html[data-mode='dark'] & {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
.placeholder {
|
||||
display: flex;
|
||||
|
||||
&.claim-list__item-title {
|
||||
&.claim-preview-title {
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
}
|
||||
|
|
|
@ -90,7 +90,6 @@
|
|||
|
||||
.icon {
|
||||
margin-right: var(--spacing-small);
|
||||
margin-bottom: 0.2rem;
|
||||
stroke: $lbry-gray-5;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@mixin placeholder {
|
||||
animation: pulse 2s infinite ease-in-out;
|
||||
background-color: $lbry-gray-2;
|
||||
background-color: $lbry-gray-1;
|
||||
border-radius: var(--card-radius);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<meta property="og:description" content="All your favorite LBRY content in your browser." />
|
||||
<meta property="og:image" content="/og.png" />
|
||||
<!-- @endif -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- <meta name="viewport" content="width=device-width, initial-scale=1" /> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -6641,9 +6641,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#b3bf3f6d53410ff1c5415b51ca425341e364959f:
|
||||
lbry-redux@lbryio/lbry-redux#2930ad82a90ca91f6caf3761597ef9a67da7db66:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/b3bf3f6d53410ff1c5415b51ca425341e364959f"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/2930ad82a90ca91f6caf3761597ef9a67da7db66"
|
||||
dependencies:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
|
Loading…
Reference in a new issue