2021-10-17 10:36:14 +02:00
|
|
|
// @flow
|
|
|
|
import { normalizeURI, parseURI, isURIValid } from 'util/lbryURI';
|
|
|
|
import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
|
|
|
|
import { createSelector } from 'reselect';
|
2021-10-08 03:57:48 +02:00
|
|
|
import { createCachedSelector } from 're-reselect';
|
2021-10-17 10:36:14 +02:00
|
|
|
import { isClaimNsfw, filterClaims } from 'util/claim';
|
|
|
|
import * as CLAIM from 'constants/claim';
|
|
|
|
|
2021-10-08 03:57:48 +02:00
|
|
|
type State = { claims: any };
|
2021-10-17 10:36:14 +02:00
|
|
|
|
2021-10-23 04:41:43 +02:00
|
|
|
const selectState = (state: State) => state.claims || {};
|
2021-10-17 10:36:14 +02:00
|
|
|
|
2021-10-08 03:57:48 +02:00
|
|
|
export const selectById = (state: State) => selectState(state).byId || {};
|
|
|
|
export const selectPendingClaimsById = (state: State) => selectState(state).pendingById || {};
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
export const selectClaimsById = createSelector(selectById, selectPendingClaimsById, (byId, pendingById) => {
|
|
|
|
return Object.assign(byId, pendingById); // do I need merged to keep metadata?
|
|
|
|
});
|
|
|
|
|
2021-10-08 03:57:48 +02:00
|
|
|
export const selectClaimIdsByUri = (state: State) => selectState(state).claimsByUri || {};
|
2021-10-23 04:41:43 +02:00
|
|
|
export const selectCurrentChannelPage = (state: State) => selectState(state).currentChannelPage || 1;
|
|
|
|
export const selectCreatingChannel = (state: State) => selectState(state).creatingChannel;
|
|
|
|
export const selectCreateChannelError = (state: State) => selectState(state).createChannelError;
|
|
|
|
export const selectRepostLoading = (state: State) => selectState(state).repostLoading;
|
|
|
|
export const selectRepostError = (state: State) => selectState(state).repostError;
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
export const selectClaimsByUri = createSelector(selectClaimIdsByUri, selectClaimsById, (byUri, byId) => {
|
|
|
|
const claims = {};
|
|
|
|
|
|
|
|
Object.keys(byUri).forEach((uri) => {
|
|
|
|
const claimId = byUri[uri];
|
|
|
|
|
|
|
|
// NOTE returning a null claim allows us to differentiate between an
|
|
|
|
// undefined (never fetched claim) and one which just doesn't exist. Not
|
|
|
|
// the cleanest solution but couldn't think of anything better right now
|
|
|
|
if (claimId === null) {
|
|
|
|
claims[uri] = null;
|
|
|
|
} else {
|
|
|
|
claims[uri] = byId[claimId];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return claims;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const selectAllClaimsByChannel = createSelector(selectState, (state) => state.paginatedClaimsByChannel || {});
|
|
|
|
|
|
|
|
export const selectPendingIds = createSelector(selectState, (state) => Object.keys(state.pendingById) || []);
|
|
|
|
|
|
|
|
export const selectPendingClaims = createSelector(selectPendingClaimsById, (pendingById) => Object.values(pendingById));
|
|
|
|
|
|
|
|
export const makeSelectClaimIsPending = (uri: string) =>
|
|
|
|
createSelector(selectClaimIdsByUri, selectPendingClaimsById, (idsByUri, pendingById) => {
|
|
|
|
const claimId = idsByUri[normalizeURI(uri)];
|
|
|
|
|
|
|
|
if (claimId) {
|
|
|
|
return Boolean(pendingById[claimId]);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectClaimIdIsPending = (claimId: string) =>
|
|
|
|
createSelector(selectPendingClaimsById, (pendingById) => {
|
|
|
|
return Boolean(pendingById[claimId]);
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectClaimIdForUri = (uri: string) =>
|
|
|
|
createSelector(selectClaimIdsByUri, (claimIds) => claimIds[uri]);
|
|
|
|
|
2021-10-23 04:41:43 +02:00
|
|
|
export const selectReflectingById = (state: State) => selectState(state).reflectingById;
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
export const makeSelectClaimForClaimId = (claimId: string) => createSelector(selectClaimsById, (byId) => byId[claimId]);
|
|
|
|
|
2021-10-08 03:57:48 +02:00
|
|
|
export const selectClaimForUri = createCachedSelector(
|
|
|
|
selectClaimIdsByUri,
|
|
|
|
selectClaimsById,
|
|
|
|
(state, uri) => uri,
|
|
|
|
(state, uri, returnRepost = true) => returnRepost,
|
|
|
|
(byUri, byId, uri, returnRepost) => {
|
2021-11-02 17:37:53 +01:00
|
|
|
const validUri = isURIValid(uri, false);
|
2021-10-08 03:57:48 +02:00
|
|
|
|
|
|
|
if (validUri && byUri) {
|
2021-11-02 17:37:53 +01:00
|
|
|
const normalizedUri = normalizeURI(uri);
|
|
|
|
const claimId = uri && byUri[normalizedUri];
|
2021-10-08 03:57:48 +02:00
|
|
|
const claim = byId[claimId];
|
|
|
|
|
|
|
|
// Make sure to return the claim as is so apps can check if it's been resolved before (null) or still needs to be resolved (undefined)
|
|
|
|
if (claimId === null) {
|
|
|
|
return null;
|
|
|
|
} else if (claimId === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const repostedClaim = claim && claim.reposted_claim;
|
|
|
|
if (repostedClaim && returnRepost) {
|
|
|
|
const channelUrl =
|
|
|
|
claim.signing_channel && (claim.signing_channel.canonical_url || claim.signing_channel.permanent_url);
|
|
|
|
|
|
|
|
return {
|
|
|
|
...repostedClaim,
|
2021-11-02 17:37:53 +01:00
|
|
|
repost_url: normalizedUri,
|
2021-10-08 03:57:48 +02:00
|
|
|
repost_channel_url: channelUrl,
|
|
|
|
repost_bid_amount: claim && claim.meta && claim.meta.effective_amount,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return claim;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-10-25 06:58:34 +02:00
|
|
|
)((state, uri, returnRepost = true) => `${String(uri)}:${returnRepost ? '1' : '0'}`);
|
2021-10-08 03:57:48 +02:00
|
|
|
|
2021-11-02 17:37:53 +01:00
|
|
|
// Note: this is deprecated. Use "selectClaimForUri(state, uri)" instead.
|
2021-10-17 10:36:14 +02:00
|
|
|
export const makeSelectClaimForUri = (uri: string, returnRepost: boolean = true) =>
|
|
|
|
createSelector(selectClaimIdsByUri, selectClaimsById, (byUri, byId) => {
|
2021-11-02 17:37:53 +01:00
|
|
|
const validUri = isURIValid(uri, false);
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
if (validUri && byUri) {
|
2021-11-02 17:37:53 +01:00
|
|
|
const normalizedUri = normalizeURI(uri);
|
|
|
|
const claimId = uri && byUri[normalizedUri];
|
2021-10-17 10:36:14 +02:00
|
|
|
const claim = byId[claimId];
|
|
|
|
|
|
|
|
// Make sure to return the claim as is so apps can check if it's been resolved before (null) or still needs to be resolved (undefined)
|
|
|
|
if (claimId === null) {
|
|
|
|
return null;
|
|
|
|
} else if (claimId === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const repostedClaim = claim && claim.reposted_claim;
|
|
|
|
if (repostedClaim && returnRepost) {
|
|
|
|
const channelUrl =
|
|
|
|
claim.signing_channel && (claim.signing_channel.canonical_url || claim.signing_channel.permanent_url);
|
|
|
|
|
|
|
|
return {
|
|
|
|
...repostedClaim,
|
2021-11-02 17:37:53 +01:00
|
|
|
repost_url: normalizedUri,
|
2021-10-17 10:36:14 +02:00
|
|
|
repost_channel_url: channelUrl,
|
|
|
|
repost_bid_amount: claim && claim.meta && claim.meta.effective_amount,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return claim;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2021-11-04 03:10:40 +01:00
|
|
|
// Returns your claim IDs without handling pending and abandoned claims.
|
|
|
|
export const selectMyClaimIdsRaw = (state: State) => selectState(state).myClaims;
|
|
|
|
|
2021-10-17 10:36:14 +02:00
|
|
|
export const selectMyClaimsRaw = createSelector(selectState, selectClaimsById, (state, byId) => {
|
|
|
|
const ids = state.myClaims;
|
|
|
|
if (!ids) {
|
|
|
|
return ids;
|
|
|
|
}
|
|
|
|
|
|
|
|
const claims = [];
|
|
|
|
ids.forEach((id) => {
|
|
|
|
if (byId[id]) {
|
|
|
|
// I'm not sure why this check is necessary, but it ought to be a quick fix for https://github.com/lbryio/lbry-desktop/issues/544
|
|
|
|
claims.push(byId[id]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return claims;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const selectAbandoningIds = createSelector(selectState, (state) => Object.keys(state.abandoningById || {}));
|
|
|
|
|
|
|
|
export const makeSelectAbandoningClaimById = (claimId: string) =>
|
|
|
|
createSelector(selectAbandoningIds, (ids) => ids.includes(claimId));
|
|
|
|
|
|
|
|
export const makeSelectIsAbandoningClaimForUri = (uri: string) =>
|
|
|
|
createSelector(selectClaimIdsByUri, selectAbandoningIds, (claimIdsByUri, abandoningById) => {
|
|
|
|
const claimId = claimIdsByUri[normalizeURI(uri)];
|
|
|
|
return abandoningById.indexOf(claimId) >= 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const selectMyActiveClaims = createSelector(
|
|
|
|
selectMyClaimsRaw,
|
|
|
|
selectAbandoningIds,
|
|
|
|
(claims, abandoningIds) =>
|
|
|
|
new Set(
|
|
|
|
claims &&
|
|
|
|
claims.map((claim) => claim.claim_id).filter((claimId) => Object.keys(abandoningIds).indexOf(claimId) === -1)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
export const makeSelectClaimIsMine = (rawUri: string) => {
|
|
|
|
let uri;
|
|
|
|
try {
|
|
|
|
uri = normalizeURI(rawUri);
|
|
|
|
} catch (e) {}
|
|
|
|
|
|
|
|
return createSelector(selectClaimsByUri, selectMyActiveClaims, (claims, myClaims) => {
|
2021-11-02 17:37:53 +01:00
|
|
|
if (!isURIValid(uri, false)) {
|
2021-10-17 10:36:14 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
claims &&
|
|
|
|
claims[uri] &&
|
|
|
|
(claims[uri].is_my_output || (claims[uri].claim_id && myClaims.has(claims[uri].claim_id)))
|
|
|
|
);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2021-10-23 04:41:43 +02:00
|
|
|
export const selectMyPurchases = (state: State) => selectState(state).myPurchases;
|
|
|
|
export const selectPurchaseUriSuccess = (state: State) => selectState(state).purchaseUriSuccess;
|
|
|
|
export const selectMyPurchasesCount = (state: State) => selectState(state).myPurchasesPageTotalResults;
|
|
|
|
export const selectIsFetchingMyPurchases = (state: State) => selectState(state).fetchingMyPurchases;
|
|
|
|
export const selectFetchingMyPurchasesError = (state: State) => selectState(state).fetchingMyPurchasesError;
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
export const makeSelectMyPurchasesForPage = (query: ?string, page: number = 1) =>
|
|
|
|
createSelector(
|
|
|
|
selectMyPurchases,
|
|
|
|
selectClaimsByUri,
|
|
|
|
(myPurchases: Array<string>, claimsByUri: { [string]: Claim }) => {
|
|
|
|
if (!myPurchases) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!query) {
|
|
|
|
// ensure no duplicates from double purchase bugs
|
|
|
|
// return [...new Set(myPurchases)];
|
|
|
|
return Array.from(new Set(myPurchases));
|
|
|
|
}
|
|
|
|
|
|
|
|
const fileInfos = myPurchases.map((uri) => claimsByUri[uri]);
|
|
|
|
const matchingFileInfos = filterClaims(fileInfos, query);
|
|
|
|
const start = (Number(page) - 1) * Number(CLAIM.PAGE_SIZE);
|
|
|
|
const end = Number(page) * Number(CLAIM.PAGE_SIZE);
|
|
|
|
return matchingFileInfos && matchingFileInfos.length
|
|
|
|
? matchingFileInfos.slice(start, end).map((fileInfo) => fileInfo.canonical_url || fileInfo.permanent_url)
|
|
|
|
: [];
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
export const makeSelectClaimWasPurchased = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
|
|
|
return claim && claim.purchase_receipt !== undefined;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const selectAllFetchingChannelClaims = createSelector(selectState, (state) => state.fetchingChannelClaims || {});
|
|
|
|
|
|
|
|
export const makeSelectFetchingChannelClaims = (uri: string) =>
|
|
|
|
createSelector(selectAllFetchingChannelClaims, (fetching) => fetching && fetching[uri]);
|
|
|
|
|
|
|
|
export const makeSelectClaimsInChannelForPage = (uri: string, page?: number) =>
|
|
|
|
createSelector(selectClaimsById, selectAllClaimsByChannel, (byId, allClaims) => {
|
|
|
|
const byChannel = allClaims[uri] || {};
|
|
|
|
const claimIds = byChannel[page || 1];
|
|
|
|
|
|
|
|
if (!claimIds) return claimIds;
|
|
|
|
|
|
|
|
return claimIds.map((claimId) => byId[claimId]);
|
|
|
|
});
|
|
|
|
|
|
|
|
// THIS IS LEFT OVER FROM ONE TAB CHANNEL_CONTENT
|
|
|
|
export const makeSelectTotalClaimsInChannelSearch = (uri: string) =>
|
|
|
|
createSelector(selectClaimsById, selectAllClaimsByChannel, (byId, allClaims) => {
|
|
|
|
const byChannel = allClaims[uri] || {};
|
|
|
|
return byChannel['itemCount'];
|
|
|
|
});
|
|
|
|
|
|
|
|
// THIS IS LEFT OVER FROM ONE_TAB CHANNEL CONTENT
|
|
|
|
export const makeSelectTotalPagesInChannelSearch = (uri: string) =>
|
|
|
|
createSelector(selectClaimsById, selectAllClaimsByChannel, (byId, allClaims) => {
|
|
|
|
const byChannel = allClaims[uri] || {};
|
|
|
|
return byChannel['pageCount'];
|
|
|
|
});
|
|
|
|
|
2021-10-08 07:52:30 +02:00
|
|
|
export const selectMetadataForUri = createCachedSelector(selectClaimForUri, (claim, uri) => {
|
|
|
|
const metadata = claim && claim.value;
|
|
|
|
return metadata || (claim === undefined ? undefined : null);
|
2021-10-25 06:58:34 +02:00
|
|
|
})((state, uri) => String(uri));
|
2021-10-08 07:52:30 +02:00
|
|
|
|
2021-10-17 10:36:14 +02:00
|
|
|
export const makeSelectMetadataForUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
|
|
|
const metadata = claim && claim.value;
|
|
|
|
return metadata || (claim === undefined ? undefined : null);
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectMetadataItemForUri = (uri: string, key: string) =>
|
|
|
|
createSelector(makeSelectMetadataForUri(uri), (metadata: ChannelMetadata | StreamMetadata) => {
|
|
|
|
return metadata ? metadata[key] : undefined;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectTitleForUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectMetadataForUri(uri), (metadata) => metadata && metadata.title);
|
|
|
|
|
2021-10-08 03:57:48 +02:00
|
|
|
export const selectDateForUri = createCachedSelector(
|
|
|
|
selectClaimForUri, // input: (state, uri, ?returnRepost)
|
|
|
|
(claim) => {
|
2021-10-17 10:36:14 +02:00
|
|
|
const timestamp =
|
|
|
|
claim &&
|
|
|
|
claim.value &&
|
|
|
|
(claim.value.release_time
|
|
|
|
? claim.value.release_time * 1000
|
|
|
|
: claim.meta && claim.meta.creation_timestamp
|
|
|
|
? claim.meta.creation_timestamp * 1000
|
|
|
|
: null);
|
|
|
|
if (!timestamp) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
const dateObj = new Date(timestamp);
|
|
|
|
return dateObj;
|
2021-10-08 03:57:48 +02:00
|
|
|
}
|
2021-10-25 06:58:34 +02:00
|
|
|
)((state, uri) => String(uri));
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
export const makeSelectAmountForUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
|
|
|
return claim && claim.amount;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectEffectiveAmountForUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri, false), (claim) => {
|
|
|
|
return (
|
|
|
|
claim && claim.meta && typeof claim.meta.effective_amount === 'string' && Number(claim.meta.effective_amount)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectContentTypeForUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
|
|
|
const source = claim && claim.value && claim.value.source;
|
|
|
|
return source ? source.media_type : undefined;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectThumbnailForUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
|
|
|
const thumbnail = claim && claim.value && claim.value.thumbnail;
|
|
|
|
return thumbnail && thumbnail.url ? thumbnail.url.trim().replace(/^http:\/\//i, 'https://') : undefined;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectCoverForUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
|
|
|
const cover = claim && claim.value && claim.value.cover;
|
|
|
|
return cover && cover.url ? cover.url.trim().replace(/^http:\/\//i, 'https://') : undefined;
|
|
|
|
});
|
|
|
|
|
2021-10-23 04:41:43 +02:00
|
|
|
export const selectIsFetchingClaimListMine = (state: State) => selectState(state).isFetchingClaimListMine;
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
export const selectMyClaimsPage = createSelector(selectState, (state) => state.myClaimsPageResults || []);
|
|
|
|
|
|
|
|
export const selectMyClaimsPageNumber = createSelector(
|
|
|
|
selectState,
|
|
|
|
(state) => (state.claimListMinePage && state.claimListMinePage.items) || [],
|
|
|
|
|
|
|
|
(state) => (state.txoPage && state.txoPage.page) || 1
|
|
|
|
);
|
|
|
|
|
2021-10-23 04:41:43 +02:00
|
|
|
export const selectMyClaimsPageItemCount = (state: State) => selectState(state).myClaimsPageTotalResults || 1;
|
|
|
|
export const selectFetchingMyClaimsPageError = (state: State) => selectState(state).fetchingClaimListMinePageError;
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
export const selectMyClaims = createSelector(
|
|
|
|
selectMyActiveClaims,
|
|
|
|
selectClaimsById,
|
|
|
|
selectAbandoningIds,
|
|
|
|
(myClaimIds, byId, abandoningIds) => {
|
|
|
|
const claims = [];
|
|
|
|
|
|
|
|
myClaimIds.forEach((id) => {
|
|
|
|
const claim = byId[id];
|
|
|
|
|
|
|
|
if (claim && abandoningIds.indexOf(id) === -1) claims.push(claim);
|
|
|
|
});
|
|
|
|
|
|
|
|
return [...claims];
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
export const selectMyClaimsWithoutChannels = createSelector(selectMyClaims, (myClaims) =>
|
|
|
|
myClaims.filter((claim) => claim && !claim.name.match(/^@/)).sort((a, b) => a.timestamp - b.timestamp)
|
|
|
|
);
|
|
|
|
|
|
|
|
export const selectMyClaimUrisWithoutChannels = createSelector(selectMyClaimsWithoutChannels, (myClaims) => {
|
|
|
|
return myClaims
|
|
|
|
.sort((a, b) => {
|
|
|
|
if (a.height < 1) {
|
|
|
|
return -1;
|
|
|
|
} else if (b.height < 1) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return b.timestamp - a.timestamp;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.map((claim) => {
|
|
|
|
return claim.canonical_url || claim.permanent_url;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
export const selectAllMyClaimsByOutpoint = createSelector(
|
|
|
|
selectMyClaimsRaw,
|
|
|
|
(claims) => new Set(claims && claims.length ? claims.map((claim) => `${claim.txid}:${claim.nout}`) : null)
|
|
|
|
);
|
|
|
|
|
|
|
|
export const selectMyClaimsOutpoints = createSelector(selectMyClaims, (myClaims) => {
|
|
|
|
const outpoints = [];
|
|
|
|
|
|
|
|
myClaims.forEach((claim) => outpoints.push(`${claim.txid}:${claim.nout}`));
|
|
|
|
|
|
|
|
return outpoints;
|
|
|
|
});
|
|
|
|
|
2021-10-23 04:41:43 +02:00
|
|
|
export const selectFetchingMyChannels = (state: State) => selectState(state).fetchingMyChannels;
|
|
|
|
export const selectFetchingMyCollections = (state: State) => selectState(state).fetchingMyCollections;
|
2021-10-17 10:36:14 +02:00
|
|
|
|
2021-11-08 07:27:14 +01:00
|
|
|
export const selectMyChannelClaimIds = (state: State) => selectState(state).myChannelClaims;
|
|
|
|
|
2021-11-09 00:02:41 +01:00
|
|
|
export const selectMyChannelClaims = createSelector(selectState, selectClaimsById, (state, byId) => {
|
|
|
|
const ids = state.myChannelClaims;
|
|
|
|
if (!ids) {
|
|
|
|
return ids;
|
`byId[]` fixes to reduce invalidation (#239)
* Don't update 'pendingById' if no changes.
'pendingById' isn't frequently updated, but using it as a proof-of-concept to fix how reducers should be written to avoid unnecessary updates.
ImmutableJS apparently does all of this for us, but there are cons to using it as well, so using own wrappers for now.
* Don't update 'byId' if no changes + add 'selectClaimWithId'
## Ticket
116 Claim store optimization ideas (reducing unnecessary renders)
## Changes
- Ignore things like `confirmations` so that already-fetched claims aren't invalidated and causes re-rendering. The `stringify` might look expensive, but the amount of avoided re-renders outweighs it. There might be faster ways to compare, though.
- With `byId[claimId]` references more stable now, memoized selectors can now use 'selectClaimWithId' to pick a specific claim to depend on, instead of 'byId' which changes on every update.
* Fix memo: selectMyChannelClaims, selectActiveChannelClaim
## Issue
These should never recalculate after `channel_list` has been fetched, but they do because of poor selector dependency.
## Change
With the `byId` changes from the previous commit, we are now able to memoize these selectors correctly.
2021-11-08 18:25:29 +01:00
|
|
|
}
|
|
|
|
|
2021-10-17 10:36:14 +02:00
|
|
|
const claims = [];
|
2021-11-09 00:02:41 +01:00
|
|
|
ids.forEach((id) => {
|
2021-10-17 10:36:14 +02:00
|
|
|
if (byId[id]) {
|
|
|
|
// I'm not sure why this check is necessary, but it ought to be a quick fix for https://github.com/lbryio/lbry-desktop/issues/544
|
|
|
|
claims.push(byId[id]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return claims;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const selectMyChannelUrls = createSelector(selectMyChannelClaims, (claims) =>
|
|
|
|
claims ? claims.map((claim) => claim.canonical_url || claim.permanent_url) : undefined
|
|
|
|
);
|
|
|
|
|
2021-11-08 07:27:14 +01:00
|
|
|
export const selectHasChannels = (state: State) => {
|
|
|
|
const myChannelClaimIds = selectMyChannelClaimIds(state);
|
|
|
|
return myChannelClaimIds ? myChannelClaimIds.length > 0 : false;
|
|
|
|
};
|
|
|
|
|
2021-10-23 04:41:43 +02:00
|
|
|
export const selectMyCollectionIds = (state: State) => selectState(state).myCollectionClaims;
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
export const selectResolvingUris = createSelector(selectState, (state) => state.resolvingUris || []);
|
|
|
|
|
2021-10-23 04:41:43 +02:00
|
|
|
export const selectChannelImportPending = (state: State) => selectState(state).pendingChannelImport;
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
export const makeSelectIsUriResolving = (uri: string) =>
|
|
|
|
createSelector(selectResolvingUris, (resolvingUris) => resolvingUris && resolvingUris.indexOf(uri) !== -1);
|
|
|
|
|
2021-10-23 04:41:43 +02:00
|
|
|
export const selectPlayingUri = (state: State) => selectState(state).playingUri;
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
export const selectChannelClaimCounts = createSelector(selectState, (state) => state.channelClaimCounts || {});
|
|
|
|
|
|
|
|
export const makeSelectPendingClaimForUri = (uri: string) =>
|
|
|
|
createSelector(selectPendingClaimsById, (pendingById) => {
|
|
|
|
let uriStreamName;
|
|
|
|
let uriChannelName;
|
|
|
|
try {
|
|
|
|
({ streamName: uriStreamName, channelName: uriChannelName } = parseURI(uri));
|
|
|
|
} catch (e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const pendingClaims = (Object.values(pendingById): any);
|
|
|
|
const matchingClaim = pendingClaims.find((claim: GenericClaim) => {
|
|
|
|
return claim.normalized_name === uriChannelName || claim.normalized_name === uriStreamName;
|
|
|
|
});
|
|
|
|
return matchingClaim || null;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectTotalItemsForChannel = (uri: string) =>
|
|
|
|
createSelector(selectChannelClaimCounts, (byUri) => byUri && byUri[normalizeURI(uri)]);
|
|
|
|
|
|
|
|
export const makeSelectTotalPagesForChannel = (uri: string, pageSize: number = 10) =>
|
|
|
|
createSelector(
|
|
|
|
selectChannelClaimCounts,
|
|
|
|
(byUri) => byUri && byUri[uri] && Math.ceil(byUri[normalizeURI(uri)] / pageSize)
|
|
|
|
);
|
|
|
|
|
|
|
|
export const makeSelectNsfwCountFromUris = (uris: Array<string>) =>
|
|
|
|
createSelector(selectClaimsByUri, (claims) =>
|
|
|
|
uris.reduce((acc, uri) => {
|
|
|
|
const claim = claims[uri];
|
|
|
|
if (claim && isClaimNsfw(claim)) {
|
|
|
|
return acc + 1;
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}, 0)
|
|
|
|
);
|
|
|
|
|
|
|
|
export const makeSelectOmittedCountForChannel = (uri: string) =>
|
|
|
|
createSelector(
|
|
|
|
makeSelectTotalItemsForChannel(uri),
|
|
|
|
makeSelectTotalClaimsInChannelSearch(uri),
|
|
|
|
(claimsInChannel, claimsInSearch) => {
|
|
|
|
if (claimsInChannel && typeof claimsInSearch === 'number' && claimsInSearch >= 0) {
|
|
|
|
return claimsInChannel - claimsInSearch;
|
|
|
|
} else return 0;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
export const makeSelectClaimIsNsfw = (uri: string) =>
|
|
|
|
createSelector(
|
|
|
|
makeSelectClaimForUri(uri),
|
|
|
|
// Eventually these will come from some list of tags that are considered adult
|
|
|
|
// Or possibly come from users settings of what tags they want to hide
|
|
|
|
// For now, there is just a hard coded list of tags inside `isClaimNsfw`
|
|
|
|
// selectNaughtyTags(),
|
|
|
|
(claim: Claim) => {
|
|
|
|
if (!claim) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return isClaimNsfw(claim);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Returns the associated channel uri for a given claim uri
|
|
|
|
// accepts a regular claim uri lbry://something
|
|
|
|
// returns the channel uri that created this claim lbry://@channel
|
|
|
|
export const makeSelectChannelForClaimUri = (uri: string, includePrefix: boolean = false) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim: ?Claim) => {
|
|
|
|
if (!claim || !claim.signing_channel || !claim.is_channel_signature_valid) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { canonical_url: canonicalUrl, permanent_url: permanentUrl } = claim.signing_channel;
|
|
|
|
|
|
|
|
if (canonicalUrl) {
|
|
|
|
return includePrefix ? canonicalUrl : canonicalUrl.slice('lbry://'.length);
|
|
|
|
} else {
|
|
|
|
return includePrefix ? permanentUrl : permanentUrl.slice('lbry://'.length);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectChannelPermUrlForClaimUri = (uri: string, includePrefix: boolean = false) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim: ?Claim) => {
|
|
|
|
if (claim && claim.value_type === 'channel') {
|
|
|
|
return claim.permanent_url;
|
|
|
|
}
|
|
|
|
if (!claim || !claim.signing_channel || !claim.is_channel_signature_valid) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return claim.signing_channel.permanent_url;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectMyChannelPermUrlForName = (name: string) =>
|
|
|
|
createSelector(selectMyChannelClaims, (claims) => {
|
|
|
|
const matchingClaim = claims && claims.find((claim) => claim.name === name);
|
|
|
|
return matchingClaim ? matchingClaim.permanent_url : null;
|
|
|
|
});
|
|
|
|
|
2021-10-08 07:52:30 +02:00
|
|
|
export const selectTagsForUri = createCachedSelector(selectMetadataForUri, (metadata: ?GenericMetadata) => {
|
|
|
|
return (metadata && metadata.tags) || [];
|
2021-10-25 06:58:34 +02:00
|
|
|
})((state, uri) => String(uri));
|
2021-10-08 07:52:30 +02:00
|
|
|
|
2021-10-17 10:36:14 +02:00
|
|
|
export const makeSelectTagsForUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectMetadataForUri(uri), (metadata: ?GenericMetadata) => {
|
|
|
|
return (metadata && metadata.tags) || [];
|
|
|
|
});
|
|
|
|
|
|
|
|
export const selectFetchingClaimSearchByQuery = createSelector(
|
|
|
|
selectState,
|
|
|
|
(state) => state.fetchingClaimSearchByQuery || {}
|
|
|
|
);
|
|
|
|
|
|
|
|
export const selectFetchingClaimSearch = createSelector(
|
|
|
|
selectFetchingClaimSearchByQuery,
|
|
|
|
(fetchingClaimSearchByQuery) => Boolean(Object.keys(fetchingClaimSearchByQuery).length)
|
|
|
|
);
|
|
|
|
|
|
|
|
export const selectClaimSearchByQuery = createSelector(selectState, (state) => state.claimSearchByQuery || {});
|
|
|
|
|
|
|
|
export const selectClaimSearchByQueryLastPageReached = createSelector(
|
|
|
|
selectState,
|
|
|
|
(state) => state.claimSearchByQueryLastPageReached || {}
|
|
|
|
);
|
|
|
|
|
|
|
|
export const makeSelectShortUrlForUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => claim && claim.short_url);
|
|
|
|
|
|
|
|
export const makeSelectCanonicalUrlForUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => claim && claim.canonical_url);
|
|
|
|
|
|
|
|
export const makeSelectPermanentUrlForUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => claim && claim.permanent_url);
|
|
|
|
|
|
|
|
export const makeSelectSupportsForUri = (uri: string) =>
|
|
|
|
createSelector(selectSupportsByOutpoint, makeSelectClaimForUri(uri), (byOutpoint, claim: ?StreamClaim) => {
|
|
|
|
if (!claim || !claim.is_my_output) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { claim_id: claimId } = claim;
|
|
|
|
let total = 0;
|
|
|
|
|
|
|
|
Object.values(byOutpoint).forEach((support) => {
|
|
|
|
// $FlowFixMe
|
|
|
|
const { claim_id, amount } = support;
|
|
|
|
total = claim_id === claimId && amount ? total + parseFloat(amount) : total;
|
|
|
|
});
|
|
|
|
|
|
|
|
return total;
|
|
|
|
});
|
|
|
|
|
2021-10-23 04:41:43 +02:00
|
|
|
export const selectUpdatingChannel = (state: State) => selectState(state).updatingChannel;
|
|
|
|
export const selectUpdateChannelError = (state: State) => selectState(state).updateChannelError;
|
2021-10-17 10:36:14 +02:00
|
|
|
|
|
|
|
export const makeSelectReflectingClaimForUri = (uri: string) =>
|
|
|
|
createSelector(selectClaimIdsByUri, selectReflectingById, (claimIdsByUri, reflectingById) => {
|
|
|
|
const claimId = claimIdsByUri[normalizeURI(uri)];
|
|
|
|
return reflectingById[claimId];
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectMyStreamUrlsForPage = (page: number = 1) =>
|
|
|
|
createSelector(selectMyClaimUrisWithoutChannels, (urls) => {
|
|
|
|
const start = (Number(page) - 1) * Number(CLAIM.PAGE_SIZE);
|
|
|
|
const end = Number(page) * Number(CLAIM.PAGE_SIZE);
|
|
|
|
|
|
|
|
return urls && urls.length ? urls.slice(start, end) : [];
|
|
|
|
});
|
|
|
|
|
|
|
|
export const selectMyStreamUrlsCount = createSelector(selectMyClaimUrisWithoutChannels, (channels) => channels.length);
|
|
|
|
|
|
|
|
export const makeSelectTagInClaimOrChannelForUri = (uri: string, tag: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
|
|
|
const claimTags = (claim && claim.value && claim.value.tags) || [];
|
|
|
|
const channelTags =
|
|
|
|
(claim && claim.signing_channel && claim.signing_channel.value && claim.signing_channel.value.tags) || [];
|
|
|
|
return claimTags.includes(tag) || channelTags.includes(tag);
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectClaimHasSource = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
|
|
|
if (!claim) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Boolean(claim.value.source);
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectClaimIsStreamPlaceholder = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
|
|
|
if (!claim) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return Boolean(claim.value_type === 'stream' && !claim.value.source);
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectTotalStakedAmountForChannelUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectClaimForUri(uri), (claim) => {
|
|
|
|
if (!claim || !claim.amount || !claim.meta || !claim.meta.support_amount) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return parseFloat(claim.amount) + parseFloat(claim.meta.support_amount) || 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
export const makeSelectStakedLevelForChannelUri = (uri: string) =>
|
|
|
|
createSelector(makeSelectTotalStakedAmountForChannelUri(uri), (amount) => {
|
|
|
|
let level = 1;
|
|
|
|
switch (true) {
|
|
|
|
case amount >= CLAIM.LEVEL_2_STAKED_AMOUNT && amount < CLAIM.LEVEL_3_STAKED_AMOUNT:
|
|
|
|
level = 2;
|
|
|
|
break;
|
|
|
|
case amount >= CLAIM.LEVEL_3_STAKED_AMOUNT && amount < CLAIM.LEVEL_4_STAKED_AMOUNT:
|
|
|
|
level = 3;
|
|
|
|
break;
|
|
|
|
case amount >= CLAIM.LEVEL_4_STAKED_AMOUNT && amount < CLAIM.LEVEL_5_STAKED_AMOUNT:
|
|
|
|
level = 4;
|
|
|
|
break;
|
|
|
|
case amount >= CLAIM.LEVEL_5_STAKED_AMOUNT:
|
|
|
|
level = 5;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return level;
|
|
|
|
});
|
|
|
|
|
2021-10-23 04:41:43 +02:00
|
|
|
export const selectUpdatingCollection = (state: State) => selectState(state).updatingCollection;
|
|
|
|
export const selectUpdateCollectionError = (state: State) => selectState(state).updateCollectionError;
|
|
|
|
export const selectCreatingCollection = (state: State) => selectState(state).creatingCollection;
|
|
|
|
export const selectCreateCollectionError = (state: State) => selectState(state).createCollectionError;
|