Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nsfw overlay #3126

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions flow-typed/Claims.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ declare type ClaimsState = {
fetchingMyPurchasedClaims: ?boolean,
fetchingMyPurchasedClaimsError: ?string,
costInfosById: { [claimId: string]: { cost: number, includesData?: boolean } },
ageRestrictionAllowedByClaimId: { [claimID: string]: boolean },
};

declare type ClaimSearchResultsInfo = {|
Expand Down
9 changes: 8 additions & 1 deletion static/app-strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -1630,7 +1630,7 @@
"Syncing %total_videos% videos from your channel with %total_subs% subscriptions.": "Syncing %total_videos% videos from your channel with %total_subs% subscriptions.",
"Confirm Your Age": "Confirm Your Age",
"I confirm I am over 18 years old.": "I confirm I am over 18 years old.",
"This is only for regulatory compliance and the data will not be stored.": "This is only for regulatory compliance and the data will not be stored.",
"This is only for regulatory compliance and the data will be stored in your private settings if you're signed in.": "This is only for regulatory compliance and the data will be stored in your private settings if you're signed in.",
"Whoa!": "Whoa!",
"You've already claimed your referrer, but we've followed this channel for you.": "You've already claimed your referrer, but we've followed this channel for you.",
"You've already claimed your referrer.": "You've already claimed your referrer.",
Expand Down Expand Up @@ -2953,6 +2953,13 @@
"Receive 1 LBC for inviting a friend, an enemy, a frenemy, or an enefriend. Everyone needs content freedom.": "Receive 1 LBC for inviting a friend, an enemy, a frenemy, or an enefriend. Everyone needs content freedom.",
"Receive a credit of at least 0.05 LBC for watching cool stuff at least 3 days during the week.": "Receive a credit of at least 0.05 LBC for watching cool stuff at least 3 days during the week.",
"Are you a supermodel or rockstar that received a custom Credits code? Claim it here.": "Are you a supermodel or rockstar that received a custom Credits code? Claim it here.",
"The following content is intended for Mature Audiences aged 18 years and over. Viewer discretion is advised.": "The following content is intended for Mature Audiences aged 18 years and over. Viewer discretion is advised.",
"Add tags that are relevant to your content so those who're looking for it can find it more easily.": "Add tags that are relevant to your content so those who're looking for it can find it more easily.",
"Contains nudity, violence or other allowed 18+ content. See %community_guidelines%": "Contains nudity, violence or other allowed 18+ content. See %community_guidelines%",
"Profile/Cover image contains nudity, violence or other allowed 18+ content. See %community_guidelines%": "Profile/Cover image contains nudity, violence or other allowed 18+ content. See %community_guidelines%",
"Don't obscure age restricted content": "Don't obscure age restricted content",
"Don't blur thumbnails or warn about age restricted content": "Don't blur thumbnails or warn about age restricted content",
"18+": "18+",

"--end--": "--end--"
}
3 changes: 1 addition & 2 deletions ui/component/channelEdit/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import analytics from 'analytics';
import LbcSymbol from 'component/common/lbc-symbol';
import SUPPORTED_LANGUAGES from 'constants/supported_languages';
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
import { SIMPLE_SITE, THUMBNAIL_CDN_SIZE_LIMIT_BYTES } from 'config';
import { THUMBNAIL_CDN_SIZE_LIMIT_BYTES } from 'config';
import { sortLanguageMap } from 'util/default-languages';
import ThumbnailBrokenImage from 'component/selectThumbnail/thumbnail-broken.png';
import Gerbil from 'component/channelThumbnail/gerbil.png';
Expand Down Expand Up @@ -478,7 +478,6 @@ function ChannelForm(props: Props) {
body={
<div className="publish-row">
<TagsSearch
suggestMature={!SIMPLE_SITE}
disableAutoFocus
limitSelect={MAX_TAG_SELECT}
tagsPassedIn={params.tags || []}
Expand Down
7 changes: 7 additions & 0 deletions ui/component/channelThumbnail/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import {
selectClaimForUri,
selectIsUriResolving,
selectClaimsByUri,
makeSelectTagInClaimOrChannelForUri,
selectClaimIsMine,
} from 'redux/selectors/claims';
import { doResolveUri } from 'redux/actions/claims';
import { selectOdyseeMembershipForChannelId } from 'redux/selectors/memberships';
import { getChannelIdFromClaim } from 'util/claim';
import { selectIsAgeRestrictedContentAllowed } from 'redux/selectors/settings';
import { AGE_RESTRICED_CHANNEL_IMAGES_TAG } from 'constants/tags';
import ChannelThumbnail from './view';

const select = (state, props) => {
Expand All @@ -20,6 +24,9 @@ const select = (state, props) => {
isResolving: selectIsUriResolving(state, uri),
claimsByUri: selectClaimsByUri(state),
odyseeMembership: selectOdyseeMembershipForChannelId(state, getChannelIdFromClaim(claim)),
isImagesAgeRestricted: makeSelectTagInClaimOrChannelForUri(props.uri, AGE_RESTRICED_CHANNEL_IMAGES_TAG)(state),
channelIsMine: selectClaimIsMine(state, claim),
isAgeRestrictedContentAllowed: selectIsAgeRestrictedContentAllowed(state),
};
};

Expand Down
9 changes: 9 additions & 0 deletions ui/component/channelThumbnail/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type Props = {
isChannel?: boolean,
odyseeMembership: ?string,
tooltipTitle?: string,
isImagesAgeRestricted: boolean,
channelIsMine: boolean,
isAgeRestrictedContentAllowed: boolean,
};

function ChannelThumbnail(props: Props) {
Expand All @@ -55,6 +58,9 @@ function ChannelThumbnail(props: Props) {
isChannel,
odyseeMembership,
tooltipTitle,
isImagesAgeRestricted,
channelIsMine,
isAgeRestrictedContentAllowed,
} = props;
const [thumbLoadError, setThumbLoadError] = React.useState(ThumbUploadError);
const shouldResolve = !isResolving && claim === undefined;
Expand All @@ -64,6 +70,7 @@ function ChannelThumbnail(props: Props) {
const channelThumbnail = thumbnailPreview || thumbnail || defaultAvatar;
const isAnimated = channelThumbnail && (channelThumbnail.endsWith('gif') || channelThumbnail.endsWith('webp'));
const showThumb = (!obscure && !!thumbnail) || thumbnailPreview;
const shouldBlur = !channelIsMine && isImagesAgeRestricted && !isAgeRestrictedContentAllowed;

const badgeProps = React.useMemo(() => {
return {
Expand Down Expand Up @@ -99,6 +106,7 @@ function ChannelThumbnail(props: Props) {
<FreezeframeWrapper
src={url}
className={classnames('channel-thumbnail', className, {
'age-restricted': shouldBlur,
'channel-thumbnail--small': small,
'channel-thumbnail--xsmall': xsmall,
'channel-thumbnail--xxsmall': xxsmall,
Expand All @@ -114,6 +122,7 @@ function ChannelThumbnail(props: Props) {
return (
<div
className={classnames('channel-thumbnail', className, {
'age-restricted': shouldBlur,
[colorClassName]: !showThumb,
'channel-thumbnail--small': small,
'channel-thumbnail--xsmall': xsmall,
Expand Down
5 changes: 4 additions & 1 deletion ui/component/fileThumbnail/internal/thumb.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@ const Thumb = (props: Props) => {
const thumbnailRef = React.useRef(null);
useLazyLoading(thumbnailRef, fallback || '');

const inlineStyle = {};
if (forceReload) inlineStyle.backgroundImage = 'url(' + String(thumb) + ')';

return (
<div
ref={thumbnailRef}
data-background-image={thumb}
style={forceReload && { backgroundImage: 'url(' + String(thumb) + ')' }}
style={inlineStyle}
className={classnames('media__thumb', { className, 'media__thumb--small': small })}
>
{children}
Expand Down
3 changes: 3 additions & 0 deletions ui/component/fileThumbnail/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import FreezeframeWrapper from 'component/common/freezeframe-wrapper';
import classnames from 'classnames';
import Thumb from './internal/thumb';
import PreviewOverlayProtectedContent from '../previewOverlayProtectedContent';
import PreviewOverlayAgeRestrictedContent from '../previewOverlayAgeRestrictedContent';

const FALLBACK = MISSING_THUMB_DEFAULT ? getThumbnailCdnUrl({ thumbnail: MISSING_THUMB_DEFAULT }) : undefined;

Expand Down Expand Up @@ -77,6 +78,7 @@ function FileThumbnail(props: Props) {
'media__thumb--small': small,
})}
>
<PreviewOverlayAgeRestrictedContent uri={uri} />
<PreviewOverlayProtectedContent uri={uri} />
{children}
</FreezeframeWrapper>
Expand Down Expand Up @@ -110,6 +112,7 @@ function FileThumbnail(props: Props) {
className={className}
forceReload={forceReload}
>
<PreviewOverlayAgeRestrictedContent uri={uri} />
<PreviewOverlayProtectedContent uri={uri} />
{children}
</Thumb>
Expand Down
5 changes: 4 additions & 1 deletion ui/component/fileVisibility/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { connect } from 'react-redux';
import FileVisibility from './view';

import { selectIsUriUnlisted } from 'redux/selectors/claims';
import { AGE_RESTRICED_CONTENT_TAG } from 'constants/tags';

import { selectIsUriUnlisted, makeSelectTagInClaimOrChannelForUri } from 'redux/selectors/claims';

const select = (state, props) => {
return {
isUnlisted: selectIsUriUnlisted(state, props.uri),
isAgeRestricted: makeSelectTagInClaimOrChannelForUri(props.uri, AGE_RESTRICED_CONTENT_TAG)(state),
};
};

Expand Down
6 changes: 6 additions & 0 deletions ui/component/fileVisibility/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@
background-color: var(--color-visibility-label);
max-height: 1.3rem;
white-space: nowrap;
display: inline;

svg {
margin-right: var(--spacing-xxs);
}
}

.file-visibility-age-restricted {
background-color: var(--color-button-toggle-bg-active);
color: white;
}
30 changes: 18 additions & 12 deletions ui/component/fileVisibility/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,30 @@ import Icon from 'component/common/icon';
import * as ICONS from 'constants/icons';

type Props = {
uri: ?string,
shownFields: ?Array<string>,
// --- internal ---
isUnlisted: boolean,
isAgeRestricted: boolean,
};

function FileVisibility(props: Props) {
const { isUnlisted } = props;
const { isUnlisted, isAgeRestricted, shownFields } = props;
const showUnlisted = !shownFields ? true : shownFields.includes('unlisted');
const showAgeRestriced = !shownFields ? true : shownFields.includes('age-restriced');

if (isUnlisted) {
return (
<div className="file-visibility">
<Icon icon={ICONS.COPY_LINK} size={9} />
{__('unlisted')}
</div>
);
}

return null;
return (
<>
{isUnlisted && showUnlisted && (
<div className="file-visibility">
<Icon icon={ICONS.COPY_LINK} size={9} />
{__('unlisted')}
</div>
)}
{isAgeRestricted && showAgeRestriced && (
<div className="file-visibility file-visibility-age-restricted">{__('18+')}</div>
)}
</>
);
}

export default FileVisibility;
27 changes: 27 additions & 0 deletions ui/component/previewOverlayAgeRestrictedContent/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { connect } from 'react-redux';

import {
makeSelectTagInClaimOrChannelForUri,
selectIsAgeRestrictedContentAllowedForClaimId,
selectClaimForUri,
selectClaimIsMine,
} from 'redux/selectors/claims';

import { AGE_RESTRICED_CONTENT_TAG } from 'constants/tags';

import PreviewOverlayAgeRestrictedContent from './view';

const select = (state, props) => {
const { uri } = props;
const claim = selectClaimForUri(state, uri);
const claimId = claim && claim.claim_id;

return {
uri,
isAgeRestricted: makeSelectTagInClaimOrChannelForUri(uri, AGE_RESTRICED_CONTENT_TAG)(state),
isAgeRestrictedContentAllowed: selectIsAgeRestrictedContentAllowedForClaimId(state, claimId),
isMine: Boolean(selectClaimIsMine(state, claim)),
};
};

export default connect(select, null)(PreviewOverlayAgeRestrictedContent);
26 changes: 26 additions & 0 deletions ui/component/previewOverlayAgeRestrictedContent/view.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// @flow
import * as React from 'react';
import FileVisibility from 'component/fileVisibility';

type Props = {
uri: string,
isAgeRestricted: boolean,
isAgeRestrictedContentAllowed: boolean,
isMine: boolean,
};

const PreviewOverlayAgeRestrictedContent = (props: Props) => {
const { uri, isAgeRestricted, isAgeRestrictedContentAllowed, isMine } = props;

if (isAgeRestricted) {
return (
<div className={`age-restricted-content__wrapper ${isMine || isAgeRestrictedContentAllowed ? 'no-blur' : ''}`}>
<FileVisibility uri={uri} shownFields={['age-restriced']} />
</div>
);
}

return null;
};

export default PreviewOverlayAgeRestrictedContent;
7 changes: 4 additions & 3 deletions ui/component/publish/livestream/livestreamForm/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import type { DoPublishDesktop } from 'redux/actions/publish';
File upload is carried out in the background by that function.
*/

import { SITE_NAME, SIMPLE_SITE } from 'config';
import { SITE_NAME } from 'config';
import * as ICONS from 'constants/icons';
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router';
import Lbry from 'lbry';
import { buildURI, isURIValid, isNameValid } from 'util/lbryURI';
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
import * as TAGS from 'constants/tags';
import { BITRATE } from 'constants/publish';
import Button from 'component/button';
import ChannelSelector from 'component/channelSelector';
Expand Down Expand Up @@ -511,15 +512,15 @@ function LivestreamForm(props: Props) {
body={
<div className="publish-row">
<TagsSelect
suggestMature={!SIMPLE_SITE}
disableAutoFocus
hideHeader
label={__('Selected Tags')}
empty={__('No tags added')}
limitSelect={TAGS_LIMIT}
help={__(
"Add tags that are relevant to your content so those who're looking for it can find it more easily. If your content is best suited for mature audiences, ensure it is tagged 'mature'."
"Add tags that are relevant to your content so those who're looking for it can find it more easily."
)}
disabledControlTags={[TAGS.AGE_RESTRICED_CHANNEL_IMAGES_TAG]}
placeholder={__('gaming, crypto')}
onSelect={(newTags) => {
const validatedTags = [];
Expand Down
7 changes: 4 additions & 3 deletions ui/component/publish/post/postForm/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import type { DoPublishDesktop } from 'redux/actions/publish';
File upload is carried out in the background by that function.
*/

import { SITE_NAME, SIMPLE_SITE } from 'config';
import { SITE_NAME } from 'config';
import React, { useEffect } from 'react';
import { buildURI, isURIValid, isNameValid } from 'util/lbryURI';
import { lazyImport } from 'util/lazyImport';
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
import * as TAGS from 'constants/tags';
import Button from 'component/button';
import ChannelSelector from 'component/channelSelector';
import classnames from 'classnames';
Expand Down Expand Up @@ -430,15 +431,15 @@ function PostForm(props: Props) {
body={
<div className="publish-row">
<TagsSelect
suggestMature={!SIMPLE_SITE}
disableAutoFocus
hideHeader
label={__('Selected Tags')}
empty={__('No tags added')}
limitSelect={TAGS_LIMIT}
help={__(
"Add tags that are relevant to your content so those who're looking for it can find it more easily. If your content is best suited for mature audiences, ensure it is tagged 'mature'."
"Add tags that are relevant to your content so those who're looking for it can find it more easily."
)}
disabledControlTags={[TAGS.AGE_RESTRICED_CHANNEL_IMAGES_TAG]}
placeholder={__('gaming, crypto')}
onSelect={(newTags) => {
const validatedTags = [];
Expand Down
7 changes: 4 additions & 3 deletions ui/component/publish/upload/uploadForm/view.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { DoPublishDesktop } from 'redux/actions/publish';
File upload is carried out in the background by that function.
*/

import { SITE_NAME, SIMPLE_SITE } from 'config';
import { SITE_NAME } from 'config';
import React, { useEffect, useState } from 'react';
import { buildURI, isURIValid, isNameValid } from 'util/lbryURI';
import { lazyImport } from 'util/lazyImport';
Expand All @@ -34,6 +34,7 @@ import { BITRATE } from 'constants/publish';
import { SOURCE_NONE } from 'constants/publish_sources';

import * as ICONS from 'constants/icons';
import * as TAGS from 'constants/tags';
import Icon from 'component/common/icon';

const SelectThumbnail = lazyImport(() => import('component/selectThumbnail' /* webpackChunkName: "selectThumbnail" */));
Expand Down Expand Up @@ -459,15 +460,15 @@ function UploadForm(props: Props) {
body={
<div className="publish-row">
<TagsSelect
suggestMature={!SIMPLE_SITE}
disableAutoFocus
hideHeader
label={__('Selected Tags')}
empty={__('No tags added')}
limitSelect={TAGS_LIMIT}
help={__(
"Add tags that are relevant to your content so those who're looking for it can find it more easily. If your content is best suited for mature audiences, ensure it is tagged 'mature'."
"Add tags that are relevant to your content so those who're looking for it can find it more easily."
)}
disabledControlTags={[TAGS.AGE_RESTRICED_CHANNEL_IMAGES_TAG]}
placeholder={__('gaming, crypto')}
onSelect={(newTags) => {
const validatedTags = [];
Expand Down
Loading
Loading