import { SUGGESTED_FEATURED, SUGGESTED_TOP_SUBSCRIBED } from 'constants/subscriptions'; import { createSelector } from 'reselect'; import { selectAllClaimsByChannel, selectClaimsById, selectAllFetchingChannelClaims, makeSelectChannelForClaimUri, selectClaimsByUri, parseURI, makeSelectClaimForUri, } from 'lbry-redux'; import { swapKeyAndValue } from 'util/swap-json'; // Returns the entire subscriptions state const selectState = state => state.subscriptions || {}; // Returns the list of channel uris a user is subscribed to export const selectSubscriptions = createSelector( selectState, state => state.subscriptions && state.subscriptions.sort((a, b) => a.channelName.localeCompare(b.channelName)) ); // Fetching list of users subscriptions export const selectIsFetchingSubscriptions = createSelector( selectState, state => state.loading ); // The current view mode on the subscriptions page export const selectViewMode = createSelector( selectState, state => state.viewMode ); // Suggested subscriptions from internal apis export const selectSuggested = createSelector( selectState, state => state.suggested ); export const selectIsFetchingSuggested = createSelector( selectState, state => state.loadingSuggested ); export const selectSuggestedChannels = createSelector( selectSubscriptions, selectSuggested, (userSubscriptions, suggested) => { if (!suggested) { return null; } // Swap the key/value because we will use the uri for everything, this just makes it easier // suggested is returned from the api with the form: // { // featured: { "Channel label": uri, ... }, // top_subscribed: { "@channel": uri, ... } // top_bid: { "@channel": uri, ... } // } // To properly compare the suggested subscriptions from our current subscribed channels // We only care about the uri, not the label // We also only care about top_subscribed and featured // top_bid could just be porn or a channel with no content const topSubscribedSuggestions = swapKeyAndValue(suggested[SUGGESTED_TOP_SUBSCRIBED]); const featuredSuggestions = swapKeyAndValue(suggested[SUGGESTED_FEATURED]); // Make sure there are no duplicates // If a uri isn't already in the suggested object, add it const suggestedChannels = { ...topSubscribedSuggestions }; Object.keys(featuredSuggestions).forEach(uri => { if (!suggestedChannels[uri]) { const channelLabel = featuredSuggestions[uri]; suggestedChannels[uri] = channelLabel; } }); userSubscriptions.forEach(({ uri }) => { // Note to passer bys: // Maybe we should just remove the `lbry://` prefix from subscription uris // Most places don't store them like that const subscribedUri = uri.slice('lbry://'.length); if (suggestedChannels[subscribedUri]) { delete suggestedChannels[subscribedUri]; } }); return Object.keys(suggestedChannels).map(uri => ({ uri, label: suggestedChannels[uri], })); } ); export const selectFirstRunCompleted = createSelector( selectState, state => state.firstRunCompleted ); export const selectshowSuggestedSubs = createSelector( selectState, state => state.showSuggestedSubs ); // Fetching any claims that are a part of a users subscriptions export const selectSubscriptionsBeingFetched = createSelector( selectSubscriptions, selectAllFetchingChannelClaims, (subscriptions, fetchingChannelClaims) => { const fetchingSubscriptionMap = {}; subscriptions.forEach(sub => { const isFetching = fetchingChannelClaims && fetchingChannelClaims[sub.uri]; if (isFetching) { fetchingSubscriptionMap[sub.uri] = true; } }); return fetchingSubscriptionMap; } ); export const selectUnreadByChannel = createSelector( selectState, state => state.unread ); // Returns the current total of unread subscriptions export const selectUnreadAmount = createSelector( selectUnreadByChannel, unreadByChannel => { const unreadChannels = Object.keys(unreadByChannel); let badges = 0; if (!unreadChannels.length) { return badges; } unreadChannels.forEach(channel => { badges += unreadByChannel[channel].uris.length; }); return badges; } ); // Returns the uris with channels as an array with the channel with the newest content first // If you just want the `unread` state, use selectUnread export const selectUnreadSubscriptions = createSelector( selectUnreadAmount, selectUnreadByChannel, selectClaimsByUri, (unreadAmount, unreadByChannel, claimsByUri) => { // determine which channel has the newest content const unreadList = []; if (!unreadAmount) { return unreadList; } const channelUriList = Object.keys(unreadByChannel); // There is only one channel with unread notifications if (unreadAmount === 1) { channelUriList.forEach(channel => { const unreadChannel = { channel, uris: unreadByChannel[channel].uris, }; unreadList.push(unreadChannel); }); return unreadList; } channelUriList .sort((channel1, channel2) => { const latestUriFromChannel1 = unreadByChannel[channel1].uris[0]; const latestClaimFromChannel1 = claimsByUri[latestUriFromChannel1] || {}; const latestUriFromChannel2 = unreadByChannel[channel2].uris[0]; const latestClaimFromChannel2 = claimsByUri[latestUriFromChannel2] || {}; const latestHeightFromChannel1 = latestClaimFromChannel1.height || 0; const latestHeightFromChannel2 = latestClaimFromChannel2.height || 0; if (latestHeightFromChannel1 !== latestHeightFromChannel2) { return latestHeightFromChannel2 - latestHeightFromChannel1; } return 0; }) .forEach(channel => { const unreadSubscription = unreadByChannel[channel]; const unreadChannel = { channel, uris: unreadSubscription.uris, }; unreadList.push(unreadChannel); }); return unreadList; } ); // Returns all unread subscriptions for a uri passed in export const makeSelectUnreadByChannel = uri => createSelector( selectUnreadByChannel, unread => unread[uri] ); // Returns the first page of claims for every channel a user is subscribed to export const selectSubscriptionClaims = createSelector( selectAllClaimsByChannel, selectClaimsById, selectSubscriptions, selectUnreadByChannel, (channelIds, allClaims, savedSubscriptions, unreadByChannel) => { // no claims loaded yet if (!Object.keys(channelIds).length) { return []; } let fetchedSubscriptions = []; savedSubscriptions.forEach(subscription => { let channelClaims = []; // if subscribed channel has content if (channelIds[subscription.uri] && channelIds[subscription.uri]['1']) { // This will need to be more robust, we will want to be able to load more than the first page // Strip out any ids that will be shown as notifications const pageOneChannelIds = channelIds[subscription.uri]['1']; // we have the channel ids and the corresponding claims // loop over the list of ids and grab the claim pageOneChannelIds.forEach(id => { const grabbedClaim = allClaims[id]; if ( unreadByChannel[subscription.uri] && unreadByChannel[subscription.uri].uris.some(uri => uri.includes(id)) ) { grabbedClaim.isNew = true; } channelClaims = channelClaims.concat([grabbedClaim]); }); } fetchedSubscriptions = fetchedSubscriptions.concat(channelClaims); }); return fetchedSubscriptions; } ); // Returns true if a user is subscribed to the channel associated with the uri passed in // Accepts content or channel uris export const makeSelectChannelInSubscriptions = uri => createSelector( selectSubscriptions, subscriptions => subscriptions.some(sub => sub.uri === uri) ); export const makeSelectIsSubscribed = uri => createSelector( selectSubscriptions, makeSelectChannelForClaimUri(uri, true), makeSelectClaimForUri(uri), (subscriptions, channelUri, claim) => { if (channelUri) { return subscriptions.some(sub => sub.uri === channelUri); } // If we couldn't get a channel uri from the claim uri, the uri passed in might be a channel already let isChannel; try { ({ isChannel } = parseURI(uri)); } catch (e) {} if (isChannel && claim) { const uri = claim.permanent_url; return subscriptions.some(sub => sub.uri === uri); } return false; } ); export const makeSelectIsNew = uri => createSelector( makeSelectIsSubscribed(uri), makeSelectChannelForClaimUri(uri), selectUnreadByChannel, (isSubscribed, channel, unreadByChannel) => { if (!isSubscribed) { return false; } const unreadForChannel = unreadByChannel[`lbry://${channel}`]; if (unreadForChannel) { return unreadForChannel.uris.includes(uri); } return false; // If they are subscribed, check to see if this uri is in the list of unreads } );