Recommended changes #7089
8 changed files with 149 additions and 193 deletions
|
@ -1,31 +1,24 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectClaimIsNsfw, makeSelectClaimForUri } from 'lbry-redux';
|
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||||
import { doRecommendationUpdate, doRecommendationClicked } from 'redux/actions/content';
|
|
||||||
import { doFetchRecommendedContent } from 'redux/actions/search';
|
import { doFetchRecommendedContent } from 'redux/actions/search';
|
||||||
import { makeSelectRecommendedContentForUri, selectIsSearching } from 'redux/selectors/search';
|
import { makeSelectRecommendedContentForUri, selectIsSearching } from 'redux/selectors/search';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import { makeSelectNextUnplayedRecommended } from 'redux/selectors/content';
|
|
||||||
import RecommendedContent from './view';
|
import RecommendedContent from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const claim = makeSelectClaimForUri(props.uri)(state);
|
const claim = makeSelectClaimForUri(props.uri)(state);
|
||||||
const { claim_id: claimId } = claim;
|
const { claim_id: claimId } = claim;
|
||||||
|
const recommendedContentUris = makeSelectRecommendedContentForUri(props.uri)(state);
|
||||||
|
const nextRecommendedUri = recommendedContentUris && recommendedContentUris[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mature: makeSelectClaimIsNsfw(props.uri)(state),
|
|
||||||
recommendedContentUris: makeSelectRecommendedContentForUri(props.uri)(state),
|
|
||||||
nextRecommendedUri: makeSelectNextUnplayedRecommended(props.uri)(state),
|
|
||||||
isSearching: selectIsSearching(state),
|
|
||||||
isAuthenticated: selectUserVerifiedEmail(state),
|
|
||||||
claim,
|
claim,
|
||||||
claimId,
|
claimId,
|
||||||
|
recommendedContentUris,
|
||||||
|
nextRecommendedUri,
|
||||||
|
isSearching: selectIsSearching(state),
|
||||||
|
isAuthenticated: selectUserVerifiedEmail(state),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const perform = (dispatch) => ({
|
export default connect(select, { doFetchRecommendedContent })(RecommendedContent);
|
||||||
doFetchRecommendedContent: (uri, mature) => dispatch(doFetchRecommendedContent(uri, mature)),
|
|
||||||
doRecommendationUpdate: (claimId, urls, id, parentId) =>
|
|
||||||
dispatch(doRecommendationUpdate(claimId, urls, id, parentId)),
|
|
||||||
doRecommendationClicked: (claimId, index) => dispatch(doRecommendationClicked(claimId, index)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, perform)(RecommendedContent);
|
|
||||||
|
|
|
@ -18,11 +18,9 @@ type Props = {
|
||||||
recommendedContentUris: Array<string>,
|
recommendedContentUris: Array<string>,
|
||||||
nextRecommendedUri: string,
|
nextRecommendedUri: string,
|
||||||
isSearching: boolean,
|
isSearching: boolean,
|
||||||
doFetchRecommendedContent: (string, boolean) => void,
|
doFetchRecommendedContent: (string) => void,
|
||||||
mature: boolean,
|
|
||||||
isAuthenticated: boolean,
|
isAuthenticated: boolean,
|
||||||
claim: ?StreamClaim,
|
claim: ?StreamClaim,
|
||||||
doRecommendationUpdate: (claimId: string, urls: Array<string>, id: string, parentId: string) => void,
|
|
||||||
claimId: string,
|
claimId: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,7 +28,6 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
|
||||||
const {
|
const {
|
||||||
uri,
|
uri,
|
||||||
doFetchRecommendedContent,
|
doFetchRecommendedContent,
|
||||||
mature,
|
|
||||||
recommendedContentUris,
|
recommendedContentUris,
|
||||||
nextRecommendedUri,
|
nextRecommendedUri,
|
||||||
isSearching,
|
isSearching,
|
||||||
|
@ -39,53 +36,29 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
|
||||||
claimId,
|
claimId,
|
||||||
} = props;
|
} = props;
|
||||||
const [viewMode, setViewMode] = React.useState(VIEW_ALL_RELATED);
|
const [viewMode, setViewMode] = React.useState(VIEW_ALL_RELATED);
|
||||||
const [recommendationUrls, setRecommendationUrls] = React.useState();
|
|
||||||
const signingChannel = claim && claim.signing_channel;
|
const signingChannel = claim && claim.signing_channel;
|
||||||
const channelName = signingChannel ? signingChannel.name : null;
|
const channelName = signingChannel ? signingChannel.name : null;
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const isMedium = useIsMediumScreen();
|
const isMedium = useIsMediumScreen();
|
||||||
const { onRecsLoaded: onRecommendationsLoaded, onClickedRecommended: onRecommendationClicked } = RecSys;
|
const { onRecsLoaded: onRecommendationsLoaded, onClickedRecommended: onRecommendationClicked } = RecSys;
|
||||||
React.useEffect(() => {
|
|
||||||
function moveAutoplayNextItemToTop(recommendedContent) {
|
|
||||||
let newList = recommendedContent;
|
|
||||||
if (newList) {
|
|
||||||
const index = newList.indexOf(nextRecommendedUri);
|
|
||||||
if (index > 0) {
|
|
||||||
const a = newList[0];
|
|
||||||
newList[0] = nextRecommendedUri;
|
|
||||||
newList[index] = a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newList;
|
|
||||||
}
|
|
||||||
|
|
||||||
function listEq(prev, next) {
|
|
||||||
if (prev && next) {
|
|
||||||
return prev.length === next.length && prev.every((value, index) => value === next[index]);
|
|
||||||
} else {
|
|
||||||
return prev === next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newRecommendationUrls = moveAutoplayNextItemToTop(recommendedContentUris);
|
|
||||||
|
|
||||||
if (claim && !listEq(recommendationUrls, newRecommendationUrls)) {
|
|
||||||
setRecommendationUrls(newRecommendationUrls);
|
|
||||||
}
|
|
||||||
}, [recommendedContentUris, nextRecommendedUri, recommendationUrls, setRecommendationUrls, claim]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
doFetchRecommendedContent(uri, mature);
|
doFetchRecommendedContent(uri);
|
||||||
}, [uri, mature, doFetchRecommendedContent]);
|
}, [uri, doFetchRecommendedContent]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
// Right now we only want to record the recs if they actually saw them.
|
// Right now we only want to record the recs if they actually saw them.
|
||||||
if (recommendationUrls && recommendationUrls.length && nextRecommendedUri && viewMode === VIEW_ALL_RELATED) {
|
if (
|
||||||
onRecommendationsLoaded(claimId, recommendationUrls);
|
recommendedContentUris &&
|
||||||
|
recommendedContentUris.length &&
|
||||||
|
nextRecommendedUri &&
|
||||||
|
viewMode === VIEW_ALL_RELATED
|
||||||
|
) {
|
||||||
|
onRecommendationsLoaded(claimId, recommendedContentUris);
|
||||||
}
|
}
|
||||||
}, [recommendationUrls, onRecommendationsLoaded, claimId, nextRecommendedUri, viewMode]);
|
}, [recommendedContentUris, onRecommendationsLoaded, claimId, nextRecommendedUri, viewMode]);
|
||||||
|
|
||||||
function handleRecommendationClicked(e, clickedClaim, index: number) {
|
function handleRecommendationClicked(e, clickedClaim) {
|
||||||
if (claim) {
|
if (claim) {
|
||||||
onRecommendationClicked(claim.claim_id, clickedClaim.claim_id);
|
onRecommendationClicked(claim.claim_id, clickedClaim.claim_id);
|
||||||
}
|
}
|
||||||
|
@ -124,7 +97,7 @@ export default React.memo<Props>(function RecommendedContent(props: Props) {
|
||||||
<ClaimList
|
<ClaimList
|
||||||
type="small"
|
type="small"
|
||||||
loading={isSearching}
|
loading={isSearching}
|
||||||
uris={recommendationUrls}
|
uris={recommendedContentUris}
|
||||||
hideMenu={isMobile}
|
hideMenu={isMobile}
|
||||||
injectedItem={SHOW_ADS && IS_WEB && !isAuthenticated && <Ads small type={'video'} />}
|
injectedItem={SHOW_ADS && IS_WEB && !isAuthenticated && <Ads small type={'video'} />}
|
||||||
empty={__('No related content found')}
|
empty={__('No related content found')}
|
||||||
|
@ -164,7 +137,6 @@ function areEqual(prevProps: Props, nextProps: Props) {
|
||||||
a.nextRecommendedUri !== b.nextRecommendedUri ||
|
a.nextRecommendedUri !== b.nextRecommendedUri ||
|
||||||
a.isAuthenticated !== b.isAuthenticated ||
|
a.isAuthenticated !== b.isAuthenticated ||
|
||||||
a.isSearching !== b.isSearching ||
|
a.isSearching !== b.isSearching ||
|
||||||
a.mature !== b.mature ||
|
|
||||||
(a.recommendedContentUris && !b.recommendedContentUris) ||
|
(a.recommendedContentUris && !b.recommendedContentUris) ||
|
||||||
(!a.recommendedContentUris && b.recommendedContentUris) ||
|
(!a.recommendedContentUris && b.recommendedContentUris) ||
|
||||||
(a.claim && !b.claim) ||
|
(a.claim && !b.claim) ||
|
||||||
|
|
|
@ -16,12 +16,8 @@ import {
|
||||||
} from 'redux/actions/app';
|
} from 'redux/actions/app';
|
||||||
import { selectVolume, selectMute } from 'redux/selectors/app';
|
import { selectVolume, selectMute } from 'redux/selectors/app';
|
||||||
import { savePosition, clearPosition, doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
import { savePosition, clearPosition, doPlayUri, doSetPlayingUri } from 'redux/actions/content';
|
||||||
import {
|
import { makeSelectContentPositionForUri, makeSelectIsPlayerFloating, selectPlayingUri } from 'redux/selectors/content';
|
||||||
makeSelectContentPositionForUri,
|
import { makeSelectRecommendedContentForUri } from 'redux/selectors/search';
|
||||||
makeSelectIsPlayerFloating,
|
|
||||||
makeSelectNextUnplayedRecommended,
|
|
||||||
selectPlayingUri,
|
|
||||||
} from 'redux/selectors/content';
|
|
||||||
import VideoViewer from './view';
|
import VideoViewer from './view';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
||||||
|
@ -47,7 +43,8 @@ const select = (state, props) => {
|
||||||
nextRecommendedUri = makeSelectNextUrlForCollectionAndUrl(collectionId, uri)(state);
|
nextRecommendedUri = makeSelectNextUrlForCollectionAndUrl(collectionId, uri)(state);
|
||||||
previousListUri = makeSelectPreviousUrlForCollectionAndUrl(collectionId, uri)(state);
|
previousListUri = makeSelectPreviousUrlForCollectionAndUrl(collectionId, uri)(state);
|
||||||
} else {
|
} else {
|
||||||
nextRecommendedUri = makeSelectNextUnplayedRecommended(uri)(state);
|
const recommendedContent = makeSelectRecommendedContentForUri(uri)(state);
|
||||||
|
nextRecommendedUri = recommendedContent && recommendedContent[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -13,9 +13,11 @@ export const SEARCH_TYPES = {
|
||||||
export const SEARCH_OPTIONS = {
|
export const SEARCH_OPTIONS = {
|
||||||
RESULT_COUNT: 'size',
|
RESULT_COUNT: 'size',
|
||||||
CLAIM_TYPE: 'claimType',
|
CLAIM_TYPE: 'claimType',
|
||||||
|
RELATED_TO: 'related_to',
|
||||||
INCLUDE_FILES: 'file',
|
INCLUDE_FILES: 'file',
|
||||||
INCLUDE_CHANNELS: 'channel',
|
INCLUDE_CHANNELS: 'channel',
|
||||||
INCLUDE_FILES_AND_CHANNELS: 'file,channel',
|
INCLUDE_FILES_AND_CHANNELS: 'file,channel',
|
||||||
|
INCLUDE_MATURE: 'nsfw',
|
||||||
now I did 👍 now I did 👍
|
|||||||
MEDIA_AUDIO: 'audio',
|
MEDIA_AUDIO: 'audio',
|
||||||
MEDIA_VIDEO: 'video',
|
MEDIA_VIDEO: 'video',
|
||||||
MEDIA_TEXT: 'text',
|
MEDIA_TEXT: 'text',
|
||||||
|
@ -25,6 +27,7 @@ export const SEARCH_OPTIONS = {
|
||||||
SORT_ACCENDING: '^release_time',
|
SORT_ACCENDING: '^release_time',
|
||||||
SORT_DESCENDING: 'release_time',
|
SORT_DESCENDING: 'release_time',
|
||||||
EXACT: 'exact',
|
EXACT: 'exact',
|
||||||
|
PRICE_FILTER_FREE: 'free_only',
|
||||||
TIME_FILTER: 'time_filter',
|
TIME_FILTER: 'time_filter',
|
||||||
TIME_FILTER_LAST_HOUR: 'lasthour',
|
TIME_FILTER_LAST_HOUR: 'lasthour',
|
||||||
TIME_FILTER_TODAY: 'today',
|
TIME_FILTER_TODAY: 'today',
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import { SEARCH_OPTIONS } from 'constants/search';
|
import { SEARCH_OPTIONS } from 'constants/search';
|
||||||
import { buildURI, doResolveUris, batchActions, isURIValid, makeSelectClaimForUri } from 'lbry-redux';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
|
import {
|
||||||
|
buildURI,
|
||||||
|
doResolveUris,
|
||||||
|
batchActions,
|
||||||
|
isURIValid,
|
||||||
|
makeSelectClaimForUri,
|
||||||
|
makeSelectClaimIsNsfw,
|
||||||
|
} from 'lbry-redux';
|
||||||
import { makeSelectSearchUrisForQuery, selectSearchValue } from 'redux/selectors/search';
|
import { makeSelectSearchUrisForQuery, selectSearchValue } from 'redux/selectors/search';
|
||||||
import handleFetchResponse from 'util/handle-fetch';
|
import handleFetchResponse from 'util/handle-fetch';
|
||||||
import { getSearchQueryString } from 'util/query-params';
|
import { getSearchQueryString } from 'util/query-params';
|
||||||
|
@ -125,21 +133,26 @@ export const doUpdateSearchOptions = (newOptions: SearchOptions, additionalOptio
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doFetchRecommendedContent = (uri: string, mature: boolean) => (dispatch: Dispatch, getState: GetState) => {
|
export const doFetchRecommendedContent = (uri: string) => (dispatch: Dispatch, getState: GetState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const claim = makeSelectClaimForUri(uri)(state);
|
const claim = makeSelectClaimForUri(uri)(state);
|
||||||
|
const matureEnabled = selectShowMatureContent(state);
|
||||||
|
const claimIsMature = makeSelectClaimIsNsfw(uri)(state);
|
||||||
|
|
||||||
if (claim && claim.value && claim.claim_id) {
|
if (claim && claim.value && claim.claim_id) {
|
||||||
const options: SearchOptions = { size: 20, related_to: claim.claim_id, isBackgroundSearch: true };
|
const options: SearchOptions = { size: 20, nsfw: matureEnabled, isBackgroundSearch: true };
|
||||||
if (!mature) {
|
|
||||||
options['nsfw'] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SIMPLE_SITE) {
|
if (SIMPLE_SITE) {
|
||||||
options[SEARCH_OPTIONS.CLAIM_TYPE] = SEARCH_OPTIONS.INCLUDE_FILES;
|
options[SEARCH_OPTIONS.CLAIM_TYPE] = SEARCH_OPTIONS.INCLUDE_FILES;
|
||||||
options[SEARCH_OPTIONS.MEDIA_VIDEO] = true;
|
options[SEARCH_OPTIONS.MEDIA_VIDEO] = true;
|
||||||
|
options[SEARCH_OPTIONS.PRICE_FILTER_FREE] = true;
|
||||||
}
|
}
|
||||||
|
if (matureEnabled || !claimIsMature) {
|
||||||
|
options[SEARCH_OPTIONS.RELATED_TO] = claim.claim_id;
|
||||||
|
}
|
||||||
|
|
||||||
const { title } = claim.value;
|
const { title } = claim.value;
|
||||||
|
|
||||||
if (title && options) {
|
if (title && options) {
|
||||||
dispatch(doSearch(title, options));
|
dispatch(doSearch(title, options));
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,10 @@ import {
|
||||||
makeSelectClaimIsMine,
|
makeSelectClaimIsMine,
|
||||||
makeSelectMediaTypeForUri,
|
makeSelectMediaTypeForUri,
|
||||||
selectBalance,
|
selectBalance,
|
||||||
parseURI,
|
|
||||||
makeSelectContentTypeForUri,
|
makeSelectContentTypeForUri,
|
||||||
makeSelectFileNameForUri,
|
makeSelectFileNameForUri,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { makeSelectRecommendedContentForUri } from 'redux/selectors/search';
|
import { makeSelectCostInfoForUri } from 'lbryinc';
|
||||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
|
||||||
import { selectAllCostInfoByUri, makeSelectCostInfoForUri } from 'lbryinc';
|
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
@ -35,13 +32,14 @@ export const makeSelectIsPlaying = (uri: string) =>
|
||||||
|
|
||||||
export const makeSelectIsPlayerFloating = (location: UrlLocation) =>
|
export const makeSelectIsPlayerFloating = (location: UrlLocation) =>
|
||||||
createSelector(selectPrimaryUri, selectPlayingUri, (primaryUri, playingUri) => {
|
createSelector(selectPrimaryUri, selectPlayingUri, (primaryUri, playingUri) => {
|
||||||
to me hasSource gets confused with the idea of whether a claim has a source as in livestreams. to me hasSource gets confused with the idea of whether a claim has a source as in livestreams.
hasSecondarySource maybe.
|
|||||||
|
const hasSecondarySource = playingUri && (playingUri.source === 'comment' || playingUri.source === 'markdown');
|
||||||
const isInlineSecondaryPlayer =
|
const isInlineSecondaryPlayer =
|
||||||
playingUri &&
|
playingUri && playingUri.uri !== primaryUri && location.pathname === playingUri.pathname && hasSecondarySource;
|
||||||
playingUri.uri !== primaryUri &&
|
|
||||||
location.pathname === playingUri.pathname &&
|
|
||||||
(playingUri.source === 'comment' || playingUri.source === 'markdown');
|
|
||||||
|
|
||||||
if ((playingUri && playingUri.primaryUri === primaryUri) || isInlineSecondaryPlayer) {
|
if (
|
||||||
|
(playingUri && (hasSecondarySource ? playingUri.primaryUri === primaryUri : playingUri.uri === primaryUri)) ||
|
||||||
|
isInlineSecondaryPlayer
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,78 +75,6 @@ export const makeSelectHistoryForUri = (uri: string) =>
|
||||||
export const makeSelectHasVisitedUri = (uri: string) =>
|
export const makeSelectHasVisitedUri = (uri: string) =>
|
||||||
createSelector(makeSelectHistoryForUri(uri), (history) => Boolean(history));
|
createSelector(makeSelectHistoryForUri(uri), (history) => Boolean(history));
|
||||||
|
|
||||||
export const makeSelectNextUnplayedRecommended = (uri: string) =>
|
|
||||||
createSelector(
|
|
||||||
makeSelectRecommendedContentForUri(uri),
|
|
||||||
selectHistory,
|
|
||||||
selectClaimsByUri,
|
|
||||||
selectAllCostInfoByUri,
|
|
||||||
selectMutedChannels,
|
|
||||||
(
|
|
||||||
recommendedForUri: Array<string>,
|
|
||||||
history: Array<{ uri: string }>,
|
|
||||||
claimsByUri: { [string]: ?Claim },
|
|
||||||
costInfoByUri: { [string]: { cost: 0 | string } },
|
|
||||||
blockedChannels: Array<string>
|
|
||||||
) => {
|
|
||||||
if (recommendedForUri) {
|
|
||||||
// Make sure we don't autoplay paid content, channels, or content from blocked channels
|
|
||||||
for (let i = 0; i < recommendedForUri.length; i++) {
|
|
||||||
const recommendedUri = recommendedForUri[i];
|
|
||||||
const claim = claimsByUri[recommendedUri];
|
|
||||||
|
|
||||||
if (!claim) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { isChannel } = parseURI(recommendedUri);
|
|
||||||
if (isChannel) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const costInfo = costInfoByUri[recommendedUri];
|
|
||||||
if (!costInfo || costInfo.cost !== 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We already check if it's a channel above
|
|
||||||
// $FlowFixMe
|
|
||||||
const isVideo = claim.value && claim.value.stream_type === 'video';
|
|
||||||
// $FlowFixMe
|
|
||||||
const isAudio = claim.value && claim.value.stream_type === 'audio';
|
|
||||||
if (!isVideo && !isAudio) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const channel = claim && claim.signing_channel;
|
|
||||||
if (channel && blockedChannels.some((blockedUri) => blockedUri === channel.permanent_url)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const recommendedUriInfo = parseURI(recommendedUri);
|
|
||||||
const recommendedUriShort = recommendedUriInfo.claimName + '#' + recommendedUriInfo.claimId.substring(0, 1);
|
|
||||||
|
|
||||||
if (claimsByUri[uri] && claimsByUri[uri].claim_id === recommendedUriInfo.claimId) {
|
|
||||||
// Skip myself (same claim ID)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!history.some((h) => {
|
|
||||||
const directMatch = h.uri === recommendedForUri[i];
|
|
||||||
const shortUriMatch = h.uri.includes(recommendedUriShort);
|
|
||||||
const idMatch = claimsByUri[h.uri] && claimsByUri[h.uri].claim_id === recommendedUriInfo.claimId;
|
|
||||||
|
|
||||||
return directMatch || shortUriMatch || idMatch;
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return recommendedForUri[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const selectRecentHistory = createSelector(selectHistory, (history) => {
|
export const selectRecentHistory = createSelector(selectHistory, (history) => {
|
||||||
return history.slice(0, RECENT_HISTORY_AMOUNT);
|
return history.slice(0, RECENT_HISTORY_AMOUNT);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { getSearchQueryString } from 'util/query-params';
|
import { getSearchQueryString } from 'util/query-params';
|
||||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||||
|
import { SEARCH_OPTIONS } from 'constants/search';
|
||||||
import {
|
import {
|
||||||
parseURI,
|
parseURI,
|
||||||
|
selectClaimsByUri,
|
||||||
makeSelectClaimForUri,
|
makeSelectClaimForUri,
|
||||||
makeSelectClaimForClaimId,
|
makeSelectClaimForClaimId,
|
||||||
makeSelectClaimIsNsfw,
|
makeSelectClaimIsNsfw,
|
||||||
buildURI,
|
|
||||||
isClaimNsfw,
|
isClaimNsfw,
|
||||||
makeSelectPendingClaimForUri,
|
makeSelectPendingClaimForUri,
|
||||||
makeSelectIsUriResolving,
|
makeSelectIsUriResolving,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { createNormalizedSearchKey } from 'util/search';
|
import { createNormalizedSearchKey } from 'util/search';
|
||||||
|
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||||
|
import { selectHistory } from 'redux/selectors/content';
|
||||||
|
import { selectAllCostInfoByUri } from 'lbryinc';
|
||||||
|
import { SIMPLE_SITE } from 'config';
|
||||||
|
|
||||||
type State = { search: SearchState };
|
type State = { search: SearchState };
|
||||||
|
|
||||||
|
@ -38,14 +43,12 @@ export const selectHasReachedMaxResultsLength: (state: State) => { [boolean]: Ar
|
||||||
);
|
);
|
||||||
|
|
||||||
export const makeSelectSearchUrisForQuery = (query: string): ((state: State) => Array<string>) =>
|
export const makeSelectSearchUrisForQuery = (query: string): ((state: State) => Array<string>) =>
|
||||||
// replace statement below is kind of ugly, and repeated in doSearch action
|
|
||||||
createSelector(selectSearchResultByQuery, (byQuery) => {
|
createSelector(selectSearchResultByQuery, (byQuery) => {
|
||||||
if (query) {
|
if (!query) return;
|
||||||
|
// replace statement below is kind of ugly, and repeated in doSearch action
|
||||||
query = query.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
|
query = query.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
|
||||||
const normalizedQuery = createNormalizedSearchKey(query);
|
const normalizedQuery = createNormalizedSearchKey(query);
|
||||||
return byQuery[normalizedQuery] && byQuery[normalizedQuery]['uris'];
|
return byQuery[normalizedQuery] && byQuery[normalizedQuery]['uris'];
|
||||||
}
|
|
||||||
return byQuery[query] && byQuery[query]['uris'];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const makeSelectHasReachedMaxResultsLength = (query: string): ((state: State) => boolean) =>
|
export const makeSelectHasReachedMaxResultsLength = (query: string): ((state: State) => boolean) =>
|
||||||
|
@ -60,34 +63,91 @@ export const makeSelectHasReachedMaxResultsLength = (query: string): ((state: St
|
||||||
|
|
||||||
export const makeSelectRecommendedContentForUri = (uri: string) =>
|
export const makeSelectRecommendedContentForUri = (uri: string) =>
|
||||||
createSelector(
|
createSelector(
|
||||||
makeSelectClaimForUri(uri),
|
selectHistory,
|
||||||
|
selectClaimsByUri,
|
||||||
|
selectShowMatureContent,
|
||||||
|
selectMutedChannels,
|
||||||
|
selectAllCostInfoByUri,
|
||||||
selectSearchResultByQuery,
|
selectSearchResultByQuery,
|
||||||
makeSelectClaimIsNsfw(uri),
|
makeSelectClaimIsNsfw(uri),
|
||||||
(claim, searchUrisByQuery, isMature) => {
|
(history, claimsByUri, matureEnabled, blockedChannels, costInfoByUri, searchUrisByQuery, isMature) => {
|
||||||
|
const claim = claimsByUri[uri];
|
||||||
|
|
||||||
|
if (!claim) return;
|
||||||
|
|
||||||
let recommendedContent;
|
let recommendedContent;
|
||||||
if (claim) {
|
// always grab the claimId - this value won't change for filtering
|
||||||
// always grab full URL - this can change once search returns canonical
|
const currentClaimId = claim.claim_id;
|
||||||
const currentUri = buildURI({ streamClaimId: claim.claim_id, streamName: claim.name });
|
|
||||||
|
|
||||||
const { title } = claim.value;
|
const { title } = claim.value;
|
||||||
|
|
||||||
if (!title) {
|
if (!title) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: {
|
const options: {
|
||||||
related_to?: string,
|
size: number,
|
||||||
nsfw?: boolean,
|
nsfw?: boolean,
|
||||||
isBackgroundSearch?: boolean,
|
isBackgroundSearch?: boolean,
|
||||||
} = { related_to: claim.claim_id, isBackgroundSearch: true };
|
} = { size: 20, nsfw: matureEnabled, isBackgroundSearch: true };
|
||||||
|
|
||||||
|
if (SIMPLE_SITE) {
|
||||||
|
options[SEARCH_OPTIONS.CLAIM_TYPE] = SEARCH_OPTIONS.INCLUDE_FILES;
|
||||||
|
options[SEARCH_OPTIONS.MEDIA_VIDEO] = true;
|
||||||
|
options[SEARCH_OPTIONS.PRICE_FILTER_FREE] = true;
|
||||||
|
}
|
||||||
|
if (matureEnabled || (!matureEnabled && !isMature)) {
|
||||||
|
options[SEARCH_OPTIONS.RELATED_TO] = claim.claim_id;
|
||||||
|
}
|
||||||
|
|
||||||
options['nsfw'] = isMature;
|
|
||||||
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options);
|
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options);
|
||||||
const normalizedSearchQuery = createNormalizedSearchKey(searchQuery);
|
const normalizedSearchQuery = createNormalizedSearchKey(searchQuery);
|
||||||
|
|
||||||
let searchResult = searchUrisByQuery[normalizedSearchQuery];
|
let searchResult = searchUrisByQuery[normalizedSearchQuery];
|
||||||
|
|
||||||
if (searchResult) {
|
if (searchResult) {
|
||||||
recommendedContent = searchResult['uris'].filter((searchUri) => searchUri !== currentUri);
|
// Filter from recommended: The same claim and blocked channels
|
||||||
|
recommendedContent = searchResult['uris'].filter((searchUri) => {
|
||||||
|
const searchClaim = claimsByUri[searchUri];
|
||||||
|
|
||||||
|
if (!searchClaim) return;
|
||||||
|
|
||||||
|
const signingChannel = searchClaim && searchClaim.signing_channel;
|
||||||
|
const channelUri = signingChannel && signingChannel.canonical_url;
|
||||||
|
const blockedMatch = blockedChannels.some((blockedUri) => blockedUri.includes(channelUri));
|
||||||
|
|
||||||
|
let isEqualUri;
|
||||||
|
try {
|
||||||
|
const { claimId: searchId } = parseURI(searchUri);
|
||||||
|
isEqualUri = searchId === currentClaimId;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
return !isEqualUri && !blockedMatch;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Claim to play next: playable and free claims not played before in history
|
||||||
|
const nextUriToPlay = recommendedContent.filter((nextRecommendedUri) => {
|
||||||
nice nice
|
|||||||
|
const costInfo = costInfoByUri[nextRecommendedUri] && costInfoByUri[nextRecommendedUri].cost;
|
||||||
|
const recommendedClaim = claimsByUri[nextRecommendedUri];
|
||||||
|
const isVideo = recommendedClaim && recommendedClaim.value && recommendedClaim.value.stream_type === 'video';
|
||||||
|
const isAudio = recommendedClaim && recommendedClaim.value && recommendedClaim.value.stream_type === 'audio';
|
||||||
|
|
||||||
|
let historyMatch = false;
|
||||||
|
try {
|
||||||
|
const { claimId: nextRecommendedId } = parseURI(nextRecommendedUri);
|
||||||
|
|
||||||
|
historyMatch = history.some(
|
||||||
|
(historyItem) =>
|
||||||
|
(claimsByUri[historyItem.uri] && claimsByUri[historyItem.uri].claim_id) === nextRecommendedId
|
||||||
|
);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
return !historyMatch && costInfo === 0 && (isVideo || isAudio);
|
||||||
|
})[0];
|
||||||
|
|
||||||
|
const index = recommendedContent.indexOf(nextUriToPlay);
|
||||||
|
if (index > 0) {
|
||||||
|
const a = recommendedContent[0];
|
||||||
|
recommendedContent[0] = nextUriToPlay;
|
||||||
|
recommendedContent[index] = a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return recommendedContent;
|
return recommendedContent;
|
||||||
|
@ -206,6 +266,6 @@ export const makeSelectIsResolvingWinningUri = (query: string = '') => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const makeSelectUrlForClaimId = (claimId: string) =>
|
export const makeSelectUrlForClaimId = (claimId: string) =>
|
||||||
createSelector(
|
createSelector(makeSelectClaimForClaimId(claimId), (claim) =>
|
||||||
makeSelectClaimForClaimId(claimId), (claim) => claim ? claim.canonical_url || claim.permanent_url : null
|
claim ? claim.canonical_url || claim.permanent_url : null
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { SEARCH_OPTIONS } from 'constants/search';
|
import { SEARCH_OPTIONS } from 'constants/search';
|
||||||
import { SIMPLE_SITE } from 'config';
|
|
||||||
|
|
||||||
const DEFAULT_SEARCH_RESULT_FROM = 0;
|
const DEFAULT_SEARCH_RESULT_FROM = 0;
|
||||||
const DEFAULT_SEARCH_SIZE = 20;
|
const DEFAULT_SEARCH_SIZE = 20;
|
||||||
|
@ -33,7 +32,6 @@ export function updateQueryParam(uri: string, key: string, value: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSearchQueryString = (query: string, options: any = {}) => {
|
export const getSearchQueryString = (query: string, options: any = {}) => {
|
||||||
const FORCE_FREE_ONLY = SIMPLE_SITE;
|
|
||||||
const isSurroundedByQuotes = (str) => str[0] === '"' && str[str.length - 1] === '"';
|
const isSurroundedByQuotes = (str) => str[0] === '"' && str[str.length - 1] === '"';
|
||||||
const encodedQuery = encodeURIComponent(query);
|
const encodedQuery = encodeURIComponent(query);
|
||||||
const queryParams = [
|
const queryParams = [
|
||||||
|
@ -81,9 +79,11 @@ export const getSearchQueryString = (query: string, options: any = {}) => {
|
||||||
const additionalOptions = {};
|
const additionalOptions = {};
|
||||||
const { related_to } = options;
|
const { related_to } = options;
|
||||||
const { nsfw } = options;
|
const { nsfw } = options;
|
||||||
|
const { free_only } = options;
|
||||||
|
|
||||||
if (related_to) additionalOptions['related_to'] = related_to;
|
if (related_to) additionalOptions[SEARCH_OPTIONS.RELATED_TO] = related_to;
|
||||||
if (nsfw === false) additionalOptions['nsfw'] = false;
|
if (free_only) additionalOptions[SEARCH_OPTIONS.PRICE_FILTER_FREE] = true;
|
||||||
|
if (nsfw === false) additionalOptions[SEARCH_OPTIONS.INCLUDE_MATURE] = false;
|
||||||
|
|
||||||
if (additionalOptions) {
|
if (additionalOptions) {
|
||||||
Object.keys(additionalOptions).forEach((key) => {
|
Object.keys(additionalOptions).forEach((key) => {
|
||||||
|
@ -92,13 +92,5 @@ export const getSearchQueryString = (query: string, options: any = {}) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FORCE_FREE_ONLY) {
|
|
||||||
const index = queryParams.findIndex((q) => q.startsWith('free_only'));
|
|
||||||
if (index > -1) {
|
|
||||||
queryParams.splice(index, 1);
|
|
||||||
}
|
|
||||||
queryParams.push(`free_only=true`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return queryParams.join('&');
|
return queryParams.join('&');
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue
this is good - did you use it anywhere?