From aa3902598ebb61ca8c721577ee6f9bed2fecfab0 Mon Sep 17 00:00:00 2001 From: miko Date: Tue, 16 Jul 2024 17:13:57 +0300 Subject: [PATCH 01/17] Nsfw overlay - remove old mature tagging texts - add `c:nsfw` control tag --- flow-typed/Claims.js | 1 + static/app-strings.json | 3 ++ ui/component/channelEdit/view.jsx | 3 +- .../livestream/livestreamForm/view.jsx | 5 ++-- ui/component/publish/post/postForm/view.jsx | 5 ++-- .../publish/upload/uploadForm/view.jsx | 5 ++-- ui/component/tagsSearch/view.jsx | 3 ++ ui/constants/action_types.js | 1 + ui/constants/tags.js | 3 ++ ui/hocs/withStreamClaimRender/index.js | 10 +++++++ .../internal/nsfwContentOverlay/index.js | 17 +++++++++++ .../internal/nsfwContentOverlay/view.jsx | 25 ++++++++++++++++ ui/hocs/withStreamClaimRender/view.jsx | 18 ++++++++++-- .../internal/collectionGeneralTab/view.jsx | 4 +-- ui/redux/actions/claims.js | 9 ++++++ ui/redux/reducers/claims.js | 10 +++++++ ui/redux/selectors/claims.js | 4 +++ ui/scss/component/_claim-preview.scss | 29 +++++++++++++++++++ ui/scss/component/_file-render.scss | 12 ++++++++ 19 files changed, 151 insertions(+), 16 deletions(-) create mode 100644 ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/index.js create mode 100644 ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx diff --git a/flow-typed/Claims.js b/flow-typed/Claims.js index 9e14c9ae58..e41b7d1ad7 100644 --- a/flow-typed/Claims.js +++ b/flow-typed/Claims.js @@ -62,6 +62,7 @@ declare type ClaimsState = { fetchingMyPurchasedClaims: ?boolean, fetchingMyPurchasedClaimsError: ?string, costInfosById: { [claimId: string]: { cost: number, includesData?: boolean } }, + nsfwAknowledgedById: { [claimID: string]: boolean }, }; declare type ClaimSearchResultsInfo = {| diff --git a/static/app-strings.json b/static/app-strings.json index 870496624a..0e323d6ce4 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2953,6 +2953,9 @@ "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.", + "This content is marked as NSWF": "This content is marked as NSWF", + "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.", + "Mark content as NSFW": "Mark content as NSFW", "--end--": "--end--" } diff --git a/ui/component/channelEdit/view.jsx b/ui/component/channelEdit/view.jsx index 11d9a6c74b..d863a86ef7 100644 --- a/ui/component/channelEdit/view.jsx +++ b/ui/component/channelEdit/view.jsx @@ -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'; @@ -478,7 +478,6 @@ function ChannelForm(props: Props) { body={
{ diff --git a/ui/component/publish/post/postForm/view.jsx b/ui/component/publish/post/postForm/view.jsx index e5c20607a3..84e7c7cf60 100644 --- a/ui/component/publish/post/postForm/view.jsx +++ b/ui/component/publish/post/postForm/view.jsx @@ -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 } from 'react'; import { buildURI, isURIValid, isNameValid } from 'util/lbryURI'; import { lazyImport } from 'util/lazyImport'; @@ -430,14 +430,13 @@ function PostForm(props: Props) { body={
{ diff --git a/ui/component/publish/upload/uploadForm/view.jsx b/ui/component/publish/upload/uploadForm/view.jsx index 47c913e679..c4d5639a44 100644 --- a/ui/component/publish/upload/uploadForm/view.jsx +++ b/ui/component/publish/upload/uploadForm/view.jsx @@ -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'; @@ -459,14 +459,13 @@ function UploadForm(props: Props) { body={
{ diff --git a/ui/component/tagsSearch/view.jsx b/ui/component/tagsSearch/view.jsx index a9062b8ed9..de5c7adb30 100644 --- a/ui/component/tagsSearch/view.jsx +++ b/ui/component/tagsSearch/view.jsx @@ -19,6 +19,7 @@ import { DISABLE_REACTIONS_COMMENTS_TAG, DISABLE_SLIMES_VIDEO_TAG, DISABLE_SLIMES_COMMENTS_TAG, + AGE_RESTRICED_CONTENT_TAG, } from 'constants/tags'; import { removeInternalTags } from 'util/tags'; @@ -127,6 +128,8 @@ export default function TagsSearch(props: Props) { label = __('Disable Dislikes - Content'); } else if (t === DISABLE_SLIMES_COMMENTS_TAG) { label = __('Disable Dislikes - Comments'); + } else if (t === AGE_RESTRICED_CONTENT_TAG) { + label = __('Mark content as NSFW'); } else { label = __( t diff --git a/ui/constants/action_types.js b/ui/constants/action_types.js index 29b2d8a932..6021f1ee5d 100644 --- a/ui/constants/action_types.js +++ b/ui/constants/action_types.js @@ -222,6 +222,7 @@ export const PURCHASE_LIST_FAILED = 'PURCHASE_LIST_FAILED'; export const CHECK_IF_PURCHASED_STARTED = 'CHECK_IF_PURCHASED_STARTED'; export const CHECK_IF_PURCHASED_COMPLETED = 'CHECK_IF_PURCHASED_COMPLETED'; export const CHECK_IF_PURCHASED_FAILED = 'CHECK_IF_PURCHASED_FAILED'; +export const AKNOWLEDGE_NSFW = 'AKNOWLEDGE_NSFW'; export const SHOW_AUTOPLAY_COUNTDOWN = 'CHECK_IF_PURCHASED_FAILED'; diff --git a/ui/constants/tags.js b/ui/constants/tags.js index 7721f2285e..57db7afd8b 100644 --- a/ui/constants/tags.js +++ b/ui/constants/tags.js @@ -28,6 +28,8 @@ export const DISABLE_SLIMES_ALL_TAG = 'c:disable-slimes-all'; export const DISABLE_SLIMES_VIDEO_TAG = 'c:disable-slimes-video'; export const DISABLE_SLIMES_COMMENTS_TAG = 'c:disable-slimes-comments'; +export const NSFW_CONTENT_TAG = 'c:nsfw'; + export const PURCHASE_TAG = 'c:purchase'; export const RENTAL_TAG = 'c:rental'; export const PURCHASE_TAG_OLD = 'purchase:'; @@ -50,6 +52,7 @@ export const SCHEDULED_TAGS = Object.freeze({ // Control tags are special tags that are available to the user in some situations. export const CONTROL_TAGS = [ + NSFW_CONTENT_TAG, DISABLE_SUPPORT_TAG, DISABLE_DOWNLOAD_BUTTON_TAG, DISABLE_REACTIONS_VIDEO_TAG, diff --git a/ui/hocs/withStreamClaimRender/index.js b/ui/hocs/withStreamClaimRender/index.js index 4da97dc5b5..18632fe577 100644 --- a/ui/hocs/withStreamClaimRender/index.js +++ b/ui/hocs/withStreamClaimRender/index.js @@ -13,6 +13,9 @@ import { selectPendingFiatPaymentForUri, selectSdkFeePendingForUri, selectScheduledStateForUri, + makeSelectTagInClaimOrChannelForUri, + selectClaimIsMine, + selectIsNsfwAknowledgedForClaimId, // selectClaimWasPurchasedForUri, // selectIsFiatPaidForUri, } from 'redux/selectors/claims'; @@ -30,9 +33,12 @@ import { selectVideoSourceLoadedForUri } from 'redux/selectors/app'; import { doStartFloatingPlayingUri, doClearPlayingUri } from 'redux/actions/content'; import { doFileGetForUri } from 'redux/actions/file'; +import { doAknowledgeNsfw } from 'redux/actions/claims'; import { doCheckIfPurchasedClaimId } from 'redux/actions/stripe'; import { doMembershipMine, doMembershipList } from 'redux/actions/memberships'; +import { NSFW_CONTENT_TAG } from 'constants/tags'; + import withStreamClaimRender from './view'; const select = (state, props) => { @@ -66,8 +72,11 @@ const select = (state, props) => { sdkFeePending: selectSdkFeePendingForUri(state, uri), pendingUnlockedRestrictions: selectPendingUnlockedRestrictionsForUri(state, uri), canViewFile: selectCanViewFileForUri(state, uri), + isNsfw: makeSelectTagInClaimOrChannelForUri(props.uri, NSFW_CONTENT_TAG)(state), + isNsfwAknowledged: selectIsNsfwAknowledgedForClaimId(state, claimId), channelLiveFetched: selectChannelIsLiveFetchedForUri(state, uri), sourceLoaded: selectVideoSourceLoadedForUri(state, uri), + claimIsMine: Boolean(selectClaimIsMine(state, claim)), }; }; @@ -78,6 +87,7 @@ const perform = { doStartFloatingPlayingUri, doMembershipList, doClearPlayingUri, + doAknowledgeNsfw, }; export default (Component) => withRouter(connect(select, perform)(withStreamClaimRender(Component))); diff --git a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/index.js b/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/index.js new file mode 100644 index 0000000000..a918eb3904 --- /dev/null +++ b/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/index.js @@ -0,0 +1,17 @@ +import { connect } from 'react-redux'; + +import { selectClaimForUri, selectClaimIsMine } from 'redux/selectors/claims'; +import { doAknowledgeNsfw } from 'redux/actions/claims'; + +import NsfwContentOverlay from './view'; + +const select = (state, props) => { + const claim = selectClaimForUri(state, props.uri); + + return { + claimId: claim.claim_id, + claimIsMine: selectClaimIsMine(state, claim), + }; +}; + +export default connect(select, { doAknowledgeNsfw })(NsfwContentOverlay); diff --git a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx b/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx new file mode 100644 index 0000000000..ac00ce02f0 --- /dev/null +++ b/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx @@ -0,0 +1,25 @@ +// @flow +import React from 'react'; +import Button from 'component/button'; + +type Props = { + claimId: string, + claimIsMine: boolean, + doAknowledgeNsfw: (claimId: string) => void, +}; + +const NsfwContentOverlay = (props: Props) => { + const { claimId, claimIsMine, doAknowledgeNsfw } = props; + + if (claimIsMine) return null; + + return ( +
+ {__('This content is marked as NSWF')} + +
+ ); +}; + +export default NsfwContentOverlay; diff --git a/ui/hocs/withStreamClaimRender/view.jsx b/ui/hocs/withStreamClaimRender/view.jsx index d841195608..fa65f9bc01 100644 --- a/ui/hocs/withStreamClaimRender/view.jsx +++ b/ui/hocs/withStreamClaimRender/view.jsx @@ -10,6 +10,7 @@ import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle'; import ProtectedContentOverlay from './internal/protectedContentOverlay'; import ClaimCoverRender from 'component/claimCoverRender'; import PaidContentOverlay from './internal/paidContentOverlay'; +import NsfwContentOverlay from './internal/nsfwContentOverlay'; import LoadingScreen from 'component/common/loading-screen'; import ScheduledInfo from 'component/scheduledInfo'; import Button from 'component/button'; @@ -48,8 +49,11 @@ type Props = { sdkFeePending: ?boolean, pendingUnlockedRestrictions: ?boolean, canViewFile: ?boolean, + isNsfw: ?boolean, + isNsfwAknowledged: ?boolean, channelLiveFetched: boolean, sourceLoaded: boolean, + claimIsMine: boolean, doCheckIfPurchasedClaimId: (claimId: string) => void, doFileGetForUri: (uri: string, opt?: ?FileGetOptions) => void, doMembershipMine: () => void, @@ -96,8 +100,11 @@ const withStreamClaimRender = (StreamClaimComponent: FunctionalComponentParam) = sdkFeePending, pendingUnlockedRestrictions, canViewFile, + isNsfw, + isNsfwAknowledged, channelLiveFetched, sourceLoaded, + claimIsMine, doCheckIfPurchasedClaimId, doFileGetForUri, doMembershipMine, @@ -114,6 +121,7 @@ const withStreamClaimRender = (StreamClaimComponent: FunctionalComponentParam) = const [currentStreamingUri, setCurrentStreamingUri] = React.useState(); const [clickProps, setClickProps] = React.useState(); + const requiresNsfwAknowledgedment = isNsfw && !claimIsMine; const { search, href, state: locationState, pathname } = location; const { forceDisableAutoplay } = locationState || {}; const currentUriPlaying = playingUri.uri === uri && claimLinkId === playingUri.sourceId; @@ -146,6 +154,7 @@ const withStreamClaimRender = (StreamClaimComponent: FunctionalComponentParam) = const urlTimeParam = href && href.indexOf('t=') > -1; const autoplayEnabled = !forceDisableAutoplay && + !(requiresNsfwAknowledgedment && !isNsfwAknowledged) && (!embedded || (urlParams && urlParams.get('autoplay'))) && (forceAutoplayParam || urlTimeParam || (isLivestreamClaim ? isCurrentClaimLive : autoplay)); @@ -298,11 +307,16 @@ const withStreamClaimRender = (StreamClaimComponent: FunctionalComponentParam) = }, []); // -- Restricted State -- render instead of component, until no longer restricted - if (!canViewFile) { + if (!canViewFile || (requiresNsfwAknowledgedment && !isNsfwAknowledged)) { // console.log('doCheckIfPurchasedClaimId: ', doCheckIfPurchasedClaimId) return ( - {pendingFiatPayment || sdkFeePending ? ( + {requiresNsfwAknowledgedment && !isNsfwAknowledged ? ( + <> + {embedded && } + + + ) : pendingFiatPayment || sdkFeePending ? ( <> {embedded && } diff --git a/ui/page/collection/internal/collectionPublishForm/internal/collectionGeneralTab/view.jsx b/ui/page/collection/internal/collectionPublishForm/internal/collectionGeneralTab/view.jsx index fe0a32a62e..bdbecb2621 100644 --- a/ui/page/collection/internal/collectionPublishForm/internal/collectionGeneralTab/view.jsx +++ b/ui/page/collection/internal/collectionPublishForm/internal/collectionGeneralTab/view.jsx @@ -1,7 +1,6 @@ // @flow import React from 'react'; import { useHistory } from 'react-router-dom'; -import { SIMPLE_SITE } from 'config'; import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field'; import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses'; @@ -155,14 +154,13 @@ function CollectionGeneralTab(props: Props) { body={
{ diff --git a/ui/redux/actions/claims.js b/ui/redux/actions/claims.js index c63c7e6cda..7cb50ddb50 100644 --- a/ui/redux/actions/claims.js +++ b/ui/redux/actions/claims.js @@ -1107,3 +1107,12 @@ export const doFetchNoSourceClaimsForChannelId = order_by: ['release_time'], }) ); + +export function doAknowledgeNsfw(claimId: string) { + return (dispatch: Dispatch) => { + dispatch({ + type: ACTIONS.AKNOWLEDGE_NSFW, + data: { claimId }, + }); + }; +} diff --git a/ui/redux/reducers/claims.js b/ui/redux/reducers/claims.js index 77b91a7326..025fe773d3 100644 --- a/ui/redux/reducers/claims.js +++ b/ui/redux/reducers/claims.js @@ -68,6 +68,7 @@ const defaultState: ClaimsState = { fetchingMyPurchasedClaims: undefined, fetchingMyPurchasedClaimsError: undefined, costInfosById: {}, + nsfwAknowledgedById: {}, }; // **************************************************************************** @@ -1120,6 +1121,15 @@ reducers[ACTIONS.CHECK_IF_PURCHASED_COMPLETED] = (state: ClaimsState, action: an }; }; +reducers[ACTIONS.AKNOWLEDGE_NSFW] = (state: ClaimsState, action: any): ClaimsState => { + let nsfwAknowledgedById = Object.assign({}, state.nsfwAknowledgedById); + nsfwAknowledgedById[action.data.claimId] = true; + return { + ...state, + nsfwAknowledgedById, + }; +}; + // --- Collection Claims --- reducers[ACTIONS.COLLECTION_CLAIM_ITEMS_RESOLVE_COMPLETE] = (state: ClaimsState, action: any) => { diff --git a/ui/redux/selectors/claims.js b/ui/redux/selectors/claims.js index 6faa863211..ec2a2ccca4 100644 --- a/ui/redux/selectors/claims.js +++ b/ui/redux/selectors/claims.js @@ -55,6 +55,10 @@ export const selectLatestByUri = (state: State) => selectState(state).latestByUr export const selectResolvedCollectionsById = (state: State) => selectState(state).resolvedCollectionsById; export const selectMyCollectionClaimIds = (state: State) => selectState(state).myCollectionClaimIds; +export const selectIsNsfwAknowledgedForClaimId = (state: State, claimId: string) => { + return Boolean(selectState(state).nsfwAknowledgedById[claimId]); +}; + export const selectMyCollectionClaimsById = createSelector( selectResolvedCollectionsById, selectMyCollectionClaimIds, diff --git a/ui/scss/component/_claim-preview.scss b/ui/scss/component/_claim-preview.scss index 1ebdfc5c43..a7680b1832 100644 --- a/ui/scss/component/_claim-preview.scss +++ b/ui/scss/component/_claim-preview.scss @@ -1545,6 +1545,35 @@ margin-top: -3px; } +.nsfw-content-overlay { + @include font-sans; + + background-color: rgba(0, 0, 0, 0.5); + + position: absolute; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + z-index: 5; + width: 100%; + height: 100%; + -webkit-backdrop-filter: blur(100px); + backdrop-filter: blur(100px); + color: white; + border-radius: var(--border-radius); + + .button { + margin-top: var(--spacing-s); + } + + @media (max-width: $breakpoint-small) { + span { + text-align: center; + } + } +} + .protected-content-overlay { @include font-sans; diff --git a/ui/scss/component/_file-render.scss b/ui/scss/component/_file-render.scss index fb9cc153c2..9485b32ac7 100644 --- a/ui/scss/component/_file-render.scss +++ b/ui/scss/component/_file-render.scss @@ -1330,6 +1330,18 @@ $control-bar-icon-size: 30px; } } +.nsfw-content-overlay { + .button { + border: 1px solid white; + transition: all 0.2s; + opacity: 0.9; + &:hover { + transform: scale(1.08); + opacity: 1; + } + } +} + .recommendation-overlay-wrapper { position: absolute; left: 0; From a2058973cde4f6b237b46ec3091c2f2e711f6bf9 Mon Sep 17 00:00:00 2001 From: miko Date: Mon, 29 Jul 2024 17:47:01 +0300 Subject: [PATCH 02/17] Fix texts --- static/app-strings.json | 4 ++-- ui/component/tagsSearch/view.jsx | 17 ++++++++++++++++- .../internal/nsfwContentOverlay/view.jsx | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/static/app-strings.json b/static/app-strings.json index 0e323d6ce4..eb4c4d9090 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2953,9 +2953,9 @@ "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.", - "This content is marked as NSWF": "This content is marked as NSWF", + "This content is marked as mature": "This content is marked as mature", "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.", - "Mark content as NSFW": "Mark content as NSFW", + "Contains nudity, violence or other allowed 18+ content. See %community_guidelines%": "Contains nudity, violence or other allowed 18+ content. See %community_guidelines%", "--end--": "--end--" } diff --git a/ui/component/tagsSearch/view.jsx b/ui/component/tagsSearch/view.jsx index de5c7adb30..186fffec3b 100644 --- a/ui/component/tagsSearch/view.jsx +++ b/ui/component/tagsSearch/view.jsx @@ -1,5 +1,6 @@ // @flow import React, { useState } from 'react'; +import Button from 'component/button'; import { Form, FormField } from 'component/common/form'; import Tag from 'component/tag'; import { setUnion, setDifference } from 'util/set-operations'; @@ -129,7 +130,21 @@ export default function TagsSearch(props: Props) { } else if (t === DISABLE_SLIMES_COMMENTS_TAG) { label = __('Disable Dislikes - Comments'); } else if (t === AGE_RESTRICED_CONTENT_TAG) { - label = __('Mark content as NSFW'); + label = ( + + ), + }} + > + Contains nudity, violence or other allowed 18+ content. See %community_guidelines% + + ); } else { label = __( t diff --git a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx b/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx index ac00ce02f0..5357f65c89 100644 --- a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx +++ b/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx @@ -15,7 +15,7 @@ const NsfwContentOverlay = (props: Props) => { return (
- {__('This content is marked as NSWF')} + {__('This content is marked as mature')}
From 7166712fd818c4912ccf1ca666f35d8e2b9f3a91 Mon Sep 17 00:00:00 2001 From: miko Date: Tue, 30 Jul 2024 13:26:03 +0300 Subject: [PATCH 03/17] Thumbnail blur --- ui/component/fileThumbnail/index.js | 17 ++++++++++++++++- ui/component/fileThumbnail/internal/thumb.jsx | 9 +++++++-- ui/component/fileThumbnail/view.jsx | 7 +++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/ui/component/fileThumbnail/index.js b/ui/component/fileThumbnail/index.js index e46eaeb651..56fb2dc8d5 100644 --- a/ui/component/fileThumbnail/index.js +++ b/ui/component/fileThumbnail/index.js @@ -1,15 +1,30 @@ import { connect } from 'react-redux'; import { doResolveUri } from 'redux/actions/claims'; -import { selectHasResolvedClaimForUri, selectThumbnailForUri } from 'redux/selectors/claims'; +import { + selectHasResolvedClaimForUri, + selectThumbnailForUri, + makeSelectTagInClaimOrChannelForUri, + selectIsNsfwAknowledgedForClaimId, + selectClaimForUri, + selectClaimIsMine, +} from 'redux/selectors/claims'; import CardMedia from './view'; +import { NSFW_CONTENT_TAG } from 'constants/tags'; + const select = (state, props) => { const { uri, secondaryUri } = props; + const claim = selectClaimForUri(state, uri); + const { claim_id: claimId } = claim || {}; + return { hasResolvedClaim: uri ? selectHasResolvedClaimForUri(state, uri) : undefined, thumbnailFromClaim: selectThumbnailForUri(state, uri), thumbnailFromSecondaryClaim: selectThumbnailForUri(state, secondaryUri, true), + isNsfw: makeSelectTagInClaimOrChannelForUri(props.uri, NSFW_CONTENT_TAG)(state), + isNsfwAknowledged: selectIsNsfwAknowledgedForClaimId(state, claimId), + claimIsMine: Boolean(selectClaimIsMine(state, claim)), }; }; diff --git a/ui/component/fileThumbnail/internal/thumb.jsx b/ui/component/fileThumbnail/internal/thumb.jsx index 65b4c0d52e..cc64461732 100644 --- a/ui/component/fileThumbnail/internal/thumb.jsx +++ b/ui/component/fileThumbnail/internal/thumb.jsx @@ -11,18 +11,23 @@ type Props = { className?: string, small?: boolean, forceReload?: boolean, + shouldBlur: ?boolean, }; const Thumb = (props: Props) => { - const { thumb, fallback, children, className, small, forceReload } = props; + const { thumb, fallback, children, className, small, forceReload, shouldBlur } = props; const thumbnailRef = React.useRef(null); useLazyLoading(thumbnailRef, fallback || ''); + const inlineStyle = {}; + if (forceReload) inlineStyle.backgroundImage = 'url(' + String(thumb) + ')'; + if (shouldBlur) inlineStyle.filter = 'blur(12px)'; + return (
{children} diff --git a/ui/component/fileThumbnail/view.jsx b/ui/component/fileThumbnail/view.jsx index 7b0621559f..805aaccd92 100644 --- a/ui/component/fileThumbnail/view.jsx +++ b/ui/component/fileThumbnail/view.jsx @@ -30,6 +30,9 @@ type Props = { // forcePlaceholder?: boolean, forceReload?: boolean, // -- redux -- + isNsfw: ?boolean, + isNsfwAknowledged: ?boolean, + claimIsMine: Boolean, hasResolvedClaim: ?boolean, // undefined if uri is not given (irrelevant); boolean otherwise. thumbnailFromClaim: ?string, thumbnailFromSecondaryClaim: ?string, @@ -48,6 +51,9 @@ function FileThumbnail(props: Props) { // forcePlaceholder, forceReload, // -- redux -- + isNsfw, + isNsfwAknowledged, + claimIsMine, hasResolvedClaim, thumbnailFromClaim, thumbnailFromSecondaryClaim, @@ -109,6 +115,7 @@ function FileThumbnail(props: Props) { fallback={FALLBACK} className={className} forceReload={forceReload} + shouldBlur={!claimIsMine && isNsfw && !isNsfwAknowledged} > {children} From 10e7914089e71fd896271018552bbabf84a6c4e0 Mon Sep 17 00:00:00 2001 From: miko Date: Tue, 30 Jul 2024 14:23:57 +0300 Subject: [PATCH 04/17] Add setting to remove nsfw warning/blur --- static/app-strings.json | 2 ++ ui/component/settingContent/index.js | 3 ++- ui/component/settingContent/view.jsx | 12 ++++++++++++ ui/constants/settings.js | 1 + ui/constants/shared_preferences.js | 1 + ui/redux/reducers/settings.js | 1 + ui/redux/selectors/claims.js | 3 ++- ui/redux/selectors/settings.js | 4 ++++ 8 files changed, 25 insertions(+), 2 deletions(-) diff --git a/static/app-strings.json b/static/app-strings.json index eb4c4d9090..2128d7a134 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2956,6 +2956,8 @@ "This content is marked as mature": "This content is marked as mature", "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%", + "Don't obscure mature content": "Don't obscure mature content", + "Don't blur thumbnails or warn about content being marked as mature": "Don't blur thumbnails or warn about content being marked as mature", "--end--": "--end--" } diff --git a/ui/component/settingContent/index.js b/ui/component/settingContent/index.js index 7540b54942..878f76d07a 100644 --- a/ui/component/settingContent/index.js +++ b/ui/component/settingContent/index.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux'; import * as SETTINGS from 'constants/settings'; import { doOpenModal } from 'redux/actions/app'; import { doSetClientSetting } from 'redux/actions/settings'; -import { selectShowMatureContent, selectClientSetting } from 'redux/selectors/settings'; +import { selectShowMatureContent, selectIsNsfwAknowledged, selectClientSetting } from 'redux/selectors/settings'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; import SettingContent from './view'; @@ -13,6 +13,7 @@ const select = (state, props) => ({ hideReposts: selectClientSetting(state, SETTINGS.HIDE_REPOSTS), hideShorts: selectClientSetting(state, SETTINGS.HIDE_SHORTS), showNsfw: selectShowMatureContent(state), + isNsfwAknowledged: selectIsNsfwAknowledged(state), instantPurchaseEnabled: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_ENABLED), instantPurchaseMax: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_MAX), enablePublishPreview: selectClientSetting(state, SETTINGS.ENABLE_PUBLISH_PREVIEW), diff --git a/ui/component/settingContent/view.jsx b/ui/component/settingContent/view.jsx index 90758eb7cf..07d536b259 100644 --- a/ui/component/settingContent/view.jsx +++ b/ui/component/settingContent/view.jsx @@ -25,6 +25,7 @@ type Props = { hideReposts: ?boolean, hideShorts: ?boolean, showNsfw: boolean, + isNsfwAknowledged: boolean, instantPurchaseEnabled: boolean, instantPurchaseMax: Price, enablePublishPreview: boolean, @@ -40,6 +41,7 @@ export default function SettingContent(props: Props) { hideReposts, hideShorts, showNsfw, + isNsfwAknowledged, instantPurchaseEnabled, instantPurchaseMax, enablePublishPreview, @@ -89,6 +91,15 @@ export default function SettingContent(props: Props) { /> + + setClientSetting(SETTINGS.NSFW_AKNOWLEDGED, !isNsfwAknowledged)} + /> + + {!SIMPLE_SITE && ( <> @@ -183,6 +194,7 @@ const HELP = { HIDE_SHORTS: 'You will not see content under 1min long. Also hides non-video/audio content.', HIDE_FYP: 'You will not see the personal recommendations in the homepage.', SHOW_MATURE: 'Mature content may include nudity, intense sexuality, profanity, or other adult content. By displaying mature content, you are affirming you are of legal age to view mature content in your country or jurisdiction. ', + NO_OBSCURE_MATURE: "Don't blur thumbnails or warn about content being marked as mature", MAX_PURCHASE_PRICE: 'This will prevent you from purchasing any content over a certain cost, as a safety measure.', ONLY_CONFIRM_OVER_AMOUNT: '', // [feel redundant. Disable for now] "When this option is chosen, LBRY won't ask you to confirm purchases or tips below your chosen amount.", PUBLISH_PREVIEW: 'Show preview and confirmation dialog before publishing content.', diff --git a/ui/constants/settings.js b/ui/constants/settings.js index a2306b5616..badc84a564 100644 --- a/ui/constants/settings.js +++ b/ui/constants/settings.js @@ -6,6 +6,7 @@ export const EMAIL_COLLECTION_ACKNOWLEDGED = 'email_collection_acknowledged'; export const INVITE_ACKNOWLEDGED = 'invite_acknowledged'; export const LANGUAGE = 'language'; export const SHOW_MATURE = 'show_mature'; +export const NSFW_AKNOWLEDGED = 'nsfw_aknowledged'; export const SHOW_ANONYMOUS = 'show_anonymous'; export const SHOW_UNAVAILABLE = 'show_unavailable'; export const INSTANT_PURCHASE_ENABLED = 'instant_purchase_enabled'; diff --git a/ui/constants/shared_preferences.js b/ui/constants/shared_preferences.js index fef1acbd32..9ca7a8fbf5 100644 --- a/ui/constants/shared_preferences.js +++ b/ui/constants/shared_preferences.js @@ -17,6 +17,7 @@ export const SDK_SYNC_KEYS = [DAEMON_SETTINGS.LBRYUM_SERVERS, DAEMON_SETTINGS.SH export const CLIENT_SYNC_KEYS = [ SETTINGS.CLOCK_24H, SETTINGS.SHOW_MATURE, + SETTINGS.NSFW_AKNOWLEDGED, SETTINGS.HIDE_MEMBERS_ONLY_CONTENT, SETTINGS.HIDE_REPOSTS, SETTINGS.HIDE_SCHEDULED_LIVESTREAMS, diff --git a/ui/redux/reducers/settings.js b/ui/redux/reducers/settings.js index ab5e308650..507102f429 100644 --- a/ui/redux/reducers/settings.js +++ b/ui/redux/reducers/settings.js @@ -66,6 +66,7 @@ const defaultState = { // Content [SETTINGS.SHOW_MATURE]: false, + [SETTINGS.NSFW_AKNOWLEDGED]: false, [SETTINGS.AUTOPLAY_MEDIA]: true, [SETTINGS.FLOATING_PLAYER]: true, [SETTINGS.AUTO_DOWNLOAD]: true, diff --git a/ui/redux/selectors/claims.js b/ui/redux/selectors/claims.js index ec2a2ccca4..3574218f95 100644 --- a/ui/redux/selectors/claims.js +++ b/ui/redux/selectors/claims.js @@ -6,6 +6,7 @@ import { normalizeURI, parseURI, isURIValid, buildURI } from 'util/lbryURI'; import { selectGeoBlockLists } from 'redux/selectors/blocked'; import { selectUserLocale, selectYoutubeChannels } from 'redux/selectors/user'; import { selectSupportsByOutpoint } from 'redux/selectors/wallet'; +import { selectIsNsfwAknowledged } from 'redux/selectors/settings'; import { createSelector } from 'reselect'; import { createCachedSelector } from 're-reselect'; import { ODYSEE_CHANNEL } from 'constants/channels'; @@ -56,7 +57,7 @@ export const selectResolvedCollectionsById = (state: State) => selectState(state export const selectMyCollectionClaimIds = (state: State) => selectState(state).myCollectionClaimIds; export const selectIsNsfwAknowledgedForClaimId = (state: State, claimId: string) => { - return Boolean(selectState(state).nsfwAknowledgedById[claimId]); + return Boolean(selectState(state).nsfwAknowledgedById[claimId]) || selectIsNsfwAknowledged(state); }; export const selectMyCollectionClaimsById = createSelector( diff --git a/ui/redux/selectors/settings.js b/ui/redux/selectors/settings.js index 50e14cbfa5..f5437ef655 100644 --- a/ui/redux/selectors/settings.js +++ b/ui/redux/selectors/settings.js @@ -41,6 +41,10 @@ export const selectTheme = (state) => { return theme; }; +export const selectIsNsfwAknowledged = (state) => { + return selectClientSetting(state, SETTINGS.NSFW_AKNOWLEDGED); +}; + export const selectAutomaticDarkModeEnabled = (state) => selectClientSetting(state, SETTINGS.AUTOMATIC_DARK_MODE_ENABLED); export const selectIsNight = (state) => selectState(state).isNight; From 2d374041910acddc180ac99cafb9bf5228348c59 Mon Sep 17 00:00:00 2001 From: miko Date: Thu, 15 Aug 2024 10:59:09 +0300 Subject: [PATCH 05/17] Add confirm age modal --- ui/component/settingContent/index.js | 1 + ui/component/settingContent/view.jsx | 16 ++++++++++++---- ui/constants/settings.js | 1 + ui/constants/shared_preferences.js | 1 + .../internal/nsfwContentOverlay/index.js | 13 ++++++++++++- .../internal/nsfwContentOverlay/view.jsx | 19 +++++++++++++++++-- ui/modal/modalConfirmAge/view.jsx | 8 +++++--- ui/redux/reducers/settings.js | 1 + 8 files changed, 50 insertions(+), 10 deletions(-) diff --git a/ui/component/settingContent/index.js b/ui/component/settingContent/index.js index 878f76d07a..cb9186cd61 100644 --- a/ui/component/settingContent/index.js +++ b/ui/component/settingContent/index.js @@ -12,6 +12,7 @@ const select = (state, props) => ({ hideMembersOnlyContent: selectClientSetting(state, SETTINGS.HIDE_MEMBERS_ONLY_CONTENT), hideReposts: selectClientSetting(state, SETTINGS.HIDE_REPOSTS), hideShorts: selectClientSetting(state, SETTINGS.HIDE_SHORTS), + ageConfirmed: selectClientSetting(state, SETTINGS.AGE_CONFIRMED), showNsfw: selectShowMatureContent(state), isNsfwAknowledged: selectIsNsfwAknowledged(state), instantPurchaseEnabled: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_ENABLED), diff --git a/ui/component/settingContent/view.jsx b/ui/component/settingContent/view.jsx index 07d536b259..f3497b153a 100644 --- a/ui/component/settingContent/view.jsx +++ b/ui/component/settingContent/view.jsx @@ -26,12 +26,13 @@ type Props = { hideShorts: ?boolean, showNsfw: boolean, isNsfwAknowledged: boolean, + ageConfirmed: boolean, instantPurchaseEnabled: boolean, instantPurchaseMax: Price, enablePublishPreview: boolean, // --- perform --- setClientSetting: (string, boolean | string | number) => void, - openModal: (string) => void, + openModal: (string, props?: { cb: () => void }) => void, }; export default function SettingContent(props: Props) { @@ -42,6 +43,7 @@ export default function SettingContent(props: Props) { hideShorts, showNsfw, isNsfwAknowledged, + ageConfirmed, instantPurchaseEnabled, instantPurchaseMax, enablePublishPreview, @@ -96,7 +98,13 @@ export default function SettingContent(props: Props) { type="checkbox" name="aknowledge_nsfw" checked={isNsfwAknowledged} - onChange={() => setClientSetting(SETTINGS.NSFW_AKNOWLEDGED, !isNsfwAknowledged)} + onChange={() => + isNsfwAknowledged || ageConfirmed + ? setClientSetting(SETTINGS.NSFW_AKNOWLEDGED, !isNsfwAknowledged) + : openModal(MODALS.CONFIRM_AGE, { + cb: () => setClientSetting(SETTINGS.NSFW_AKNOWLEDGED, !isNsfwAknowledged), + }) + } /> @@ -108,9 +116,9 @@ export default function SettingContent(props: Props) { name="show_nsfw" checked={showNsfw} onChange={() => - !IS_WEB || showNsfw + !IS_WEB || showNsfw || ageConfirmed ? setClientSetting(SETTINGS.SHOW_MATURE, !showNsfw) - : openModal(MODALS.CONFIRM_AGE) + : openModal(MODALS.CONFIRM_AGE, { cb: () => setClientSetting(SETTINGS.SHOW_MATURE, !showNsfw) }) } /> diff --git a/ui/constants/settings.js b/ui/constants/settings.js index badc84a564..f028f84703 100644 --- a/ui/constants/settings.js +++ b/ui/constants/settings.js @@ -7,6 +7,7 @@ export const INVITE_ACKNOWLEDGED = 'invite_acknowledged'; export const LANGUAGE = 'language'; export const SHOW_MATURE = 'show_mature'; export const NSFW_AKNOWLEDGED = 'nsfw_aknowledged'; +export const AGE_CONFIRMED = 'age_confirmed'; export const SHOW_ANONYMOUS = 'show_anonymous'; export const SHOW_UNAVAILABLE = 'show_unavailable'; export const INSTANT_PURCHASE_ENABLED = 'instant_purchase_enabled'; diff --git a/ui/constants/shared_preferences.js b/ui/constants/shared_preferences.js index 9ca7a8fbf5..afc3df69e8 100644 --- a/ui/constants/shared_preferences.js +++ b/ui/constants/shared_preferences.js @@ -18,6 +18,7 @@ export const CLIENT_SYNC_KEYS = [ SETTINGS.CLOCK_24H, SETTINGS.SHOW_MATURE, SETTINGS.NSFW_AKNOWLEDGED, + SETTINGS.AGE_CONFIRMED, SETTINGS.HIDE_MEMBERS_ONLY_CONTENT, SETTINGS.HIDE_REPOSTS, SETTINGS.HIDE_SCHEDULED_LIVESTREAMS, diff --git a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/index.js b/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/index.js index a918eb3904..b89d0a4f36 100644 --- a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/index.js +++ b/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/index.js @@ -1,7 +1,11 @@ import { connect } from 'react-redux'; +import * as SETTINGS from 'constants/settings'; +import { selectClientSetting } from 'redux/selectors/settings'; import { selectClaimForUri, selectClaimIsMine } from 'redux/selectors/claims'; +import { selectUser } from 'redux/selectors/user'; import { doAknowledgeNsfw } from 'redux/actions/claims'; +import { doOpenModal } from 'redux/actions/app'; import NsfwContentOverlay from './view'; @@ -10,8 +14,15 @@ const select = (state, props) => { return { claimId: claim.claim_id, + user: selectUser(state), + ageConfirmed: selectClientSetting(state, SETTINGS.AGE_CONFIRMED), claimIsMine: selectClaimIsMine(state, claim), }; }; -export default connect(select, { doAknowledgeNsfw })(NsfwContentOverlay); +const perform = (dispatch) => ({ + doAknowledgeNsfw: (id) => dispatch(doAknowledgeNsfw(id)), + openModal: (id, params) => dispatch(doOpenModal(id, params)), +}); + +export default connect(select, perform)(NsfwContentOverlay); diff --git a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx b/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx index 5357f65c89..f662312d02 100644 --- a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx +++ b/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx @@ -1,15 +1,20 @@ // @flow import React from 'react'; import Button from 'component/button'; +import * as MODALS from 'constants/modal_types'; type Props = { claimId: string, + user: ?Object, + ageConfirmed: boolean, claimIsMine: boolean, doAknowledgeNsfw: (claimId: string) => void, + openModal: (string, props?: { cb: () => void }) => void, }; const NsfwContentOverlay = (props: Props) => { - const { claimId, claimIsMine, doAknowledgeNsfw } = props; + const { claimId, user, ageConfirmed, claimIsMine, doAknowledgeNsfw, openModal } = props; + const hasVerifiedEmail = user && user.has_verified_email; if (claimIsMine) return null; @@ -17,7 +22,17 @@ const NsfwContentOverlay = (props: Props) => {
{__('This content is marked as mature')} -
); }; diff --git a/ui/modal/modalConfirmAge/view.jsx b/ui/modal/modalConfirmAge/view.jsx index e569b432ed..41a4bb21ab 100644 --- a/ui/modal/modalConfirmAge/view.jsx +++ b/ui/modal/modalConfirmAge/view.jsx @@ -7,16 +7,18 @@ import Button from 'component/button'; import { FormField } from 'component/common/form'; type Props = { + cb: () => void, doHideModal: () => void, - doSetClientSetting: (string, any) => void, + doSetClientSetting: (string, any, boolean) => void, }; function ModalAffirmPurchase(props: Props) { - const { doHideModal, doSetClientSetting } = props; + const { cb, doHideModal, doSetClientSetting } = props; const [confirmed, setConfirmed] = React.useState(false); function handleConfirmAge() { - doSetClientSetting(SETTINGS.SHOW_MATURE, true); + doSetClientSetting(SETTINGS.AGE_CONFIRMED, true, true); + cb(); doHideModal(); } diff --git a/ui/redux/reducers/settings.js b/ui/redux/reducers/settings.js index 507102f429..d06aa386c7 100644 --- a/ui/redux/reducers/settings.js +++ b/ui/redux/reducers/settings.js @@ -67,6 +67,7 @@ const defaultState = { // Content [SETTINGS.SHOW_MATURE]: false, [SETTINGS.NSFW_AKNOWLEDGED]: false, + [SETTINGS.AGE_CONFIRMED]: false, [SETTINGS.AUTOPLAY_MEDIA]: true, [SETTINGS.FLOATING_PLAYER]: true, [SETTINGS.AUTO_DOWNLOAD]: true, From d750d1201552ee213e88f61310ed5ebabdd249c5 Mon Sep 17 00:00:00 2001 From: miko Date: Thu, 15 Aug 2024 19:51:13 +0300 Subject: [PATCH 06/17] Nswf -> ageRestricted --- flow-typed/Claims.js | 2 +- static/app-strings.json | 4 ++-- ui/component/fileThumbnail/index.js | 8 +++---- ui/component/fileThumbnail/view.jsx | 10 ++++----- ui/component/settingContent/index.js | 8 +++++-- ui/component/settingContent/view.jsx | 22 +++++++++++-------- ui/constants/action_types.js | 2 +- ui/constants/settings.js | 2 +- ui/constants/shared_preferences.js | 2 +- ui/constants/tags.js | 4 ++-- ui/hocs/withStreamClaimRender/index.js | 12 +++++----- .../index.js | 8 +++---- .../view.jsx | 14 ++++++------ ui/hocs/withStreamClaimRender/view.jsx | 20 ++++++++--------- ui/redux/actions/claims.js | 4 ++-- ui/redux/reducers/claims.js | 10 ++++----- ui/redux/reducers/settings.js | 2 +- ui/redux/selectors/claims.js | 8 ++++--- ui/redux/selectors/settings.js | 4 ++-- ui/scss/component/_claim-preview.scss | 2 +- ui/scss/component/_file-render.scss | 2 +- 21 files changed, 80 insertions(+), 70 deletions(-) rename ui/hocs/withStreamClaimRender/internal/{nsfwContentOverlay => ageRestrictedContentOverlay}/index.js (72%) rename ui/hocs/withStreamClaimRender/internal/{nsfwContentOverlay => ageRestrictedContentOverlay}/view.jsx (62%) diff --git a/flow-typed/Claims.js b/flow-typed/Claims.js index e41b7d1ad7..fb68101380 100644 --- a/flow-typed/Claims.js +++ b/flow-typed/Claims.js @@ -62,7 +62,7 @@ declare type ClaimsState = { fetchingMyPurchasedClaims: ?boolean, fetchingMyPurchasedClaimsError: ?string, costInfosById: { [claimId: string]: { cost: number, includesData?: boolean } }, - nsfwAknowledgedById: { [claimID: string]: boolean }, + ageRestrictionAllowedByClaimId: { [claimID: string]: boolean }, }; declare type ClaimSearchResultsInfo = {| diff --git a/static/app-strings.json b/static/app-strings.json index 2128d7a134..7a33e5f404 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2956,8 +2956,8 @@ "This content is marked as mature": "This content is marked as mature", "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%", - "Don't obscure mature content": "Don't obscure mature content", - "Don't blur thumbnails or warn about content being marked as mature": "Don't blur thumbnails or warn about content being marked as mature", + "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", "--end--": "--end--" } diff --git a/ui/component/fileThumbnail/index.js b/ui/component/fileThumbnail/index.js index 56fb2dc8d5..f5eb0b0c97 100644 --- a/ui/component/fileThumbnail/index.js +++ b/ui/component/fileThumbnail/index.js @@ -4,13 +4,13 @@ import { selectHasResolvedClaimForUri, selectThumbnailForUri, makeSelectTagInClaimOrChannelForUri, - selectIsNsfwAknowledgedForClaimId, + selectIsAgeRestrictedContentAllowedForClaimId, selectClaimForUri, selectClaimIsMine, } from 'redux/selectors/claims'; import CardMedia from './view'; -import { NSFW_CONTENT_TAG } from 'constants/tags'; +import { OVER_18_CONTENT_TAG } from 'constants/tags'; const select = (state, props) => { const { uri, secondaryUri } = props; @@ -22,8 +22,8 @@ const select = (state, props) => { hasResolvedClaim: uri ? selectHasResolvedClaimForUri(state, uri) : undefined, thumbnailFromClaim: selectThumbnailForUri(state, uri), thumbnailFromSecondaryClaim: selectThumbnailForUri(state, secondaryUri, true), - isNsfw: makeSelectTagInClaimOrChannelForUri(props.uri, NSFW_CONTENT_TAG)(state), - isNsfwAknowledged: selectIsNsfwAknowledgedForClaimId(state, claimId), + isAgeRestricted: makeSelectTagInClaimOrChannelForUri(props.uri, OVER_18_CONTENT_TAG)(state), + isAgeRestrictedContentAllowed: selectIsAgeRestrictedContentAllowedForClaimId(state, claimId), claimIsMine: Boolean(selectClaimIsMine(state, claim)), }; }; diff --git a/ui/component/fileThumbnail/view.jsx b/ui/component/fileThumbnail/view.jsx index 805aaccd92..31198438f0 100644 --- a/ui/component/fileThumbnail/view.jsx +++ b/ui/component/fileThumbnail/view.jsx @@ -30,8 +30,8 @@ type Props = { // forcePlaceholder?: boolean, forceReload?: boolean, // -- redux -- - isNsfw: ?boolean, - isNsfwAknowledged: ?boolean, + isAgeRestricted: ?boolean, + isAgeRestrictedContentAllowed: ?boolean, claimIsMine: Boolean, hasResolvedClaim: ?boolean, // undefined if uri is not given (irrelevant); boolean otherwise. thumbnailFromClaim: ?string, @@ -51,8 +51,8 @@ function FileThumbnail(props: Props) { // forcePlaceholder, forceReload, // -- redux -- - isNsfw, - isNsfwAknowledged, + isAgeRestricted, + isAgeRestrictedContentAllowed, claimIsMine, hasResolvedClaim, thumbnailFromClaim, @@ -115,7 +115,7 @@ function FileThumbnail(props: Props) { fallback={FALLBACK} className={className} forceReload={forceReload} - shouldBlur={!claimIsMine && isNsfw && !isNsfwAknowledged} + shouldBlur={!claimIsMine && isAgeRestricted && !isAgeRestrictedContentAllowed} > {children} diff --git a/ui/component/settingContent/index.js b/ui/component/settingContent/index.js index cb9186cd61..8af52048e7 100644 --- a/ui/component/settingContent/index.js +++ b/ui/component/settingContent/index.js @@ -2,7 +2,11 @@ import { connect } from 'react-redux'; import * as SETTINGS from 'constants/settings'; import { doOpenModal } from 'redux/actions/app'; import { doSetClientSetting } from 'redux/actions/settings'; -import { selectShowMatureContent, selectIsNsfwAknowledged, selectClientSetting } from 'redux/selectors/settings'; +import { + selectShowMatureContent, + selectIsAgeRestrictedContentAllowed, + selectClientSetting, +} from 'redux/selectors/settings'; import { selectUserVerifiedEmail } from 'redux/selectors/user'; import SettingContent from './view'; @@ -14,7 +18,7 @@ const select = (state, props) => ({ hideShorts: selectClientSetting(state, SETTINGS.HIDE_SHORTS), ageConfirmed: selectClientSetting(state, SETTINGS.AGE_CONFIRMED), showNsfw: selectShowMatureContent(state), - isNsfwAknowledged: selectIsNsfwAknowledged(state), + isAgeRestrictedContentAllowed: selectIsAgeRestrictedContentAllowed(state), instantPurchaseEnabled: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_ENABLED), instantPurchaseMax: selectClientSetting(state, SETTINGS.INSTANT_PURCHASE_MAX), enablePublishPreview: selectClientSetting(state, SETTINGS.ENABLE_PUBLISH_PREVIEW), diff --git a/ui/component/settingContent/view.jsx b/ui/component/settingContent/view.jsx index f3497b153a..151b0fc1ca 100644 --- a/ui/component/settingContent/view.jsx +++ b/ui/component/settingContent/view.jsx @@ -25,7 +25,7 @@ type Props = { hideReposts: ?boolean, hideShorts: ?boolean, showNsfw: boolean, - isNsfwAknowledged: boolean, + isAgeRestrictedContentAllowed: boolean, ageConfirmed: boolean, instantPurchaseEnabled: boolean, instantPurchaseMax: Price, @@ -42,7 +42,7 @@ export default function SettingContent(props: Props) { hideReposts, hideShorts, showNsfw, - isNsfwAknowledged, + isAgeRestrictedContentAllowed, ageConfirmed, instantPurchaseEnabled, instantPurchaseMax, @@ -93,16 +93,20 @@ export default function SettingContent(props: Props) { /> - + - isNsfwAknowledged || ageConfirmed - ? setClientSetting(SETTINGS.NSFW_AKNOWLEDGED, !isNsfwAknowledged) + isAgeRestrictedContentAllowed || ageConfirmed + ? setClientSetting(SETTINGS.AGE_RESTRICTED_CONTENT_ALLOWED, !isAgeRestrictedContentAllowed) : openModal(MODALS.CONFIRM_AGE, { - cb: () => setClientSetting(SETTINGS.NSFW_AKNOWLEDGED, !isNsfwAknowledged), + cb: () => + setClientSetting(SETTINGS.AGE_RESTRICTED_CONTENT_ALLOWED, !isAgeRestrictedContentAllowed), }) } /> @@ -202,7 +206,7 @@ const HELP = { HIDE_SHORTS: 'You will not see content under 1min long. Also hides non-video/audio content.', HIDE_FYP: 'You will not see the personal recommendations in the homepage.', SHOW_MATURE: 'Mature content may include nudity, intense sexuality, profanity, or other adult content. By displaying mature content, you are affirming you are of legal age to view mature content in your country or jurisdiction. ', - NO_OBSCURE_MATURE: "Don't blur thumbnails or warn about content being marked as mature", + NO_OBSCURE_AGE_RESTRICTED_CONTENT: "Don't blur thumbnails or warn about age restricted content", MAX_PURCHASE_PRICE: 'This will prevent you from purchasing any content over a certain cost, as a safety measure.', ONLY_CONFIRM_OVER_AMOUNT: '', // [feel redundant. Disable for now] "When this option is chosen, LBRY won't ask you to confirm purchases or tips below your chosen amount.", PUBLISH_PREVIEW: 'Show preview and confirmation dialog before publishing content.', diff --git a/ui/constants/action_types.js b/ui/constants/action_types.js index 6021f1ee5d..991fc754ad 100644 --- a/ui/constants/action_types.js +++ b/ui/constants/action_types.js @@ -222,7 +222,7 @@ export const PURCHASE_LIST_FAILED = 'PURCHASE_LIST_FAILED'; export const CHECK_IF_PURCHASED_STARTED = 'CHECK_IF_PURCHASED_STARTED'; export const CHECK_IF_PURCHASED_COMPLETED = 'CHECK_IF_PURCHASED_COMPLETED'; export const CHECK_IF_PURCHASED_FAILED = 'CHECK_IF_PURCHASED_FAILED'; -export const AKNOWLEDGE_NSFW = 'AKNOWLEDGE_NSFW'; +export const ALLOW_AGE_RESTRICTED_CONTENT = 'ALLOW_AGE_RESTRICTED_CONTENT'; export const SHOW_AUTOPLAY_COUNTDOWN = 'CHECK_IF_PURCHASED_FAILED'; diff --git a/ui/constants/settings.js b/ui/constants/settings.js index f028f84703..bbac516c23 100644 --- a/ui/constants/settings.js +++ b/ui/constants/settings.js @@ -6,7 +6,7 @@ export const EMAIL_COLLECTION_ACKNOWLEDGED = 'email_collection_acknowledged'; export const INVITE_ACKNOWLEDGED = 'invite_acknowledged'; export const LANGUAGE = 'language'; export const SHOW_MATURE = 'show_mature'; -export const NSFW_AKNOWLEDGED = 'nsfw_aknowledged'; +export const AGE_RESTRICTED_CONTENT_ALLOWED = 'age_restricted_content_allowed'; export const AGE_CONFIRMED = 'age_confirmed'; export const SHOW_ANONYMOUS = 'show_anonymous'; export const SHOW_UNAVAILABLE = 'show_unavailable'; diff --git a/ui/constants/shared_preferences.js b/ui/constants/shared_preferences.js index afc3df69e8..136aa6270b 100644 --- a/ui/constants/shared_preferences.js +++ b/ui/constants/shared_preferences.js @@ -17,7 +17,7 @@ export const SDK_SYNC_KEYS = [DAEMON_SETTINGS.LBRYUM_SERVERS, DAEMON_SETTINGS.SH export const CLIENT_SYNC_KEYS = [ SETTINGS.CLOCK_24H, SETTINGS.SHOW_MATURE, - SETTINGS.NSFW_AKNOWLEDGED, + SETTINGS.AGE_RESTRICTED_CONTENT_ALLOWED, SETTINGS.AGE_CONFIRMED, SETTINGS.HIDE_MEMBERS_ONLY_CONTENT, SETTINGS.HIDE_REPOSTS, diff --git a/ui/constants/tags.js b/ui/constants/tags.js index 57db7afd8b..b6d57bdbd6 100644 --- a/ui/constants/tags.js +++ b/ui/constants/tags.js @@ -28,7 +28,7 @@ export const DISABLE_SLIMES_ALL_TAG = 'c:disable-slimes-all'; export const DISABLE_SLIMES_VIDEO_TAG = 'c:disable-slimes-video'; export const DISABLE_SLIMES_COMMENTS_TAG = 'c:disable-slimes-comments'; -export const NSFW_CONTENT_TAG = 'c:nsfw'; +export const OVER_18_CONTENT_TAG = 'c:requires_18+'; export const PURCHASE_TAG = 'c:purchase'; export const RENTAL_TAG = 'c:rental'; @@ -52,7 +52,7 @@ export const SCHEDULED_TAGS = Object.freeze({ // Control tags are special tags that are available to the user in some situations. export const CONTROL_TAGS = [ - NSFW_CONTENT_TAG, + OVER_18_CONTENT_TAG, DISABLE_SUPPORT_TAG, DISABLE_DOWNLOAD_BUTTON_TAG, DISABLE_REACTIONS_VIDEO_TAG, diff --git a/ui/hocs/withStreamClaimRender/index.js b/ui/hocs/withStreamClaimRender/index.js index 18632fe577..484487d066 100644 --- a/ui/hocs/withStreamClaimRender/index.js +++ b/ui/hocs/withStreamClaimRender/index.js @@ -15,7 +15,7 @@ import { selectScheduledStateForUri, makeSelectTagInClaimOrChannelForUri, selectClaimIsMine, - selectIsNsfwAknowledgedForClaimId, + selectIsAgeRestrictedContentAllowedForClaimId, // selectClaimWasPurchasedForUri, // selectIsFiatPaidForUri, } from 'redux/selectors/claims'; @@ -33,11 +33,11 @@ import { selectVideoSourceLoadedForUri } from 'redux/selectors/app'; import { doStartFloatingPlayingUri, doClearPlayingUri } from 'redux/actions/content'; import { doFileGetForUri } from 'redux/actions/file'; -import { doAknowledgeNsfw } from 'redux/actions/claims'; +import { doAllowAgeRestrictedContent } from 'redux/actions/claims'; import { doCheckIfPurchasedClaimId } from 'redux/actions/stripe'; import { doMembershipMine, doMembershipList } from 'redux/actions/memberships'; -import { NSFW_CONTENT_TAG } from 'constants/tags'; +import { OVER_18_CONTENT_TAG } from 'constants/tags'; import withStreamClaimRender from './view'; @@ -72,8 +72,8 @@ const select = (state, props) => { sdkFeePending: selectSdkFeePendingForUri(state, uri), pendingUnlockedRestrictions: selectPendingUnlockedRestrictionsForUri(state, uri), canViewFile: selectCanViewFileForUri(state, uri), - isNsfw: makeSelectTagInClaimOrChannelForUri(props.uri, NSFW_CONTENT_TAG)(state), - isNsfwAknowledged: selectIsNsfwAknowledgedForClaimId(state, claimId), + isAgeRestricted: makeSelectTagInClaimOrChannelForUri(props.uri, OVER_18_CONTENT_TAG)(state), + isAgeRestrictedContentAllowed: selectIsAgeRestrictedContentAllowedForClaimId(state, claimId), channelLiveFetched: selectChannelIsLiveFetchedForUri(state, uri), sourceLoaded: selectVideoSourceLoadedForUri(state, uri), claimIsMine: Boolean(selectClaimIsMine(state, claim)), @@ -87,7 +87,7 @@ const perform = { doStartFloatingPlayingUri, doMembershipList, doClearPlayingUri, - doAknowledgeNsfw, + doAllowAgeRestrictedContent, }; export default (Component) => withRouter(connect(select, perform)(withStreamClaimRender(Component))); diff --git a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/index.js b/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/index.js similarity index 72% rename from ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/index.js rename to ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/index.js index b89d0a4f36..c114181ddf 100644 --- a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/index.js +++ b/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/index.js @@ -4,10 +4,10 @@ import * as SETTINGS from 'constants/settings'; import { selectClientSetting } from 'redux/selectors/settings'; import { selectClaimForUri, selectClaimIsMine } from 'redux/selectors/claims'; import { selectUser } from 'redux/selectors/user'; -import { doAknowledgeNsfw } from 'redux/actions/claims'; +import { doAllowAgeRestrictedContent } from 'redux/actions/claims'; import { doOpenModal } from 'redux/actions/app'; -import NsfwContentOverlay from './view'; +import AgeRestricedContentOverlay from './view'; const select = (state, props) => { const claim = selectClaimForUri(state, props.uri); @@ -21,8 +21,8 @@ const select = (state, props) => { }; const perform = (dispatch) => ({ - doAknowledgeNsfw: (id) => dispatch(doAknowledgeNsfw(id)), + doAllowAgeRestrictedContent: (id) => dispatch(doAllowAgeRestrictedContent(id)), openModal: (id, params) => dispatch(doOpenModal(id, params)), }); -export default connect(select, perform)(NsfwContentOverlay); +export default connect(select, perform)(AgeRestricedContentOverlay); diff --git a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx b/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/view.jsx similarity index 62% rename from ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx rename to ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/view.jsx index f662312d02..0fb9469cb4 100644 --- a/ui/hocs/withStreamClaimRender/internal/nsfwContentOverlay/view.jsx +++ b/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/view.jsx @@ -8,18 +8,18 @@ type Props = { user: ?Object, ageConfirmed: boolean, claimIsMine: boolean, - doAknowledgeNsfw: (claimId: string) => void, + doAllowAgeRestrictedContent: (claimId: string) => void, openModal: (string, props?: { cb: () => void }) => void, }; -const NsfwContentOverlay = (props: Props) => { - const { claimId, user, ageConfirmed, claimIsMine, doAknowledgeNsfw, openModal } = props; +const AgeRestricedContentOverlay = (props: Props) => { + const { claimId, user, ageConfirmed, claimIsMine, doAllowAgeRestrictedContent, openModal } = props; const hasVerifiedEmail = user && user.has_verified_email; if (claimIsMine) return null; return ( -
+
{__('This content is marked as mature')}
)} {isAgeRestricted && showAgeRestriced && ( -
{__('18+')}
+
{__('18+')}
)} ); From 9f2d5f14fb411f09bb5ea1e1426a525e94403b5c Mon Sep 17 00:00:00 2001 From: miko Date: Tue, 20 Aug 2024 17:11:06 +0300 Subject: [PATCH 13/17] app-strings --- static/app-strings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/static/app-strings.json b/static/app-strings.json index ec8bc79e08..7feb79b245 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2959,6 +2959,7 @@ "Contains nudity, violence or other allowed 18+ content. See %community_guidelines%": "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--" } From eeb11ebe4f887af6183fef17de93415c50588026 Mon Sep 17 00:00:00 2001 From: miko Date: Fri, 23 Aug 2024 08:13:17 +0300 Subject: [PATCH 14/17] Remove age confirmation prompt Update age restricted content text --- static/app-strings.json | 3 +- ui/component/settingContent/view.jsx | 7 +---- .../ageRestrictedContentOverlay/index.js | 10 +------ .../ageRestrictedContentOverlay/view.jsx | 28 +++++-------------- 4 files changed, 10 insertions(+), 38 deletions(-) diff --git a/static/app-strings.json b/static/app-strings.json index 7feb79b245..d49deb59c8 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2953,8 +2953,7 @@ "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.", - "This content is marked as mature": "This content is marked as mature", - "You are trying to access age restriced content": "You are trying to access age restriced content", + "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%", "Don't obscure age restricted content": "Don't obscure age restricted content", diff --git a/ui/component/settingContent/view.jsx b/ui/component/settingContent/view.jsx index 151b0fc1ca..d06b1da456 100644 --- a/ui/component/settingContent/view.jsx +++ b/ui/component/settingContent/view.jsx @@ -102,12 +102,7 @@ export default function SettingContent(props: Props) { name="allow_age_restricted_content" checked={isAgeRestrictedContentAllowed} onChange={() => - isAgeRestrictedContentAllowed || ageConfirmed - ? setClientSetting(SETTINGS.AGE_RESTRICTED_CONTENT_ALLOWED, !isAgeRestrictedContentAllowed) - : openModal(MODALS.CONFIRM_AGE, { - cb: () => - setClientSetting(SETTINGS.AGE_RESTRICTED_CONTENT_ALLOWED, !isAgeRestrictedContentAllowed), - }) + setClientSetting(SETTINGS.AGE_RESTRICTED_CONTENT_ALLOWED, !isAgeRestrictedContentAllowed) } /> diff --git a/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/index.js b/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/index.js index c114181ddf..26e2713973 100644 --- a/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/index.js +++ b/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/index.js @@ -1,11 +1,7 @@ import { connect } from 'react-redux'; -import * as SETTINGS from 'constants/settings'; -import { selectClientSetting } from 'redux/selectors/settings'; -import { selectClaimForUri, selectClaimIsMine } from 'redux/selectors/claims'; -import { selectUser } from 'redux/selectors/user'; +import { selectClaimForUri } from 'redux/selectors/claims'; import { doAllowAgeRestrictedContent } from 'redux/actions/claims'; -import { doOpenModal } from 'redux/actions/app'; import AgeRestricedContentOverlay from './view'; @@ -14,15 +10,11 @@ const select = (state, props) => { return { claimId: claim.claim_id, - user: selectUser(state), - ageConfirmed: selectClientSetting(state, SETTINGS.AGE_CONFIRMED), - claimIsMine: selectClaimIsMine(state, claim), }; }; const perform = (dispatch) => ({ doAllowAgeRestrictedContent: (id) => dispatch(doAllowAgeRestrictedContent(id)), - openModal: (id, params) => dispatch(doOpenModal(id, params)), }); export default connect(select, perform)(AgeRestricedContentOverlay); diff --git a/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/view.jsx b/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/view.jsx index 689357e34c..d383c5113a 100644 --- a/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/view.jsx +++ b/ui/hocs/withStreamClaimRender/internal/ageRestrictedContentOverlay/view.jsx @@ -1,38 +1,24 @@ // @flow import React from 'react'; import Button from 'component/button'; -import * as MODALS from 'constants/modal_types'; type Props = { claimId: string, - user: ?Object, - ageConfirmed: boolean, - claimIsMine: boolean, doAllowAgeRestrictedContent: (claimId: string) => void, - openModal: (string, props?: { cb: () => void }) => void, }; const AgeRestricedContentOverlay = (props: Props) => { - const { claimId, user, ageConfirmed, claimIsMine, doAllowAgeRestrictedContent, openModal } = props; - const hasVerifiedEmail = user && user.has_verified_email; - - if (claimIsMine) return null; + const { claimId, doAllowAgeRestrictedContent } = props; return (
- {__('You are trying to access age restriced content')} + + {__( + 'The following content is intended for Mature Audiences aged 18 years and over. Viewer discretion is advised.' + )} + -
); }; From 4cc7034f02ed02258dbd6e1bb9987f62a8b3c30d Mon Sep 17 00:00:00 2001 From: miko Date: Mon, 30 Sep 2024 14:32:16 +0300 Subject: [PATCH 15/17] Tag channel cover/profile image as mature --- ui/component/channelThumbnail/index.js | 7 +++++ ui/component/channelThumbnail/view.jsx | 9 ++++++ .../livestream/livestreamForm/view.jsx | 2 ++ ui/component/publish/post/postForm/view.jsx | 2 ++ .../publish/upload/uploadForm/view.jsx | 2 ++ ui/component/tagsSearch/view.jsx | 28 +++++++++++-------- ui/component/tagsSelect/view.jsx | 3 ++ ui/constants/tags.js | 2 ++ .../internal/channelPage/index.js | 6 ++-- .../internal/channelPage/view.jsx | 8 +++++- .../internal/collectionGeneralTab/view.jsx | 2 ++ ui/scss/component/_channel.scss | 11 ++++++++ 12 files changed, 68 insertions(+), 14 deletions(-) diff --git a/ui/component/channelThumbnail/index.js b/ui/component/channelThumbnail/index.js index dd8a002b30..08042e6d61 100644 --- a/ui/component/channelThumbnail/index.js +++ b/ui/component/channelThumbnail/index.js @@ -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) => { @@ -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), }; }; diff --git a/ui/component/channelThumbnail/view.jsx b/ui/component/channelThumbnail/view.jsx index 981d8f98a0..48a4c6ceea 100644 --- a/ui/component/channelThumbnail/view.jsx +++ b/ui/component/channelThumbnail/view.jsx @@ -31,6 +31,9 @@ type Props = { isChannel?: boolean, odyseeMembership: ?string, tooltipTitle?: string, + isImagesAgeRestricted: boolean, + channelIsMine: boolean, + isAgeRestrictedContentAllowed: boolean, }; function ChannelThumbnail(props: Props) { @@ -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; @@ -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 { @@ -99,6 +106,7 @@ function ChannelThumbnail(props: Props) { { const validatedTags = []; diff --git a/ui/component/publish/post/postForm/view.jsx b/ui/component/publish/post/postForm/view.jsx index 84e7c7cf60..b979473f07 100644 --- a/ui/component/publish/post/postForm/view.jsx +++ b/ui/component/publish/post/postForm/view.jsx @@ -14,6 +14,7 @@ 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'; @@ -438,6 +439,7 @@ function PostForm(props: Props) { help={__( "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 = []; diff --git a/ui/component/publish/upload/uploadForm/view.jsx b/ui/component/publish/upload/uploadForm/view.jsx index c4d5639a44..ec8eae031c 100644 --- a/ui/component/publish/upload/uploadForm/view.jsx +++ b/ui/component/publish/upload/uploadForm/view.jsx @@ -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" */)); @@ -467,6 +468,7 @@ function UploadForm(props: Props) { help={__( "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 = []; diff --git a/ui/component/tagsSearch/view.jsx b/ui/component/tagsSearch/view.jsx index aade6d4bec..f8b4e60c8e 100644 --- a/ui/component/tagsSearch/view.jsx +++ b/ui/component/tagsSearch/view.jsx @@ -48,6 +48,7 @@ type Props = { limitShow?: number, user: User, disableControlTags?: boolean, + disabledControlTags?: Array, help?: string, }; @@ -82,6 +83,7 @@ export default function TagsSearch(props: Props) { limitSelect = TAG_FOLLOW_MAX, limitShow = 5, disableControlTags, + disabledControlTags, help, } = props; const [newTag, setNewTag] = useState(''); @@ -304,17 +306,21 @@ export default function TagsSearch(props: Props) { onSelect && ( // onSelect ensures this does not appear on TagFollow - {CONTROL_TAGS.map((t) => ( - te.name === t)} - onChange={() => handleUtilityTagCheckbox(t)} - /> - ))} + {CONTROL_TAGS.map( + (t) => + // $FlowIgnore + !disabledControlTags?.includes(t) && ( + te.name === t)} + onChange={() => handleUtilityTagCheckbox(t)} + /> + ) + )} )} diff --git a/ui/component/tagsSelect/view.jsx b/ui/component/tagsSelect/view.jsx index 5ce798eabe..0d84a7aa8d 100644 --- a/ui/component/tagsSelect/view.jsx +++ b/ui/component/tagsSelect/view.jsx @@ -26,6 +26,7 @@ type Props = { hideHeader?: boolean, limitShow?: number, limitSelect?: number, + disabledControlTags?: Array, }; /* @@ -48,6 +49,7 @@ export default function TagsSelect(props: Props) { label, limitShow, limitSelect, + disabledControlTags, } = props; const [hasClosed, setHasClosed] = usePersistedState('tag-select:has-closed', false); const tagsToDisplay = tagsChosen || followedTags; @@ -102,6 +104,7 @@ export default function TagsSelect(props: Props) { placeholder={placeholder} limitShow={limitShow} limitSelect={limitSelect} + disabledControlTags={disabledControlTags} help={ help !== false && ( diff --git a/ui/constants/tags.js b/ui/constants/tags.js index 003051fd06..f00b89195d 100644 --- a/ui/constants/tags.js +++ b/ui/constants/tags.js @@ -29,6 +29,7 @@ export const DISABLE_SLIMES_VIDEO_TAG = 'c:disable-slimes-video'; export const DISABLE_SLIMES_COMMENTS_TAG = 'c:disable-slimes-comments'; export const AGE_RESTRICED_CONTENT_TAG = 'c:requires_18+'; +export const AGE_RESTRICED_CHANNEL_IMAGES_TAG = 'c:channel_images_require_18+'; export const PURCHASE_TAG = 'c:purchase'; export const RENTAL_TAG = 'c:rental'; @@ -52,6 +53,7 @@ export const SCHEDULED_TAGS = Object.freeze({ // Control tags are special tags that are available to the user in some situations. export const CONTROL_TAGS = [ + AGE_RESTRICED_CHANNEL_IMAGES_TAG, AGE_RESTRICED_CONTENT_TAG, DISABLE_SUPPORT_TAG, DISABLE_DOWNLOAD_BUTTON_TAG, diff --git a/ui/page/claim/internal/claimPageComponent/internal/channelPage/index.js b/ui/page/claim/internal/claimPageComponent/internal/channelPage/index.js index 8f77f8cd39..d81a23757d 100644 --- a/ui/page/claim/internal/claimPageComponent/internal/channelPage/index.js +++ b/ui/page/claim/internal/claimPageComponent/internal/channelPage/index.js @@ -15,11 +15,11 @@ import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions'; import { selectModerationBlockList } from 'redux/selectors/comments'; import { selectMutedChannels } from 'redux/selectors/blocked'; import { doOpenModal } from 'redux/actions/app'; -import { selectLanguage } from 'redux/selectors/settings'; +import { selectLanguage, selectIsAgeRestrictedContentAllowed } from 'redux/selectors/settings'; import { selectOdyseeMembershipForChannelId, selectMembershipMineFetched } from 'redux/selectors/memberships'; import { getThumbnailFromClaim, isClaimNsfw } from 'util/claim'; import { doGetMembershipTiersForChannelClaimId, doMembershipMine } from 'redux/actions/memberships'; -import { PREFERENCE_EMBED } from 'constants/tags'; +import { PREFERENCE_EMBED, AGE_RESTRICED_CHANNEL_IMAGES_TAG } from 'constants/tags'; import ChannelPage from './view'; const select = (state, props) => { @@ -46,6 +46,8 @@ const select = (state, props) => { preferEmbed: makeSelectTagInClaimOrChannelForUri(props.uri, PREFERENCE_EMBED)(state), banState: selectBanStateForUri(state, props.uri), isMature: claim ? isClaimNsfw(claim) : false, + isImagesAgeRestricted: makeSelectTagInClaimOrChannelForUri(props.uri, AGE_RESTRICED_CHANNEL_IMAGES_TAG)(state), + isAgeRestrictedContentAllowed: selectIsAgeRestrictedContentAllowed(state), }; }; diff --git a/ui/page/claim/internal/claimPageComponent/internal/channelPage/view.jsx b/ui/page/claim/internal/claimPageComponent/internal/channelPage/view.jsx index f6909eb07d..7685220149 100644 --- a/ui/page/claim/internal/claimPageComponent/internal/channelPage/view.jsx +++ b/ui/page/claim/internal/claimPageComponent/internal/channelPage/view.jsx @@ -76,6 +76,8 @@ type Props = { preferEmbed: boolean, banState: any, isMature: boolean, + isImagesAgeRestricted: boolean, + isAgeRestrictedContentAllowed: boolean, }; function ChannelPage(props: Props) { @@ -103,6 +105,8 @@ function ChannelPage(props: Props) { preferEmbed, banState, isMature, + isImagesAgeRestricted, + isAgeRestrictedContentAllowed, } = props; const { push, @@ -113,6 +117,8 @@ function ChannelPage(props: Props) { const { claims_in_channel } = meta; const showClaims = Boolean(claims_in_channel) && !preferEmbed && !banState.filtered && !banState.blacklisted; + const shouldBlur = !channelIsMine && isImagesAgeRestricted && !isAgeRestrictedContentAllowed; + const [viewBlockedChannel, setViewBlockedChannel] = React.useState(false); const urlParams = new URLSearchParams(search); @@ -363,7 +369,7 @@ function ChannelPage(props: Props) { return (
diff --git a/ui/page/collection/internal/collectionPublishForm/internal/collectionGeneralTab/view.jsx b/ui/page/collection/internal/collectionPublishForm/internal/collectionGeneralTab/view.jsx index bdbecb2621..e7ce67cd15 100644 --- a/ui/page/collection/internal/collectionPublishForm/internal/collectionGeneralTab/view.jsx +++ b/ui/page/collection/internal/collectionPublishForm/internal/collectionGeneralTab/view.jsx @@ -4,6 +4,7 @@ import { useHistory } from 'react-router-dom'; import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field'; import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses'; +import * as TAGS from 'constants/tags'; import { COLLECTION_PAGE } from 'constants/urlParams'; import { FormField, FormUrlName } from 'component/common/form'; @@ -162,6 +163,7 @@ function CollectionGeneralTab(props: Props) { help={__( "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 = []; diff --git a/ui/scss/component/_channel.scss b/ui/scss/component/_channel.scss index fea279371c..494e83b0a6 100644 --- a/ui/scss/component/_channel.scss +++ b/ui/scss/component/_channel.scss @@ -403,6 +403,10 @@ $actions-z-index: 2; } } +.channel-thumbnail.age-restricted { + filter: blur(5px); +} + .channel-thumbnail { display: flex; position: relative; @@ -1172,6 +1176,13 @@ $actions-z-index: 2; } } +.channel-cover.age-restricted { + &:after { + -webkit-backdrop-filter: blur(100px); + backdrop-filter: blur(100px); + } +} + .channel-cover { position: relative; background-image: linear-gradient(to right, #637ad2, #318794 80%); From 45cd05a4bba1bbc22b1fd68707ecc6b9bea8e3c1 Mon Sep 17 00:00:00 2001 From: miko Date: Mon, 30 Sep 2024 14:40:28 +0300 Subject: [PATCH 16/17] Clean up some leftover from rebase --- ui/component/tagsSearch/view.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/ui/component/tagsSearch/view.jsx b/ui/component/tagsSearch/view.jsx index f8b4e60c8e..4aa68a192b 100644 --- a/ui/component/tagsSearch/view.jsx +++ b/ui/component/tagsSearch/view.jsx @@ -14,15 +14,12 @@ import { RENTAL_TAG, RENTAL_TAG_OLD, PURCHASE_TAG_OLD, -<<<<<<< HEAD DISABLE_SUPPORT_TAG, DISABLE_DOWNLOAD_BUTTON_TAG, DISABLE_REACTIONS_VIDEO_TAG, DISABLE_REACTIONS_COMMENTS_TAG, DISABLE_SLIMES_VIDEO_TAG, DISABLE_SLIMES_COMMENTS_TAG, -======= ->>>>>>> 3488087fe (Add mature content indicator on thumbnail + content page) AGE_RESTRICED_CONTENT_TAG, } from 'constants/tags'; import { removeInternalTags } from 'util/tags'; From 4ac7258c0b498a839588105493011f8d183bbc20 Mon Sep 17 00:00:00 2001 From: miko Date: Mon, 30 Sep 2024 14:56:04 +0300 Subject: [PATCH 17/17] Update label text --- static/app-strings.json | 1 + ui/component/tagsSearch/view.jsx | 17 +++++++++++++++++ ui/constants/tags.js | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/static/app-strings.json b/static/app-strings.json index d49deb59c8..73e74f025e 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2956,6 +2956,7 @@ "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+", diff --git a/ui/component/tagsSearch/view.jsx b/ui/component/tagsSearch/view.jsx index 4aa68a192b..e936d28631 100644 --- a/ui/component/tagsSearch/view.jsx +++ b/ui/component/tagsSearch/view.jsx @@ -21,6 +21,7 @@ import { DISABLE_SLIMES_VIDEO_TAG, DISABLE_SLIMES_COMMENTS_TAG, AGE_RESTRICED_CONTENT_TAG, + AGE_RESTRICED_CHANNEL_IMAGES_TAG, } from 'constants/tags'; import { removeInternalTags } from 'util/tags'; @@ -147,6 +148,22 @@ export default function TagsSearch(props: Props) { Contains nudity, violence or other allowed 18+ content. See %community_guidelines% ); + } else if (t === AGE_RESTRICED_CHANNEL_IMAGES_TAG) { + label = ( + + ), + }} + > + Profile/Cover image contains nudity, violence or other allowed 18+ content. See %community_guidelines% + + ); } else { label = __( t diff --git a/ui/constants/tags.js b/ui/constants/tags.js index f00b89195d..a7e78e33ad 100644 --- a/ui/constants/tags.js +++ b/ui/constants/tags.js @@ -53,8 +53,8 @@ export const SCHEDULED_TAGS = Object.freeze({ // Control tags are special tags that are available to the user in some situations. export const CONTROL_TAGS = [ - AGE_RESTRICED_CHANNEL_IMAGES_TAG, AGE_RESTRICED_CONTENT_TAG, + AGE_RESTRICED_CHANNEL_IMAGES_TAG, DISABLE_SUPPORT_TAG, DISABLE_DOWNLOAD_BUTTON_TAG, DISABLE_REACTIONS_VIDEO_TAG,