lbry-desktop/ui/redux/reducers/claims.js
infinite-persistence 3ed05e62a5 Resolve: store repost's original channel too
## Issue
- Closes "1535 bunch of channels (from featured?) resolved on page load"
- It was due to the use of reposts in the Category. As we did not store the repost's original channel claim, `ChannelThumbnail` couldn't find the author and requested a separate `resolve`.

## Change
Store the repost's original channel claim as well.
2022-05-26 08:00:58 -04:00

1038 lines
33 KiB
JavaScript

// @flow
// This file has a lot of FlowFixMe comments
// It's due to Flow's support of Object.{values,entries}
// https://github.com/facebook/flow/issues/2221
// We could move to es6 Sets/Maps, but those are not recommended for redux
// https://github.com/reduxjs/redux/issues/1499
// Unsure of the best solution at the momentf
// - Sean
import * as ACTIONS from 'constants/action_types';
import mergeClaim from 'util/merge-claim';
type State = {
createChannelError: ?string,
createCollectionError: ?string,
channelClaimCounts: { [string]: number },
claimsByUri: { [string]: string },
byId: { [string]: Claim },
pendingById: { [string]: Claim }, // keep pending claims
resolvingUris: Array<string>,
reflectingById: { [string]: ReflectingUpdate },
myClaims: ?Array<string>,
myChannelClaims: ?Array<string>,
myCollectionClaims: ?Array<string>,
abandoningById: { [string]: boolean },
fetchingChannelClaims: { [string]: number },
fetchingMyChannels: boolean,
fetchingMyCollections: boolean,
fetchingClaimSearchByQuery: { [string]: boolean },
purchaseUriSuccess: boolean,
myPurchases: ?Array<string>,
myPurchasesPageNumber: ?number,
myPurchasesPageTotalResults: ?number,
fetchingMyPurchases: boolean,
fetchingMyPurchasesError: ?string,
claimSearchByQuery: { [string]: Array<string> },
claimSearchByQueryLastPageReached: { [string]: Array<boolean> },
creatingChannel: boolean,
creatingCollection: boolean,
paginatedClaimsByChannel: {
[string]: {
all: Array<string>,
pageCount: number,
itemCount: number,
[number]: Array<string>,
},
},
updateChannelError: ?string,
updateCollectionError: ?string,
updatingChannel: boolean,
updatingCollection: boolean,
pendingChannelImport: string | boolean,
repostLoading: boolean,
repostError: ?string,
fetchingClaimListMinePageError: ?string,
myClaimsPageResults: Array<string>,
myClaimsPageNumber: ?number,
myClaimsPageTotalResults: ?number,
isFetchingClaimListMine: boolean,
isCheckingNameForPublish: boolean,
checkingPending: boolean,
checkingReflecting: boolean,
latestByUri: { [string]: any },
};
const reducers = {};
const defaultState = {
byId: {},
claimsByUri: {},
paginatedClaimsByChannel: {},
channelClaimCounts: {},
fetchingChannelClaims: {},
resolvingUris: [],
myChannelClaims: undefined,
myCollectionClaims: [],
myClaims: undefined,
myPurchases: undefined,
myPurchasesPageNumber: undefined,
myPurchasesPageTotalResults: undefined,
purchaseUriSuccess: false,
fetchingMyPurchases: false,
fetchingMyPurchasesError: undefined,
fetchingMyChannels: false,
fetchingMyCollections: false,
abandoningById: {},
pendingById: {},
reflectingById: {},
claimSearchError: false,
claimSearchByQuery: {},
claimSearchByQueryLastPageReached: {},
fetchingClaimSearchByQuery: {},
updateChannelError: '',
updateCollectionError: '',
updatingChannel: false,
creatingChannel: false,
createChannelError: undefined,
updatingCollection: false,
creatingCollection: false,
createCollectionError: undefined,
pendingChannelImport: false,
repostLoading: false,
repostError: undefined,
fetchingClaimListMinePageError: undefined,
myClaimsPageResults: [],
myClaimsPageNumber: undefined,
myClaimsPageTotalResults: undefined,
isFetchingClaimListMine: false,
isFetchingMyPurchases: false,
isCheckingNameForPublish: false,
checkingPending: false,
checkingReflecting: false,
latestByUri: {},
};
// ****************************************************************************
// Helpers
// ****************************************************************************
function isObjEmpty(object: any) {
return Object.keys(object).length === 0;
}
function resolveDelta(original: any, delta: any) {
if (isObjEmpty(delta)) {
// Don't invalidate references when there are no changes, so return original.
return original;
} else {
// When there are changes: create a new object, spread existing references,
// and overwrite specific items with new data.
return { ...original, ...delta };
}
}
function claimHasNewData(original, fresh) {
// Don't blow away 'is_my_output' just because the next query didn't ask for it.
const ignoreIsMyOutput = original.is_my_output !== undefined && fresh.is_my_output === undefined;
// Something is causing the tags to be re-ordered differently
// (https://github.com/OdyseeTeam/odysee-frontend/issues/116#issuecomment-962747147).
// Just do a length comparison for now, which covers 99% of cases while we
// figure out what's causing the order to change.
const ignoreTags =
original &&
original.value &&
original.value.tags &&
original.value.tags.length &&
fresh &&
fresh.value &&
fresh.value.tags &&
fresh.value.tags.length &&
original.value.tags.length !== fresh.value.tags.length;
const excludeKeys = (key, value) => {
if (key === 'confirmations' || (ignoreTags && key === 'tags') || (ignoreIsMyOutput && key === 'is_my_output')) {
return undefined;
}
return value;
};
const originalStringified = JSON.stringify(original, excludeKeys);
const freshStringified = JSON.stringify(fresh, excludeKeys);
return originalStringified !== freshStringified;
}
/**
* Adds the new value to the delta if the value is different from the original.
*
* @param original The original state object.
* @param delta The delta state object containing a list of changes.
* @param key
* @param newValue
*/
function updateIfValueChanged(original, delta, key, newValue) {
if (original[key] !== newValue) {
delta[key] = newValue;
}
}
/**
* Adds the new claim to the delta if the claim contains changes that the GUI
* would care about.
*
* @param original The original state object.
* @param delta The delta state object containing a list of changes.
* @param key
* @param newClaim
*/
function updateIfClaimChanged(original, delta, key, newClaim) {
if (!original[key] || claimHasNewData(original[key], newClaim)) {
delta[key] = newClaim;
}
}
// ****************************************************************************
// handleClaimAction
// ****************************************************************************
function handleClaimAction(state: State, action: any): State {
const { resolveInfo }: ClaimActionResolveInfo = action.data;
const byUriDelta = {};
const byIdDelta = {};
const channelClaimCounts = Object.assign({}, state.channelClaimCounts);
const pendingById = state.pendingById;
let newResolvingUrls = new Set(state.resolvingUris);
let myClaimIds = new Set(state.myClaims);
Object.entries(resolveInfo).forEach(([url, resolveResponse]) => {
// $FlowFixMe
const { claimsInChannel, stream, channel: channelFromResolve, collection } = resolveResponse;
const channel = channelFromResolve || (stream && stream.signing_channel);
const repostSrcChannel = stream && stream.reposted_claim ? stream.reposted_claim.signing_channel : null;
if (stream) {
if (pendingById[stream.claim_id]) {
byIdDelta[stream.claim_id] = mergeClaim(stream, state.byId[stream.claim_id]);
} else {
updateIfClaimChanged(state.byId, byIdDelta, stream.claim_id, stream);
}
updateIfValueChanged(state.claimsByUri, byUriDelta, url, stream.claim_id);
// If url isn't a canonical_url, make sure that is added too
updateIfValueChanged(state.claimsByUri, byUriDelta, stream.canonical_url, stream.claim_id);
// Also add the permanent_url here until lighthouse returns canonical_url for search results
updateIfValueChanged(state.claimsByUri, byUriDelta, stream.permanent_url, stream.claim_id);
newResolvingUrls.delete(stream.canonical_url);
newResolvingUrls.delete(stream.permanent_url);
if (stream.is_my_output) {
myClaimIds.add(stream.claim_id);
}
}
if (channel && channel.claim_id) {
if (!stream) {
updateIfValueChanged(state.claimsByUri, byUriDelta, url, channel.claim_id);
}
if (claimsInChannel) {
channelClaimCounts[url] = claimsInChannel;
channelClaimCounts[channel.canonical_url] = claimsInChannel;
}
if (pendingById[channel.claim_id]) {
byIdDelta[channel.claim_id] = mergeClaim(channel, state.byId[channel.claim_id]);
} else {
updateIfClaimChanged(state.byId, byIdDelta, channel.claim_id, channel);
}
updateIfValueChanged(state.claimsByUri, byUriDelta, channel.permanent_url, channel.claim_id);
updateIfValueChanged(state.claimsByUri, byUriDelta, channel.canonical_url, channel.claim_id);
newResolvingUrls.delete(channel.canonical_url);
newResolvingUrls.delete(channel.permanent_url);
}
if (repostSrcChannel && repostSrcChannel.claim_id) {
updateIfClaimChanged(state.byId, byIdDelta, repostSrcChannel.claim_id, repostSrcChannel);
updateIfValueChanged(state.claimsByUri, byUriDelta, repostSrcChannel.permanent_url, repostSrcChannel.claim_id);
updateIfValueChanged(state.claimsByUri, byUriDelta, repostSrcChannel.canonical_url, repostSrcChannel.claim_id);
}
if (collection) {
if (pendingById[collection.claim_id]) {
byIdDelta[collection.claim_id] = mergeClaim(collection, state.byId[collection.claim_id]);
} else {
updateIfClaimChanged(state.byId, byIdDelta, collection.claim_id, collection);
}
updateIfValueChanged(state.claimsByUri, byUriDelta, url, collection.claim_id);
updateIfValueChanged(state.claimsByUri, byUriDelta, collection.canonical_url, collection.claim_id);
updateIfValueChanged(state.claimsByUri, byUriDelta, collection.permanent_url, collection.claim_id);
newResolvingUrls.delete(collection.canonical_url);
newResolvingUrls.delete(collection.permanent_url);
if (collection.is_my_output) {
myClaimIds.add(collection.claim_id);
}
}
newResolvingUrls.delete(url);
if (!stream && !channel && !collection && !pendingById[state.claimsByUri[url]]) {
updateIfValueChanged(state.claimsByUri, byUriDelta, url, null);
}
});
return Object.assign({}, state, {
byId: resolveDelta(state.byId, byIdDelta),
claimsByUri: resolveDelta(state.claimsByUri, byUriDelta),
channelClaimCounts,
resolvingUris: Array.from(newResolvingUrls),
...(!state.myClaims || myClaimIds.size !== state.myClaims.length ? { myClaims: Array.from(myClaimIds) } : {}),
});
}
// ****************************************************************************
// Reducers
// ****************************************************************************
reducers[ACTIONS.RESOLVE_URIS_STARTED] = (state: State, action: any): State => {
const { uris }: { uris: Array<string> } = action.data;
const oldResolving = state.resolvingUris || [];
const newResolving = oldResolving.slice();
uris.forEach((uri) => {
if (!newResolving.includes(uri)) {
newResolving.push(uri);
}
});
return Object.assign({}, state, {
resolvingUris: newResolving,
});
};
reducers[ACTIONS.RESOLVE_URIS_COMPLETED] = (state: State, action: any): State => {
return {
...handleClaimAction(state, action),
};
};
reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_STARTED] = (state: State): State =>
Object.assign({}, state, {
isFetchingClaimListMine: true,
});
reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED] = (state: State, action: any): State => {
const { result }: { result: ClaimListResponse } = action.data;
const claims = result.items;
const page = result.page;
const totalItems = result.total_items;
const byIdDelta = {};
const byUriDelta = {};
const pendingByIdDelta = {};
let myClaimIds = new Set(state.myClaims);
let urlsForCurrentPage = [];
claims.forEach((claim: Claim) => {
const { permanent_url: permanentUri, claim_id: claimId, canonical_url: canonicalUri } = claim;
if (claim.type && claim.type.match(/claim|update/)) {
urlsForCurrentPage.push(permanentUri);
if (claim.confirmations < 1) {
pendingByIdDelta[claimId] = claim;
if (state.byId[claimId]) {
byIdDelta[claimId] = mergeClaim(claim, state.byId[claimId]);
} else {
byIdDelta[claimId] = claim;
}
} else {
updateIfClaimChanged(state.byId, byIdDelta, claimId, claim);
}
updateIfValueChanged(state.claimsByUri, byUriDelta, permanentUri, claimId);
updateIfValueChanged(state.claimsByUri, byUriDelta, canonicalUri, claimId);
myClaimIds.add(claimId);
}
});
return Object.assign({}, state, {
isFetchingClaimListMine: false,
myClaims: Array.from(myClaimIds),
byId: resolveDelta(state.byId, byIdDelta),
pendingById: resolveDelta(state.pendingById, pendingByIdDelta),
claimsByUri: resolveDelta(state.claimsByUri, byUriDelta),
myClaimsPageResults: urlsForCurrentPage,
myClaimsPageNumber: page,
myClaimsPageTotalResults: totalItems,
});
};
reducers[ACTIONS.FETCH_CHANNEL_LIST_STARTED] = (state: State): State =>
Object.assign({}, state, { fetchingMyChannels: true });
reducers[ACTIONS.FETCH_CHANNEL_LIST_COMPLETED] = (state: State, action: any): State => {
const { claims }: { claims: Array<ChannelClaim> } = action.data;
let myClaimIds = new Set(state.myClaims);
const pendingByIdDelta = {};
let myChannelClaims;
const byIdDelta = {};
const byUriDelta = {};
const channelClaimCounts = Object.assign({}, state.channelClaimCounts);
if (!claims.length) {
// $FlowFixMe
myChannelClaims = null;
} else {
myChannelClaims = new Set(state.myChannelClaims);
claims.forEach((claim) => {
const { meta } = claim;
const { claims_in_channel: claimsInChannel } = meta;
const { canonical_url: canonicalUrl, permanent_url: permanentUrl, claim_id: claimId, confirmations } = claim;
updateIfValueChanged(state.claimsByUri, byUriDelta, canonicalUrl, claimId);
updateIfValueChanged(state.claimsByUri, byUriDelta, permanentUrl, claimId);
channelClaimCounts[canonicalUrl] = claimsInChannel;
channelClaimCounts[permanentUrl] = claimsInChannel;
// $FlowFixMe
myChannelClaims.add(claimId);
if (confirmations < 1) {
pendingByIdDelta[claimId] = claim;
if (state.byId[claimId]) {
byIdDelta[claimId] = mergeClaim(claim, state.byId[claimId]);
} else {
byIdDelta[claimId] = claim;
}
} else {
updateIfClaimChanged(state.byId, byIdDelta, claimId, claim);
}
myClaimIds.add(claimId);
});
}
return Object.assign({}, state, {
byId: resolveDelta(state.byId, byIdDelta),
pendingById: resolveDelta(state.pendingById, pendingByIdDelta),
claimsByUri: resolveDelta(state.claimsByUri, byUriDelta),
channelClaimCounts,
fetchingMyChannels: false,
myChannelClaims: myChannelClaims ? Array.from(myChannelClaims) : null,
myClaims: myClaimIds ? Array.from(myClaimIds) : null,
});
};
reducers[ACTIONS.FETCH_CHANNEL_LIST_FAILED] = (state: State, action: any): State => {
return Object.assign({}, state, {
fetchingMyChannels: false,
});
};
reducers[ACTIONS.FETCH_COLLECTION_LIST_STARTED] = (state: State): State => ({
...state,
fetchingMyCollections: true,
});
reducers[ACTIONS.FETCH_COLLECTION_LIST_COMPLETED] = (state: State, action: any): State => {
const { claims }: { claims: Array<CollectionClaim> } = action.data;
const myClaims = state.myClaims || [];
let myClaimIds = new Set(myClaims);
const pendingByIdDelta = {};
let myCollectionClaimsSet = new Set([]);
const byIdDelta = {};
const byUriDelta = {};
if (claims.length) {
myCollectionClaimsSet = new Set(state.myCollectionClaims);
claims.forEach((claim) => {
const { canonical_url: canonicalUrl, permanent_url: permanentUrl, claim_id: claimId, confirmations } = claim;
updateIfValueChanged(state.claimsByUri, byUriDelta, canonicalUrl, claimId);
updateIfValueChanged(state.claimsByUri, byUriDelta, permanentUrl, claimId);
// $FlowFixMe
myCollectionClaimsSet.add(claimId);
// we don't want to overwrite a pending result with a resolve
if (confirmations < 1) {
pendingByIdDelta[claimId] = claim;
if (state.byId[claimId]) {
byIdDelta[claimId] = mergeClaim(claim, state.byId[claimId]);
} else {
byIdDelta[claimId] = claim;
}
} else {
updateIfClaimChanged(state.byId, byIdDelta, claimId, claim);
}
myClaimIds.add(claimId);
});
}
return {
...state,
byId: resolveDelta(state.byId, byIdDelta),
pendingById: resolveDelta(state.pendingById, pendingByIdDelta),
claimsByUri: resolveDelta(state.claimsByUri, byUriDelta),
fetchingMyCollections: false,
myCollectionClaims: Array.from(myCollectionClaimsSet),
myClaims: myClaimIds ? Array.from(myClaimIds) : null,
};
};
reducers[ACTIONS.FETCH_COLLECTION_LIST_FAILED] = (state: State): State => {
return { ...state, fetchingMyCollections: false };
};
reducers[ACTIONS.FETCH_CHANNEL_CLAIMS_STARTED] = (state: State, action: any): State => {
const { uri, page } = action.data;
const fetchingChannelClaims = Object.assign({}, state.fetchingChannelClaims);
fetchingChannelClaims[uri] = page;
return Object.assign({}, state, {
fetchingChannelClaims,
currentChannelPage: page,
});
};
reducers[ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED] = (state: State, action: any): State => {
const {
uri,
claims,
claimsInChannel,
page,
totalPages,
}: {
uri: string,
claims: Array<StreamClaim>,
claimsInChannel?: number,
page: number,
totalPages: number,
} = action.data;
// byChannel keeps claim_search relevant results by page. If the total changes, erase it.
const channelClaimCounts = Object.assign({}, state.channelClaimCounts);
const paginatedClaimsByChannel = Object.assign({}, state.paginatedClaimsByChannel);
// check if count has changed - that means cached pagination will be wrong, so clear it
const previousCount = paginatedClaimsByChannel[uri] && paginatedClaimsByChannel[uri]['itemCount'];
const byChannel = claimsInChannel === previousCount ? Object.assign({}, paginatedClaimsByChannel[uri]) : {};
const allClaimIds = new Set(byChannel.all);
const currentPageClaimIds = [];
const byIdDelta = {};
const fetchingChannelClaims = Object.assign({}, state.fetchingChannelClaims);
const claimsByUriDelta = {};
if (claims !== undefined) {
claims.forEach((claim) => {
allClaimIds.add(claim.claim_id);
currentPageClaimIds.push(claim.claim_id);
updateIfClaimChanged(state.byId, byIdDelta, claim.claim_id, claim);
updateIfValueChanged(state.claimsByUri, claimsByUriDelta, claim.canonical_url, claim.claim_id);
});
}
byChannel.all = allClaimIds;
byChannel.pageCount = totalPages;
byChannel.itemCount = claimsInChannel;
byChannel[page] = currentPageClaimIds;
paginatedClaimsByChannel[uri] = byChannel;
delete fetchingChannelClaims[uri];
return Object.assign({}, state, {
paginatedClaimsByChannel,
byId: resolveDelta(state.byId, byIdDelta),
fetchingChannelClaims,
claimsByUri: resolveDelta(state.claimsByUri, claimsByUriDelta),
channelClaimCounts,
currentChannelPage: page,
});
};
reducers[ACTIONS.ABANDON_CLAIM_STARTED] = (state: State, action: any): State => {
const { claimId }: { claimId: string } = action.data;
const abandoningById = Object.assign({}, state.abandoningById);
abandoningById[claimId] = true;
return Object.assign({}, state, {
abandoningById,
});
};
reducers[ACTIONS.UPDATE_PENDING_CLAIMS] = (state: State, action: any): State => {
const { claims: pendingClaims }: { claims: Array<Claim> } = action.data;
const byIdDelta = {};
const pendingById = Object.assign({}, state.pendingById);
const byUriDelta = {};
let myClaimIds = new Set(state.myClaims);
const myChannelClaims = new Set(state.myChannelClaims);
// $FlowFixMe
pendingClaims.forEach((claim: Claim) => {
let newClaim;
const { permanent_url: uri, claim_id: claimId, type, value_type: valueType } = claim;
pendingById[claimId] = claim; // make sure we don't need to merge?
const oldClaim = state.byId[claimId];
if (oldClaim && oldClaim.canonical_url) {
newClaim = mergeClaim(oldClaim, claim);
} else {
newClaim = claim;
}
if (valueType === 'channel') {
myChannelClaims.add(claimId);
}
if (type && type.match(/claim|update/)) {
updateIfClaimChanged(state.byId, byIdDelta, claimId, newClaim);
updateIfValueChanged(state.claimsByUri, byUriDelta, uri, claimId);
}
myClaimIds.add(claimId);
});
return Object.assign({}, state, {
myClaims: Array.from(myClaimIds),
byId: resolveDelta(state.byId, byIdDelta),
pendingById,
myChannelClaims: Array.from(myChannelClaims),
claimsByUri: resolveDelta(state.claimsByUri, byUriDelta),
});
};
reducers[ACTIONS.UPDATE_CONFIRMED_CLAIMS] = (state: State, action: any): State => {
const {
claims: confirmedClaims,
pending: pendingClaims,
}: { claims: Array<Claim>, pending: { [string]: Claim } } = action.data;
const byIdDelta = {};
confirmedClaims.forEach((claim: GenericClaim) => {
const { claim_id: claimId, type } = claim;
let newClaim = claim;
const oldClaim = state.byId[claimId];
if (oldClaim && oldClaim.canonical_url) {
newClaim = mergeClaim(oldClaim, claim);
}
if (type && type.match(/claim|update|channel/)) {
updateIfClaimChanged(state.byId, byIdDelta, claimId, newClaim);
}
});
return Object.assign({}, state, {
pendingById: pendingClaims,
byId: resolveDelta(state.byId, byIdDelta),
});
};
reducers[ACTIONS.ABANDON_CLAIM_SUCCEEDED] = (state: State, action: any): State => {
const { claimId }: { claimId: string } = action.data;
const byId = Object.assign({}, state.byId);
const newMyClaims = state.myClaims ? state.myClaims.slice() : [];
let myClaimsPageResults = null;
const newMyChannelClaims = state.myChannelClaims ? state.myChannelClaims.slice() : [];
const claimsByUri = Object.assign({}, state.claimsByUri);
const abandoningById = Object.assign({}, state.abandoningById);
const newMyCollectionClaims = state.myCollectionClaims ? state.myCollectionClaims.slice() : [];
let abandonedUris = [];
Object.keys(claimsByUri).forEach((uri) => {
if (claimsByUri[uri] === claimId) {
abandonedUris.push(uri);
delete claimsByUri[uri];
}
});
if (abandonedUris.length > 0 && state.myClaimsPageResults) {
myClaimsPageResults = state.myClaimsPageResults.filter((uri) => !abandonedUris.includes(uri));
}
if (abandoningById[claimId]) {
delete abandoningById[claimId];
}
const myClaims = newMyClaims.filter((i) => i !== claimId);
const myChannelClaims = newMyChannelClaims.filter((i) => i !== claimId);
const myCollectionClaims = newMyCollectionClaims.filter((i) => i !== claimId);
delete byId[claimId];
return Object.assign({}, state, {
myClaims,
myChannelClaims,
myCollectionClaims,
byId,
claimsByUri,
abandoningById,
myClaimsPageResults: myClaimsPageResults || state.myClaimsPageResults,
});
};
reducers[ACTIONS.CLEAR_CHANNEL_ERRORS] = (state: State): State => ({
...state,
createChannelError: null,
updateChannelError: null,
});
reducers[ACTIONS.CREATE_CHANNEL_STARTED] = (state: State): State => ({
...state,
creatingChannel: true,
createChannelError: null,
});
reducers[ACTIONS.CREATE_CHANNEL_COMPLETED] = (state: State, action: any): State => {
return Object.assign({}, state, {
creatingChannel: false,
});
};
reducers[ACTIONS.CREATE_CHANNEL_FAILED] = (state: State, action: any): State => {
return Object.assign({}, state, {
creatingChannel: false,
createChannelError: action.data,
});
};
reducers[ACTIONS.UPDATE_CHANNEL_STARTED] = (state: State, action: any): State => {
return Object.assign({}, state, {
updateChannelError: '',
updatingChannel: true,
});
};
reducers[ACTIONS.UPDATE_CHANNEL_COMPLETED] = (state: State, action: any): State => {
return Object.assign({}, state, {
updateChannelError: '',
updatingChannel: false,
});
};
reducers[ACTIONS.UPDATE_CHANNEL_FAILED] = (state: State, action: any): State => {
return Object.assign({}, state, {
updateChannelError: action.data.message,
updatingChannel: false,
});
};
reducers[ACTIONS.CLEAR_COLLECTION_ERRORS] = (state: State): State => ({
...state,
createCollectionError: null,
updateCollectionError: null,
});
reducers[ACTIONS.COLLECTION_PUBLISH_STARTED] = (state: State): State => ({
...state,
creatingCollection: true,
createCollectionError: null,
});
reducers[ACTIONS.COLLECTION_PUBLISH_COMPLETED] = (state: State, action: any): State => {
const myCollections = state.myCollectionClaims || [];
const myClaims = state.myClaims || [];
const { claimId } = action.data;
let myClaimIds = new Set(myClaims);
let myCollectionClaimsSet = new Set(myCollections);
myClaimIds.add(claimId);
myCollectionClaimsSet.add(claimId);
return Object.assign({}, state, {
creatingCollection: false,
myClaims: Array.from(myClaimIds),
myCollectionClaims: Array.from(myCollectionClaimsSet),
});
};
reducers[ACTIONS.COLLECTION_PUBLISH_FAILED] = (state: State, action: any): State => {
return Object.assign({}, state, {
creatingCollection: false,
createCollectionError: action.data.error,
});
};
reducers[ACTIONS.COLLECTION_PUBLISH_UPDATE_STARTED] = (state: State, action: any): State => {
return Object.assign({}, state, {
updateCollectionError: '',
updatingCollection: true,
});
};
reducers[ACTIONS.COLLECTION_PUBLISH_UPDATE_COMPLETED] = (state: State, action: any): State => {
return Object.assign({}, state, {
updateCollectionError: '',
updatingCollection: false,
});
};
reducers[ACTIONS.COLLECTION_PUBLISH_UPDATE_FAILED] = (state: State, action: any): State => {
return Object.assign({}, state, {
updateCollectionError: action.data.error,
updatingCollection: false,
});
};
reducers[ACTIONS.IMPORT_CHANNEL_STARTED] = (state: State): State =>
Object.assign({}, state, { pendingChannelImports: true });
reducers[ACTIONS.IMPORT_CHANNEL_COMPLETED] = (state: State): State =>
Object.assign({}, state, { pendingChannelImports: false });
reducers[ACTIONS.CLEAR_CLAIM_SEARCH_HISTORY] = (state: State): State => {
return {
...state,
claimSearchByQuery: {},
claimSearchByQueryLastPageReached: {},
};
};
reducers[ACTIONS.CLAIM_SEARCH_STARTED] = (state: State, action: any): State => {
const fetchingClaimSearchByQuery = Object.assign({}, state.fetchingClaimSearchByQuery);
fetchingClaimSearchByQuery[action.data.query] = true;
return Object.assign({}, state, {
fetchingClaimSearchByQuery,
});
};
reducers[ACTIONS.CLAIM_SEARCH_COMPLETED] = (state: State, action: any): State => {
const fetchingClaimSearchByQuery = Object.assign({}, state.fetchingClaimSearchByQuery);
const claimSearchByQuery = Object.assign({}, state.claimSearchByQuery);
const claimSearchByQueryLastPageReached = Object.assign({}, state.claimSearchByQueryLastPageReached);
const { append, query, urls, pageSize } = action.data;
if (append) {
// todo: check for duplicate urls when concatenating?
claimSearchByQuery[query] =
claimSearchByQuery[query] && claimSearchByQuery[query].length ? claimSearchByQuery[query].concat(urls) : urls;
} else {
claimSearchByQuery[query] = urls;
}
// the returned number of urls is less than the page size, so we're on the last page
claimSearchByQueryLastPageReached[query] = urls.length < pageSize;
delete fetchingClaimSearchByQuery[query];
return Object.assign({}, state, {
...handleClaimAction(state, action),
claimSearchByQuery,
claimSearchByQueryLastPageReached,
fetchingClaimSearchByQuery,
});
};
reducers[ACTIONS.CLAIM_SEARCH_FAILED] = (state: State, action: any): State => {
const { query } = action.data;
const claimSearchByQuery = Object.assign({}, state.claimSearchByQuery);
const fetchingClaimSearchByQuery = Object.assign({}, state.fetchingClaimSearchByQuery);
const claimSearchByQueryLastPageReached = Object.assign({}, state.claimSearchByQueryLastPageReached);
delete fetchingClaimSearchByQuery[query];
if (claimSearchByQuery[query] && claimSearchByQuery[query].length !== 0) {
claimSearchByQueryLastPageReached[query] = true;
} else {
claimSearchByQuery[query] = null;
}
return Object.assign({}, state, {
fetchingClaimSearchByQuery,
claimSearchByQuery,
claimSearchByQueryLastPageReached,
});
};
reducers[ACTIONS.CLAIM_REPOST_STARTED] = (state: State): State => {
return {
...state,
repostLoading: true,
repostError: null,
};
};
reducers[ACTIONS.CLAIM_REPOST_COMPLETED] = (state: State, action: any): State => {
const { originalClaimId, repostClaim } = action.data;
const byId = { ...state.byId };
const claimsByUri = { ...state.claimsByUri };
const claimThatWasReposted = byId[originalClaimId];
const repostStub = { ...repostClaim, reposted_claim: claimThatWasReposted };
byId[repostStub.claim_id] = repostStub;
claimsByUri[repostStub.permanent_url] = repostStub.claim_id;
return {
...state,
byId,
claimsByUri,
repostLoading: false,
repostError: null,
};
};
reducers[ACTIONS.CLAIM_REPOST_FAILED] = (state: State, action: any): State => {
const { error } = action.data;
return {
...state,
repostLoading: false,
repostError: error,
};
};
reducers[ACTIONS.CLEAR_REPOST_ERROR] = (state: State): State => {
return {
...state,
repostError: null,
};
};
reducers[ACTIONS.ADD_FILES_REFLECTING] = (state: State, action): State => {
const pendingClaim = action.data;
const { reflectingById } = state;
const claimId = pendingClaim && pendingClaim.claim_id;
reflectingById[claimId] = { fileListItem: pendingClaim, progress: 0, stalled: false };
return Object.assign({}, state, {
...state,
reflectingById: reflectingById,
});
};
reducers[ACTIONS.UPDATE_FILES_REFLECTING] = (state: State, action): State => {
const newReflectingById = action.data;
return Object.assign({}, state, {
...state,
reflectingById: newReflectingById,
});
};
reducers[ACTIONS.TOGGLE_CHECKING_REFLECTING] = (state: State, action): State => {
const checkingReflecting = action.data;
return Object.assign({}, state, {
...state,
checkingReflecting,
});
};
reducers[ACTIONS.TOGGLE_CHECKING_PENDING] = (state: State, action): State => {
const checking = action.data;
return Object.assign({}, state, {
...state,
checkingPending: checking,
});
};
reducers[ACTIONS.PURCHASE_LIST_STARTED] = (state: State): State => {
return {
...state,
fetchingMyPurchases: true,
fetchingMyPurchasesError: null,
};
};
reducers[ACTIONS.FETCH_LATEST_FOR_CHANNEL_DONE] = (state: State, action: any): State => {
const { uri, results } = action.data;
const latestByUri = Object.assign({}, state.latestByUri);
latestByUri[uri] = results;
return Object.assign({}, state, {
...state,
latestByUri,
});
};
reducers[ACTIONS.PURCHASE_LIST_COMPLETED] = (state: State, action: any): State => {
const { result }: { result: PurchaseListResponse, resolve: boolean } = action.data;
const page = result.page;
const totalItems = result.total_items;
let byIdDelta = {};
let byUriDelta = {};
let urlsForCurrentPage = [];
result.items.forEach((item) => {
if (!item.claim) {
// Abandoned claim
return;
}
const { claim, ...purchaseInfo } = item;
claim.purchase_receipt = purchaseInfo;
const claimId = claim.claim_id;
const uri = claim.canonical_url;
updateIfClaimChanged(state.byId, byIdDelta, claimId, claim);
updateIfValueChanged(state.claimsByUri, byUriDelta, uri, claimId);
urlsForCurrentPage.push(uri);
});
return Object.assign({}, state, {
byId: resolveDelta(state.byId, byIdDelta),
claimsByUri: resolveDelta(state.claimsByUri, byUriDelta),
myPurchases: urlsForCurrentPage,
myPurchasesPageNumber: page,
myPurchasesPageTotalResults: totalItems,
fetchingMyPurchases: false,
});
};
reducers[ACTIONS.PURCHASE_LIST_FAILED] = (state: State, action: any): State => {
const { error } = action.data;
return {
...state,
fetchingMyPurchases: false,
fetchingMyPurchasesError: error,
};
};
reducers[ACTIONS.PURCHASE_URI_COMPLETED] = (state: State, action: any): State => {
const { uri, purchaseReceipt } = action.data;
let byId = Object.assign({}, state.byId);
let byUri = Object.assign({}, state.claimsByUri);
let myPurchases = state.myPurchases ? state.myPurchases.slice() : [];
const claimId = byUri[uri];
if (claimId) {
let claim = byId[claimId];
claim.purchase_receipt = purchaseReceipt;
}
myPurchases.push(uri);
return {
...state,
byId,
myPurchases,
purchaseUriSuccess: true,
};
};
reducers[ACTIONS.PURCHASE_URI_FAILED] = (state: State): State => {
return {
...state,
purchaseUriSuccess: false,
};
};
reducers[ACTIONS.CLEAR_PURCHASED_URI_SUCCESS] = (state: State): State => {
return {
...state,
purchaseUriSuccess: false,
};
};
export function claimsReducer(state: State = defaultState, action: any) {
const handler = reducers[action.type];
if (handler) return handler(state, action);
return state;
}