// @flow import { createSelector } from 'reselect'; import { selectClaimsByUri, selectClaimIsNsfwForUri, selectClaimIsMineForUri, makeSelectContentTypeForUri, selectClaimForUri, } from 'redux/selectors/claims'; import { makeSelectMediaTypeForUri, makeSelectFileNameForUri } from 'redux/selectors/file_info'; import { selectBalance } from 'redux/selectors/wallet'; import { selectCostInfoForUri } 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; type State = { claims: any, content: ContentState, user: UserState }; export const selectState = (state: State) => state.content || {}; export const selectPlayingUri = (state: State) => selectState(state).playingUri; export const selectPrimaryUri = (state: State) => selectState(state).primaryUri; export const selectListLoop = (state: State) => selectState(state).loopList; export const selectListShuffle = (state: State) => selectState(state).shuffleList; export const makeSelectIsPlaying = (uri: string) => createSelector(selectPrimaryUri, (primaryUri) => primaryUri === uri); export const selectIsUriCurrentlyPlaying = createSelector( (state, uri) => uri, selectPlayingUri, (uri, playingUri) => Boolean(playingUri.uri === uri) ); export const makeSelectIsPlayerFloating = (location: UrlLocation) => createSelector(selectPrimaryUri, selectPlayingUri, (primaryUri, playingUri) => { if (!playingUri.uri) return false; const { pathname, search } = location; const hasSecondarySource = Boolean(playingUri.source); const isComment = playingUri.source === 'comment'; const isInlineSecondaryPlayer = hasSecondarySource && playingUri.uri !== primaryUri && pathname === playingUri.pathname; if (isComment && isInlineSecondaryPlayer && search && search !== '?view=discussion') return true; if ( isInlineSecondaryPlayer || (hasSecondarySource && !isComment ? playingUri.primaryUri === primaryUri : playingUri.uri === primaryUri) ) { return false; } return true; }); export const selectContentPositionForUri = (state: State, uri: string) => { const claim = selectClaimForUri(state, uri); if (claim) { const outpoint = `${claim.txid}:${claim.nout}`; const id = claim.claim_id; const positions = selectState(state).positions; return positions[id] ? positions[id][outpoint] : null; } return null; }; export const selectHistory = (state: State) => selectState(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 selectRecentHistory = createSelector(selectHistory, (history) => { return history.slice(0, RECENT_HISTORY_AMOUNT); }); export const selectWatchHistoryUris = createSelector(selectHistory, (history) => { const uris = []; for (let entry of history) { if (entry.uri.indexOf('@') !== -1) { uris.push(entry.uri); } } return uris; }); export const selectShouldObscurePreviewForUri = (state: State, uri: string) => { const showMatureContent = selectShowMatureContent(state); const isClaimMature = selectClaimIsNsfwForUri(state, uri); 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) || mediaType === 'livestream') { 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 selectInsufficientCreditsForUri = (state: State, uri: string) => { const isMine = selectClaimIsMineForUri(state, uri); const costInfo = selectCostInfoForUri(state, uri); const balance = selectBalance(state); return !isMine && costInfo && costInfo.cost > 0 && costInfo.cost > balance; };