Allow users to hide content they've already viewed
This commit is contained in:
parent
f36282555c
commit
17332979b6
9 changed files with 156 additions and 54 deletions
Binary file not shown.
|
@ -106,6 +106,7 @@ function ChannelForm(props: Props) {
|
|||
const languageParam = params.languages;
|
||||
const primaryLanguage = Array.isArray(languageParam) && languageParam.length && languageParam[0];
|
||||
const secondaryLanguage = Array.isArray(languageParam) && languageParam.length >= 2 && languageParam[1];
|
||||
const [hideWatched, setHideWatched] = usePersistedState('hideWatched', false); // UPDATE: Experimenting with hiding watched content
|
||||
const submitLabel = React.useMemo(() => {
|
||||
if (isClaimingInitialRewards) {
|
||||
return __('Claiming credits...');
|
||||
|
@ -240,7 +241,24 @@ function ChannelForm(props: Props) {
|
|||
errorMsg = __('Invalid %error_type%', { error_type: (thumbError && 'thumbnail') || (coverError && 'cover image') });
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
// UPDATE: Add "Hide Watched" to channel pages
|
||||
function getHideWatchedElem() {
|
||||
return (
|
||||
<div className={classnames(`card claim-search__menus`)}>
|
||||
<FormField
|
||||
label={__('Hide Watched')}
|
||||
name="hide_watched"
|
||||
type="checkbox"
|
||||
checked={hideWatched}
|
||||
onChange={() => {
|
||||
setHideWatched((prev) => !prev);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
let nameError;
|
||||
if (!name && name !== undefined) {
|
||||
nameError = __('A name is required for your url');
|
||||
|
@ -525,7 +543,8 @@ function ChannelForm(props: Props) {
|
|||
<div className="section__actions">
|
||||
<ClaimAbandonButton uri={uri} abandonActionCallback={() => replace(`/$/${PAGES.CHANNELS}`)} />
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
{getHideWatchedElem()}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -318,25 +318,21 @@ function ClaimListDiscover(props: Props) {
|
|||
if (orderParam === CS.ORDER_BY_TOP && freshnessParam !== CS.FRESH_ALL) {
|
||||
options.release_time = `>${Math.floor(moment().subtract(1, freshnessParam).startOf('hour').unix())}`;
|
||||
} else if (orderParam === CS.ORDER_BY_NEW || orderParam === CS.ORDER_BY_TRENDING) {
|
||||
// Warning - hack below
|
||||
// If users are following more than 10 channels or tags, limit results to stuff less than a year old
|
||||
// For more than 20, drop it down to 6 months
|
||||
// This helps with timeout issues for users that are following a ton of stuff
|
||||
// https://github.com/lbryio/lbry-sdk/issues/2420
|
||||
if (
|
||||
(options.channel_ids && options.channel_ids.length > 20) ||
|
||||
(options.any_tags && options.any_tags.length > 20)
|
||||
) {
|
||||
options.release_time = `>${Math.floor(moment().subtract(3, CS.FRESH_MONTH).startOf('week').unix())}`;
|
||||
} else if (
|
||||
(options.channel_ids && options.channel_ids.length > 10) ||
|
||||
(options.any_tags && options.any_tags.length > 10)
|
||||
) {
|
||||
options.release_time = `>${Math.floor(moment().subtract(1, CS.FRESH_YEAR).startOf('week').unix())}`;
|
||||
} else {
|
||||
// Hack for at least the New page until https://github.com/lbryio/lbry-sdk/issues/2591 is fixed
|
||||
options.release_time = `<${Math.floor(moment().startOf('minute').unix())}`;
|
||||
}
|
||||
// UPDATE: Commented out these lines of code to truly sort by oldest first...
|
||||
//if (
|
||||
// (options.channel_ids && options.channel_ids.length > 20) ||
|
||||
// (options.any_tags && options.any_tags.length > 20)
|
||||
//) {
|
||||
// options.release_time = `>${Math.floor(moment().subtract(3, CS.FRESH_MONTH).startOf('week').unix())}`;
|
||||
//} else if (
|
||||
// (options.channel_ids && options.channel_ids.length > 10) ||
|
||||
// (options.any_tags && options.any_tags.length > 10)
|
||||
//) {
|
||||
// options.release_time = `>${Math.floor(moment().subtract(1, CS.FRESH_YEAR).startOf('week').unix())}`;
|
||||
//} else {
|
||||
// // Hack for at least the New page until https://github.com/lbryio/lbry-sdk/issues/2591 is fixed
|
||||
// options.release_time = `<${Math.floor(moment().startOf('minute').unix())}`;
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ function ClaimListHeader(props: Props) {
|
|||
const [orderParamUser, setOrderParamUser] = usePersistedState(`orderUser-${location.pathname}`, CS.ORDER_BY_TRENDING);
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const freshnessParam = freshness || urlParams.get(CS.FRESH_KEY) || defaultFreshness;
|
||||
const [hideWatched, setHideWatched] = usePersistedState('hideWatched', false); // UPDATE: Experimenting with hiding watched content
|
||||
const contentTypeParam = urlParams.get(CS.CONTENT_KEY);
|
||||
const streamTypeParam =
|
||||
streamType || (CS.FILE_TYPES.includes(contentTypeParam) && contentTypeParam) || defaultStreamType || null;
|
||||
|
@ -97,7 +98,24 @@ function ClaimListHeader(props: Props) {
|
|||
|
||||
const shouldHighlight = searchInLanguage
|
||||
? languageParam !== languageSetting && languageParam !== null
|
||||
: languageParam !== CS.LANGUAGES_ALL && languageParam !== null;
|
||||
: languageParam !== CS.LANGUAGES_ALL && languageParam !== null;
|
||||
|
||||
// UPDATE: Experimenting with hiding watched content
|
||||
// Adding a Hide Watched checkbox to the main menu
|
||||
function getHideWatchedElem() {
|
||||
return (
|
||||
<div className={`claim-search__checkbox`}>
|
||||
<FormField
|
||||
name="hide_watched"
|
||||
type="checkbox"
|
||||
checked={hideWatched}
|
||||
onChange={() => {
|
||||
setHideWatched((prev) => !prev);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if (action !== 'POP' && isFiltered()) {
|
||||
|
@ -481,10 +499,10 @@ function ClaimListHeader(props: Props) {
|
|||
</option>
|
||||
);
|
||||
})}
|
||||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
)}
|
||||
{channelIdsInUrl && (
|
||||
<div className={'claim-search__input-container'}>
|
||||
<label>{__('Advanced Filters from URL')}</label>
|
||||
|
@ -495,10 +513,14 @@ function ClaimListHeader(props: Props) {
|
|||
onClick={handleAdvancedReset}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
<div className={'checkbox-label'}>
|
||||
<label>Hide Watched Content</label>
|
||||
{getHideWatchedElem()}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasMatureTags && hiddenNsfwMessage}
|
||||
|
|
|
@ -21,6 +21,7 @@ import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions';
|
|||
import { isClaimNsfw } from 'util/claim';
|
||||
import ClaimPreview from './view';
|
||||
import formatMediaDuration from 'util/formatMediaDuration';
|
||||
import { makeSelectContentWatchedPercentageForUri } from 'redux/selectors/content'; // UPDATE: Added mSCWPFU (Watched content)
|
||||
|
||||
const select = (state, props) => {
|
||||
const claim = props.uri && selectClaimForUri(state, props.uri);
|
||||
|
@ -46,6 +47,7 @@ const select = (state, props) => {
|
|||
wasPurchased: props.uri && makeSelectClaimWasPurchased(props.uri)(state),
|
||||
isCollectionMine: makeSelectCollectionIsMine(props.collectionId)(state),
|
||||
lang: selectLanguage(state),
|
||||
isWatched: makeSelectContentWatchedPercentageForUri(props.uri)(state) > 80, // UPDATE: Added isWatched, getting percent watched
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import ClaimPreviewNoContent from './claim-preview-no-content';
|
|||
import CollectionEditButtons from 'component/collectionEditButtons';
|
||||
import { useIsMobile } from 'effects/use-screensize';
|
||||
import AbandonedChannelPreview from 'component/abandonedChannelPreview';
|
||||
import usePersistedState from 'effects/use-persisted-state'; // UPDATE: usePersistedState is required for watched content
|
||||
|
||||
// preview images used on the landing page and on the channel page
|
||||
type Props = {
|
||||
|
@ -82,6 +83,7 @@ type Props = {
|
|||
showEdit?: boolean,
|
||||
dragHandleProps?: any,
|
||||
unavailableUris?: Array<string>,
|
||||
isWatched: boolean, // UPDATE: Declare isWatched variable
|
||||
};
|
||||
|
||||
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||
|
@ -141,10 +143,11 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
showEdit,
|
||||
dragHandleProps,
|
||||
unavailableUris,
|
||||
isWatched, // UPDATE: Variables to use in the ClaimPreviewTile
|
||||
} = props;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const [hideWatched, setHideWatched] = usePersistedState('hideWatched', false); //UPDATE: Use hideWatched
|
||||
const isCollection = claim && claim.value_type === 'collection';
|
||||
const collectionClaimId = isCollection && claim && claim.claim_id;
|
||||
const listId = collectionId || collectionClaimId;
|
||||
|
@ -253,8 +256,9 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
if (customShouldHide(claim)) {
|
||||
shouldHide = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Weird placement warning
|
||||
// Make sure this happens after we figure out if this claim needs to be hidden
|
||||
const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, shouldHide);
|
||||
|
@ -272,15 +276,25 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
|||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (isValid && !isResolvingUri && shouldFetch && uri) {
|
||||
resolveUri(uri);
|
||||
}
|
||||
}, [isValid, uri, isResolvingUri, shouldFetch, resolveUri]);
|
||||
|
||||
if (shouldHide && !showNullPlaceholder) {
|
||||
// UPDATE: Hiding watched content
|
||||
if (isWatched && hideWatched) {
|
||||
shouldHide = true;
|
||||
}
|
||||
|
||||
if (shouldHide) {
|
||||
return null;
|
||||
}
|
||||
// END OF UPDATE:
|
||||
|
||||
if (shouldHide && !showNullPlaceholder) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (placeholder === 'loading' || (uri && !claim && isResolvingUri)) {
|
||||
return <ClaimPreviewLoading isChannel={isChannelUri} type={type} />;
|
||||
|
|
|
@ -10,6 +10,7 @@ import { doFileGet } from 'redux/actions/file';
|
|||
import { doResolveUri } from 'redux/actions/claims';
|
||||
import { selectViewCountForUri, selectBanStateForUri } from 'lbryinc';
|
||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||
import { makeSelectContentWatchedPercentageForUri } from 'redux/selectors/content'; // UPDATE: Added percentage watched element
|
||||
import { isClaimNsfw } from 'util/claim';
|
||||
import ClaimPreviewTile from './view';
|
||||
import formatMediaDuration from 'util/formatMediaDuration';
|
||||
|
@ -30,6 +31,7 @@ const select = (state, props) => {
|
|||
showMature: selectShowMatureContent(state),
|
||||
isMature: claim ? isClaimNsfw(claim) : false,
|
||||
viewCount: selectViewCountForUri(state, props.uri),
|
||||
isWatched: makeSelectContentWatchedPercentageForUri(props.uri)(state) > 80, // UPDATE: Get isWatched view percentage and only show < 80%
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import ClaimMenuList from 'component/claimMenuList';
|
|||
import CollectionPreviewOverlay from 'component/collectionPreviewOverlay';
|
||||
// $FlowFixMe cannot resolve ...
|
||||
import PlaceholderTx from 'static/img/placeholderTx.gif';
|
||||
import usePersistedState from 'effects/use-persisted-state'; // UPDATE: usePersistedState is required for watched content
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
|
@ -42,6 +43,7 @@ type Props = {
|
|||
collectionId?: string,
|
||||
viewCount: string,
|
||||
swipeLayout: boolean,
|
||||
isWatched: boolean, // UPDATE: Declare isWatched variable
|
||||
};
|
||||
|
||||
// preview image cards used in related video functionality, channel overview page and homepage
|
||||
|
@ -67,10 +69,12 @@ function ClaimPreviewTile(props: Props) {
|
|||
mediaDuration,
|
||||
viewCount,
|
||||
swipeLayout = false,
|
||||
isWatched, // UPDATE: Variables to use in the ClaimPreviewTile
|
||||
} = props;
|
||||
const isRepost = claim && claim.repost_channel_url;
|
||||
const isCollection = claim && claim.value_type === 'collection';
|
||||
const isStream = claim && claim.value_type === 'stream';
|
||||
const [hideWatched, setHideWatched] = usePersistedState('hideWatched', false); //UPDATE: Use hideWatched
|
||||
// $FlowFixMe
|
||||
const isPlayable =
|
||||
claim &&
|
||||
|
@ -126,18 +130,23 @@ function ClaimPreviewTile(props: Props) {
|
|||
|
||||
let shouldHide = false;
|
||||
|
||||
if (isMature && !showMature) {
|
||||
// Unfortunately needed until this is resolved
|
||||
// https://github.com/lbryio/lbry-sdk/issues/2785
|
||||
shouldHide = true;
|
||||
} else {
|
||||
shouldHide =
|
||||
banState.blacklisted || banState.filtered || (!showHiddenByUser && (banState.muted || banState.blocked));
|
||||
}
|
||||
// UPDATE: Hiding watched content
|
||||
if (isMature && !showMature) {
|
||||
// Unfortunately needed until this is resolved
|
||||
// https://github.com/lbryio/lbry-sdk/issues/2785
|
||||
shouldHide = true;
|
||||
} else {
|
||||
shouldHide =
|
||||
banState.blacklisted ||
|
||||
banState.filtered ||
|
||||
(!showHiddenByUser && (banState.muted || banState.blocked)) ||
|
||||
(isWatched && hideWatched);
|
||||
}
|
||||
|
||||
if (shouldHide) {
|
||||
return null;
|
||||
}
|
||||
if (shouldHide) {
|
||||
return null;
|
||||
}
|
||||
// END OF UPDATE:
|
||||
|
||||
const isChannelPage = location.pathname.startsWith('/@');
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import { Form, FormField } from 'component/common/form';
|
|||
import Button from 'component/button';
|
||||
import Icon from 'component/common/icon';
|
||||
import classnames from 'classnames';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
|
||||
const CLAIM_TYPES = {
|
||||
[SEARCH_OPTIONS.INCLUDE_FILES]: 'Files',
|
||||
|
@ -59,9 +60,11 @@ const SearchOptions = (props: Props) => {
|
|||
if (simple) {
|
||||
delete TYPES_ADVANCED[SEARCH_OPTIONS.MEDIA_APPLICATION];
|
||||
delete TYPES_ADVANCED[SEARCH_OPTIONS.MEDIA_IMAGE];
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const [hideWatched, setHideWatched] = usePersistedState('hideWatched', false); // UPDATE: Experimenting with hiding watched content
|
||||
|
||||
React.useEffect(() => {
|
||||
// We no longer let the user set the search results count, but the value
|
||||
// will be in local storage for existing users. Override that.
|
||||
if (options[SEARCH_OPTIONS.RESULT_COUNT] !== SEARCH_PAGE_SIZE) {
|
||||
|
@ -69,7 +72,32 @@ const SearchOptions = (props: Props) => {
|
|||
}
|
||||
}, []);
|
||||
|
||||
function updateSearchOptions(option, value) {
|
||||
// UPDATE: Adding Hide Watched Content checkbox to search
|
||||
function getHideWatchedElem() {
|
||||
return (
|
||||
<div className={`claim-search__checkbox_searchbox`}>
|
||||
<FormField
|
||||
name="hide_watched"
|
||||
type="checkbox"
|
||||
checked={hideWatched}
|
||||
onChange={() => {
|
||||
setHideWatched((prev) => !prev);
|
||||
}}
|
||||
/>
|
||||
<Icon
|
||||
className="icon--help"
|
||||
icon={ICONS.HELP}
|
||||
tooltip
|
||||
size={16}
|
||||
customTooltipText={__(
|
||||
'Hide content you have already viewed from search results.'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function updateSearchOptions(option, value) {
|
||||
setSearchOption(option, value);
|
||||
if (onSearchOptionsChanged) {
|
||||
onSearchOptionsChanged(option);
|
||||
|
@ -149,7 +177,8 @@ const SearchOptions = (props: Props) => {
|
|||
</>
|
||||
);
|
||||
|
||||
const otherOptionsElem = (
|
||||
// UPDATE: Changed element name to exactMatchElem
|
||||
const exactMatchElem = (
|
||||
<>
|
||||
<div className="filter-values">
|
||||
<FormField
|
||||
|
@ -157,7 +186,6 @@ const SearchOptions = (props: Props) => {
|
|||
name="exact-match"
|
||||
checked={options[SEARCH_OPTIONS.EXACT]}
|
||||
onChange={() => updateSearchOptions(SEARCH_OPTIONS.EXACT, !options[SEARCH_OPTIONS.EXACT])}
|
||||
label={__('Exact match')}
|
||||
/>
|
||||
<Icon
|
||||
className="icon--help"
|
||||
|
@ -167,8 +195,9 @@ const SearchOptions = (props: Props) => {
|
|||
customTooltipText={__(
|
||||
'Find results that include all the given words in the exact order.\nThis can also be done by surrounding the search query with quotation marks (e.g. "hello world").'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
|
||||
</>
|
||||
);
|
||||
|
||||
|
@ -192,7 +221,14 @@ const SearchOptions = (props: Props) => {
|
|||
onClick={() => updateSearchOptions(SEARCH_OPTIONS.TIME_FILTER, '')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
|
||||
// UPDATE: Declare constant for the Hide Watch Content element
|
||||
const hideWatchedElem = (
|
||||
<div>
|
||||
{getHideWatchedElem()}
|
||||
</div>
|
||||
);
|
||||
|
||||
const sortByElem = (
|
||||
<div className="filter-values">
|
||||
|
@ -208,10 +244,11 @@ const SearchOptions = (props: Props) => {
|
|||
</div>
|
||||
);
|
||||
|
||||
const uploadDateLabel =
|
||||
const uploadDateLabel =
|
||||
options[SEARCH_OPTIONS.CLAIM_TYPE] === SEARCH_OPTIONS.INCLUDE_CHANNELS ? __('Creation Date') : __('Upload Date');
|
||||
|
||||
return (
|
||||
// UPDATE: Added row to table for hiding watched content in search settings
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
button="alt"
|
||||
|
@ -230,7 +267,8 @@ const SearchOptions = (props: Props) => {
|
|||
{addRow(__('Type'), typeElem)}
|
||||
{addRow(uploadDateLabel, uploadDateElem)}
|
||||
{addRow(__('Sort By'), sortByElem)}
|
||||
{addRow(__('Other Options'), otherOptionsElem)}
|
||||
{addRow(__('Exact Match'), exactMatchElem)}
|
||||
{addRow(__('Hide Watched Content'), hideWatchedElem)}
|
||||
</tbody>
|
||||
</table>
|
||||
</Form>
|
||||
|
|
Loading…
Reference in a new issue