2019-03-05 05:46:57 +01:00
|
|
|
// @flow
|
|
|
|
import { createSelector } from 'reselect';
|
2019-08-02 08:28:14 +02:00
|
|
|
import {
|
|
|
|
makeSelectClaimForUri,
|
|
|
|
selectClaimsByUri,
|
|
|
|
makeSelectClaimsInChannelForCurrentPageState,
|
|
|
|
makeSelectClaimIsNsfw,
|
2020-04-01 20:43:50 +02:00
|
|
|
makeSelectClaimIsMine,
|
2019-09-09 19:31:00 +02:00
|
|
|
makeSelectRecommendedContentForUri,
|
2019-12-10 20:45:41 +01:00
|
|
|
makeSelectMediaTypeForUri,
|
2020-04-01 20:43:50 +02:00
|
|
|
selectBalance,
|
2020-01-28 19:17:07 +01:00
|
|
|
parseURI,
|
2020-04-29 22:50:06 +02:00
|
|
|
buildURI,
|
2020-04-01 20:43:50 +02:00
|
|
|
makeSelectContentTypeForUri,
|
|
|
|
makeSelectFileNameForUri,
|
2019-08-02 08:28:14 +02:00
|
|
|
} from 'lbry-redux';
|
2020-06-23 21:27:14 +02:00
|
|
|
import { selectBlockedChannels } from 'redux/selectors/blocked';
|
2020-04-01 20:43:50 +02:00
|
|
|
import { selectAllCostInfoByUri, makeSelectCostInfoForUri } from 'lbryinc';
|
2019-08-02 08:28:14 +02:00
|
|
|
import { selectShowMatureContent } from 'redux/selectors/settings';
|
2020-04-01 20:43:50 +02:00
|
|
|
import * as RENDER_MODES from 'constants/file_render_modes';
|
|
|
|
import path from 'path';
|
2020-05-09 05:24:02 +02:00
|
|
|
import { FORCE_CONTENT_TYPE_PLAYER, FORCE_CONTENT_TYPE_COMIC } from 'constants/claim';
|
2019-04-01 01:04:01 +02:00
|
|
|
|
|
|
|
const RECENT_HISTORY_AMOUNT = 10;
|
|
|
|
const HISTORY_ITEMS_PER_PAGE = 50;
|
2019-03-05 05:46:57 +01:00
|
|
|
|
|
|
|
export const selectState = (state: any) => state.content || {};
|
|
|
|
|
2020-04-01 20:43:50 +02:00
|
|
|
export const selectPlayingUri = createSelector(selectState, state => state.playingUri);
|
2020-04-29 22:50:06 +02:00
|
|
|
export const selectFloatingUri = createSelector(selectState, state => state.floatingUri);
|
2019-03-05 05:46:57 +01:00
|
|
|
|
2020-04-01 20:43:50 +02:00
|
|
|
export const makeSelectIsPlaying = (uri: string) => createSelector(selectPlayingUri, playingUri => playingUri === uri);
|
2019-08-02 08:28:14 +02:00
|
|
|
|
2020-04-14 01:48:11 +02:00
|
|
|
// below is dumb, some context: https://stackoverflow.com/questions/39622864/access-react-router-state-in-selector
|
2020-04-29 22:50:06 +02:00
|
|
|
export const makeSelectIsPlayerFloating = (location: UrlLocation) =>
|
2020-04-30 20:25:51 +02:00
|
|
|
createSelector(selectFloatingUri, selectPlayingUri, selectClaimsByUri, (floatingUri, playingUri, claimsByUri) => {
|
|
|
|
if (playingUri && floatingUri && playingUri !== floatingUri) {
|
|
|
|
return true;
|
2020-04-29 22:50:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// If there is no floatingPlayer explicitly set, see if the playingUri can float
|
2020-04-14 01:48:11 +02:00
|
|
|
try {
|
2020-04-30 20:25:51 +02:00
|
|
|
const { pathname } = location;
|
2020-05-11 17:54:39 +02:00
|
|
|
const { streamName, streamClaimId, channelName, channelClaimId } = parseURI(pathname.slice(1).replace(/:/g, '#'));
|
|
|
|
const pageUrl = buildURI({ streamName, streamClaimId, channelName, channelClaimId });
|
2020-04-30 20:25:51 +02:00
|
|
|
const claimFromUrl = claimsByUri[pageUrl];
|
|
|
|
const playingClaim = claimsByUri[playingUri];
|
|
|
|
return (claimFromUrl && claimFromUrl.claim_id) !== (playingClaim && playingClaim.claim_id);
|
2020-04-14 01:48:11 +02:00
|
|
|
} catch (e) {}
|
2020-04-29 22:50:06 +02:00
|
|
|
|
2020-04-14 01:48:11 +02:00
|
|
|
return !!playingUri;
|
|
|
|
});
|
|
|
|
|
2019-03-05 05:46:57 +01:00
|
|
|
export const makeSelectContentPositionForUri = (uri: string) =>
|
2020-04-01 20:43:50 +02:00
|
|
|
createSelector(selectState, makeSelectClaimForUri(uri), (state, claim) => {
|
|
|
|
if (!claim) {
|
|
|
|
return null;
|
2019-03-05 05:46:57 +01:00
|
|
|
}
|
2020-04-01 20:43:50 +02:00
|
|
|
const outpoint = `${claim.txid}:${claim.nout}`;
|
|
|
|
const id = claim.claim_id;
|
|
|
|
return state.positions[id] ? state.positions[id][outpoint] : null;
|
|
|
|
});
|
2019-03-05 05:46:57 +01:00
|
|
|
|
2020-04-01 20:43:50 +02:00
|
|
|
export const selectHistory = createSelector(selectState, state => state.history || []);
|
2019-04-01 01:04:01 +02:00
|
|
|
|
2020-04-01 20:43:50 +02:00
|
|
|
export const selectHistoryPageCount = createSelector(selectHistory, history =>
|
|
|
|
Math.ceil(history.length / HISTORY_ITEMS_PER_PAGE)
|
2019-03-05 05:46:57 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
export const makeSelectHistoryForPage = (page: number) =>
|
2020-04-01 20:43:50 +02:00
|
|
|
createSelector(selectHistory, selectClaimsByUri, (history, claimsByUri) => {
|
|
|
|
const left = page * HISTORY_ITEMS_PER_PAGE;
|
|
|
|
const historyItemsForPage = history.slice(left, left + HISTORY_ITEMS_PER_PAGE);
|
|
|
|
return historyItemsForPage;
|
|
|
|
});
|
2019-03-05 05:46:57 +01:00
|
|
|
|
|
|
|
export const makeSelectHistoryForUri = (uri: string) =>
|
2020-04-01 20:43:50 +02:00
|
|
|
createSelector(selectHistory, history => history.find(i => i.uri === uri));
|
2019-03-05 05:46:57 +01:00
|
|
|
|
2019-07-11 20:06:25 +02:00
|
|
|
export const makeSelectHasVisitedUri = (uri: string) =>
|
2020-04-01 20:43:50 +02:00
|
|
|
createSelector(makeSelectHistoryForUri(uri), history => Boolean(history));
|
2019-07-11 20:06:25 +02:00
|
|
|
|
2019-09-09 19:31:00 +02:00
|
|
|
export const makeSelectNextUnplayedRecommended = (uri: string) =>
|
|
|
|
createSelector(
|
|
|
|
makeSelectRecommendedContentForUri(uri),
|
|
|
|
selectHistory,
|
2020-01-28 19:17:07 +01:00
|
|
|
selectClaimsByUri,
|
|
|
|
selectAllCostInfoByUri,
|
|
|
|
selectBlockedChannels,
|
|
|
|
(
|
|
|
|
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];
|
2020-02-05 16:07:48 +01:00
|
|
|
const claim = claimsByUri[recommendedUri];
|
|
|
|
|
|
|
|
if (!claim) {
|
|
|
|
continue;
|
|
|
|
}
|
2020-01-28 19:17:07 +01:00
|
|
|
|
|
|
|
const { isChannel } = parseURI(recommendedUri);
|
|
|
|
if (isChannel) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const costInfo = costInfoByUri[recommendedUri];
|
|
|
|
if (!costInfo || costInfo.cost !== 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-02-05 16:07:48 +01:00
|
|
|
// We already check if it's a channel above
|
|
|
|
// $FlowFixMe
|
|
|
|
const isVideo = claim.value && claim.value.stream_type === 'video';
|
2020-02-05 21:46:44 +01:00
|
|
|
// $FlowFixMe
|
|
|
|
const isAudio = claim.value && claim.value.stream_type === 'audio';
|
2020-02-07 07:22:48 +01:00
|
|
|
if (!isVideo && !isAudio) {
|
2020-02-05 16:07:48 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-01-28 19:17:07 +01:00
|
|
|
const channel = claim && claim.signing_channel;
|
2020-01-29 17:16:03 +01:00
|
|
|
if (channel && blockedChannels.some(blockedUri => blockedUri === channel.permanent_url)) {
|
2020-01-28 19:17:07 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-01-29 17:16:03 +01:00
|
|
|
if (!history.some(item => item.uri === recommendedForUri[i])) {
|
2020-01-28 19:17:07 +01:00
|
|
|
return recommendedForUri[i];
|
2019-09-09 19:31:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2020-04-01 20:43:50 +02:00
|
|
|
export const selectRecentHistory = createSelector(selectHistory, history => {
|
|
|
|
return history.slice(0, RECENT_HISTORY_AMOUNT);
|
|
|
|
});
|
2019-04-01 01:04:01 +02:00
|
|
|
|
2019-03-05 05:46:57 +01:00
|
|
|
export const makeSelectCategoryListUris = (uris: ?Array<string>, channel: string) =>
|
2020-04-01 20:43:50 +02:00
|
|
|
createSelector(makeSelectClaimsInChannelForCurrentPageState(channel), channelClaims => {
|
|
|
|
if (uris) return uris;
|
2019-03-05 05:46:57 +01:00
|
|
|
|
2020-04-01 20:43:50 +02:00
|
|
|
if (channelClaims) {
|
|
|
|
const CATEGORY_LIST_SIZE = 10;
|
|
|
|
return channelClaims.slice(0, CATEGORY_LIST_SIZE).map(({ name, claim_id: claimId }) => `${name}#${claimId}`);
|
2019-03-05 05:46:57 +01:00
|
|
|
}
|
2019-08-02 08:28:14 +02:00
|
|
|
|
2020-04-01 20:43:50 +02:00
|
|
|
return null;
|
|
|
|
});
|
2019-12-10 20:45:41 +01:00
|
|
|
|
2020-04-01 20:43:50 +02:00
|
|
|
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) =>
|
2019-12-10 20:45:41 +01:00
|
|
|
createSelector(
|
2020-04-01 20:43:50 +02:00
|
|
|
makeSelectContentTypeForUri(uri),
|
2019-12-10 20:45:41 +01:00
|
|
|
makeSelectMediaTypeForUri(uri),
|
2020-04-01 20:43:50 +02:00
|
|
|
makeSelectFileExtensionForUri(uri),
|
|
|
|
(contentType, mediaType, extension) => {
|
|
|
|
if (mediaType === 'video' || FORCE_CONTENT_TYPE_PLAYER.includes(contentType)) {
|
|
|
|
return RENDER_MODES.VIDEO;
|
|
|
|
}
|
2020-04-03 16:18:07 +02:00
|
|
|
if (mediaType === 'audio') {
|
|
|
|
return RENDER_MODES.AUDIO;
|
|
|
|
}
|
2020-04-01 20:43:50 +02:00
|
|
|
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;
|
|
|
|
}
|
2020-05-09 05:24:02 +02:00
|
|
|
// Force content type for fallback support of older claims
|
|
|
|
if (mediaType === 'comic-book' || FORCE_CONTENT_TYPE_COMIC.includes(contentType)) {
|
2020-04-01 20:43:50 +02:00
|
|
|
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;
|
2019-12-10 20:45:41 +01:00
|
|
|
}
|
|
|
|
);
|
2020-01-06 21:57:49 +01:00
|
|
|
|
2020-04-01 20:43:50 +02:00
|
|
|
export const makeSelectInsufficientCreditsForUri = (uri: string) =>
|
2020-01-06 21:57:49 +01:00
|
|
|
createSelector(
|
2020-04-01 20:43:50 +02:00
|
|
|
makeSelectClaimIsMine(uri),
|
|
|
|
makeSelectCostInfoForUri(uri),
|
|
|
|
selectBalance,
|
|
|
|
(isMine, costInfo, balance) => {
|
|
|
|
return !isMine && costInfo && costInfo.cost > 0 && costInfo.cost > balance;
|
2020-01-06 21:57:49 +01:00
|
|
|
}
|
|
|
|
);
|