Actually hide NSFW content #1748
16 changed files with 690 additions and 676 deletions
|
@ -48,7 +48,7 @@
|
||||||
"formik": "^0.10.4",
|
"formik": "^0.10.4",
|
||||||
"hast-util-sanitize": "^1.1.2",
|
"hast-util-sanitize": "^1.1.2",
|
||||||
"keytar": "^4.2.1",
|
"keytar": "^4.2.1",
|
||||||
"lbry-redux": "lbryio/lbry-redux#a0d2d1ac532ade639d39c92f79678ac26e904dfd",
|
"lbry-redux": "lbryio/lbry-redux#177ef2c1916f9672e713267500e447d671ae1bc3",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
"mime": "^2.3.1",
|
"mime": "^2.3.1",
|
||||||
"mixpanel-browser": "^2.17.1",
|
"mixpanel-browser": "^2.17.1",
|
||||||
|
|
|
@ -4,46 +4,20 @@ import classnames from 'classnames';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
thumbnail: ?string, // externally sourced image
|
thumbnail: ?string, // externally sourced image
|
||||||
nsfw: ?boolean,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const autoThumbColors = [
|
|
||||||
'purple',
|
|
||||||
'red',
|
|
||||||
'pink',
|
|
||||||
'indigo',
|
|
||||||
'blue',
|
|
||||||
'light-blue',
|
|
||||||
'cyan',
|
|
||||||
'teal',
|
|
||||||
'green',
|
|
||||||
'yellow',
|
|
||||||
'orange',
|
|
||||||
];
|
|
||||||
|
|
||||||
class CardMedia extends React.PureComponent<Props> {
|
class CardMedia extends React.PureComponent<Props> {
|
||||||
getAutoThumbClass = () => autoThumbColors[Math.floor(Math.random() * autoThumbColors.length)];
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { thumbnail, nsfw } = this.props;
|
const { thumbnail } = this.props;
|
||||||
|
|
||||||
const generateAutothumb = !thumbnail && !nsfw;
|
|
||||||
let autoThumbClass;
|
|
||||||
if (generateAutothumb) {
|
|
||||||
autoThumbClass = `card__media--autothumb.${this.getAutoThumbClass()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={thumbnail && !nsfw ? { backgroundImage: `url('${thumbnail}')` } : {}}
|
style={thumbnail ? { backgroundImage: `url('${thumbnail}')` } : {}}
|
||||||
className={classnames('card__media', autoThumbClass, {
|
className={classnames('card__media', {
|
||||||
'card__media--no-img': !thumbnail || nsfw,
|
'card__media--no-img': !thumbnail,
|
||||||
'card__media--nsfw': nsfw,
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{(!thumbnail || nsfw) && (
|
{!thumbnail && <span className="card__media-text">LBRY</span>}
|
||||||
<span className="card__media-text">{nsfw ? __('NSFW') : 'LBRY'}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -249,7 +249,7 @@ class CategoryList extends React.PureComponent<Props, State> {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{obscureNsfw && isCommunityTopBids ? (
|
{obscureNsfw && isCommunityTopBids ? (
|
||||||
<div className="card__content help">
|
<div className="card-row__message help">
|
||||||
{__(
|
{__(
|
||||||
'The community top bids section is only visible if you allow mature content in the app. You can change your content viewing preferences'
|
'The community top bids section is only visible if you allow mature content in the app. You can change your content viewing preferences'
|
||||||
)}{' '}
|
)}{' '}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { normalizeURI, convertToShareLink } from 'lbry-redux';
|
import { normalizeURI, convertToShareLink } from 'lbry-redux';
|
||||||
import Button from 'component/button';
|
import type { Claim, Metadata } from 'types/claim';
|
||||||
import CardMedia from 'component/cardMedia';
|
import CardMedia from 'component/cardMedia';
|
||||||
import TruncatedText from 'component/common/truncated-text';
|
import TruncatedText from 'component/common/truncated-text';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
|
@ -14,9 +14,9 @@ import { openCopyLinkMenu } from '../../util/contextMenu';
|
||||||
// TODO: iron these out
|
// TODO: iron these out
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
claim: ?{ claim_id: string },
|
claim: ?Claim,
|
||||||
fileInfo: ?{},
|
fileInfo: ?{},
|
||||||
metadata: ?{ nsfw: boolean, title: string, thumbnail: ?string },
|
metadata: ?Metadata,
|
||||||
navigate: (string, ?{}) => void,
|
navigate: (string, ?{}) => void,
|
||||||
rewardedContentClaimIds: Array<string>,
|
rewardedContentClaimIds: Array<string>,
|
||||||
obscureNsfw: boolean,
|
obscureNsfw: boolean,
|
||||||
|
@ -62,10 +62,15 @@ class FileCard extends React.PureComponent<Props> {
|
||||||
showPrice,
|
showPrice,
|
||||||
pending,
|
pending,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const shouldHide = !claimIsMine && !pending && obscureNsfw && metadata && metadata.nsfw;
|
||||||
|
if (shouldHide) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const uri = !pending ? normalizeURI(this.props.uri) : this.props.uri;
|
const uri = !pending ? normalizeURI(this.props.uri) : this.props.uri;
|
||||||
const title = metadata && metadata.title ? metadata.title : uri;
|
const title = metadata && metadata.title ? metadata.title : uri;
|
||||||
const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null;
|
const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null;
|
||||||
const shouldObscureNsfw = obscureNsfw && metadata && metadata.nsfw && !claimIsMine;
|
|
||||||
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
|
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
|
||||||
const handleContextMenu = event => {
|
const handleContextMenu = event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -86,46 +91,26 @@ class FileCard extends React.PureComponent<Props> {
|
||||||
})}
|
})}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
>
|
>
|
||||||
<CardMedia nsfw={shouldObscureNsfw} thumbnail={thumbnail} />
|
<CardMedia thumbnail={thumbnail} />
|
||||||
<div className="card-media__internal-links">{showPrice && <FilePrice uri={uri} />}</div>
|
<div className="card-media__internal-links">{showPrice && <FilePrice uri={uri} />}</div>
|
||||||
|
<div className="card__title-identity">
|
||||||
{shouldObscureNsfw ? (
|
<div className="card__title--small">
|
||||||
<div className="card__title-identity">
|
<TruncatedText lines={3}>{title}</TruncatedText>
|
||||||
<div className="card__title--small">
|
|
||||||
<TruncatedText lines={3}>
|
|
||||||
{__('This content is obscured because it is NSFW. You can change this in ')}
|
|
||||||
<Button
|
|
||||||
button="link"
|
|
||||||
label={__('Settings.')}
|
|
||||||
onClick={e => {
|
|
||||||
// Don't propagate to the onClick handler of parent element
|
|
||||||
e.stopPropagation();
|
|
||||||
navigate('/settings');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</TruncatedText>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
<div className="card__subtitle">
|
||||||
<div className="card__title-identity">
|
{pending ? (
|
||||||
<div className="card__title--small">
|
<div>Pending...</div>
|
||||||
<TruncatedText lines={3}>{title}</TruncatedText>
|
) : (
|
||||||
</div>
|
<React.Fragment>
|
||||||
<div className="card__subtitle">
|
<UriIndicator uri={uri} link />
|
||||||
{pending ? (
|
<div>
|
||||||
<div>Pending...</div>
|
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
|
||||||
) : (
|
{fileInfo && <Icon icon={icons.LOCAL} />}
|
||||||
<React.Fragment>
|
</div>
|
||||||
<UriIndicator uri={uri} link />
|
</React.Fragment>
|
||||||
<div>
|
)}
|
||||||
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
|
|
||||||
{fileInfo && <Icon icon={icons.LOCAL} />}
|
|
||||||
</div>
|
|
||||||
</React.Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
/* eslint-enable jsx-a11y/click-events-have-key-events */
|
/* eslint-enable jsx-a11y/click-events-have-key-events */
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import * as React from 'react';
|
||||||
|
import { parseURI } from 'lbry-redux';
|
||||||
import FileTile from 'component/fileTile';
|
import FileTile from 'component/fileTile';
|
||||||
import ChannelTile from 'component/channelTile';
|
import ChannelTile from 'component/channelTile';
|
||||||
import { parseURI } from 'lbry-redux';
|
import HiddenNsfwClaims from 'component/hiddenNsfwClaims';
|
||||||
|
|
||||||
const NoResults = () => <div className="file-tile">{__('No results')}</div>;
|
const NoResults = () => <div className="file-tile">{__('No results')}</div>;
|
||||||
|
|
||||||
|
@ -11,7 +12,6 @@ type Props = {
|
||||||
isSearching: boolean,
|
isSearching: boolean,
|
||||||
uris: ?Array<string>,
|
uris: ?Array<string>,
|
||||||
downloadUris: ?Array<string>,
|
downloadUris: ?Array<string>,
|
||||||
resultCount: number,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileListSearch extends React.PureComponent<Props> {
|
class FileListSearch extends React.PureComponent<Props> {
|
||||||
|
@ -33,36 +33,37 @@ class FileListSearch extends React.PureComponent<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
query && (
|
query && (
|
||||||
<div className="search__results">
|
<React.Fragment>
|
||||||
<div className="search-result__row">
|
<div className="search__results">
|
||||||
<div className="file-list__header">{__('Content')}</div>
|
<div className="search-result__row">
|
||||||
{!isSearching &&
|
<div className="file-list__header">{__('Content')}</div>
|
||||||
(fileResults.length ? (
|
<HiddenNsfwClaims uris={uris} />
|
||||||
|
{!isSearching && fileResults.length ? (
|
||||||
fileResults.map(uri => <FileTile key={uri} uri={uri} />)
|
fileResults.map(uri => <FileTile key={uri} uri={uri} />)
|
||||||
) : (
|
) : (
|
||||||
<NoResults />
|
<NoResults />
|
||||||
))}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="search-result__row">
|
<div className="search-result__row">
|
||||||
<div className="file-list__header">{__('Channels')}</div>
|
<div className="file-list__header">{__('Channels')}</div>
|
||||||
{!isSearching &&
|
{!isSearching && channelResults.length ? (
|
||||||
(channelResults.length ? (
|
|
||||||
channelResults.map(uri => <ChannelTile key={uri} uri={uri} />)
|
channelResults.map(uri => <ChannelTile key={uri} uri={uri} />)
|
||||||
) : (
|
) : (
|
||||||
<NoResults />
|
<NoResults />
|
||||||
))}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="search-result__row">
|
<div className="search-result__row">
|
||||||
<div className="file-list__header">{__('Your downloads')}</div>
|
<div className="file-list__header">{__('Your downloads')}</div>
|
||||||
{downloadUris && downloadUris.length ? (
|
{downloadUris && downloadUris.length ? (
|
||||||
downloadUris.map(uri => <FileTile hideNoResult key={uri} uri={uri} />)
|
downloadUris.map(uri => <FileTile hideNoResult key={uri} uri={uri} />)
|
||||||
) : (
|
) : (
|
||||||
<NoResults />
|
<NoResults />
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,4 +30,7 @@ const perform = dispatch => ({
|
||||||
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(FileTile);
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(FileTile);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as icons from 'constants/icons';
|
import * as icons from 'constants/icons';
|
||||||
|
import type { Claim, Metadata } from 'types/claim';
|
||||||
import { normalizeURI, parseURI } from 'lbry-redux';
|
import { normalizeURI, parseURI } from 'lbry-redux';
|
||||||
import CardMedia from 'component/cardMedia';
|
import CardMedia from 'component/cardMedia';
|
||||||
import TruncatedText from 'component/common/truncated-text';
|
import TruncatedText from 'component/common/truncated-text';
|
||||||
|
@ -19,20 +20,14 @@ type Props = {
|
||||||
uri: string,
|
uri: string,
|
||||||
isResolvingUri: boolean,
|
isResolvingUri: boolean,
|
||||||
rewardedContentClaimIds: Array<string>,
|
rewardedContentClaimIds: Array<string>,
|
||||||
claim: ?{
|
claim: ?Claim,
|
||||||
name: string,
|
metadata: ?Metadata,
|
||||||
channel_name: string,
|
|
||||||
claim_id: string,
|
|
||||||
},
|
|
||||||
metadata: ?{
|
|
||||||
title: ?string,
|
|
||||||
thumbnail: ?string,
|
|
||||||
},
|
|
||||||
resolveUri: string => void,
|
resolveUri: string => void,
|
||||||
navigate: (string, ?{}) => void,
|
navigate: (string, ?{}) => void,
|
||||||
clearPublish: () => void,
|
clearPublish: () => void,
|
||||||
updatePublishForm: ({}) => void,
|
updatePublishForm: ({}) => void,
|
||||||
hideNoResult: boolean, // don't show the tile if there is no claim at this uri
|
hideNoResult: boolean, // don't show the tile if there is no claim at this uri
|
||||||
|
displayHiddenMessage?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
class FileTile extends React.PureComponent<Props> {
|
class FileTile extends React.PureComponent<Props> {
|
||||||
|
@ -68,8 +63,20 @@ class FileTile extends React.PureComponent<Props> {
|
||||||
clearPublish,
|
clearPublish,
|
||||||
updatePublishForm,
|
updatePublishForm,
|
||||||
hideNoResult,
|
hideNoResult,
|
||||||
|
displayHiddenMessage,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const shouldHide = !claimIsMine && obscureNsfw && metadata && metadata.nsfw;
|
||||||
|
if (shouldHide) {
|
||||||
|
return displayHiddenMessage ? (
|
||||||
|
<span className="help">
|
||||||
|
{__('This file is hidden because it is marked NSFW. Update your')}{' '}
|
||||||
|
<Button button="link" navigate="/settings" label={__('content viewing preferences')} />{' '}
|
||||||
|
{__('to see it')}.
|
||||||
|
</span>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
const uri = normalizeURI(this.props.uri);
|
const uri = normalizeURI(this.props.uri);
|
||||||
const isClaimed = !!claim;
|
const isClaimed = !!claim;
|
||||||
const description = isClaimed && metadata && metadata.description ? metadata.description : '';
|
const description = isClaimed && metadata && metadata.description ? metadata.description : '';
|
||||||
|
@ -77,8 +84,6 @@ class FileTile extends React.PureComponent<Props> {
|
||||||
isClaimed && metadata && metadata.title ? metadata.title : parseURI(uri).contentName;
|
isClaimed && metadata && metadata.title ? metadata.title : parseURI(uri).contentName;
|
||||||
const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null;
|
const thumbnail = metadata && metadata.thumbnail ? metadata.thumbnail : null;
|
||||||
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
|
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
|
||||||
const shouldObscureNsfw = obscureNsfw && metadata && metadata.nsfw && !claimIsMine;
|
|
||||||
|
|
||||||
const onClick = () => navigate('/show', { uri });
|
const onClick = () => navigate('/show', { uri });
|
||||||
|
|
||||||
let name;
|
let name;
|
||||||
|
@ -98,7 +103,7 @@ class FileTile extends React.PureComponent<Props> {
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex="0"
|
tabIndex="0"
|
||||||
>
|
>
|
||||||
<CardMedia title={title || name} thumbnail={thumbnail} nsfw={shouldObscureNsfw} />
|
<CardMedia title={title || name} thumbnail={thumbnail} />
|
||||||
<div className="file-tile__info">
|
<div className="file-tile__info">
|
||||||
{isResolvingUri && <div className="card__title--small">{__('Loading...')}</div>}
|
{isResolvingUri && <div className="card__title--small">{__('Loading...')}</div>}
|
||||||
{!isResolvingUri && (
|
{!isResolvingUri && (
|
||||||
|
|
30
src/renderer/component/hiddenNsfwClaims/index.js
Normal file
30
src/renderer/component/hiddenNsfwClaims/index.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { makeSelectNsfwCountForChannel, makeSelectNsfwCountFromUris, parseURI } from 'lbry-redux';
|
||||||
|
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||||
|
import HiddenNsfwClaims from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => {
|
||||||
|
const { uri, uris } = props;
|
||||||
|
|
||||||
|
let numberOfNsfwClaims;
|
||||||
|
if (uri) {
|
||||||
|
const { isChannel } = parseURI(uri);
|
||||||
|
numberOfNsfwClaims = isChannel
|
||||||
|
? makeSelectNsfwCountForChannel(uri)(state)
|
||||||
|
: makeSelectNsfwCountFromUris([uri])(state);
|
||||||
|
} else if (uris) {
|
||||||
|
numberOfNsfwClaims = makeSelectNsfwCountFromUris(uris)(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
numberOfNsfwClaims,
|
||||||
|
obscureNsfw: !selectShowNsfw(state),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = () => ({});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(HiddenNsfwClaims);
|
23
src/renderer/component/hiddenNsfwClaims/view.jsx
Normal file
23
src/renderer/component/hiddenNsfwClaims/view.jsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
numberOfNsfwClaims: number,
|
||||||
|
obscureNsfw: boolean,
|
||||||
|
className: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
const { numberOfNsfwClaims, obscureNsfw, className } = props;
|
||||||
|
return (
|
||||||
|
obscureNsfw &&
|
||||||
|
Boolean(numberOfNsfwClaims) && (
|
||||||
|
<div className={className || 'help'}>
|
||||||
|
{numberOfNsfwClaims} {numberOfNsfwClaims > 1 ? __('files') : __('file')}{' '}
|
||||||
|
{__('hidden due to your')}{' '}
|
||||||
|
<Button button="link" navigate="/settings" label={__('content viewing preferences')} />.
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
|
@ -5,6 +5,7 @@ import {
|
||||||
makeSelectClaimsInChannelForCurrentPage,
|
makeSelectClaimsInChannelForCurrentPage,
|
||||||
makeSelectFetchingChannelClaims,
|
makeSelectFetchingChannelClaims,
|
||||||
makeSelectCurrentParam,
|
makeSelectCurrentParam,
|
||||||
|
makeSelectClaimIsMine,
|
||||||
selectCurrentParams,
|
selectCurrentParams,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { doNavigate } from 'redux/actions/navigation';
|
import { doNavigate } from 'redux/actions/navigation';
|
||||||
|
@ -18,6 +19,7 @@ const select = (state, props) => ({
|
||||||
page: makeSelectCurrentParam('page')(state),
|
page: makeSelectCurrentParam('page')(state),
|
||||||
params: selectCurrentParams(state),
|
params: selectCurrentParams(state),
|
||||||
totalPages: makeSelectTotalPagesForChannel(props.uri)(state),
|
totalPages: makeSelectTotalPagesForChannel(props.uri)(state),
|
||||||
|
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
@ -26,4 +28,7 @@ const perform = dispatch => ({
|
||||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(ChannelPage);
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(ChannelPage);
|
||||||
|
|
|
@ -7,6 +7,7 @@ import SubscribeButton from 'component/subscribeButton';
|
||||||
import ViewOnWebButton from 'component/viewOnWebButton';
|
import ViewOnWebButton from 'component/viewOnWebButton';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import FileList from 'component/fileList';
|
import FileList from 'component/fileList';
|
||||||
|
import HiddenNsfwClaims from 'component/hiddenNsfwClaims';
|
||||||
import type { Claim } from 'types/claim';
|
import type { Claim } from 'types/claim';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -16,7 +17,8 @@ type Props = {
|
||||||
fetching: boolean,
|
fetching: boolean,
|
||||||
params: { page: number },
|
params: { page: number },
|
||||||
claim: Claim,
|
claim: Claim,
|
||||||
claimsInChannel: Array<{}>,
|
claimsInChannel: Array<Claim>,
|
||||||
|
channelIsMine: boolean,
|
||||||
fetchClaims: (string, number) => void,
|
fetchClaims: (string, number) => void,
|
||||||
fetchClaimCount: string => void,
|
fetchClaimCount: string => void,
|
||||||
navigate: (string, {}) => void,
|
navigate: (string, {}) => void,
|
||||||
|
@ -51,9 +53,10 @@ class ChannelPage extends React.PureComponent<Props> {
|
||||||
paginate(e: SyntheticKeyboardEvent<*>, totalPages: number) {
|
paginate(e: SyntheticKeyboardEvent<*>, totalPages: number) {
|
||||||
// Change page if enter was pressed, and the given page is between
|
// Change page if enter was pressed, and the given page is between
|
||||||
// the first and the last.
|
// the first and the last.
|
||||||
const pageFromInput = Number(e.target.value);
|
const pageFromInput = Number(e.currentTarget.value);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
pageFromInput &&
|
||||||
e.keyCode === 13 &&
|
e.keyCode === 13 &&
|
||||||
!Number.isNaN(pageFromInput) &&
|
!Number.isNaN(pageFromInput) &&
|
||||||
pageFromInput > 0 &&
|
pageFromInput > 0 &&
|
||||||
|
@ -64,7 +67,7 @@ class ChannelPage extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { fetching, claimsInChannel, claim, page, totalPages } = this.props;
|
const { uri, fetching, claimsInChannel, claim, page, totalPages, channelIsMine } = this.props;
|
||||||
const { name, permanent_url: permanentUrl, claim_id: claimId } = claim;
|
const { name, permanent_url: permanentUrl, claim_id: claimId } = claim;
|
||||||
const currentPage = parseInt((page || 1) - 1, 10);
|
const currentPage = parseInt((page || 1) - 1, 10);
|
||||||
|
|
||||||
|
@ -116,6 +119,7 @@ class ChannelPage extends React.PureComponent<Props> {
|
||||||
/>
|
/>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
)}
|
)}
|
||||||
|
{!channelIsMine && <HiddenNsfwClaims className="card__content help" uri={uri} />}
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ class SearchPage extends React.PureComponent<Props> {
|
||||||
<Icon icon={icons.HELP} />
|
<Icon icon={icons.HELP} />
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
</div>
|
</div>
|
||||||
<FileTile fullWidth uri={normalizeURI(query)} showUri />
|
<FileTile fullWidth showUri displayHiddenMessage uri={normalizeURI(query)} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
<FileListSearch query={query} />
|
<FileListSearch query={query} />
|
||||||
|
|
|
@ -7,6 +7,7 @@ import Button from 'component/button';
|
||||||
import FileList from 'component/fileList';
|
import FileList from 'component/fileList';
|
||||||
import type { Claim } from 'types/claim';
|
import type { Claim } from 'types/claim';
|
||||||
import isDev from 'electron-is-dev';
|
import isDev from 'electron-is-dev';
|
||||||
|
import HiddenNsfwClaims from 'component/hiddenNsfwClaims';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
doFetchClaimsByChannel: (string, number) => void,
|
doFetchClaimsByChannel: (string, number) => void,
|
||||||
|
@ -51,9 +52,10 @@ export default class extends React.PureComponent<Props> {
|
||||||
if (claim.claims.length) {
|
if (claim.claims.length) {
|
||||||
subscriptionClaimMap[claim.uri] = 1;
|
subscriptionClaimMap[claim.uri] = 1;
|
||||||
} else if (isDev) {
|
} else if (isDev) {
|
||||||
console.error(
|
console
|
||||||
`claim for ${claim.uri} was added to byId in redux but there are no loaded fetched claims`
|
.error
|
||||||
);
|
// `claim for ${claim.uri} was added to byId in redux but there are no loaded fetched claims`
|
||||||
|
();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,8 +74,11 @@ export default class extends React.PureComponent<Props> {
|
||||||
claimList = claimList.concat(claimData.claims);
|
claimList = claimList.concat(claimData.claims);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const subscriptionUris = claimList.map(claim => `lbry://${claim.name}#${claim.claim_id}`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page notContained loading={isFetchingSubscriptions}>
|
<Page notContained loading={isFetchingSubscriptions}>
|
||||||
|
<HiddenNsfwClaims uris={subscriptionUris} />
|
||||||
{!subscriptions.length && (
|
{!subscriptions.length && (
|
||||||
<div className="page__empty">
|
<div className="page__empty">
|
||||||
{__("It looks like you aren't subscribed to any channels yet.")}
|
{__("It looks like you aren't subscribed to any channels yet.")}
|
||||||
|
|
|
@ -281,14 +281,6 @@
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
padding-bottom: $spacing-vertical * 2/3;
|
padding-bottom: $spacing-vertical * 2/3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is only for the text that is displayed when a user has nsfw hidden
|
|
||||||
// It is not used anywhere else in the app and is needed due to the css related to
|
|
||||||
// the content that scrolls off the edge of the screen
|
|
||||||
.card__content.help {
|
|
||||||
padding: 0 $spacing-width;
|
|
||||||
white-space: normal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-row__header {
|
.card-row__header {
|
||||||
|
@ -350,6 +342,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-row__message {
|
||||||
|
padding: 0 $spacing-width;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
How cards are displayed in lists
|
How cards are displayed in lists
|
||||||
*/
|
*/
|
||||||
|
@ -357,7 +354,7 @@
|
||||||
.card {
|
.card {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
margin-bottom: 60px;
|
margin-bottom: $spacing-vertical * 3/2;
|
||||||
|
|
||||||
@media only screen and (max-width: $medium-breakpoint) {
|
@media only screen and (max-width: $medium-breakpoint) {
|
||||||
width: calc((100% / 4) - (60px / 4)); // 60px === 20px margin-right * three cards
|
width: calc((100% / 4) - (60px / 4)); // 60px === 20px margin-right * three cards
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
// Currently incomplete
|
||||||
|
export type Metadata = {
|
||||||
|
nsfw: boolean,
|
||||||
|
title: string,
|
||||||
|
thumbnail: ?string,
|
||||||
|
description: ?string,
|
||||||
|
};
|
||||||
|
|
||||||
// Actual claim type has more values than this
|
// Actual claim type has more values than this
|
||||||
// Add them as they are used
|
// Add them as they are used
|
||||||
export type Claim = {
|
export type Claim = {
|
||||||
|
@ -22,9 +30,10 @@ export type Claim = {
|
||||||
nout: number,
|
nout: number,
|
||||||
signature_is_valid: boolean,
|
signature_is_valid: boolean,
|
||||||
valid_at_height: number,
|
valid_at_height: number,
|
||||||
value: {
|
value: ?{
|
||||||
publisherSignature: ?{
|
publisherSignature: ?{
|
||||||
certificateId: ?string,
|
certificateId: ?string,
|
||||||
},
|
},
|
||||||
|
stream: ?Metadata,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue