lbry-desktop/ui/redux/selectors/content.js
jessopb 049fb2878e
recsys v0.2 (#6977)
* recsys wip

better logging

fix floating player popout playing uri bug with recsys

lint

add empty entries to create

use beacon; fire on visibilitychange

cleanup, not record recs if not seen

ifweb recsys beacon

recsys handle embeds, cleanup

use history.listen to trigger events

fix recsys embed bug

bugfix

more default data

cleaner

cleaner

* remove tentative

* disable recsys debug logging
2021-09-02 18:39:40 -04:00

235 lines
8.1 KiB
JavaScript

// @flow
import { createSelector } from 'reselect';
import {
makeSelectClaimForUri,
selectClaimsByUri,
makeSelectClaimIsNsfw,
makeSelectClaimIsMine,
makeSelectMediaTypeForUri,
selectBalance,
parseURI,
makeSelectContentTypeForUri,
makeSelectFileNameForUri,
} from 'lbry-redux';
import { makeSelectRecommendedContentForUri } from 'redux/selectors/search';
import { selectMutedChannels } from 'redux/selectors/blocked';
import { selectAllCostInfoByUri, makeSelectCostInfoForUri } from 'lbryinc';
import { selectShowMatureContent } from 'redux/selectors/settings';
import * as RENDER_MODES from 'constants/file_render_modes';
import path from 'path';
import { FORCE_CONTENT_TYPE_PLAYER, FORCE_CONTENT_TYPE_COMIC } from 'constants/claim';
const RECENT_HISTORY_AMOUNT = 10;
const HISTORY_ITEMS_PER_PAGE = 50;
export const selectState = (state: any) => state.content || {};
export const selectPlayingUri = createSelector(selectState, (state) => state.playingUri);
export const selectPrimaryUri = createSelector(selectState, (state) => state.primaryUri);
export const selectListLoop = createSelector(selectState, (state) => state.loopList);
export const selectListShuffle = createSelector(selectState, (state) => state.shuffleList);
export const makeSelectIsPlaying = (uri: string) =>
createSelector(selectPrimaryUri, (primaryUri) => primaryUri === uri);
export const makeSelectIsPlayerFloating = (location: UrlLocation) =>
createSelector(selectPrimaryUri, selectPlayingUri, selectClaimsByUri, (primaryUri, playingUri, claimsByUri) => {
const isInlineSecondaryPlayer =
playingUri &&
playingUri.uri !== primaryUri &&
location.pathname === playingUri.pathname &&
(playingUri.source === 'comment' || playingUri.source === 'markdown');
if ((playingUri && playingUri.uri === primaryUri) || isInlineSecondaryPlayer) {
return false;
}
return true;
});
export const makeSelectContentPositionForUri = (uri: string) =>
createSelector(selectState, makeSelectClaimForUri(uri), (state, claim) => {
if (!claim) {
return null;
}
const outpoint = `${claim.txid}:${claim.nout}`;
const id = claim.claim_id;
return state.positions[id] ? state.positions[id][outpoint] : null;
});
export const selectHistory = createSelector(selectState, (state) => state.history || []);
export const selectHistoryPageCount = createSelector(selectHistory, (history) =>
Math.ceil(history.length / HISTORY_ITEMS_PER_PAGE)
);
export const makeSelectHistoryForPage = (page: number) =>
createSelector(selectHistory, selectClaimsByUri, (history, claimsByUri) => {
const left = page * HISTORY_ITEMS_PER_PAGE;
const historyItemsForPage = history.slice(left, left + HISTORY_ITEMS_PER_PAGE);
return historyItemsForPage;
});
export const makeSelectHistoryForUri = (uri: string) =>
createSelector(selectHistory, (history) => history.find((i) => i.uri === uri));
export const makeSelectHasVisitedUri = (uri: string) =>
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) => {
return history.slice(0, RECENT_HISTORY_AMOUNT);
});
export const makeSelectShouldObscurePreview = (uri: string) =>
createSelector(selectShowMatureContent, makeSelectClaimIsNsfw(uri), (showMatureContent, isClaimMature) => {
return isClaimMature && !showMatureContent;
});
// should probably be in lbry-redux, yarn link was fighting me
export const makeSelectFileExtensionForUri = (uri: string) =>
createSelector(makeSelectFileNameForUri(uri), (fileName) => {
return fileName && path.extname(fileName).substring(1);
});
export const makeSelectFileRenderModeForUri = (uri: string) =>
createSelector(
makeSelectContentTypeForUri(uri),
makeSelectMediaTypeForUri(uri),
makeSelectFileExtensionForUri(uri),
(contentType, mediaType, extension) => {
if (mediaType === 'video' || FORCE_CONTENT_TYPE_PLAYER.includes(contentType)) {
return RENDER_MODES.VIDEO;
}
if (mediaType === 'audio') {
return RENDER_MODES.AUDIO;
}
if (mediaType === 'image') {
return RENDER_MODES.IMAGE;
}
if (['md', 'markdown'].includes(extension) || ['text/md', 'text/markdown'].includes(contentType)) {
return RENDER_MODES.MARKDOWN;
}
if (contentType === 'application/pdf') {
return RENDER_MODES.PDF;
}
if (['text/htm', 'text/html'].includes(contentType)) {
return RENDER_MODES.HTML;
}
if (['text', 'document', 'script'].includes(mediaType)) {
return RENDER_MODES.DOCUMENT;
}
if (extension === 'docx') {
return RENDER_MODES.DOCX;
}
// when writing this my local copy of Lbry.getMediaType had '3D-file', but I was receiving model...'
if (['3D-file', 'model'].includes(mediaType)) {
return RENDER_MODES.CAD;
}
// Force content type for fallback support of older claims
if (mediaType === 'comic-book' || FORCE_CONTENT_TYPE_COMIC.includes(contentType)) {
return RENDER_MODES.COMIC;
}
if (
[
'application/zip',
'application/x-gzip',
'application/x-gtar',
'application/x-tgz',
'application/vnd.rar',
'application/x-7z-compressed',
].includes(contentType)
) {
return RENDER_MODES.DOWNLOAD;
}
if (mediaType === 'application') {
return RENDER_MODES.APPLICATION;
}
return RENDER_MODES.UNSUPPORTED;
}
);
export const makeSelectInsufficientCreditsForUri = (uri: string) =>
createSelector(
makeSelectClaimIsMine(uri),
makeSelectCostInfoForUri(uri),
selectBalance,
(isMine, costInfo, balance) => {
return !isMine && costInfo && costInfo.cost > 0 && costInfo.cost > balance;
}
);