Add tags to publish page, infinite scroll, navigation improvements #2593
33 changed files with 236 additions and 110 deletions
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "LBRY",
|
"name": "LBRY",
|
||||||
"version": "0.33.1",
|
"version": "0.33.2",
|
||||||
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"lbry"
|
"lbry"
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
"jsmediatags": "^3.8.1",
|
"jsmediatags": "^3.8.1",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"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",
|
"lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { hot } from 'react-hot-loader/root';
|
import { hot } from 'react-hot-loader/root';
|
||||||
import { connect } from 'react-redux';
|
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 { selectUser, doRewardList, doFetchRewardedContent } from 'lbryinc';
|
||||||
import { selectThemePath } from 'redux/selectors/settings';
|
import { selectThemePath } from 'redux/selectors/settings';
|
||||||
import App from './view';
|
import App from './view';
|
||||||
|
@ -15,6 +15,7 @@ const perform = dispatch => ({
|
||||||
updateBlockHeight: () => dispatch(doUpdateBlockHeight()),
|
updateBlockHeight: () => dispatch(doUpdateBlockHeight()),
|
||||||
fetchRewards: () => dispatch(doRewardList()),
|
fetchRewards: () => dispatch(doRewardList()),
|
||||||
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
||||||
|
fetchTransactions: () => dispatch(doFetchTransactions()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default hot(
|
export default hot(
|
||||||
|
|
|
@ -9,6 +9,8 @@ import { openContextMenu } from 'util/context-menu';
|
||||||
import useKonamiListener from 'util/enhanced-layout';
|
import useKonamiListener from 'util/enhanced-layout';
|
||||||
import Yrbl from 'component/yrbl';
|
import Yrbl from 'component/yrbl';
|
||||||
|
|
||||||
|
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
alertError: (string | {}) => void,
|
alertError: (string | {}) => void,
|
||||||
pageTitle: ?string,
|
pageTitle: ?string,
|
||||||
|
@ -16,18 +18,23 @@ type Props = {
|
||||||
theme: string,
|
theme: string,
|
||||||
fetchRewards: () => void,
|
fetchRewards: () => void,
|
||||||
fetchRewardedContent: () => void,
|
fetchRewardedContent: () => void,
|
||||||
|
fetchTransactions: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function App(props: Props) {
|
function App(props: Props) {
|
||||||
const { theme, fetchRewards, fetchRewardedContent } = props;
|
const { theme, fetchRewards, fetchRewardedContent, fetchTransactions } = props;
|
||||||
const appRef = useRef();
|
const appRef = useRef();
|
||||||
const isEnhancedLayout = useKonamiListener();
|
const isEnhancedLayout = useKonamiListener();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactModal.setAppElement(appRef.current);
|
ReactModal.setAppElement(appRef.current);
|
||||||
fetchRewards();
|
|
||||||
fetchRewardedContent();
|
fetchRewardedContent();
|
||||||
}, [fetchRewards, fetchRewardedContent]);
|
|
||||||
|
// @if TARGET='app'
|
||||||
|
fetchRewards();
|
||||||
|
fetchTransactions();
|
||||||
|
// @endif
|
||||||
|
}, [fetchRewards, fetchRewardedContent, fetchTransactions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
|
@ -38,7 +45,7 @@ function App(props: Props) {
|
||||||
<div ref={appRef} onContextMenu={e => openContextMenu(e)}>
|
<div ref={appRef} onContextMenu={e => openContextMenu(e)}>
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<div className="main-wrapper">
|
<div className={MAIN_WRAPPER_CLASS}>
|
||||||
<div className="main-wrapper-inner">
|
<div className="main-wrapper-inner">
|
||||||
<Router />
|
<Router />
|
||||||
<SideBar />
|
<SideBar />
|
||||||
|
|
|
@ -35,7 +35,7 @@ function ChannelContent(props: Props) {
|
||||||
|
|
||||||
{!channelIsMine && <HiddenNsfwClaims className="card__content help" uri={uri} />}
|
{!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
|
<Paginate
|
||||||
onPageChange={page => fetchClaims(uri, page)}
|
onPageChange={page => fetchClaims(uri, page)}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import { MAIN_WRAPPER_CLASS } from 'component/app/view';
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import ClaimPreview from 'component/claimPreview';
|
import ClaimPreview from 'component/claimPreview';
|
||||||
import Spinner from 'component/spinner';
|
import Spinner from 'component/spinner';
|
||||||
|
@ -19,14 +20,25 @@ type Props = {
|
||||||
type: string,
|
type: string,
|
||||||
empty?: string,
|
empty?: string,
|
||||||
meta?: Node,
|
meta?: Node,
|
||||||
|
onScrollBottom?: any => void,
|
||||||
// If using the default header, this is a unique ID needed to persist the state of the filter setting
|
// If using the default header, this is a unique ID needed to persist the state of the filter setting
|
||||||
persistedStorageKey?: string,
|
persistedStorageKey?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function ClaimList(props: Props) {
|
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 [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
||||||
const sortedUris = uris && currentSort === SORT_NEW ? uris.slice().reverse() : uris;
|
|
||||||
const hasUris = uris && !!uris.length;
|
const hasUris = uris && !!uris.length;
|
||||||
const sortedUris = (hasUris && (currentSort === SORT_NEW ? uris : uris.slice().reverse())) || [];
|
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);
|
setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW);
|
||||||
}
|
}
|
||||||
It could potentially make sense to move this out of here and instead have the scroll state be something that is generally consumable by any component (i.e. track the scroll state app-wide). I'm not up to date on the best techniques for this though. It could potentially make sense to move this out of here and instead have the scroll state be something that is generally consumable by any component (i.e. track the scroll state app-wide).
I'm not up to date on the best techniques for this though.
|
|||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<section className={classnames('file-list')}>
|
<section className={classnames('claim-list')}>
|
||||||
{header !== false && (
|
{header !== false && (
|
||||||
<div className={classnames('claim-list__header', { 'claim-list__header--small': type === 'small' })}>
|
<div className={classnames('claim-list__header', { 'claim-list__header--small': type === 'small' })}>
|
||||||
{header || (
|
{header || (
|
||||||
|
@ -60,7 +95,7 @@ export default function ClaimList(props: Props) {
|
||||||
{sortedUris.map((uri, index) => (
|
{sortedUris.map((uri, index) => (
|
||||||
<React.Fragment key={uri}>
|
<React.Fragment key={uri}>
|
||||||
<ClaimPreview uri={uri} type={type} />
|
<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>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import usePersistedState from 'util/use-persisted-state';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
import Tag from 'component/tag';
|
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_DAY = 'day';
|
||||||
const TIME_WEEK = 'week';
|
const TIME_WEEK = 'week';
|
||||||
const TIME_MONTH = 'month';
|
const TIME_MONTH = 'month';
|
||||||
|
@ -37,11 +39,17 @@ function ClaimListDiscover(props: Props) {
|
||||||
const [personalSort, setPersonalSort] = usePersistedState('file-list-trending:personalSort', SEARCH_SORT_YOU);
|
const [personalSort, setPersonalSort] = usePersistedState('file-list-trending:personalSort', SEARCH_SORT_YOU);
|
||||||
const [typeSort, setTypeSort] = usePersistedState('file-list-trending:typeSort', TYPE_TRENDING);
|
const [typeSort, setTypeSort] = usePersistedState('file-list-trending:typeSort', TYPE_TRENDING);
|
||||||
const [timeSort, setTimeSort] = usePersistedState('file-list-trending:timeSort', TIME_WEEK);
|
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 toCapitalCase = string => string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
const tagsString = tags.join(',');
|
const tagsString = tags.join(',');
|
||||||
useEffect(() => {
|
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(',');
|
const newTags = tagsString.split(',');
|
||||||
|
|
||||||
if ((newTags && !personal) || (newTags && personal && personalSort === SEARCH_SORT_YOU)) {
|
if ((newTags && !personal) || (newTags && personal && personalSort === SEARCH_SORT_YOU)) {
|
||||||
|
@ -65,7 +73,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
doClaimSearch(20, options);
|
doClaimSearch(20, options);
|
||||||
}, [personal, personalSort, typeSort, timeSort, doClaimSearch, tagsString]);
|
}, [personal, personalSort, typeSort, timeSort, doClaimSearch, page, tagsString]);
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
<h1 className="card__title--flex">
|
<h1 className="card__title--flex">
|
||||||
|
@ -91,7 +99,10 @@ function ClaimListDiscover(props: Props) {
|
||||||
name="trending_overview"
|
name="trending_overview"
|
||||||
className="claim-list__dropdown"
|
className="claim-list__dropdown"
|
||||||
value={personalSort}
|
value={personalSort}
|
||||||
onChange={e => setPersonalSort(e.target.value)}
|
onChange={e => {
|
||||||
|
setPage(1);
|
||||||
|
setPersonalSort(e.target.value);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{SEARCH_FILTER_TYPES.map(type => (
|
{SEARCH_FILTER_TYPES.map(type => (
|
||||||
<option key={type} value={type}>
|
<option key={type} value={type}>
|
||||||
|
@ -132,7 +143,10 @@ function ClaimListDiscover(props: Props) {
|
||||||
injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem}
|
injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem}
|
||||||
header={header}
|
header={header}
|
||||||
headerAltControls={headerAltControls}
|
headerAltControls={headerAltControls}
|
||||||
|
onScrollBottom={() => setPage(page + 1)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{loading && page > 1 && new Array(PAGE_SIZE).fill(1).map((x, i) => <ClaimPreview key={i} placeholder />)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { convertToShareLink } from 'lbry-redux';
|
import { parseURI, convertToShareLink } from 'lbry-redux';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { openCopyLinkMenu } from 'util/context-menu';
|
import { openCopyLinkMenu } from 'util/context-menu';
|
||||||
import { formatLbryUriForWeb } from 'util/uri';
|
import { formatLbryUriForWeb } from 'util/uri';
|
||||||
|
@ -53,8 +53,8 @@ function ClaimPreview(props: Props) {
|
||||||
blackListedOutpoints,
|
blackListedOutpoints,
|
||||||
} = props;
|
} = props;
|
||||||
const haventFetched = claim === undefined;
|
const haventFetched = claim === undefined;
|
||||||
const abandoned = !isResolvingUri && !claim;
|
const abandoned = !isResolvingUri && !claim && !placeholder;
|
||||||
const isChannel = claim && claim.value_type === 'channel';
|
const { isChannel } = parseURI(uri);
|
||||||
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
||||||
let shouldHide = abandoned || (!claimIsMine && obscureNsfw && nsfw);
|
let shouldHide = abandoned || (!claimIsMine && obscureNsfw && nsfw);
|
||||||
|
|
||||||
|
@ -94,10 +94,10 @@ function ClaimPreview(props: Props) {
|
||||||
|
|
||||||
if (placeholder && !claim) {
|
if (placeholder && !claim) {
|
||||||
return (
|
return (
|
||||||
<li className="claim-list__item" disabled>
|
<li className="claim-preview" disabled>
|
||||||
<div className="placeholder media__thumb" />
|
<div className="placeholder media__thumb" />
|
||||||
<div className="placeholder__wrapper">
|
<div className="placeholder__wrapper">
|
||||||
<div className="placeholder claim-list__item-title" />
|
<div className="placeholder claim-preview-title" />
|
||||||
<div className="placeholder media__subtitle" />
|
<div className="placeholder media__subtitle" />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -109,15 +109,15 @@ function ClaimPreview(props: Props) {
|
||||||
role="link"
|
role="link"
|
||||||
onClick={pending ? undefined : onClick}
|
onClick={pending ? undefined : onClick}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
className={classnames('claim-list__item', {
|
className={classnames('claim-preview', {
|
||||||
'claim-list__item--large': type === 'large',
|
'claim-preview--large': type === 'large',
|
||||||
'claim-list__pending': pending,
|
'claim-list__pending': pending,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isChannel ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />}
|
{isChannel ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />}
|
||||||
<div className="claim-list__item-metadata">
|
<div className="claim-preview-metadata">
|
||||||
<div className="claim-list__item-info">
|
<div className="claim-preview-info">
|
||||||
<div className="claim-list__item-title">
|
<div className="claim-preview-title">
|
||||||
<TruncatedText text={title || (claim && claim.name)} lines={1} />
|
<TruncatedText text={title || (claim && claim.name)} lines={1} />
|
||||||
</div>
|
</div>
|
||||||
{type !== 'small' && (
|
{type !== 'small' && (
|
||||||
|
@ -128,7 +128,7 @@ function ClaimPreview(props: Props) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="claim-list__item-properties">
|
<div className="claim-preview-properties">
|
||||||
<div className="media__subtitle">
|
<div className="media__subtitle">
|
||||||
<UriIndicator uri={uri} link />
|
<UriIndicator uri={uri} link />
|
||||||
{pending && <div>Pending...</div>}
|
{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" />
|
<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>
|
</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(
|
[ICONS.ARROW_LEFT]: buildIcon(
|
||||||
<g fill="none" fillRule="evenodd" strokeLinecap="round">
|
<g fill="none" fillRule="evenodd" strokeLinecap="round">
|
||||||
<path d="M4, 12 L21, 12" />
|
<path d="M4, 12 L21, 12" />
|
||||||
|
@ -210,4 +216,12 @@ export const icons = {
|
||||||
[ICONS.CHAT]: buildIcon(
|
[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" />
|
<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>
|
<Expandable>
|
||||||
{description && (
|
{description && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="media__info-title">About</div>
|
|
||||||
<div className="media__info-text">
|
<div className="media__info-text">
|
||||||
<MarkdownPreview content={description} promptLinks />
|
<MarkdownPreview content={description} promptLinks />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectFileInfoForUri, makeSelectClaimIsMine } from 'lbry-redux';
|
import { makeSelectFileInfoForUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
||||||
import { selectRewardContentClaimIds } from 'lbryinc';
|
import { selectRewardContentClaimIds } from 'lbryinc';
|
||||||
import { makeSelectIsSubscribed, makeSelectIsNew } from 'redux/selectors/subscriptions';
|
import { makeSelectIsSubscribed, makeSelectIsNew } from 'redux/selectors/subscriptions';
|
||||||
import FileProperties from './view';
|
import FileProperties from './view';
|
||||||
|
@ -10,6 +10,7 @@ const select = (state, props) => ({
|
||||||
isSubscribed: makeSelectIsSubscribed(props.uri)(state),
|
isSubscribed: makeSelectIsSubscribed(props.uri)(state),
|
||||||
isNew: makeSelectIsNew(props.uri)(state),
|
isNew: makeSelectIsNew(props.uri)(state),
|
||||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
|
|
|
@ -7,6 +7,7 @@ import FilePrice from 'component/filePrice';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
|
claim: ?StreamClaim,
|
||||||
downloaded: boolean,
|
downloaded: boolean,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
isSubscribed: boolean,
|
isSubscribed: boolean,
|
||||||
|
@ -15,16 +16,32 @@ type Props = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FileProperties(props: 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 { claimId } = parseURI(uri);
|
||||||
const isRewardContent = rewardedContentClaimIds.includes(claimId);
|
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 (
|
return (
|
||||||
<div className="file-properties">
|
<div className="file-properties">
|
||||||
{isSubscribed && <Icon tooltip icon={icons.SUBSCRIPTION} />}
|
{isSubscribed && <Icon tooltip icon={icons.SUBSCRIPTION} />}
|
||||||
{!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />}
|
{!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />}
|
||||||
{isRewardContent && <Icon tooltip icon={icons.FEATURED} />}
|
{isRewardContent && <Icon tooltip icon={icons.FEATURED} />}
|
||||||
<FilePrice hideFree uri={uri} />
|
<FilePrice hideFree uri={uri} />
|
||||||
|
{duration && <span className="media__subtitle">{duration}</span>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { remote } from 'electron';
|
import { remote } from 'electron';
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import LoadingScreen from 'component/common/loading-screen';
|
import LoadingScreen from 'component/common/loading-screen';
|
||||||
import VideoViewer from 'component/viewers/videoViewer';
|
import VideoViewer from 'component/viewers/videoViewer';
|
||||||
|
|
||||||
// Audio player on hold until the current player is dropped
|
// Audio player on hold until the current player is dropped
|
||||||
// This component is half working
|
// This component is half working
|
||||||
// const AudioViewer = React.lazy<*>(() =>
|
// const AudioViewer = React.lazy<*>(() =>
|
||||||
|
// import(
|
||||||
|
// /* webpackChunkName: "audioViewer" */
|
||||||
|
// 'component/viewers/audioViewer'
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
// const AudioViewer = React.lazy<*>(() =>
|
||||||
// import(/* webpackChunkName: "audioViewer" */
|
// import(/* webpackChunkName: "audioViewer" */
|
||||||
// 'component/viewers/audioViewer')
|
// 'component/viewers/audioViewer')
|
||||||
// );
|
// );
|
||||||
|
|
|
@ -56,7 +56,7 @@ export default function TagSelect(props: Props) {
|
||||||
if (onSelect) {
|
if (onSelect) {
|
||||||
onSelect(tag);
|
onSelect(tag);
|
||||||
} else {
|
} else {
|
||||||
doToggleTagFollow(tag);
|
doToggleTagFollow(tag.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,10 @@ class TransactionListRecent extends React.PureComponent<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { fetchMyClaims, fetchTransactions } = this.props;
|
const { fetchMyClaims, fetchTransactions } = this.props;
|
||||||
|
|
||||||
|
// @if TARGET='app'
|
||||||
fetchMyClaims();
|
fetchMyClaims();
|
||||||
fetchTransactions();
|
fetchTransactions();
|
||||||
|
// @endif
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Button from 'component/button';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import UserEmailNew from 'component/userEmailNew';
|
import UserEmailNew from 'component/userEmailNew';
|
||||||
import UserEmailVerify from 'component/userEmailVerify';
|
import UserEmailVerify from 'component/userEmailVerify';
|
||||||
|
import cookie from 'cookie';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cancelButton: React.Node,
|
cancelButton: React.Node,
|
||||||
|
@ -22,6 +23,15 @@ function UserEmail(props: Props) {
|
||||||
isVerified = user.has_verified_email;
|
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 (
|
return (
|
||||||
<section className="card card--section">
|
<section className="card card--section">
|
||||||
{!email && <UserEmailNew />}
|
{!email && <UserEmailNew />}
|
||||||
|
@ -43,9 +53,7 @@ function UserEmail(props: Props) {
|
||||||
readOnly
|
readOnly
|
||||||
label={__('Your Email')}
|
label={__('Your Email')}
|
||||||
value={email}
|
value={email}
|
||||||
inputButton={
|
inputButton={<Button button="inverse" label={__('Change')} {...buttonsProps} />}
|
||||||
<Button button="inverse" label={__('Change')} href="https://lbry.com/faq/how-to-change-email" />
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<p className="help">
|
<p className="help">
|
||||||
|
|
|
@ -15,9 +15,8 @@ export const SEND = 'send';
|
||||||
export const SETTINGS = 'settings';
|
export const SETTINGS = 'settings';
|
||||||
export const SHOW = 'show';
|
export const SHOW = 'show';
|
||||||
export const ACCOUNT = 'account';
|
export const ACCOUNT = 'account';
|
||||||
export const SUBSCRIPTIONS = 'subscriptions';
|
export const FOLLOWING = 'following';
|
||||||
export const SEARCH = 'search';
|
export const SEARCH = 'search';
|
||||||
export const TRANSACTIONS = 'transactions';
|
export const TRANSACTIONS = 'transactions';
|
||||||
export const TAGS = 'tags';
|
export const TAGS = 'tags';
|
||||||
export const WALLET = 'wallet';
|
export const WALLET = 'wallet';
|
||||||
export const FOLLOWING = 'following';
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import InvitePage from 'page/invite';
|
||||||
|
|
||||||
const WalletPage = () => (
|
const WalletPage = () => (
|
||||||
<Page>
|
<Page>
|
||||||
|
<UserEmail />
|
||||||
|
|
||||||
{IS_WEB && <UnsupportedOnWeb />}
|
{IS_WEB && <UnsupportedOnWeb />}
|
||||||
<div className={classnames({ 'card--disabled': IS_WEB })}>
|
<div className={classnames({ 'card--disabled': IS_WEB })}>
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
|
|
|
@ -15,7 +15,7 @@ function DiscoverPage(props: Props) {
|
||||||
<ClaimListDiscover
|
<ClaimListDiscover
|
||||||
personal
|
personal
|
||||||
tags={followedTags.map(tag => tag.name)}
|
tags={followedTags.map(tag => tag.name)}
|
||||||
injectedItem={<TagsSelect showClose title={__('Make This Your Own')} />}
|
injectedItem={<TagsSelect showClose title={__('Customize Your Homepage')} />}
|
||||||
/>
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|
|
@ -184,6 +184,21 @@ class FilePage extends React.Component<Props> {
|
||||||
|
|
||||||
const insufficientCredits = !claimIsMine && costInfo && costInfo.cost > balance;
|
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 (
|
return (
|
||||||
<Page className="main--file-page">
|
<Page className="main--file-page">
|
||||||
<div className="grid-area--content card">
|
<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">
|
<div className="grid-area--info media__content media__content--large">
|
||||||
<h1 className="media__title media__title--large">{title}</h1>
|
<h1 className="media__title media__title--large">{title}</h1>
|
||||||
|
<div className="media__subtext media__subtext--large">
|
||||||
<div className="media__actions media__actions--between">
|
<div className="media__subtitle__channel">
|
||||||
<div className="media__subtext media__subtext--large">
|
<UriIndicator uri={uri} link />
|
||||||
<div className="media__subtitle__channel">
|
|
||||||
<UriIndicator uri={uri} link />
|
|
||||||
</div>
|
|
||||||
{__('Published on')} <DateTime uri={uri} show={DateTime.SHOW_DATE} />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{claimIsMine && (
|
|
||||||
<p>
|
|
||||||
{viewCount} {viewCount !== 1 ? __('Views') : __('View')}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="media__actions media__actions--between">
|
<div className="media__actions media__actions--between">
|
||||||
<div className="media__action-group--large">
|
<div className="media__action-group--large">
|
||||||
{claimIsMine && (
|
{claimIsMine && (
|
||||||
|
@ -282,6 +286,25 @@ class FilePage extends React.Component<Props> {
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div className="media__info--large">
|
||||||
<ClaimTags uri={uri} type="large" />
|
<ClaimTags uri={uri} type="large" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,26 +5,22 @@ import TagsSelect from 'component/tagsSelect';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
subscribedChannels: Array<{ uri: string }>,
|
subscribedChannels: Array<Subscription>,
|
||||||
};
|
};
|
||||||
|
|
||||||
function DiscoverPage(props: Props) {
|
function FollowingEditPage(props: Props) {
|
||||||
const { subscribedChannels } = props;
|
const { subscribedChannels } = props;
|
||||||
|
const channelUris = subscribedChannels.map(({ uri }) => uri);
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<TagsSelect showClose={false} title={__('Find New Tags To Follow')} />
|
<TagsSelect showClose={false} title={__('Find New Tags To Follow')} />
|
||||||
</div>
|
</div>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<ClaimList
|
<ClaimList uris={channelUris} />
|
||||||
header={<h1>{__('Channels You Are Following')}</h1>}
|
|
||||||
empty={__("You aren't following any channels.")}
|
|
||||||
uris={subscribedChannels.map(({ uri }) => uri)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DiscoverPage;
|
export default FollowingEditPage;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
|
@ -29,7 +29,7 @@ export default function SubscriptionsPage(props: Props) {
|
||||||
doClaimSearch,
|
doClaimSearch,
|
||||||
uris,
|
uris,
|
||||||
} = props;
|
} = props;
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
const hasSubscriptions = !!subscribedChannels.length;
|
const hasSubscriptions = !!subscribedChannels.length;
|
||||||
const { search } = location;
|
const { search } = location;
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
|
@ -53,27 +53,31 @@ export default function SubscriptionsPage(props: Props) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const ids = idString.split(',');
|
const ids = idString.split(',');
|
||||||
const options = {
|
const options = {
|
||||||
|
page,
|
||||||
channel_ids: ids,
|
channel_ids: ids,
|
||||||
order_by: ['release_time'],
|
order_by: ['release_time'],
|
||||||
};
|
};
|
||||||
|
|
||||||
doClaimSearch(20, options);
|
doClaimSearch(20, options);
|
||||||
}, [idString, doClaimSearch]);
|
}, [idString, doClaimSearch, page]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<ClaimList
|
<ClaimList
|
||||||
loading={loading}
|
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={
|
headerAltControls={
|
||||||
<Button
|
<Button
|
||||||
button="link"
|
button="link"
|
||||||
label={viewingSuggestedSubs ? hasSubscriptions && __('Following') : __('Find New Channels')}
|
label={viewingSuggestedSubs ? hasSubscriptions && __('View Your Feed') : __('Find New Channels')}
|
||||||
onClick={() => onClick()}
|
onClick={() => onClick()}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris}
|
uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris}
|
||||||
|
onScrollBottom={() => console.log('scroll bottom') || setPage(page + 1)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
@ -4,13 +4,17 @@ import WalletSend from 'component/walletSend';
|
||||||
import WalletAddress from 'component/walletAddress';
|
import WalletAddress from 'component/walletAddress';
|
||||||
import TransactionListRecent from 'component/transactionListRecent';
|
import TransactionListRecent from 'component/transactionListRecent';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
|
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
|
||||||
|
|
||||||
const WalletPage = () => (
|
const WalletPage = () => (
|
||||||
<Page>
|
<Page>
|
||||||
<WalletBalance />
|
{IS_WEB && <UnsupportedOnWeb />}
|
||||||
<TransactionListRecent />
|
<div className={IS_WEB && 'card--disabled'}>
|
||||||
<WalletSend />
|
<WalletBalance />
|
||||||
<WalletAddress />
|
<TransactionListRecent />
|
||||||
|
<WalletSend />
|
||||||
|
<WalletAddress />
|
||||||
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -13,13 +13,13 @@
|
||||||
@import 'component/button';
|
@import 'component/button';
|
||||||
@import 'component/card';
|
@import 'component/card';
|
||||||
@import 'component/channel';
|
@import 'component/channel';
|
||||||
|
@import 'component/claim-list';
|
||||||
@import 'component/comments';
|
@import 'component/comments';
|
||||||
@import 'component/content';
|
@import 'component/content';
|
||||||
@import 'component/credit';
|
@import 'component/credit';
|
||||||
@import 'component/dat-gui';
|
@import 'component/dat-gui';
|
||||||
@import 'component/expandable';
|
@import 'component/expandable';
|
||||||
@import 'component/file-download';
|
@import 'component/file-download';
|
||||||
@import 'component/file-list';
|
|
||||||
@import 'component/file-properties';
|
@import 'component/file-properties';
|
||||||
@import 'component/file-render';
|
@import 'component/file-render';
|
||||||
@import 'component/form-field';
|
@import 'component/form-field';
|
||||||
|
|
|
@ -16,7 +16,7 @@ $metadata-z-index: 1;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
filter: brightness(60%);
|
filter: brightness(50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-cover,
|
.channel-cover,
|
||||||
|
@ -27,8 +27,8 @@ $metadata-z-index: 1;
|
||||||
|
|
||||||
.channel-thumbnail {
|
.channel-thumbnail {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 5.3rem;
|
height: 5rem;
|
||||||
width: 5.4rem;
|
width: 6rem;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
margin-right: var(--spacing-medium);
|
margin-right: var(--spacing-medium);
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,6 @@ $metadata-z-index: 1;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
// margin-bottom: -1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-thumbnail,
|
.channel-thumbnail,
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
background-size: 1.2rem;
|
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");
|
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;
|
height: 2.5rem;
|
||||||
|
font-size: 1.3rem;
|
||||||
padding: 0 var(--spacing-medium);
|
padding: 0 var(--spacing-medium);
|
||||||
padding-right: var(--spacing-large);
|
padding-right: var(--spacing-large);
|
||||||
margin-bottom: 0;
|
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 {
|
.claim-list__alt-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -81,7 +72,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-list__item {
|
.claim-preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
|
@ -104,12 +95,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-list__item--injected,
|
.claim-preview--injected,
|
||||||
.claim-list__item + .claim-list__item {
|
.claim-preview {
|
||||||
border-top: 1px solid rgba($lbry-teal-5, 0.1);
|
border-top: 1px solid rgba($lbry-teal-5, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-list__item--large {
|
.claim-preview--large {
|
||||||
@include mediaThumbHoverZoom;
|
@include mediaThumbHoverZoom;
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
|
@ -138,32 +129,32 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-list__item-metadata {
|
.claim-preview-metadata {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-list__item-info {
|
.claim-preview-info {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-list__item-info,
|
.claim-preview-info,
|
||||||
.claim-list__item-properties {
|
.claim-preview-properties {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-list__item-properties {
|
.claim-preview-properties {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-list__item-title {
|
.claim-preview-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.claim-list__item-tags {
|
.claim-preview-tags {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,14 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
stroke: rgba($lbry-black, 0.5);
|
||||||
|
|
||||||
|
html[data-mode='dark'] & {
|
||||||
|
stroke: rgba($lbry-white, 0.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
& > *:not(:last-child) {
|
& > *:not(:last-child) {
|
||||||
margin-right: var(--spacing-small);
|
margin-right: var(--spacing-small);
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,10 +132,6 @@
|
||||||
color: rgba($lbry-black, 0.8);
|
color: rgba($lbry-black, 0.8);
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-bottom: var(--spacing-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-mode='dark'] & {
|
html[data-mode='dark'] & {
|
||||||
color: rgba($lbry-white, 0.7);
|
color: rgba($lbry-white, 0.7);
|
||||||
}
|
}
|
||||||
|
@ -150,7 +146,7 @@
|
||||||
|
|
||||||
.media__subtitle {
|
.media__subtitle {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
color: rgba($lbry-black, 0.8);
|
color: rgba($lbry-black, 0.6);
|
||||||
|
|
||||||
[data-mode='dark'] & {
|
[data-mode='dark'] & {
|
||||||
color: rgba($lbry-white, 0.8);
|
color: rgba($lbry-white, 0.8);
|
||||||
|
@ -167,6 +163,7 @@
|
||||||
|
|
||||||
.media__subtitle__channel {
|
.media__subtitle__channel {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
margin: var(--spacing-small) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// M E D I A
|
// M E D I A
|
||||||
|
@ -181,7 +178,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.media__info--large {
|
.media__info--large {
|
||||||
border-top: 1px solid $lbry-gray-1;
|
// border-top: 1px solid $lbry-gray-1;
|
||||||
margin-top: var(--spacing-medium);
|
margin-top: var(--spacing-medium);
|
||||||
|
|
||||||
html[data-mode='dark'] & {
|
html[data-mode='dark'] & {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
.placeholder {
|
.placeholder {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
&.claim-list__item-title {
|
&.claim-preview-title {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,6 @@
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin-right: var(--spacing-small);
|
margin-right: var(--spacing-small);
|
||||||
margin-bottom: 0.2rem;
|
|
||||||
stroke: $lbry-gray-5;
|
stroke: $lbry-gray-5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
@mixin placeholder {
|
@mixin placeholder {
|
||||||
animation: pulse 2s infinite ease-in-out;
|
animation: pulse 2s infinite ease-in-out;
|
||||||
background-color: $lbry-gray-2;
|
background-color: $lbry-gray-1;
|
||||||
border-radius: var(--card-radius);
|
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:description" content="All your favorite LBRY content in your browser." />
|
||||||
<meta property="og:image" content="/og.png" />
|
<meta property="og:image" content="/og.png" />
|
||||||
<!-- @endif -->
|
<!-- @endif -->
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<!-- <meta name="viewport" content="width=device-width, initial-scale=1" /> -->
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -6641,9 +6641,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
|
||||||
yargs "^13.2.2"
|
yargs "^13.2.2"
|
||||||
zstd-codec "^0.1.1"
|
zstd-codec "^0.1.1"
|
||||||
|
|
||||||
lbry-redux@lbryio/lbry-redux#b3bf3f6d53410ff1c5415b51ca425341e364959f:
|
lbry-redux@lbryio/lbry-redux#2930ad82a90ca91f6caf3761597ef9a67da7db66:
|
||||||
version "0.0.1"
|
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:
|
dependencies:
|
||||||
proxy-polyfill "0.1.6"
|
proxy-polyfill "0.1.6"
|
||||||
reselect "^3.0.0"
|
reselect "^3.0.0"
|
||||||
|
|
Loading…
Reference in a new issue
@kauffj does this seem like a reasonable way to do this or is there something simpler?
It is to check if a full page was loaded, if 15 items are loaded don't fetch more, because page 2 wont' exist. If that seems fine I can create a prop called
claimListSearchCount
or something and add a constant that we use for theclaim_search
params