From f9f53af06e5951c08f53f5ddcc1734d17bb32c13 Mon Sep 17 00:00:00 2001 From: zeppi Date: Sat, 6 Feb 2021 02:03:10 -0500 Subject: [PATCH 01/24] wip wip clean clean review wip wallet sync wip collection publishing build refactor, publishing, pending, editing wip wip fetch collections on resolve select collections or playlists build return edit success fix collection claimId selector small rename flow type fixes collection edit params type param and flowtypes --- dist/flow-typed/Claim.js | 80 ++++- dist/flow-typed/Collections.js | 40 +++ dist/flow-typed/Lbry.js | 35 +++ flow-typed/Claim.js | 80 ++++- flow-typed/Collections.js | 41 +++ flow-typed/Lbry.js | 35 +++ src/constants/action_types.js | 26 +- src/constants/collections.js | 5 + src/index.js | 42 +++ src/lbry.js | 4 + src/redux/actions/claims.js | 186 +++++++++++- src/redux/actions/collections.js | 459 +++++++++++++++++++++++++++++ src/redux/actions/sync.js | 15 + src/redux/reducers/claims.js | 146 ++++++++- src/redux/reducers/collections.js | 235 +++++++++++++++ src/redux/selectors/claims.js | 36 +++ src/redux/selectors/collections.js | 203 +++++++++++++ 17 files changed, 1650 insertions(+), 18 deletions(-) create mode 100644 dist/flow-typed/Collections.js create mode 100644 flow-typed/Collections.js create mode 100644 src/constants/collections.js create mode 100644 src/redux/actions/collections.js create mode 100644 src/redux/reducers/collections.js create mode 100644 src/redux/selectors/collections.js diff --git a/dist/flow-typed/Claim.js b/dist/flow-typed/Claim.js index 655a4bc..e03eeae 100644 --- a/dist/flow-typed/Claim.js +++ b/dist/flow-typed/Claim.js @@ -1,11 +1,15 @@ // @flow -declare type Claim = StreamClaim | ChannelClaim; +declare type Claim = StreamClaim | ChannelClaim | CollectionClaim; declare type ChannelClaim = GenericClaim & { value: ChannelMetadata, }; +declare type CollectionClaim = GenericClaim & { + value: CollectionMetadata, +}; + declare type StreamClaim = GenericClaim & { value: StreamMetadata, }; @@ -30,7 +34,7 @@ declare type GenericClaim = { short_url: string, // permanent_url with short id, no channel txid: string, // unique tx id type: 'claim' | 'update' | 'support', - value_type: 'stream' | 'channel', + value_type: 'stream' | 'channel' | 'collection', signing_channel?: ChannelClaim, reposted_claim?: GenericClaim, repost_channel_url?: string, @@ -74,6 +78,10 @@ declare type ChannelMetadata = GenericMetadata & { featured?: Array, }; +declare type CollectionMetadata = GenericMetadata & { + claims: Array, +} + declare type StreamMetadata = GenericMetadata & { license?: string, // License "title" ex: Creative Commons, Custom copyright license_url?: string, // Link to full license @@ -136,3 +144,71 @@ declare type PurchaseReceipt = { txid: string, type: 'purchase', }; + +declare type ClaimActionResolveInfo = { + [string]: { + stream: ?StreamClaim, + channel: ?ChannelClaim, + claimsInChannel: ?number, + collection: ?CollectionClaim, + }, +} + +declare type ChannelUpdateParams = { + claim_id: string, + bid?: string, + title?: string, + cover_url?: string, + thumbnail_url?: string, + description?: string, + website_url?: string, + email?: string, + tags?: Array, + replace?: boolean, + languages?: Array, + locations?: Array, + blocking?: boolean, +} + +declare type ChannelPublishParams = { + name: string, + bid: string, + blocking?: true, + title?: string, + cover_url?: string, + thumbnail_url?: string, + description?: string, + website_url?: string, + email?: string, + tags?: Array, + languages?: Array, +} + +declare type CollectionUpdateParams = { + claim_id: string, + claim_ids?: Array, + bid?: string, + title?: string, + cover_url?: string, + thumbnail_url?: string, + description?: string, + website_url?: string, + email?: string, + tags?: Array, + replace?: boolean, + languages?: Array, + locations?: Array, + blocking?: boolean, +} + +declare type CollectionPublishParams = { + name: string, + bid: string, + claim_ids: Array, + blocking?: true, + title?: string, + thumbnail_url?: string, + description?: string, + tags?: Array, + languages?: Array, +} diff --git a/dist/flow-typed/Collections.js b/dist/flow-typed/Collections.js new file mode 100644 index 0000000..83c33be --- /dev/null +++ b/dist/flow-typed/Collections.js @@ -0,0 +1,40 @@ +declare type CollectionUpdateParams = { + remove?: boolean, + claims?: Array, + name?: string, + order?: { from: number, to: number }, +} + +declare type Collection = { + id: string, + items: Array, + name: string, + type: string, + updatedAt: number, + totalItems?: number, + sourceid?: string, // if copied, claimId of original collection +}; + +declare type CollectionState = { + unpublished: CollectionGroup, + resolved: CollectionGroup, + pending: CollectionGroup, + edited: CollectionGroup, + builtin: CollectionGroup, + saved: Array, + isResolvingCollectionById: { [string]: boolean }, + error?: string | null, +}; + +declare type CollectionGroup = { + [string]: Collection, +} + +declare type CollectionEditParams = { + claims?: Array, + remove?: boolean, + claimIds?: Array, + replace?: boolean, + order?: { from: number, to: number }, + type?: string, +} diff --git a/dist/flow-typed/Lbry.js b/dist/flow-typed/Lbry.js index 0225fa2..7284455 100644 --- a/dist/flow-typed/Lbry.js +++ b/dist/flow-typed/Lbry.js @@ -170,6 +170,37 @@ declare type ChannelSignResponse = { signing_ts: string, }; +declare type CollectionCreateResponse = { + outputs: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +} + +declare type CollectionListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type CollectionResolveResponse = { + items: Array, + total_items: number, +}; + +declare type CollectionResolveOptions = { + claim_id: string, +}; + +declare type CollectionListOptions = { + page: number, + page_size: number, + resolve?: boolean, +}; + declare type FileListResponse = { items: Array, page: number, @@ -288,6 +319,10 @@ declare type LbryTypes = { support_abandon: (params: {}) => Promise, stream_repost: (params: StreamRepostOptions) => Promise, purchase_list: (params: PurchaseListOptions) => Promise, + collection_resolve: (params: CollectionResolveOptions) => Promise, + collection_list: (params: CollectionListOptions) => Promise, + collection_create: (params: {}) => Promise, + collection_update: (params: {}) => Promise, // File fetching and manipulation file_list: (params: {}) => Promise, diff --git a/flow-typed/Claim.js b/flow-typed/Claim.js index 655a4bc..e03eeae 100644 --- a/flow-typed/Claim.js +++ b/flow-typed/Claim.js @@ -1,11 +1,15 @@ // @flow -declare type Claim = StreamClaim | ChannelClaim; +declare type Claim = StreamClaim | ChannelClaim | CollectionClaim; declare type ChannelClaim = GenericClaim & { value: ChannelMetadata, }; +declare type CollectionClaim = GenericClaim & { + value: CollectionMetadata, +}; + declare type StreamClaim = GenericClaim & { value: StreamMetadata, }; @@ -30,7 +34,7 @@ declare type GenericClaim = { short_url: string, // permanent_url with short id, no channel txid: string, // unique tx id type: 'claim' | 'update' | 'support', - value_type: 'stream' | 'channel', + value_type: 'stream' | 'channel' | 'collection', signing_channel?: ChannelClaim, reposted_claim?: GenericClaim, repost_channel_url?: string, @@ -74,6 +78,10 @@ declare type ChannelMetadata = GenericMetadata & { featured?: Array, }; +declare type CollectionMetadata = GenericMetadata & { + claims: Array, +} + declare type StreamMetadata = GenericMetadata & { license?: string, // License "title" ex: Creative Commons, Custom copyright license_url?: string, // Link to full license @@ -136,3 +144,71 @@ declare type PurchaseReceipt = { txid: string, type: 'purchase', }; + +declare type ClaimActionResolveInfo = { + [string]: { + stream: ?StreamClaim, + channel: ?ChannelClaim, + claimsInChannel: ?number, + collection: ?CollectionClaim, + }, +} + +declare type ChannelUpdateParams = { + claim_id: string, + bid?: string, + title?: string, + cover_url?: string, + thumbnail_url?: string, + description?: string, + website_url?: string, + email?: string, + tags?: Array, + replace?: boolean, + languages?: Array, + locations?: Array, + blocking?: boolean, +} + +declare type ChannelPublishParams = { + name: string, + bid: string, + blocking?: true, + title?: string, + cover_url?: string, + thumbnail_url?: string, + description?: string, + website_url?: string, + email?: string, + tags?: Array, + languages?: Array, +} + +declare type CollectionUpdateParams = { + claim_id: string, + claim_ids?: Array, + bid?: string, + title?: string, + cover_url?: string, + thumbnail_url?: string, + description?: string, + website_url?: string, + email?: string, + tags?: Array, + replace?: boolean, + languages?: Array, + locations?: Array, + blocking?: boolean, +} + +declare type CollectionPublishParams = { + name: string, + bid: string, + claim_ids: Array, + blocking?: true, + title?: string, + thumbnail_url?: string, + description?: string, + tags?: Array, + languages?: Array, +} diff --git a/flow-typed/Collections.js b/flow-typed/Collections.js new file mode 100644 index 0000000..c2eba63 --- /dev/null +++ b/flow-typed/Collections.js @@ -0,0 +1,41 @@ +declare type CollectionUpdateParams = { + remove?: boolean, + claims?: Array, + name?: string, + order?: { from: number, to: number }, +} + +declare type Collection = { + id: string, + items: Array, + name: string, + type: string, + updatedAt: number, + totalItems?: number, + sourceid?: string, // if copied, claimId of original collection +}; + +declare type CollectionState = { + unpublished: CollectionGroup, + resolved: CollectionGroup, + pending: CollectionGroup, + edited: CollectionGroup, + builtin: CollectionGroup, + saved: Array, + isResolvingCollectionById: { [string]: boolean }, + error?: string | null, +}; + +declare type CollectionGroup = { + [string]: Collection, +} + +declare type CollectionEditParams = { + claims?: Array, + remove?: boolean, + claimIds?: Array, + replace?: boolean, + order?: { from: number, to: number }, + type?: string, + name?: string, +} diff --git a/flow-typed/Lbry.js b/flow-typed/Lbry.js index 0225fa2..7284455 100644 --- a/flow-typed/Lbry.js +++ b/flow-typed/Lbry.js @@ -170,6 +170,37 @@ declare type ChannelSignResponse = { signing_ts: string, }; +declare type CollectionCreateResponse = { + outputs: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +} + +declare type CollectionListResponse = { + items: Array, + page: number, + page_size: number, + total_items: number, + total_pages: number, +}; + +declare type CollectionResolveResponse = { + items: Array, + total_items: number, +}; + +declare type CollectionResolveOptions = { + claim_id: string, +}; + +declare type CollectionListOptions = { + page: number, + page_size: number, + resolve?: boolean, +}; + declare type FileListResponse = { items: Array, page: number, @@ -288,6 +319,10 @@ declare type LbryTypes = { support_abandon: (params: {}) => Promise, stream_repost: (params: StreamRepostOptions) => Promise, purchase_list: (params: PurchaseListOptions) => Promise, + collection_resolve: (params: CollectionResolveOptions) => Promise, + collection_list: (params: CollectionListOptions) => Promise, + collection_create: (params: {}) => Promise, + collection_update: (params: {}) => Promise, // File fetching and manipulation file_list: (params: {}) => Promise, diff --git a/src/constants/action_types.js b/src/constants/action_types.js index 5e3f672..bf16da2 100644 --- a/src/constants/action_types.js +++ b/src/constants/action_types.js @@ -102,6 +102,9 @@ export const ABANDON_CLAIM_SUCCEEDED = 'ABANDON_CLAIM_SUCCEEDED'; export const FETCH_CHANNEL_LIST_STARTED = 'FETCH_CHANNEL_LIST_STARTED'; export const FETCH_CHANNEL_LIST_COMPLETED = 'FETCH_CHANNEL_LIST_COMPLETED'; export const FETCH_CHANNEL_LIST_FAILED = 'FETCH_CHANNEL_LIST_FAILED'; +export const FETCH_COLLECTION_LIST_STARTED = 'FETCH_COLLECTION_LIST_STARTED'; +export const FETCH_COLLECTION_LIST_COMPLETED = 'FETCH_COLLECTION_LIST_COMPLETED'; +export const FETCH_COLLECTION_LIST_FAILED = 'FETCH_COLLECTION_LIST_FAILED'; export const CREATE_CHANNEL_STARTED = 'CREATE_CHANNEL_STARTED'; export const CREATE_CHANNEL_COMPLETED = 'CREATE_CHANNEL_COMPLETED'; export const CREATE_CHANNEL_FAILED = 'CREATE_CHANNEL_FAILED'; @@ -111,6 +114,7 @@ export const UPDATE_CHANNEL_FAILED = 'UPDATE_CHANNEL_FAILED'; export const IMPORT_CHANNEL_STARTED = 'IMPORT_CHANNEL_STARTED'; export const IMPORT_CHANNEL_COMPLETED = 'IMPORT_CHANNEL_COMPLETED'; export const IMPORT_CHANNEL_FAILED = 'IMPORT_CHANNEL_FAILED'; +export const CLEAR_CHANNEL_ERRORS = 'CLEAR_CHANNEL_ERRORS'; export const PUBLISH_STARTED = 'PUBLISH_STARTED'; export const PUBLISH_COMPLETED = 'PUBLISH_COMPLETED'; export const PUBLISH_FAILED = 'PUBLISH_FAILED'; @@ -129,7 +133,6 @@ export const CLAIM_REPOST_STARTED = 'CLAIM_REPOST_STARTED'; export const CLAIM_REPOST_COMPLETED = 'CLAIM_REPOST_COMPLETED'; export const CLAIM_REPOST_FAILED = 'CLAIM_REPOST_FAILED'; export const CLEAR_REPOST_ERROR = 'CLEAR_REPOST_ERROR'; -export const CLEAR_CHANNEL_ERRORS = 'CLEAR_CHANNEL_ERRORS'; export const CHECK_PUBLISH_NAME_STARTED = 'CHECK_PUBLISH_NAME_STARTED'; export const CHECK_PUBLISH_NAME_COMPLETED = 'CHECK_PUBLISH_NAME_COMPLETED'; export const UPDATE_PENDING_CLAIMS = 'UPDATE_PENDING_CLAIMS'; @@ -142,6 +145,27 @@ export const PURCHASE_LIST_STARTED = 'PURCHASE_LIST_STARTED'; export const PURCHASE_LIST_COMPLETED = 'PURCHASE_LIST_COMPLETED'; export const PURCHASE_LIST_FAILED = 'PURCHASE_LIST_FAILED'; +export const COLLECTION_PUBLISH_STARTED = 'COLLECTION_PUBLISH_STARTED'; +export const COLLECTION_PUBLISH_COMPLETED = 'COLLECTION_PUBLISH_COMPLETED'; +export const COLLECTION_PUBLISH_FAILED = 'COLLECTION_PUBLISH_FAILED'; +export const COLLECTION_PUBLISH_UPDATE_STARTED = 'COLLECTION_PUBLISH_UPDATE_STARTED'; +export const COLLECTION_PUBLISH_UPDATE_COMPLETED = 'COLLECTION_PUBLISH_UPDATE_COMPLETED'; +export const COLLECTION_PUBLISH_UPDATE_FAILED = 'COLLECTION_PUBLISH_UPDATE_FAILED'; +export const COLLECTION_PUBLISH_ABANDON_STARTED = 'COLLECTION_PUBLISH_ABANDON_STARTED'; +export const COLLECTION_PUBLISH_ABANDON_COMPLETED = 'COLLECTION_PUBLISH_ABANDON_COMPLETED'; +export const COLLECTION_PUBLISH_ABANDON_FAILED = 'COLLECTION_PUBLISH_ABANDON_FAILED'; +export const CLEAR_COLLECTION_ERRORS = 'CLEAR_COLLECTION_ERRORS'; +export const COLLECTION_ITEMS_RESOLVE_STARTED = 'COLLECTION_ITEMS_RESOLVE_STARTED'; +export const COLLECTION_ITEMS_RESOLVE_COMPLETED = 'COLLECTION_ITEMS_RESOLVE_COMPLETED'; +export const COLLECTION_ITEMS_RESOLVE_FAILED = 'COLLECTION_ITEMS_RESOLVE_FAILED'; +export const COLLECTION_NEW = 'COLLECTION_NEW'; +export const COLLECTION_DELETE = 'COLLECTION_DELETE'; +export const COLLECTION_PENDING = 'COLLECTION_PENDING'; +export const COLLECTION_EDIT = 'COLLECTION_EDIT'; +export const COLLECTION_COPY = 'COLLECTION_COPY'; +export const COLLECTION_SAVE = 'COLLECTION_SAVE'; +export const COLLECTION_ERROR = 'COLLECTION_ERROR'; + // Comments export const COMMENT_LIST_STARTED = 'COMMENT_LIST_STARTED'; export const COMMENT_LIST_COMPLETED = 'COMMENT_LIST_COMPLETED'; diff --git a/src/constants/collections.js b/src/constants/collections.js new file mode 100644 index 0000000..6b6d2f1 --- /dev/null +++ b/src/constants/collections.js @@ -0,0 +1,5 @@ +export const COLLECTION_ID = 'colid'; +export const COLLECTION_INDEX = 'colindex'; + +export const WATCH_LATER_ID = 'watchlater'; +export const FAVORITES_ID = 'favorites'; diff --git a/src/index.js b/src/index.js index d412d83..a7ca376 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ import * as TXO_LIST from 'constants/txo_list'; import * as SPEECH_URLS from 'constants/speech_urls'; import * as DAEMON_SETTINGS from 'constants/daemon_settings'; import * as SHARED_PREFERENCES from 'constants/shared_preferences'; +import * as COLLECTIONS_CONSTS from 'constants/collections'; import { DEFAULT_KNOWN_TAGS, DEFAULT_FOLLOWED_TAGS, MATURE_TAGS } from 'constants/tags'; import Lbry, { apiCall } from 'lbry'; import LbryFirst from 'lbry-first'; @@ -35,6 +36,7 @@ export { MATURE_TAGS, SPEECH_URLS, SHARED_PREFERENCES, + COLLECTIONS_CONSTS, }; // common @@ -57,6 +59,13 @@ export { buildSharedStateMiddleware } from 'redux/middleware/shared-state'; // actions export { doToast, doDismissToast, doError, doDismissError } from 'redux/actions/notifications'; +export { + doLocalCollectionCreate, + doFetchItemsInCollection, + doFetchItemsInCollections, + doCollectionEdit, + doLocalCollectionDelete, +} from 'redux/actions/collections'; export { doFetchClaimsByChannel, @@ -66,6 +75,7 @@ export { doResolveUris, doResolveUri, doFetchChannelListMine, + doFetchCollectionListMine, doCreateChannel, doUpdateChannel, doClaimSearch, @@ -76,6 +86,8 @@ export { doCheckPublishNameAvailability, doPurchaseList, doCheckPendingClaims, + doCollectionPublish, + doCollectionPublishUpdate, } from 'redux/actions/claims'; export { doClearPurchasedUriSuccess, doPurchaseUri, doFileGet } from 'redux/actions/file'; @@ -140,11 +152,34 @@ export { fileInfoReducer } from 'redux/reducers/file_info'; export { notificationsReducer } from 'redux/reducers/notifications'; export { publishReducer } from 'redux/reducers/publish'; export { walletReducer } from 'redux/reducers/wallet'; +export { collectionsReducer } from 'redux/reducers/collections'; // selectors export { makeSelectContentPositionForUri } from 'redux/selectors/content'; export { selectToast, selectError } from 'redux/selectors/notifications'; +export { + selectSavedCollectionIds, + selectBuiltinCollections, + selectResolvedCollections, + selectMyUnpublishedCollections, + selectMyEditedCollections, + selectMyPublishedCollections, + selectMyPublishedMixedCollections, + selectMyPublishedPlaylistCollections, + makeSelectEditedCollectionForId, + makeSelectPendingCollectionForId, + makeSelectPublishedCollectionForId, + makeSelectCollectionIsMine, + makeSelectMyPublishedCollectionForId, + makeSelectUnpublishedCollectionForId, + makeSelectCollectionForId, + makeSelectUrlsForCollectionId, + makeSelectClaimIdsForCollectionId, + makeSelectNameForCollectionId, + makeSelectIsResolvingCollectionForId, + makeSelectNextUrlForCollection, +} from 'redux/selectors/collections'; export { makeSelectClaimForUri, @@ -209,6 +244,8 @@ export { selectAllMyClaimsByOutpoint, selectMyClaimsOutpoints, selectFetchingMyChannels, + selectFetchingMyCollections, + selectMyCollectionIds, selectMyChannelClaims, selectResolvingUris, selectPlayingUri, @@ -237,6 +274,11 @@ export { selectFetchingMyPurchasesError, selectMyPurchasesCount, selectPurchaseUriSuccess, + makeSelectClaimIdForUri, + selectUpdatingCollection, + selectUpdateCollectionError, + selectCreatingCollection, + selectCreateCollectionError, } from 'redux/selectors/claims'; export { diff --git a/src/lbry.js b/src/lbry.js index bfa20eb..9a73384 100644 --- a/src/lbry.js +++ b/src/lbry.js @@ -90,6 +90,10 @@ const Lbry: LbryTypes = { support_create: params => daemonCallWithResult('support_create', params), support_list: params => daemonCallWithResult('support_list', params), stream_repost: params => daemonCallWithResult('stream_repost', params), + collection_resolve: params => daemonCallWithResult('collection_resolve', params), + collection_list: params => daemonCallWithResult('collection_list', params), + collection_create: params => daemonCallWithResult('collection_create', params), + collection_update: params => daemonCallWithResult('collection_update', params), // File fetching and manipulation file_list: (params = {}) => daemonCallWithResult('file_list', params), diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index cd0cb6d..98cee29 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -11,13 +11,20 @@ import { selectMyChannelClaims, selectPendingIds, selectClaimsById, + makeSelectClaimForClaimId, } from 'redux/selectors/claims'; + import { doFetchTxoPage } from 'redux/actions/wallet'; import { selectSupportsByOutpoint } from 'redux/selectors/wallet'; import { creditsToString } from 'util/format-credits'; import { batchActions } from 'util/batch-actions'; import { createNormalizedClaimSearchKey } from 'util/claim'; import { PAGE_SIZE } from 'constants/claim'; +import { + makeSelectEditedCollectionForId, + selectPendingCollections, +} from 'redux/selectors/collections'; +import { doFetchItemsInCollection, doFetchItemsInCollections } from 'redux/actions/collections'; type ResolveEntries = Array<[string, GenericClaim]>; @@ -61,12 +68,16 @@ export function doResolveUris( stream: ?StreamClaim, channel: ?ChannelClaim, claimsInChannel: ?number, + collection: ?CollectionClaim, }, } = {}; + const collectionIds: Array = []; + return Lbry.resolve({ urls: urisToResolve, ...options }).then( async(result: ResolveResponse) => { let repostedResults = {}; + const collectionClaimIdsToResolve = []; const repostsToResolve = []; const fallbackResolveInfo = { stream: null, @@ -80,6 +91,7 @@ export function doResolveUris( // https://github.com/facebook/flow/issues/2221 if (uriResolveInfo) { if (uriResolveInfo.error) { + // $FlowFixMe resolveInfo[uri] = { ...fallbackResolveInfo }; } else { if (checkReposts) { @@ -96,6 +108,10 @@ export function doResolveUris( result.channel = uriResolveInfo; // $FlowFixMe result.claimsInChannel = uriResolveInfo.meta.claims_in_channel; + } else if (uriResolveInfo.value_type === 'collection') { + result.collection = uriResolveInfo; + // $FlowFixMe + collectionIds.push(uriResolveInfo.claim_id); } else { result.stream = uriResolveInfo; if (uriResolveInfo.signing_channel) { @@ -127,6 +143,13 @@ export function doResolveUris( type: ACTIONS.RESOLVE_URIS_COMPLETED, data: { resolveInfo }, }); + + if (collectionIds.length) { + dispatch(doFetchItemsInCollections({ collectionIds: collectionIds, pageSize: 5 })); + } + // now collection claims are added, get their stuff + // if collections: doResolveCollections(claimIds) + return result; } ); @@ -573,11 +596,43 @@ export function doFetchChannelListMine( }; } +export function doFetchCollectionListMine(page: number = 1, pageSize: number = 99999) { + return (dispatch: Dispatch) => { + dispatch({ + type: ACTIONS.FETCH_COLLECTION_LIST_STARTED, + }); + + const callback = (response: CollectionListResponse) => { + const { items } = response; + dispatch({ + type: ACTIONS.FETCH_COLLECTION_LIST_COMPLETED, + data: { claims: items }, + }); + dispatch( + doFetchItemsInCollections({ + collectionIds: items.map(claim => claim.claim_id), + page_size: 5, + }) + ); + // update or fetch collections? + }; + + const failure = error => { + dispatch({ + type: ACTIONS.FETCH_COLLECTION_LIST_FAILED, + data: error, + }); + }; + + Lbry.collection_list({ page, page_size: pageSize, resolve_claims: 1 }).then(callback, failure); + }; +} + export function doClaimSearch( options: { page_size: number, page: number, - no_totals: boolean, + no_totals?: boolean, any_tags?: Array, claim_ids?: Array, channel_ids?: Array, @@ -618,7 +673,8 @@ export function doClaimSearch( pageSize: options.page_size, }, }); - return true; + // was return true + return resolveInfo; }; const failure = err => { @@ -679,6 +735,128 @@ export function doRepost(options: StreamRepostOptions) { }; } +export function doCollectionPublish( + options: { + name: string, + bid: string, + blocking: true, + title?: string, + channel_id?: string, + thumbnail_url?: string, + description?: string, + tags?: Array, + languages?: Array, + claims: Array, + }, + localId: string +) { + return (dispatch: Dispatch) => { + // $FlowFixMe + return new Promise(resolve => { + dispatch({ + type: ACTIONS.COLLECTION_PUBLISH_STARTED, + }); + + function success(response) { + const collectionClaim = response.outputs[0]; + dispatch( + batchActions( + { + type: ACTIONS.COLLECTION_PUBLISH_COMPLETED, + }, + // shift unpublished collection to pending collection with new publish id + // recent publish won't resolve this second. handle it in checkPending + { + type: ACTIONS.COLLECTION_PENDING, + data: { localId: localId, claimId: collectionClaim.claim_id }, + }, + { + type: ACTIONS.UPDATE_PENDING_CLAIMS, + data: { + claims: [collectionClaim], + }, + } + ) + ); + dispatch(doCheckPendingClaims()); + dispatch(doFetchCollectionListMine(1, 10)); + resolve(collectionClaim); + } + + function failure(error) { + dispatch({ + type: ACTIONS.COLLECTION_PUBLISH_FAILED, + data: { + error: error.message, + }, + }); + } + + Lbry.collection_create(options).then(success, failure); + }); + }; +} + +export function doCollectionPublishUpdate(options: { + bid?: string, + blocking?: true, + title?: string, + thumbnail_url?: string, + description?: string, + claim_id: string, + tags?: Array, + languages?: Array, +}) { + return (dispatch: Dispatch, getState: GetState) => { + const state = getState(); + // select claim forclaim_id + // get publish params from claim + // $FlowFixMe + + const collectionClaim = makeSelectClaimForClaimId(options.claim_id)(state); + // TODO: add old claim entries to params + const editItems = makeSelectEditedCollectionForId(options.claim_id)(state); + const oldParams: CollectionUpdateParams = { + bid: collectionClaim.amount, + }; + // $FlowFixMe + return new Promise(resolve => { + dispatch({ + type: ACTIONS.COLLECTION_PUBLISH_UPDATE_STARTED, + }); + + function success(response) { + const collectionClaim = response.outputs[0]; + dispatch({ + type: ACTIONS.COLLECTION_PUBLISH_UPDATE_COMPLETED, + data: { + collectionClaim, + }, + }); + dispatch({ + type: ACTIONS.UPDATE_PENDING_CLAIMS, + data: { + claims: [collectionClaim], + }, + }); + dispatch(doFetchCollectionListMine(1, 10)); + resolve(collectionClaim); + } + + function failure(error) { + dispatch({ + type: ACTIONS.COLLECTION_PUBLISH_UPDATE_FAILED, + data: { + error: error.message, + }, + }); + } + + Lbry.collection_update(options).then(success, failure); + }); + }; +} + export function doCheckPublishNameAvailability(name: string) { return (dispatch: Dispatch) => { dispatch({ @@ -750,6 +928,7 @@ export const doCheckPendingClaims = (onConfirmed: Function) => ( const checkClaimList = () => { const state = getState(); const pendingIdSet = new Set(selectPendingIds(state)); + const pendingCollections = selectPendingCollections(state); Lbry.claim_list({ page: 1, page_size: 10 }) .then(result => { const claims = result.items; @@ -758,6 +937,9 @@ export const doCheckPendingClaims = (onConfirmed: Function) => ( const { claim_id: claimId } = claim; if (claim.confirmations > 0 && pendingIdSet.has(claimId)) { pendingIdSet.delete(claimId); + if (Object.keys(pendingCollections).includes(claim.claim_id)) { + dispatch(doFetchItemsInCollection({ collectionId: claim.claim_id })); + } claimsToConfirm.push(claim); if (onConfirmed) { onConfirmed(claim); diff --git a/src/redux/actions/collections.js b/src/redux/actions/collections.js new file mode 100644 index 0000000..55a8c98 --- /dev/null +++ b/src/redux/actions/collections.js @@ -0,0 +1,459 @@ +// @flow +import * as ACTIONS from 'constants/action_types'; +import { v4 as uuid } from 'uuid'; +import Lbry from 'lbry'; +import { doClaimSearch } from 'redux/actions/claims'; +import { makeSelectClaimForClaimId } from 'redux/selectors/claims'; +import { + makeSelectCollectionForId, + // makeSelectPublishedCollectionForId, // for "save" or "copy" action + makeSelectMyPublishedCollectionForId, + makeSelectUnpublishedCollectionForId, + makeSelectEditedCollectionForId, +} from 'redux/selectors/collections'; +const WATCH_LATER_ID = 'watchlater'; +const FAVORITES_ID = 'favorites'; + +const BUILTIN_LISTS = [WATCH_LATER_ID, FAVORITES_ID]; + +const getTimestamp = () => { + return Math.floor(Date.now() / 1000); +}; + +// maybe take items param +export const doLocalCollectionCreate = ( + name: string, + collectionItems: string, + sourceId: string +) => (dispatch: Dispatch) => { + return dispatch({ + type: ACTIONS.COLLECTION_NEW, + data: { + entry: { + id: uuid(), // start with a uuid, this becomes a claimId after publish + name: name, + updatedAt: getTimestamp(), + items: collectionItems || [], + sourceId: sourceId, + }, + }, + }); +}; + +export const doLocalCollectionDelete = (id: string) => (dispatch: Dispatch) => { + return dispatch({ + type: ACTIONS.COLLECTION_DELETE, + data: { + id: id, + }, + }); +}; + +// Given a collection, save its collectionId to be resolved and displayed in Library +// export const doCollectionSave = ( +// id: string, +// ) => (dispatch: Dispatch) => { +// return dispatch({ +// type: ACTIONS.COLLECTION_SAVE, +// data: { +// id: id, +// }, +// }); +// }; + +// Given a collection and name, copy it to a local private collection with a name +// export const doCollectionCopy = ( +// id: string, +// ) => (dispatch: Dispatch) => { +// return dispatch({ +// type: ACTIONS.COLLECTION_COPY, +// data: { +// id: id, +// }, +// }); +// }; + +export const doFetchItemsInCollections = ( + resolveItemsOptions: { + collectionIds: Array, + pageSize?: number, + }, + resolveStartedCallback?: () => void +) => async (dispatch: Dispatch, getState: GetState) => { + let state = getState(); + // for each collection id, + // make sure the collection is resolved, the items are resolved, and build the collection objects + + const { collectionIds, pageSize } = resolveItemsOptions; + + dispatch({ + type: ACTIONS.COLLECTION_ITEMS_RESOLVE_STARTED, + data: { ids: collectionIds }, + }); + + if (resolveStartedCallback) resolveStartedCallback(); + + const collectionIdsToSearch = collectionIds.filter(claimId => !state.claims.byId[claimId]); + if (collectionIdsToSearch.length) { + let claimSearchOptions = { claim_ids: collectionIdsToSearch, page: 1, page_size: 9999 }; + await dispatch(doClaimSearch(claimSearchOptions)); + } + const invalidCollectionIds = []; + const stateAfterClaimSearch = getState(); + + async function resolveCollectionItems(claimId, totalItems, pageSize) { + // take [ {}, {} ], return {} + // only need items [ Claim... ] and total_items + const mergeResults = (arrayOfResults: Array<{ items: any, total_items: number }>) => { + const mergedResults: { items: Array, total_items: number } = { + items: [], + total_items: 0, + }; + arrayOfResults.forEach(result => { + mergedResults.items = mergedResults.items.concat(result.items); + mergedResults.total_items = result.total_items; + }); + return mergedResults; + }; + + try { + const BATCH_SIZE = 10; // up batch size when sdk bug fixed + const batches = []; + let fetchResult; + if (!pageSize) { + // batch all + for (let i = 0; i < Math.ceil(totalItems / BATCH_SIZE); i++) { + batches[i] = Lbry.collection_resolve({ + claim_id: claimId, + page: i + 1, + page_size: BATCH_SIZE, + }); + } + const resultArray = await Promise.all(batches); + fetchResult = mergeResults(resultArray); + } else { + fetchResult = await Lbry.collection_resolve({ + claim_id: claimId, + page: 1, + page_size: pageSize, + }); + } + // $FlowFixMe + const itemsById: { claimId: string, items?: ?Array } = { claimId: claimId }; + if (fetchResult.items) { + itemsById.items = fetchResult.items; + } else { + itemsById.items = null; + } + return itemsById; + } catch (e) { + return { + claimId: claimId, + items: null, + }; + } + } + + const promises = []; + collectionIds.forEach(collectionId => { + const claim = makeSelectClaimForClaimId(collectionId)(stateAfterClaimSearch); + if (!claim) { + invalidCollectionIds.push(collectionId); + } else { + const claimCount = claim.value.claims && claim.value.claims.length; + if (pageSize) { + promises.push(resolveCollectionItems(collectionId, claimCount, pageSize)); + } else { + promises.push(resolveCollectionItems(collectionId, claimCount)); + } + } + }); + + // $FlowFixMe + const resolvedCollectionItemsById: Array<{ + claimId: string, + items: ?Array, + }> = await Promise.all(promises); + + function processClaims(resultClaimsByUri) { + const processedClaims = {}; + Object.entries(resultClaimsByUri).forEach(([uri, uriResolveInfo]) => { + // Flow has terrible Object.entries support + // https://github.com/facebook/flow/issues/2221 + if (uriResolveInfo) { + let result = {}; + if (uriResolveInfo.value_type === 'channel') { + result.channel = uriResolveInfo; + // $FlowFixMe + result.claimsInChannel = uriResolveInfo.meta.claims_in_channel; + // ALSO SKIP COLLECTIONS + } else { + result.stream = uriResolveInfo; + if (uriResolveInfo.signing_channel) { + result.channel = uriResolveInfo.signing_channel; + result.claimsInChannel = + (uriResolveInfo.signing_channel.meta && + uriResolveInfo.signing_channel.meta.claims_in_channel) || + 0; + } + } + // $FlowFixMe + processedClaims[uri] = result; + } + }); + return processedClaims; + } + + const newCollectionItemsById = {}; + const flatResolvedCollectionItems = {}; + resolvedCollectionItemsById.forEach(entry => { + // $FlowFixMe + const collectionItems: Array = entry.items; + const collectionId = entry.claimId; + if (collectionItems) { + const claim = makeSelectClaimForClaimId(collectionId)(stateAfterClaimSearch); + + const editedCollection = makeSelectEditedCollectionForId(collectionId)(stateAfterClaimSearch); + const { name, timestamp } = claim || {}; + const valueTypes = new Set(); + const streamTypes = new Set(); + + let items = []; + collectionItems.forEach(collectionItem => { + // here's where we would just items.push(collectionItem.permanent_url + items.push(collectionItem.permanent_url); + valueTypes.add(collectionItem.value_type); + if (collectionItem.value.stream_type) { + streamTypes.add(collectionItem.value.stream_type); + } + flatResolvedCollectionItems[collectionItem.canonical_url] = collectionItem; + }); + const isPlaylist = + valueTypes.size === 1 && + valueTypes.has('stream') && + ((streamTypes.size === 1 && (streamTypes.has('audio') || streamTypes.has('video'))) || + (streamTypes.size === 2 && (streamTypes.has('audio') && streamTypes.has('video')))); + + newCollectionItemsById[collectionId] = { + items, + id: collectionId, + name: name, + itemCount: claim.value.claims.length, + type: isPlaylist ? 'playlist' : 'collection', + updatedAt: timestamp, + }; + // clear any stale edits + if (editedCollection && timestamp > editedCollection['updatedAt']) { + dispatch({ + type: ACTIONS.COLLECTION_DELETE, + data: { + id: collectionId, + collectionKey: 'edited', + }, + }); + } + } else { + // no collection items? probably in pending. + } + }); + const processedClaimsByUri = processClaims(flatResolvedCollectionItems); + + dispatch({ + type: ACTIONS.RESOLVE_URIS_COMPLETED, + data: { resolveInfo: processedClaimsByUri }, + }); + + dispatch({ + type: ACTIONS.COLLECTION_ITEMS_RESOLVE_COMPLETED, + data: { + resolvedCollections: newCollectionItemsById, + failedCollectionIds: invalidCollectionIds, + }, + }); +}; + +export const doFetchItemsInCollection = ( + options: { collectionId: string, pageSize?: number }, + cb?: () => void +) => { + const { collectionId, pageSize } = options; + const newOptions: { collectionIds: Array, pageSize?: number } = { + collectionIds: [collectionId], + }; + if (pageSize) newOptions.pageSize = pageSize; + return doFetchItemsInCollections(newOptions, cb); +}; + +export const doCollectionEdit = (collectionId: string, params: CollectionEditParams) => async ( + dispatch: Dispatch, + getState: GetState +) => { + const state = getState(); + const collection: Collection = makeSelectCollectionForId(collectionId)(state); + const editedCollection: Collection = makeSelectEditedCollectionForId(collectionId)(state); + const unpublishedCollection: Collection = makeSelectUnpublishedCollectionForId(collectionId)( + state + ); + const publishedCollection: Collection = makeSelectMyPublishedCollectionForId(collectionId)(state); + + const generateCollectionItemsFromSearchResult = results => { + return ( + Object.values(results) + // $FlowFixMe + .reduce( + ( + acc, + cur: { + stream: ?StreamClaim, + channel: ?ChannelClaim, + claimsInChannel: ?number, + collection: ?CollectionClaim, + } + ) => { + let url; + if (cur.stream) { + url = cur.stream.permanent_url; + } else if (cur.channel) { + url = cur.channel.permanent_url; + } else if (cur.collection) { + url = cur.collection.permanent_url; + } else { + return acc; + } + acc.push(url); + return acc; + }, + [] + ) + ); + }; + + if (!collection) { + return dispatch({ + type: ACTIONS.COLLECTION_ERROR, + data: { + message: 'collection does not exist', + }, + }); + } + + let currentItems = collection.items ? collection.items.concat() : []; + const { claims: passedClaims, order, claimIds, replace, remove, type } = params; + + const collectionType = type || 'collection'; + let newItems: Array = currentItems; + + if (passedClaims) { + if (remove) { + const passedUrls = passedClaims.map(claim => claim.permanent_url); + // $FlowFixMe // need this? + newItems = currentItems.filter((item: string) => !passedUrls.includes(item)); + } else { + passedClaims.forEach(claim => newItems.push(claim.permanent_url)); + } + } + + if (claimIds) { + const batches = []; + if (claimIds.length > 50) { + for (let i = 0; i < Math.ceil(claimIds.length / 50); i++) { + batches[i] = claimIds.slice(i * 50, (i + 1) * 50); + } + } else { + batches[0] = claimIds; + } + const resultArray = await Promise.all( + batches.map(batch => { + let options = { claim_ids: batch, page: 1, page_size: 50 }; + return dispatch(doClaimSearch(options)); + }) + ); + + const searchResults = Object.assign({}, ...resultArray); + + if (replace) { + newItems = generateCollectionItemsFromSearchResult(searchResults); + } else { + newItems = currentItems.concat(generateCollectionItemsFromSearchResult(searchResults)); + } + } + + if (order) { + const [movedItem] = currentItems.splice(order.from, 1); + currentItems.splice(order.to, 0, movedItem); + } + + if (editedCollection) { + if (publishedCollection.items.join(',') === newItems.join(',')) { + // delete edited if newItems are the same as publishedItems + dispatch({ + type: ACTIONS.COLLECTION_DELETE, + data: { + id: collectionId, + collectionKey: 'edited', + }, + }); + } else { + dispatch({ + type: ACTIONS.COLLECTION_EDIT, + data: { + id: collectionId, + collectionKey: 'edited', + collection: { + items: newItems, + id: collectionId, + name: params.name || collection.name, + updatedAt: getTimestamp(), + type: collectionType, + }, + }, + }); + } + } else if (publishedCollection) { + dispatch({ + type: ACTIONS.COLLECTION_EDIT, + data: { + id: collectionId, + collectionKey: 'edited', + collection: { + items: newItems, + id: collectionId, + name: params.name || collection.name, + updatedAt: getTimestamp(), + type: collectionType, + }, + }, + }); + } else if (BUILTIN_LISTS.includes(collectionId)) { + dispatch({ + type: ACTIONS.COLLECTION_EDIT, + data: { + id: collectionId, + collectionKey: 'builtin', + collection: { + items: newItems, + id: collectionId, + name: params.name || collection.name, + updatedAt: getTimestamp(), + type: collectionType, + }, + }, + }); + } else if (unpublishedCollection) { + dispatch({ + type: ACTIONS.COLLECTION_EDIT, + data: { + id: collectionId, + collectionKey: 'unpublished', + collection: { + items: newItems, + id: collectionId, + name: params.name || collection.name, + updatedAt: getTimestamp(), + type: collectionType, + }, + }, + }); + } + return true; +}; diff --git a/src/redux/actions/sync.js b/src/redux/actions/sync.js index 14c7f00..d1812a5 100644 --- a/src/redux/actions/sync.js +++ b/src/redux/actions/sync.js @@ -13,6 +13,9 @@ type SharedData = { settings?: any, app_welcome_version?: number, sharing_3P?: boolean, + unpublishedCollectionTest: CollectionGroup, + builtinCollectionTest: CollectionGroup, + savedCollectionTest: Array, }, }; @@ -27,6 +30,9 @@ function extractUserState(rawObj: SharedData) { settings, app_welcome_version, sharing_3P, + unpublishedCollectionTest, + builtinCollectionTest, + savedCollectionTest, } = rawObj.value; return { @@ -38,6 +44,9 @@ function extractUserState(rawObj: SharedData) { ...(settings ? { settings } : {}), ...(app_welcome_version ? { app_welcome_version } : {}), ...(sharing_3P ? { sharing_3P } : {}), + ...(unpublishedCollectionTest ? { unpublishedCollectionTest } : {}), + ...(builtinCollectionTest ? { builtinCollectionTest } : {}), + ...(savedCollectionTest ? { savedCollectionTest } : {}), }; } @@ -55,6 +64,9 @@ export function doPopulateSharedUserState(sharedSettings: any) { settings, app_welcome_version, sharing_3P, + unpublishedCollectionTest, + builtinCollectionTest, + savedCollectionTest, } = extractUserState(sharedSettings); dispatch({ type: ACTIONS.USER_STATE_POPULATE, @@ -67,6 +79,9 @@ export function doPopulateSharedUserState(sharedSettings: any) { settings, welcomeVersion: app_welcome_version, allowAnalytics: sharing_3P, + unpublishedCollectionTest, + builtinCollectionTest, + savedCollectionTest, }, }); }; diff --git a/src/redux/reducers/claims.js b/src/redux/reducers/claims.js index 1b77d3a..21b3c01 100644 --- a/src/redux/reducers/claims.js +++ b/src/redux/reducers/claims.js @@ -13,6 +13,7 @@ import mergeClaim from 'util/merge-claim'; type State = { createChannelError: ?string, + createCollectionError: ?string, channelClaimCounts: { [string]: number }, claimsByUri: { [string]: string }, byId: { [string]: Claim }, @@ -21,9 +22,11 @@ type State = { reflectingById: { [string]: ReflectingUpdate }, myClaims: ?Array, myChannelClaims: ?Array, + myCollectionClaims: ?Array, abandoningById: { [string]: boolean }, fetchingChannelClaims: { [string]: number }, fetchingMyChannels: boolean, + fetchingMyCollections: boolean, fetchingClaimSearchByQuery: { [string]: boolean }, purchaseUriSuccess: boolean, myPurchases: ?Array, @@ -34,6 +37,7 @@ type State = { claimSearchByQuery: { [string]: Array }, claimSearchByQueryLastPageReached: { [string]: Array }, creatingChannel: boolean, + creatingCollection: boolean, paginatedClaimsByChannel: { [string]: { all: Array, @@ -43,7 +47,9 @@ type State = { }, }, updateChannelError: ?string, + updateCollectionError: ?string, updatingChannel: boolean, + updatingCollection: boolean, pendingChannelImport: string | boolean, repostLoading: boolean, repostError: ?string, @@ -66,6 +72,7 @@ const defaultState = { fetchingChannelClaims: {}, resolvingUris: [], myChannelClaims: undefined, + myCollectionClaims: [], myClaims: undefined, myPurchases: undefined, myPurchasesPageNumber: undefined, @@ -74,6 +81,7 @@ const defaultState = { fetchingMyPurchases: false, fetchingMyPurchasesError: undefined, fetchingMyChannels: false, + fetchingMyCollections: false, abandoningById: {}, pendingIds: [], reflectingById: {}, @@ -82,9 +90,13 @@ const defaultState = { claimSearchByQueryLastPageReached: {}, fetchingClaimSearchByQuery: {}, updateChannelError: '', + updateCollectionError: '', updatingChannel: false, creatingChannel: false, createChannelError: undefined, + updatingCollection: false, + creatingCollection: false, + createCollectionError: undefined, pendingChannelImport: false, repostLoading: false, repostError: undefined, @@ -100,15 +112,7 @@ const defaultState = { }; function handleClaimAction(state: State, action: any): State { - const { - resolveInfo, - }: { - [string]: { - stream: ?StreamClaim, - channel: ?ChannelClaim, - claimsInChannel: ?number, - }, - } = action.data; + const { resolveInfo }: ClaimActionResolveInfo = action.data; const byUri = Object.assign({}, state.claimsByUri); const byId = Object.assign({}, state.byId); @@ -119,7 +123,7 @@ function handleClaimAction(state: State, action: any): State { Object.entries(resolveInfo).forEach(([url: string, resolveResponse: ResolveResponse]) => { // $FlowFixMe - const { claimsInChannel, stream, channel: channelFromResolve } = resolveResponse; + const { claimsInChannel, stream, channel: channelFromResolve, collection } = resolveResponse; const channel = channelFromResolve || (stream && stream.signing_channel); if (stream) { @@ -165,8 +169,29 @@ function handleClaimAction(state: State, action: any): State { newResolvingUrls.delete(channel.permanent_url); } + if (collection) { + if (pendingIds.includes(collection.claim_id)) { + byId[collection.claim_id] = mergeClaim(collection, byId[collection.claim_id]); + } else { + byId[collection.claim_id] = collection; + } + byUri[url] = collection.claim_id; + + // If url isn't a canonical_url, make sure that is added too + byUri[collection.canonical_url] = collection.claim_id; + + // Also add the permanent_url here until lighthouse returns canonical_url for search results + byUri[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 && !pendingIds.includes(byUri[url])) { + if (!stream && !channel && !collection && !pendingIds.includes(byUri[url])) { byUri[url] = null; } }); @@ -306,6 +331,57 @@ reducers[ACTIONS.FETCH_CHANNEL_LIST_FAILED] = (state: State, action: any): State }); }; +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 } = action.data; + const myClaims = state.myClaims || []; + let myClaimIds = new Set(myClaims); + const pendingIds = state.pendingIds || []; + let myCollectionClaims; + const byId = Object.assign({}, state.byId); + const byUri = Object.assign({}, state.claimsByUri); + + if (!claims.length) { + // $FlowFixMe + myCollectionClaims = null; + } else { + myCollectionClaims = new Set(state.myCollectionClaims); + claims.forEach(claim => { + const { meta } = claim; + const { canonical_url: canonicalUrl, permanent_url: permanentUrl, claim_id: claimId } = claim; + // maybe add info about items in collection + + byUri[canonicalUrl] = claimId; + byUri[permanentUrl] = claimId; + + // $FlowFixMe + myCollectionClaims.add(claimId); + // we don't want to overwrite a pending result with a resolve + if (!pendingIds.some(c => c === claimId)) { + byId[claimId] = claim; + } + myClaimIds.add(claimId); + }); + } + + return { + ...state, + byId, + claimsByUri: byUri, + fetchingMyCollections: false, + myCollectionClaims: myCollectionClaims ? Array.from(myCollectionClaims) : null, + 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); @@ -520,6 +596,54 @@ reducers[ACTIONS.UPDATE_CHANNEL_FAILED] = (state: State, action: any): State => }); }; +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 => { + return Object.assign({}, state, { + creatingCollection: false, + }); +}; + +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, + }); +}; + +// COLLECTION_PUBLISH_ABANDON_... + reducers[ACTIONS.IMPORT_CHANNEL_STARTED] = (state: State): State => Object.assign({}, state, { pendingChannelImports: true }); diff --git a/src/redux/reducers/collections.js b/src/redux/reducers/collections.js new file mode 100644 index 0000000..0f93e43 --- /dev/null +++ b/src/redux/reducers/collections.js @@ -0,0 +1,235 @@ +// @flow +import { handleActions } from 'util/redux-utils'; +import * as ACTIONS from 'constants/action_types'; +import * as COLLECTION_CONSTS from 'constants/collections'; + +const WATCH_LATER_ID = 'watchlater'; +const FAVORITES_ID = 'favorites'; + +const BUILTIN_LISTS = [WATCH_LATER_ID, FAVORITES_ID]; +const getTimestamp = () => { + return Math.floor(Date.now() / 1000); +}; + +const defaultState: CollectionState = { + builtin: { + watchlater: { + items: ['lbry://seriouspublish#c1b740eb88f96b465f65e5f1542564539df1c62e'], + id: WATCH_LATER_ID, + name: 'Watch Later', + updatedAt: getTimestamp(), + type: 'playlist', + }, + favorites: { + items: ['lbry://seriouspublish#c1b740eb88f96b465f65e5f1542564539df1c62e'], + id: FAVORITES_ID, + name: 'Favorites', + type: 'collection', + updatedAt: getTimestamp(), + }, + }, + resolved: {}, + unpublished: {}, // sync + edited: {}, + pending: {}, + saved: [], + isResolvingCollectionById: {}, + error: null, +}; + +const collectionsReducer = handleActions( + { + [ACTIONS.COLLECTION_NEW]: (state, action) => { + const { entry: params } = action.data; // { id:, items: Array} + // entry + const newListTemplate = { + id: params.id, + name: params.name, + items: [], + updatedAt: getTimestamp(), + type: params.type || 'mixed', + }; + + const newList = Object.assign({}, newListTemplate, { ...params }); + const { unpublished: lists } = state; + const newLists = Object.assign({}, lists, { [params.id]: newList }); + + return { + ...state, + unpublished: newLists, + }; + }, + + [ACTIONS.COLLECTION_DELETE]: (state, action) => { + const { id, collectionKey } = action.data; + const { edited: editList, unpublished: unpublishedList, pending: pendingList } = state; + const newEditList = Object.assign({}, editList); + const newUnpublishedList = Object.assign({}, unpublishedList); + + const newPendingList = Object.assign({}, pendingList); + + if (collectionKey && state[collectionKey] && state[collectionKey][id]) { + delete state[collectionKey][id]; + } else { + if (newEditList[id]) { + delete newEditList[id]; + } else if (newUnpublishedList[id]) { + delete newUnpublishedList[id]; + } else if (newPendingList[id]) { + delete newPendingList[id]; + } + } + return { + ...state, + edited: newEditList, + unpublished: newUnpublishedList, + pending: newPendingList, + }; + }, + + [ACTIONS.COLLECTION_PENDING]: (state, action) => { + const { localId, claimId } = action.data; + const { edited: editList, unpublished: unpublishedList, pending: pendingList } = state; + const newEditList = Object.assign({}, editList); + const newUnpublishedList = Object.assign({}, unpublishedList); + const newPendingList = Object.assign({}, pendingList); + + const isEdit = editList[localId]; + if (localId) { + // pending from unpublished -> published + // delete from local + newPendingList[claimId] = Object.assign( + {}, + newEditList[localId] || newUnpublishedList[localId] || {} + ); + if (isEdit) { + delete newEditList[localId]; + } else { + delete newUnpublishedList[localId]; + } + } else { + // pending from edited published -> published + if (isEdit) { + newPendingList[claimId] = Object.assign({}, newEditList[claimId]); + delete newEditList[claimId]; + } + } + + return { + ...state, + edited: newEditList, + unpublished: newUnpublishedList, + pending: newPendingList, + }; + }, + + [ACTIONS.COLLECTION_EDIT]: (state, action) => { + const { id, collectionKey, collection } = action.data; + + if (BUILTIN_LISTS.includes(id)) { + const { builtin: lists } = state; + return { + ...state, + [collectionKey]: { ...lists, [id]: collection }, + }; + } + + if (collectionKey === 'edited') { + const { edited: lists } = state; + return { + ...state, + edited: { ...lists, [id]: collection }, + }; + } + const { unpublished: lists } = state; + return { + ...state, + unpublished: { ...lists, [id]: collection }, + }; + }, + + [ACTIONS.COLLECTION_ERROR]: (state, action) => { + return Object.assign({}, state, { + error: action.data.message, + }); + }, + + [ACTIONS.COLLECTION_ITEMS_RESOLVE_STARTED]: (state, action) => { + const { ids } = action.data; + const { isResolvingCollectionById } = state; + const newResolving = Object.assign({}, isResolvingCollectionById); + ids.forEach(id => { + newResolving[id] = true; + }); + return Object.assign({}, state, { + ...state, + error: '', + isResolvingCollectionById: newResolving, + }); + }, + [ACTIONS.USER_STATE_POPULATE]: (state, action) => { + const { builtinCollectionTest, savedCollectionTest, unpublishedCollectionTest } = action.data; + return { + ...state, + unpublished: unpublishedCollectionTest || state.unpublished, + builtin: builtinCollectionTest || state.builtin, + saved: savedCollectionTest || state.saved, + }; + }, + [ACTIONS.COLLECTION_ITEMS_RESOLVE_COMPLETED]: (state, action) => { + const { resolvedCollections, failedCollectionIds } = action.data; + const { + pending: pendingList, + edited: editList, + isResolvingCollectionById, + resolved: lists, + } = state; + const resolvedIds = Object.keys(resolvedCollections); + const newResolving = Object.assign({}, isResolvingCollectionById); + if (resolvedCollections && resolvedCollections.length) { + resolvedIds.forEach(resolvedId => { + if (editList[resolvedId]) { + if (editList[resolvedId]['updatedAt'] < resolvedCollections[resolvedId]['updatedAt']) { + delete editList[resolvedId]; + } + } + delete newResolving[resolvedId]; + if (pendingList[resolvedId]) { + delete pendingList[resolvedId]; + } + }); + } + + if (failedCollectionIds && failedCollectionIds.length) { + failedCollectionIds.forEach(failedId => { + delete newResolving[failedId]; + }); + } + + const newLists = Object.assign({}, lists, resolvedCollections); + + return Object.assign({}, state, { + ...state, + pending: pendingList, + resolved: newLists, + isResolvingCollectionById: newResolving, + }); + }, + [ACTIONS.COLLECTION_ITEMS_RESOLVE_FAILED]: (state, action) => { + const { ids } = action.data; + const { isResolvingCollectionById } = state; + const newResolving = Object.assign({}, isResolvingCollectionById); + ids.forEach(id => { + delete newResolving[id]; + }); + return Object.assign({}, state, { + ...state, + isResolvingCollectionById: newResolving, + error: action.data.message, + }); + }, + }, + defaultState +); + +export { collectionsReducer }; diff --git a/src/redux/selectors/claims.js b/src/redux/selectors/claims.js index d7b151d..cca04fd 100644 --- a/src/redux/selectors/claims.js +++ b/src/redux/selectors/claims.js @@ -95,6 +95,12 @@ export const makeSelectClaimIsPending = (uri: string) => } ); +export const makeSelectClaimIdForUri = (uri: string) => + createSelector( + selectClaimIdsByUri, + claimIds => claimIds[uri] + ); + export const selectReflectingById = createSelector( selectState, state => state.reflectingById @@ -531,6 +537,11 @@ export const selectFetchingMyChannels = createSelector( state => state.fetchingMyChannels ); +export const selectFetchingMyCollections = createSelector( + selectState, + state => state.fetchingMyCollections +); + export const selectMyChannelClaims = createSelector( selectState, selectClaimsById, @@ -557,6 +568,11 @@ export const selectMyChannelUrls = createSelector( claims => (claims ? claims.map(claim => claim.canonical_url || claim.permanent_url) : undefined) ); +export const selectMyCollectionIds = createSelector( + selectState, + state => state.myCollectionClaims +); + export const selectResolvingUris = createSelector( selectState, state => state.resolvingUris || [] @@ -917,3 +933,23 @@ export const makeSelectStakedLevelForChannelUri = (uri: string) => return level; } ); + +export const selectUpdatingCollection = createSelector( + selectState, + state => state.updatingCollection +); + +export const selectUpdateCollectionError = createSelector( + selectState, + state => state.updateCollectionError +); + +export const selectCreatingCollection = createSelector( + selectState, + state => state.creatingCollection +); + +export const selectCreateCollectionError = createSelector( + selectState, + state => state.createCollectionError +); diff --git a/src/redux/selectors/collections.js b/src/redux/selectors/collections.js new file mode 100644 index 0000000..f895b3e --- /dev/null +++ b/src/redux/selectors/collections.js @@ -0,0 +1,203 @@ +// @flow +import { createSelector } from 'reselect'; +import { selectMyCollectionIds } from 'redux/selectors/claims'; +import { parseURI } from 'lbryURI'; + +const selectState = (state: { collections: CollectionState }) => state.collections; + +export const selectSavedCollectionIds = createSelector( + selectState, + collectionState => collectionState.saved +); + +export const selectBuiltinCollections = createSelector( + selectState, + state => state.builtin +); +export const selectResolvedCollections = createSelector( + selectState, + state => state.resolved +); + +export const selectMyUnpublishedCollections = createSelector( + selectState, + state => state.unpublished +); + +export const selectMyEditedCollections = createSelector( + selectState, + state => state.edited +); + +export const selectPendingCollections = createSelector( + selectState, + state => state.pending +); + +export const makeSelectEditedCollectionForId = (id: string) => + createSelector( + selectMyEditedCollections, + eLists => eLists[id] + ); + +export const makeSelectPendingCollectionForId = (id: string) => + createSelector( + selectPendingCollections, + pending => pending[id] + ); + +export const makeSelectPublishedCollectionForId = (id: string) => + createSelector( + selectResolvedCollections, + rLists => rLists[id] + ); + +export const makeSelectUnpublishedCollectionForId = (id: string) => + createSelector( + selectMyUnpublishedCollections, + rLists => rLists[id] + ); + +export const makeSelectCollectionIsMine = (id: string) => + createSelector( + selectMyCollectionIds, + selectMyUnpublishedCollections, + selectBuiltinCollections, + (publicIds, privateIds, builtinIds) => { + return Boolean(publicIds.includes(id) || privateIds[id] || builtinIds[id]); + } + ); + +// for library page, we want all published +export const selectMyPublishedCollections = createSelector( + selectResolvedCollections, + selectPendingCollections, + selectMyEditedCollections, + selectMyCollectionIds, + (resolved, pending, edited, myIds) => { + // all resolved in myIds, plus those in pending and edited + const myPublishedCollections = Object.fromEntries( + Object.entries(pending).concat( + Object.entries(edited) + .filter( + ([key, val]) => myIds.includes(key) + // $FlowFixMe + ) + .concat( + Object.entries(resolved).filter( + ([key, val]) => + myIds.includes(key) && + // $FlowFixMe + (!pending[key] && !edited[key]) + ) + ) + ) + ); + return myPublishedCollections; + } +); + +export const selectMyPublishedMixedCollections = createSelector( + selectMyPublishedCollections, + published => { + const myCollections = Object.fromEntries( + // $FlowFixMe + Object.entries(published).filter(([key, collection]) => { + // $FlowFixMe + return collection.type === 'collection'; + }) + ); + return myCollections; + } +); + +export const selectMyPublishedPlaylistCollections = createSelector( + selectMyPublishedCollections, + published => { + const myCollections = Object.fromEntries( + // $FlowFixMe + Object.entries(published).filter(([key, collection]) => { + // $FlowFixMe + return collection.type === 'playlist'; + }) + ); + return myCollections; + } +); + +export const makeSelectMyPublishedCollectionForId = (id: string) => + createSelector( + selectMyPublishedCollections, + myPublishedCollections => myPublishedCollections[id] + ); + +// export const selectSavedCollections = createSelector( +// selectResolvedCollections, +// selectSavedCollectionIds, +// (resolved, myIds) => { +// const mySavedCollections = Object.fromEntries( +// Object.entries(resolved).filter(([key, val]) => myIds.includes(key)) +// ); +// return mySavedCollections; +// } +// ); + +export const makeSelectIsResolvingCollectionForId = (id: string) => + createSelector( + selectState, + state => { + return state.isResolvingCollectionById[id]; + } + ); + +export const makeSelectCollectionForId = (id: string) => + createSelector( + selectBuiltinCollections, + selectResolvedCollections, + selectMyUnpublishedCollections, + selectMyEditedCollections, + selectPendingCollections, + (bLists, rLists, uLists, eLists, pLists) => { + const collection = bLists[id] || uLists[id] || eLists[id] || rLists[id] || pLists[id]; + return collection; + } + ); + +export const makeSelectUrlsForCollectionId = (id: string) => + createSelector( + makeSelectCollectionForId(id), + collection => collection && collection.items + ); + +export const makeSelectClaimIdsForCollectionId = (id: string) => + createSelector( + makeSelectCollectionForId(id), + collection => { + const items = (collection && collection.items) || []; + const ids = items.map(item => { + const { claimId } = parseURI(item); + return claimId; + }); + return ids; + } + ); + +export const makeSelectNextUrlForCollection = (id: string, index: number) => + createSelector( + makeSelectUrlsForCollectionId(id), + urls => { + const url = urls[index + 1]; + if (url) { + return url; + } + return null; + } + ); + +export const makeSelectNameForCollectionId = (id: string) => + createSelector( + makeSelectCollectionForId(id), + collection => { + return (collection && collection.name) || ''; + } + ); -- 2.45.3 From 211266cdd875a3a2bad8e720371ff8b2afe8b314 Mon Sep 17 00:00:00 2001 From: zeppi Date: Mon, 22 Mar 2021 13:14:13 -0400 Subject: [PATCH 02/24] fix pending, support new collection add ui --- dist/flow-typed/Collections.js | 1 + src/index.js | 1 + src/redux/actions/collections.js | 12 ++++++--- src/redux/middleware/shared-state.js | 1 - src/redux/reducers/collections.js | 39 +++++++++++++++------------- src/redux/selectors/collections.js | 28 ++++++++++---------- 6 files changed, 47 insertions(+), 35 deletions(-) diff --git a/dist/flow-typed/Collections.js b/dist/flow-typed/Collections.js index 83c33be..c2eba63 100644 --- a/dist/flow-typed/Collections.js +++ b/dist/flow-typed/Collections.js @@ -37,4 +37,5 @@ declare type CollectionEditParams = { replace?: boolean, order?: { from: number, to: number }, type?: string, + name?: string, } diff --git a/src/index.js b/src/index.js index a7ca376..d2acbad 100644 --- a/src/index.js +++ b/src/index.js @@ -179,6 +179,7 @@ export { makeSelectNameForCollectionId, makeSelectIsResolvingCollectionForId, makeSelectNextUrlForCollection, + makeSelectCollectionForIdHasClaimUrl, } from 'redux/selectors/collections'; export { diff --git a/src/redux/actions/collections.js b/src/redux/actions/collections.js index 55a8c98..7aca1e0 100644 --- a/src/redux/actions/collections.js +++ b/src/redux/actions/collections.js @@ -8,6 +8,7 @@ import { makeSelectCollectionForId, // makeSelectPublishedCollectionForId, // for "save" or "copy" action makeSelectMyPublishedCollectionForId, + makeSelectPublishedCollectionForId, makeSelectUnpublishedCollectionForId, makeSelectEditedCollectionForId, } from 'redux/selectors/collections'; @@ -24,6 +25,7 @@ const getTimestamp = () => { export const doLocalCollectionCreate = ( name: string, collectionItems: string, + type: string, sourceId: string ) => (dispatch: Dispatch) => { return dispatch({ @@ -35,6 +37,7 @@ export const doLocalCollectionCreate = ( updatedAt: getTimestamp(), items: collectionItems || [], sourceId: sourceId, + type: type || 'collection', }, }, }); @@ -79,7 +82,7 @@ export const doFetchItemsInCollections = ( pageSize?: number, }, resolveStartedCallback?: () => void -) => async (dispatch: Dispatch, getState: GetState) => { +) => async(dispatch: Dispatch, getState: GetState) => { let state = getState(); // for each collection id, // make sure the collection is resolved, the items are resolved, and build the collection objects @@ -284,7 +287,7 @@ export const doFetchItemsInCollection = ( return doFetchItemsInCollections(newOptions, cb); }; -export const doCollectionEdit = (collectionId: string, params: CollectionEditParams) => async ( +export const doCollectionEdit = (collectionId: string, params: CollectionEditParams) => async( dispatch: Dispatch, getState: GetState ) => { @@ -294,7 +297,7 @@ export const doCollectionEdit = (collectionId: string, params: CollectionEditPar const unpublishedCollection: Collection = makeSelectUnpublishedCollectionForId(collectionId)( state ); - const publishedCollection: Collection = makeSelectMyPublishedCollectionForId(collectionId)(state); + const publishedCollection: Collection = makeSelectPublishedCollectionForId(collectionId)(state); // needs to be published only const generateCollectionItemsFromSearchResult = results => { return ( @@ -383,8 +386,11 @@ export const doCollectionEdit = (collectionId: string, params: CollectionEditPar currentItems.splice(order.to, 0, movedItem); } + // console.log('p&e', publishedCollection.items, newItems, publishedCollection.items.join(','), newItems.join(',')) if (editedCollection) { if (publishedCollection.items.join(',') === newItems.join(',')) { + // print these + // delete edited if newItems are the same as publishedItems dispatch({ type: ACTIONS.COLLECTION_DELETE, diff --git a/src/redux/middleware/shared-state.js b/src/redux/middleware/shared-state.js index 3d40834..031e9a4 100644 --- a/src/redux/middleware/shared-state.js +++ b/src/redux/middleware/shared-state.js @@ -26,7 +26,6 @@ export const buildSharedStateMiddleware = ( clearTimeout(timeout); const actionResult = next(action); // Call `getState` after calling `next` to ensure the state has updated in response to the action - function runPreferences() { const nextState: { user: any, settings: any } = getState(); const syncEnabled = diff --git a/src/redux/reducers/collections.js b/src/redux/reducers/collections.js index 0f93e43..691a821 100644 --- a/src/redux/reducers/collections.js +++ b/src/redux/reducers/collections.js @@ -69,7 +69,12 @@ const collectionsReducer = handleActions( const newPendingList = Object.assign({}, pendingList); if (collectionKey && state[collectionKey] && state[collectionKey][id]) { - delete state[collectionKey][id]; + const newList = Object.assign({}, state[collectionKey]); + delete newList[id]; + return { + ...state, + [collectionKey]: newList, + }; } else { if (newEditList[id]) { delete newEditList[id]; @@ -178,40 +183,38 @@ const collectionsReducer = handleActions( }, [ACTIONS.COLLECTION_ITEMS_RESOLVE_COMPLETED]: (state, action) => { const { resolvedCollections, failedCollectionIds } = action.data; - const { - pending: pendingList, - edited: editList, - isResolvingCollectionById, - resolved: lists, - } = state; + const { pending, edited, isResolvingCollectionById, resolved } = state; + const newPending = Object.assign({}, pending); + const newEdited = Object.assign({}, edited); + const newResolved = Object.assign({}, resolved, resolvedCollections); + const resolvedIds = Object.keys(resolvedCollections); const newResolving = Object.assign({}, isResolvingCollectionById); - if (resolvedCollections && resolvedCollections.length) { + if (resolvedCollections && Object.keys(resolvedCollections).length) { resolvedIds.forEach(resolvedId => { - if (editList[resolvedId]) { - if (editList[resolvedId]['updatedAt'] < resolvedCollections[resolvedId]['updatedAt']) { - delete editList[resolvedId]; + if (newEdited[resolvedId]) { + if (newEdited[resolvedId]['updatedAt'] < resolvedCollections[resolvedId]['updatedAt']) { + delete newEdited[resolvedId]; } } delete newResolving[resolvedId]; - if (pendingList[resolvedId]) { - delete pendingList[resolvedId]; + if (newPending[resolvedId]) { + delete newPending[resolvedId]; } }); } - if (failedCollectionIds && failedCollectionIds.length) { + if (failedCollectionIds && Object.keys(failedCollectionIds).length) { failedCollectionIds.forEach(failedId => { delete newResolving[failedId]; }); } - const newLists = Object.assign({}, lists, resolvedCollections); - return Object.assign({}, state, { ...state, - pending: pendingList, - resolved: newLists, + pending: newPending, + resolved: newResolved, + edited: newEdited, isResolvingCollectionById: newResolving, }); }, diff --git a/src/redux/selectors/collections.js b/src/redux/selectors/collections.js index f895b3e..e59fb7c 100644 --- a/src/redux/selectors/collections.js +++ b/src/redux/selectors/collections.js @@ -68,7 +68,6 @@ export const makeSelectCollectionIsMine = (id: string) => } ); -// for library page, we want all published export const selectMyPublishedCollections = createSelector( selectResolvedCollections, selectPendingCollections, @@ -78,21 +77,18 @@ export const selectMyPublishedCollections = createSelector( // all resolved in myIds, plus those in pending and edited const myPublishedCollections = Object.fromEntries( Object.entries(pending).concat( - Object.entries(edited) - .filter( - ([key, val]) => myIds.includes(key) + Object.entries(resolved).filter( + ([key, val]) => + myIds.includes(key) && // $FlowFixMe - ) - .concat( - Object.entries(resolved).filter( - ([key, val]) => - myIds.includes(key) && - // $FlowFixMe - (!pending[key] && !edited[key]) - ) - ) + !pending[key] + ) ) ); + // now add in edited: + Object.entries(edited).forEach(([id, item]) => { + myPublishedCollections[id] = item; + }); return myPublishedCollections; } ); @@ -163,6 +159,12 @@ export const makeSelectCollectionForId = (id: string) => } ); +export const makeSelectCollectionForIdHasClaimUrl = (id: string, url: string) => + createSelector( + makeSelectCollectionForId(id), + collection => collection.items.includes(url) + ); + export const makeSelectUrlsForCollectionId = (id: string) => createSelector( makeSelectCollectionForId(id), -- 2.45.3 From a1dce5581c9b6b78bf576b8354726eb202cd5232 Mon Sep 17 00:00:00 2001 From: zeppi Date: Thu, 6 May 2021 18:41:02 -0400 Subject: [PATCH 03/24] make edits work --- dist/bundle.es.js | 1227 +++++++++++++++++++++++++++- src/constants/collections.js | 5 + src/redux/actions/claims.js | 43 +- src/redux/actions/collections.js | 11 +- src/redux/reducers/claims.js | 13 +- src/redux/reducers/collections.js | 26 +- src/redux/selectors/collections.js | 2 +- 7 files changed, 1254 insertions(+), 73 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 2af1339..85ec3f6 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -134,6 +134,9 @@ const ABANDON_CLAIM_SUCCEEDED = 'ABANDON_CLAIM_SUCCEEDED'; const FETCH_CHANNEL_LIST_STARTED = 'FETCH_CHANNEL_LIST_STARTED'; const FETCH_CHANNEL_LIST_COMPLETED = 'FETCH_CHANNEL_LIST_COMPLETED'; const FETCH_CHANNEL_LIST_FAILED = 'FETCH_CHANNEL_LIST_FAILED'; +const FETCH_COLLECTION_LIST_STARTED = 'FETCH_COLLECTION_LIST_STARTED'; +const FETCH_COLLECTION_LIST_COMPLETED = 'FETCH_COLLECTION_LIST_COMPLETED'; +const FETCH_COLLECTION_LIST_FAILED = 'FETCH_COLLECTION_LIST_FAILED'; const CREATE_CHANNEL_STARTED = 'CREATE_CHANNEL_STARTED'; const CREATE_CHANNEL_COMPLETED = 'CREATE_CHANNEL_COMPLETED'; const CREATE_CHANNEL_FAILED = 'CREATE_CHANNEL_FAILED'; @@ -143,6 +146,7 @@ const UPDATE_CHANNEL_FAILED = 'UPDATE_CHANNEL_FAILED'; const IMPORT_CHANNEL_STARTED = 'IMPORT_CHANNEL_STARTED'; const IMPORT_CHANNEL_COMPLETED = 'IMPORT_CHANNEL_COMPLETED'; const IMPORT_CHANNEL_FAILED = 'IMPORT_CHANNEL_FAILED'; +const CLEAR_CHANNEL_ERRORS = 'CLEAR_CHANNEL_ERRORS'; const PUBLISH_STARTED = 'PUBLISH_STARTED'; const PUBLISH_COMPLETED = 'PUBLISH_COMPLETED'; const PUBLISH_FAILED = 'PUBLISH_FAILED'; @@ -161,7 +165,6 @@ const CLAIM_REPOST_STARTED = 'CLAIM_REPOST_STARTED'; const CLAIM_REPOST_COMPLETED = 'CLAIM_REPOST_COMPLETED'; const CLAIM_REPOST_FAILED = 'CLAIM_REPOST_FAILED'; const CLEAR_REPOST_ERROR = 'CLEAR_REPOST_ERROR'; -const CLEAR_CHANNEL_ERRORS = 'CLEAR_CHANNEL_ERRORS'; const CHECK_PUBLISH_NAME_STARTED = 'CHECK_PUBLISH_NAME_STARTED'; const CHECK_PUBLISH_NAME_COMPLETED = 'CHECK_PUBLISH_NAME_COMPLETED'; const UPDATE_PENDING_CLAIMS = 'UPDATE_PENDING_CLAIMS'; @@ -174,6 +177,27 @@ const PURCHASE_LIST_STARTED = 'PURCHASE_LIST_STARTED'; const PURCHASE_LIST_COMPLETED = 'PURCHASE_LIST_COMPLETED'; const PURCHASE_LIST_FAILED = 'PURCHASE_LIST_FAILED'; +const COLLECTION_PUBLISH_STARTED = 'COLLECTION_PUBLISH_STARTED'; +const COLLECTION_PUBLISH_COMPLETED = 'COLLECTION_PUBLISH_COMPLETED'; +const COLLECTION_PUBLISH_FAILED = 'COLLECTION_PUBLISH_FAILED'; +const COLLECTION_PUBLISH_UPDATE_STARTED = 'COLLECTION_PUBLISH_UPDATE_STARTED'; +const COLLECTION_PUBLISH_UPDATE_COMPLETED = 'COLLECTION_PUBLISH_UPDATE_COMPLETED'; +const COLLECTION_PUBLISH_UPDATE_FAILED = 'COLLECTION_PUBLISH_UPDATE_FAILED'; +const COLLECTION_PUBLISH_ABANDON_STARTED = 'COLLECTION_PUBLISH_ABANDON_STARTED'; +const COLLECTION_PUBLISH_ABANDON_COMPLETED = 'COLLECTION_PUBLISH_ABANDON_COMPLETED'; +const COLLECTION_PUBLISH_ABANDON_FAILED = 'COLLECTION_PUBLISH_ABANDON_FAILED'; +const CLEAR_COLLECTION_ERRORS = 'CLEAR_COLLECTION_ERRORS'; +const COLLECTION_ITEMS_RESOLVE_STARTED = 'COLLECTION_ITEMS_RESOLVE_STARTED'; +const COLLECTION_ITEMS_RESOLVE_COMPLETED = 'COLLECTION_ITEMS_RESOLVE_COMPLETED'; +const COLLECTION_ITEMS_RESOLVE_FAILED = 'COLLECTION_ITEMS_RESOLVE_FAILED'; +const COLLECTION_NEW = 'COLLECTION_NEW'; +const COLLECTION_DELETE = 'COLLECTION_DELETE'; +const COLLECTION_PENDING = 'COLLECTION_PENDING'; +const COLLECTION_EDIT = 'COLLECTION_EDIT'; +const COLLECTION_COPY = 'COLLECTION_COPY'; +const COLLECTION_SAVE = 'COLLECTION_SAVE'; +const COLLECTION_ERROR = 'COLLECTION_ERROR'; + // Comments const COMMENT_LIST_STARTED = 'COMMENT_LIST_STARTED'; const COMMENT_LIST_COMPLETED = 'COMMENT_LIST_COMPLETED'; @@ -409,6 +433,9 @@ var action_types = /*#__PURE__*/Object.freeze({ FETCH_CHANNEL_LIST_STARTED: FETCH_CHANNEL_LIST_STARTED, FETCH_CHANNEL_LIST_COMPLETED: FETCH_CHANNEL_LIST_COMPLETED, FETCH_CHANNEL_LIST_FAILED: FETCH_CHANNEL_LIST_FAILED, + FETCH_COLLECTION_LIST_STARTED: FETCH_COLLECTION_LIST_STARTED, + FETCH_COLLECTION_LIST_COMPLETED: FETCH_COLLECTION_LIST_COMPLETED, + FETCH_COLLECTION_LIST_FAILED: FETCH_COLLECTION_LIST_FAILED, CREATE_CHANNEL_STARTED: CREATE_CHANNEL_STARTED, CREATE_CHANNEL_COMPLETED: CREATE_CHANNEL_COMPLETED, CREATE_CHANNEL_FAILED: CREATE_CHANNEL_FAILED, @@ -418,6 +445,7 @@ var action_types = /*#__PURE__*/Object.freeze({ IMPORT_CHANNEL_STARTED: IMPORT_CHANNEL_STARTED, IMPORT_CHANNEL_COMPLETED: IMPORT_CHANNEL_COMPLETED, IMPORT_CHANNEL_FAILED: IMPORT_CHANNEL_FAILED, + CLEAR_CHANNEL_ERRORS: CLEAR_CHANNEL_ERRORS, PUBLISH_STARTED: PUBLISH_STARTED, PUBLISH_COMPLETED: PUBLISH_COMPLETED, PUBLISH_FAILED: PUBLISH_FAILED, @@ -436,7 +464,6 @@ var action_types = /*#__PURE__*/Object.freeze({ CLAIM_REPOST_COMPLETED: CLAIM_REPOST_COMPLETED, CLAIM_REPOST_FAILED: CLAIM_REPOST_FAILED, CLEAR_REPOST_ERROR: CLEAR_REPOST_ERROR, - CLEAR_CHANNEL_ERRORS: CLEAR_CHANNEL_ERRORS, CHECK_PUBLISH_NAME_STARTED: CHECK_PUBLISH_NAME_STARTED, CHECK_PUBLISH_NAME_COMPLETED: CHECK_PUBLISH_NAME_COMPLETED, UPDATE_PENDING_CLAIMS: UPDATE_PENDING_CLAIMS, @@ -448,6 +475,26 @@ var action_types = /*#__PURE__*/Object.freeze({ PURCHASE_LIST_STARTED: PURCHASE_LIST_STARTED, PURCHASE_LIST_COMPLETED: PURCHASE_LIST_COMPLETED, PURCHASE_LIST_FAILED: PURCHASE_LIST_FAILED, + COLLECTION_PUBLISH_STARTED: COLLECTION_PUBLISH_STARTED, + COLLECTION_PUBLISH_COMPLETED: COLLECTION_PUBLISH_COMPLETED, + COLLECTION_PUBLISH_FAILED: COLLECTION_PUBLISH_FAILED, + COLLECTION_PUBLISH_UPDATE_STARTED: COLLECTION_PUBLISH_UPDATE_STARTED, + COLLECTION_PUBLISH_UPDATE_COMPLETED: COLLECTION_PUBLISH_UPDATE_COMPLETED, + COLLECTION_PUBLISH_UPDATE_FAILED: COLLECTION_PUBLISH_UPDATE_FAILED, + COLLECTION_PUBLISH_ABANDON_STARTED: COLLECTION_PUBLISH_ABANDON_STARTED, + COLLECTION_PUBLISH_ABANDON_COMPLETED: COLLECTION_PUBLISH_ABANDON_COMPLETED, + COLLECTION_PUBLISH_ABANDON_FAILED: COLLECTION_PUBLISH_ABANDON_FAILED, + CLEAR_COLLECTION_ERRORS: CLEAR_COLLECTION_ERRORS, + COLLECTION_ITEMS_RESOLVE_STARTED: COLLECTION_ITEMS_RESOLVE_STARTED, + COLLECTION_ITEMS_RESOLVE_COMPLETED: COLLECTION_ITEMS_RESOLVE_COMPLETED, + COLLECTION_ITEMS_RESOLVE_FAILED: COLLECTION_ITEMS_RESOLVE_FAILED, + COLLECTION_NEW: COLLECTION_NEW, + COLLECTION_DELETE: COLLECTION_DELETE, + COLLECTION_PENDING: COLLECTION_PENDING, + COLLECTION_EDIT: COLLECTION_EDIT, + COLLECTION_COPY: COLLECTION_COPY, + COLLECTION_SAVE: COLLECTION_SAVE, + COLLECTION_ERROR: COLLECTION_ERROR, COMMENT_LIST_STARTED: COMMENT_LIST_STARTED, COMMENT_LIST_COMPLETED: COMMENT_LIST_COMPLETED, COMMENT_LIST_FAILED: COMMENT_LIST_FAILED, @@ -993,6 +1040,28 @@ var shared_preferences = /*#__PURE__*/Object.freeze({ CLIENT_SYNC_KEYS: CLIENT_SYNC_KEYS }); +const COLLECTION_ID = 'colid'; +const COLLECTION_INDEX = 'colindex'; + +const COL_TYPE_PLAYLIST = 'playlist'; +const COL_TYPE_CHANNELS = 'channelCollection'; + +const WATCH_LATER_ID = 'watchlater'; +const FAVORITES_ID = 'favorites'; +const FAVORITE_CHANNELS_ID = 'favoriteChannels'; +const BUILTIN_LISTS = [WATCH_LATER_ID, FAVORITES_ID, FAVORITE_CHANNELS_ID]; + +var collections = /*#__PURE__*/Object.freeze({ + COLLECTION_ID: COLLECTION_ID, + COLLECTION_INDEX: COLLECTION_INDEX, + COL_TYPE_PLAYLIST: COL_TYPE_PLAYLIST, + COL_TYPE_CHANNELS: COL_TYPE_CHANNELS, + WATCH_LATER_ID: WATCH_LATER_ID, + FAVORITES_ID: FAVORITES_ID, + FAVORITE_CHANNELS_ID: FAVORITE_CHANNELS_ID, + BUILTIN_LISTS: BUILTIN_LISTS +}); + const DEFAULT_FOLLOWED_TAGS = ['art', 'automotive', 'blockchain', 'comedy', 'economics', 'education', 'gaming', 'music', 'news', 'science', 'sports', 'technology']; const MATURE_TAGS = ['porn', 'porno', 'nsfw', 'mature', 'xxx', 'sex', 'creampie', 'blowjob', 'handjob', 'vagina', 'boobs', 'big boobs', 'big dick', 'pussy', 'cumshot', 'anal', 'hard fucking', 'ass', 'fuck', 'hentai']; @@ -1085,6 +1154,10 @@ const Lbry = { support_create: params => daemonCallWithResult('support_create', params), support_list: params => daemonCallWithResult('support_list', params), stream_repost: params => daemonCallWithResult('stream_repost', params), + collection_resolve: params => daemonCallWithResult('collection_resolve', params), + collection_list: params => daemonCallWithResult('collection_list', params), + collection_create: params => daemonCallWithResult('collection_create', params), + collection_update: params => daemonCallWithResult('collection_update', params), // File fetching and manipulation file_list: (params = {}) => daemonCallWithResult('file_list', params), @@ -1755,10 +1828,13 @@ function extractUserState(rawObj) { coin_swap_codes, settings, app_welcome_version, - sharing_3P + sharing_3P, + unpublishedCollectionTest, + builtinCollectionTest, + savedCollectionTest } = rawObj.value; - return _extends$1({}, subscriptions ? { subscriptions } : {}, following ? { following } : {}, tags ? { tags } : {}, blocked ? { blocked } : {}, coin_swap_codes ? { coin_swap_codes } : {}, settings ? { settings } : {}, app_welcome_version ? { app_welcome_version } : {}, sharing_3P ? { sharing_3P } : {}); + return _extends$1({}, subscriptions ? { subscriptions } : {}, following ? { following } : {}, tags ? { tags } : {}, blocked ? { blocked } : {}, coin_swap_codes ? { coin_swap_codes } : {}, settings ? { settings } : {}, app_welcome_version ? { app_welcome_version } : {}, sharing_3P ? { sharing_3P } : {}, unpublishedCollectionTest ? { unpublishedCollectionTest } : {}, builtinCollectionTest ? { builtinCollectionTest } : {}, savedCollectionTest ? { savedCollectionTest } : {}); } return {}; @@ -1774,7 +1850,10 @@ function doPopulateSharedUserState(sharedSettings) { coin_swap_codes, settings, app_welcome_version, - sharing_3P + sharing_3P, + unpublishedCollectionTest, + builtinCollectionTest, + savedCollectionTest } = extractUserState(sharedSettings); dispatch({ type: USER_STATE_POPULATE, @@ -1786,7 +1865,10 @@ function doPopulateSharedUserState(sharedSettings) { coinSwapCodes: coin_swap_codes, settings, welcomeVersion: app_welcome_version, - allowAnalytics: sharing_3P + allowAnalytics: sharing_3P, + unpublishedCollectionTest, + builtinCollectionTest, + savedCollectionTest } }); }; @@ -1867,7 +1949,6 @@ const buildSharedStateMiddleware = (actions, sharedStateFilters, sharedStateCb) clearTimeout(timeout); const actionResult = next(action); // Call `getState` after calling `next` to ensure the state has updated in response to the action - function runPreferences() { const nextState = getState(); const syncEnabled = nextState.settings && nextState.settings.clientSettings && nextState.settings.clientSettings.enable_sync; @@ -2328,6 +2409,8 @@ const makeSelectClaimIsPending = uri => reselect.createSelector(selectClaimIdsBy return false; }); +const makeSelectClaimIdForUri = uri => reselect.createSelector(selectClaimIdsByUri, claimIds => claimIds[uri]); + const selectReflectingById = reselect.createSelector(selectState$1, state => state.reflectingById); const makeSelectClaimForClaimId = claimId => reselect.createSelector(selectClaimsById, byId => byId[claimId]); @@ -2568,6 +2651,8 @@ const selectMyClaimsOutpoints = reselect.createSelector(selectMyClaims, myClaims const selectFetchingMyChannels = reselect.createSelector(selectState$1, state => state.fetchingMyChannels); +const selectFetchingMyCollections = reselect.createSelector(selectState$1, state => state.fetchingMyCollections); + const selectMyChannelClaims = reselect.createSelector(selectState$1, selectClaimsById, (state, byId) => { const ids = state.myChannelClaims; if (!ids) { @@ -2587,6 +2672,8 @@ const selectMyChannelClaims = reselect.createSelector(selectState$1, selectClaim const selectMyChannelUrls = reselect.createSelector(selectMyChannelClaims, claims => claims ? claims.map(claim => claim.canonical_url || claim.permanent_url) : undefined); +const selectMyCollectionIds = reselect.createSelector(selectState$1, state => state.myCollectionClaims); + const selectResolvingUris = reselect.createSelector(selectState$1, state => state.resolvingUris || []); const selectChannelImportPending = reselect.createSelector(selectState$1, state => state.pendingChannelImport); @@ -2802,6 +2889,14 @@ const makeSelectStakedLevelForChannelUri = uri => reselect.createSelector(makeSe return level; }); +const selectUpdatingCollection = reselect.createSelector(selectState$1, state => state.updatingCollection); + +const selectUpdateCollectionError = reselect.createSelector(selectState$1, state => state.updateCollectionError); + +const selectCreatingCollection = reselect.createSelector(selectState$1, state => state.creatingCollection); + +const selectCreateCollectionError = reselect.createSelector(selectState$1, state => state.createCollectionError); + function numberWithCommas(x) { var parts = x.toString().split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); @@ -3505,6 +3600,112 @@ function batchActions(...actions) { }; } +// + +const selectState$2 = state => state.collections; + +const selectSavedCollectionIds = reselect.createSelector(selectState$2, collectionState => collectionState.saved); + +const selectBuiltinCollections = reselect.createSelector(selectState$2, state => state.builtin); +const selectResolvedCollections = reselect.createSelector(selectState$2, state => state.resolved); + +const selectMyUnpublishedCollections = reselect.createSelector(selectState$2, state => state.unpublished); + +const selectMyEditedCollections = reselect.createSelector(selectState$2, state => state.edited); + +const selectPendingCollections = reselect.createSelector(selectState$2, state => state.pending); + +const makeSelectEditedCollectionForId = id => reselect.createSelector(selectMyEditedCollections, eLists => eLists[id]); + +const makeSelectPendingCollectionForId = id => reselect.createSelector(selectPendingCollections, pending => pending[id]); + +const makeSelectPublishedCollectionForId = id => reselect.createSelector(selectResolvedCollections, rLists => rLists[id]); + +const makeSelectUnpublishedCollectionForId = id => reselect.createSelector(selectMyUnpublishedCollections, rLists => rLists[id]); + +const makeSelectCollectionIsMine = id => reselect.createSelector(selectMyCollectionIds, selectMyUnpublishedCollections, selectBuiltinCollections, (publicIds, privateIds, builtinIds) => { + return Boolean(publicIds.includes(id) || privateIds[id] || builtinIds[id]); +}); + +const selectMyPublishedCollections = reselect.createSelector(selectResolvedCollections, selectPendingCollections, selectMyEditedCollections, selectMyCollectionIds, (resolved, pending, edited, myIds) => { + // all resolved in myIds, plus those in pending and edited + const myPublishedCollections = Object.fromEntries(Object.entries(pending).concat(Object.entries(resolved).filter(([key, val]) => myIds.includes(key) && + // $FlowFixMe + !pending[key]))); + // now add in edited: + Object.entries(edited).forEach(([id, item]) => { + myPublishedCollections[id] = item; + }); + return myPublishedCollections; +}); + +const selectMyPublishedMixedCollections = reselect.createSelector(selectMyPublishedCollections, published => { + const myCollections = Object.fromEntries( + // $FlowFixMe + Object.entries(published).filter(([key, collection]) => { + // $FlowFixMe + return collection.type === 'collection'; + })); + return myCollections; +}); + +const selectMyPublishedPlaylistCollections = reselect.createSelector(selectMyPublishedCollections, published => { + const myCollections = Object.fromEntries( + // $FlowFixMe + Object.entries(published).filter(([key, collection]) => { + // $FlowFixMe + return collection.type === 'playlist'; + })); + return myCollections; +}); + +const makeSelectMyPublishedCollectionForId = id => reselect.createSelector(selectMyPublishedCollections, myPublishedCollections => myPublishedCollections[id]); + +// export const selectSavedCollections = createSelector( +// selectResolvedCollections, +// selectSavedCollectionIds, +// (resolved, myIds) => { +// const mySavedCollections = Object.fromEntries( +// Object.entries(resolved).filter(([key, val]) => myIds.includes(key)) +// ); +// return mySavedCollections; +// } +// ); + +const makeSelectIsResolvingCollectionForId = id => reselect.createSelector(selectState$2, state => { + return state.isResolvingCollectionById[id]; +}); + +const makeSelectCollectionForId = id => reselect.createSelector(selectBuiltinCollections, selectResolvedCollections, selectMyUnpublishedCollections, selectMyEditedCollections, selectPendingCollections, (bLists, rLists, uLists, eLists, pLists) => { + const collection = bLists[id] || uLists[id] || eLists[id] || rLists[id] || pLists[id]; + return collection; +}); + +const makeSelectCollectionForIdHasClaimUrl = (id, url) => reselect.createSelector(makeSelectCollectionForId(id), collection => collection && collection.items.includes(url)); + +const makeSelectUrlsForCollectionId = id => reselect.createSelector(makeSelectCollectionForId(id), collection => collection && collection.items); + +const makeSelectClaimIdsForCollectionId = id => reselect.createSelector(makeSelectCollectionForId(id), collection => { + const items = collection && collection.items || []; + const ids = items.map(item => { + const { claimId } = parseURI(item); + return claimId; + }); + return ids; +}); + +const makeSelectNextUrlForCollection = (id, index) => reselect.createSelector(makeSelectUrlsForCollectionId(id), urls => { + const url = urls[index + 1]; + if (url) { + return url; + } + return null; +}); + +const makeSelectNameForCollectionId = id => reselect.createSelector(makeSelectCollectionForId(id), collection => { + return collection && collection.name || ''; +}); + var _extends$5 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function _asyncToGenerator$1(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } @@ -3542,6 +3743,8 @@ function doResolveUris(uris, returnCachedClaims = false, resolveReposts = true) const resolveInfo = {}; + const collectionIds = []; + return lbryProxy.resolve(_extends$5({ urls: urisToResolve }, options)).then((() => { var _ref = _asyncToGenerator$1(function* (result) { let repostedResults = {}; @@ -3558,6 +3761,7 @@ function doResolveUris(uris, returnCachedClaims = false, resolveReposts = true) // https://github.com/facebook/flow/issues/2221 if (uriResolveInfo) { if (uriResolveInfo.error) { + // $FlowFixMe resolveInfo[uri] = _extends$5({}, fallbackResolveInfo); } else { if (checkReposts) { @@ -3574,6 +3778,10 @@ function doResolveUris(uris, returnCachedClaims = false, resolveReposts = true) result.channel = uriResolveInfo; // $FlowFixMe result.claimsInChannel = uriResolveInfo.meta.claims_in_channel; + } else if (uriResolveInfo.value_type === 'collection') { + result.collection = uriResolveInfo; + // $FlowFixMe + collectionIds.push(uriResolveInfo.claim_id); } else { result.stream = uriResolveInfo; if (uriResolveInfo.signing_channel) { @@ -3602,6 +3810,13 @@ function doResolveUris(uris, returnCachedClaims = false, resolveReposts = true) type: RESOLVE_URIS_COMPLETED, data: { resolveInfo } }); + + if (collectionIds.length) { + dispatch(doFetchItemsInCollections({ collectionIds: collectionIds, pageSize: 5 })); + } + // now collection claims are added, get their stuff + // if collections: doResolveCollections(claimIds) + return result; }); @@ -4001,6 +4216,36 @@ function doFetchChannelListMine(page = 1, pageSize = 99999, resolve = true) { }; } +function doFetchCollectionListMine(page = 1, pageSize = 99999) { + return dispatch => { + dispatch({ + type: FETCH_COLLECTION_LIST_STARTED + }); + + const callback = response => { + const { items } = response; + dispatch({ + type: FETCH_COLLECTION_LIST_COMPLETED, + data: { claims: items } + }); + dispatch(doFetchItemsInCollections({ + collectionIds: items.map(claim => claim.claim_id), + page_size: 5 + })); + // update or fetch collections? + }; + + const failure = error => { + dispatch({ + type: FETCH_COLLECTION_LIST_FAILED, + data: error + }); + }; + + lbryProxy.collection_list({ page, page_size: pageSize, resolve_claims: 1 }).then(callback, failure); + }; +} + function doClaimSearch(options = { no_totals: true, page_size: 10, @@ -4032,7 +4277,8 @@ function doClaimSearch(options = { pageSize: options.page_size } }); - return true; + // was return true + return resolveInfo; }; const failure = function (err) { @@ -4097,6 +4343,117 @@ function doRepost(options) { }; } +function doCollectionPublish(options, localId) { + return dispatch => { + // $FlowFixMe + return new Promise(resolve => { + dispatch({ + type: COLLECTION_PUBLISH_STARTED + }); + + function success(response) { + const collectionClaim = response.outputs[0]; + dispatch(batchActions({ + type: COLLECTION_PUBLISH_COMPLETED + }, + // shift unpublished collection to pending collection with new publish id + // recent publish won't resolve this second. handle it in checkPending + { + type: COLLECTION_PENDING, + data: { localId: localId, claimId: collectionClaim.claim_id } + }, { + type: UPDATE_PENDING_CLAIMS, + data: { + claims: [collectionClaim] + } + })); + dispatch(doCheckPendingClaims()); + dispatch(doFetchCollectionListMine(1, 10)); + resolve(collectionClaim); + } + + function failure(error) { + dispatch({ + type: COLLECTION_PUBLISH_FAILED, + data: { + error: error.message + } + }); + } + + lbryProxy.collection_create(options).then(success, failure); + }); + }; +} + +function doCollectionPublishUpdate(options) { + return (dispatch, getState) => { + const state = getState(); + // select claim forclaim_id + // get publish params from claim + // $FlowFixMe + + const updateParams = { + bid: creditsToString(options.bid), + title: options.title, + thumbnail_url: options.thumbnailUrl, + description: options.description, + tags: [], + languages: options.languages || [], + locations: [], + blocking: true + }; + + if (options.claim_id) { + updateParams['claim_id'] = options.claim_id; + } + + if (options.tags) { + updateParams['tags'] = options.tags.map(tag => tag.name); + } + + if (options.claims) { + updateParams['claims'] = options.claims; + } + // $FlowFixMe + return new Promise(resolve => { + dispatch({ + type: COLLECTION_PUBLISH_UPDATE_STARTED + }); + + function success(response) { + const collectionClaim = response.outputs[0]; + dispatch({ + type: COLLECTION_PUBLISH_UPDATE_COMPLETED, + data: { + collectionClaim + } + }); + dispatch({ + type: UPDATE_PENDING_CLAIMS, + data: { + claims: [collectionClaim] + } + }); + dispatch(doCheckPendingClaims()); + dispatch(doFetchCollectionListMine(1, 10)); + resolve(collectionClaim); + } + + function failure(error) { + dispatch({ + type: COLLECTION_PUBLISH_UPDATE_FAILED, + data: { + error: error.message + } + }); + } + + lbryProxy.collection_update(updateParams).then(success, failure); + }); + }; +} + function doCheckPublishNameAvailability(name) { return dispatch => { dispatch({ @@ -4165,6 +4522,7 @@ const doCheckPendingClaims = onConfirmed => (dispatch, getState) => { const checkClaimList = () => { const state = getState(); const pendingIdSet = new Set(selectPendingIds(state)); + const pendingCollections = selectPendingCollections(state); lbryProxy.claim_list({ page: 1, page_size: 10 }).then(result => { const claims = result.items; const claimsToConfirm = []; @@ -4172,6 +4530,9 @@ const doCheckPendingClaims = onConfirmed => (dispatch, getState) => { const { claim_id: claimId } = claim; if (claim.confirmations > 0 && pendingIdSet.has(claimId)) { pendingIdSet.delete(claimId); + if (Object.keys(pendingCollections).includes(claim.claim_id)) { + dispatch(doFetchItemsInCollection({ collectionId: claim.claim_id })); + } claimsToConfirm.push(claim); if (onConfirmed) { onConfirmed(claim); @@ -4199,11 +4560,440 @@ const doCheckPendingClaims = onConfirmed => (dispatch, getState) => { }, 30000); }; -const selectState$2 = state => state.fileInfo || {}; +function _asyncToGenerator$2(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } -const selectFileInfosByOutpoint = reselect.createSelector(selectState$2, state => state.byOutpoint || {}); +const getTimestamp = () => { + return Math.floor(Date.now() / 1000); +}; -const selectIsFetchingFileList = reselect.createSelector(selectState$2, state => state.isFetchingFileList); +// maybe take items param +const doLocalCollectionCreate = (name, collectionItems, type, sourceId) => dispatch => { + return dispatch({ + type: COLLECTION_NEW, + data: { + entry: { + id: uuid.v4(), // start with a uuid, this becomes a claimId after publish + name: name, + updatedAt: getTimestamp(), + items: collectionItems || [], + sourceId: sourceId, + type: type + } + } + }); +}; + +const doLocalCollectionDelete = id => dispatch => { + return dispatch({ + type: COLLECTION_DELETE, + data: { + id: id + } + }); +}; + +// Given a collection, save its collectionId to be resolved and displayed in Library +// export const doCollectionSave = ( +// id: string, +// ) => (dispatch: Dispatch) => { +// return dispatch({ +// type: ACTIONS.COLLECTION_SAVE, +// data: { +// id: id, +// }, +// }); +// }; + +// Given a collection and name, copy it to a local private collection with a name +// export const doCollectionCopy = ( +// id: string, +// ) => (dispatch: Dispatch) => { +// return dispatch({ +// type: ACTIONS.COLLECTION_COPY, +// data: { +// id: id, +// }, +// }); +// }; + +const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) => (() => { + var _ref = _asyncToGenerator$2(function* (dispatch, getState) { + let resolveCollectionItems = (() => { + var _ref2 = _asyncToGenerator$2(function* (claimId, totalItems, pageSize) { + // take [ {}, {} ], return {} + // only need items [ Claim... ] and total_items + const mergeResults = function (arrayOfResults) { + const mergedResults = { + items: [], + total_items: 0 + }; + arrayOfResults.forEach(function (result) { + mergedResults.items = mergedResults.items.concat(result.items); + mergedResults.total_items = result.total_items; + }); + return mergedResults; + }; + + try { + const BATCH_SIZE = 10; // up batch size when sdk bug fixed + const batches = []; + let fetchResult; + if (!pageSize) { + // batch all + for (let i = 0; i < Math.ceil(totalItems / BATCH_SIZE); i++) { + batches[i] = lbryProxy.collection_resolve({ + claim_id: claimId, + page: i + 1, + page_size: BATCH_SIZE + }); + } + const resultArray = yield Promise.all(batches); + fetchResult = mergeResults(resultArray); + } else { + fetchResult = yield lbryProxy.collection_resolve({ + claim_id: claimId, + page: 1, + page_size: pageSize + }); + } + // $FlowFixMe + const itemsById = { claimId: claimId }; + if (fetchResult.items) { + itemsById.items = fetchResult.items; + } else { + itemsById.items = null; + } + return itemsById; + } catch (e) { + return { + claimId: claimId, + items: null + }; + } + }); + + return function resolveCollectionItems(_x3, _x4, _x5) { + return _ref2.apply(this, arguments); + }; + })(); + + let state = getState(); + // for each collection id, + // make sure the collection is resolved, the items are resolved, and build the collection objects + + const { collectionIds, pageSize } = resolveItemsOptions; + + dispatch({ + type: COLLECTION_ITEMS_RESOLVE_STARTED, + data: { ids: collectionIds } + }); + + if (resolveStartedCallback) resolveStartedCallback(); + + const collectionIdsToSearch = collectionIds.filter(function (claimId) { + return !state.claims.byId[claimId]; + }); + if (collectionIdsToSearch.length) { + let claimSearchOptions = { claim_ids: collectionIdsToSearch, page: 1, page_size: 9999 }; + yield dispatch(doClaimSearch(claimSearchOptions)); + } + const invalidCollectionIds = []; + const stateAfterClaimSearch = getState(); + + const promises = []; + collectionIds.forEach(function (collectionId) { + const claim = makeSelectClaimForClaimId(collectionId)(stateAfterClaimSearch); + if (!claim) { + invalidCollectionIds.push(collectionId); + } else { + const claimCount = claim.value.claims && claim.value.claims.length; + if (pageSize) { + promises.push(resolveCollectionItems(collectionId, claimCount, pageSize)); + } else { + promises.push(resolveCollectionItems(collectionId, claimCount)); + } + } + }); + + // $FlowFixMe + const resolvedCollectionItemsById = yield Promise.all(promises); + + function processClaims(resultClaimsByUri) { + const processedClaims = {}; + Object.entries(resultClaimsByUri).forEach(([uri, uriResolveInfo]) => { + // Flow has terrible Object.entries support + // https://github.com/facebook/flow/issues/2221 + if (uriResolveInfo) { + let result = {}; + if (uriResolveInfo.value_type === 'channel') { + result.channel = uriResolveInfo; + // $FlowFixMe + result.claimsInChannel = uriResolveInfo.meta.claims_in_channel; + // ALSO SKIP COLLECTIONS + } else { + result.stream = uriResolveInfo; + if (uriResolveInfo.signing_channel) { + result.channel = uriResolveInfo.signing_channel; + result.claimsInChannel = uriResolveInfo.signing_channel.meta && uriResolveInfo.signing_channel.meta.claims_in_channel || 0; + } + } + // $FlowFixMe + processedClaims[uri] = result; + } + }); + return processedClaims; + } + + const newCollectionItemsById = {}; + const flatResolvedCollectionItems = {}; + resolvedCollectionItemsById.forEach(function (entry) { + // $FlowFixMe + const collectionItems = entry.items; + const collectionId = entry.claimId; + if (collectionItems) { + const claim = makeSelectClaimForClaimId(collectionId)(stateAfterClaimSearch); + + const editedCollection = makeSelectEditedCollectionForId(collectionId)(stateAfterClaimSearch); + const { name, timestamp } = claim || {}; + const valueTypes = new Set(); + const streamTypes = new Set(); + + let items = []; + collectionItems.forEach(function (collectionItem) { + // here's where we would just items.push(collectionItem.permanent_url + items.push(collectionItem.permanent_url); + valueTypes.add(collectionItem.value_type); + if (collectionItem.value.stream_type) { + streamTypes.add(collectionItem.value.stream_type); + } + flatResolvedCollectionItems[collectionItem.canonical_url] = collectionItem; + }); + const isPlaylist = valueTypes.size === 1 && valueTypes.has('stream') && (streamTypes.size === 1 && (streamTypes.has('audio') || streamTypes.has('video')) || streamTypes.size === 2 && streamTypes.has('audio') && streamTypes.has('video')); + + newCollectionItemsById[collectionId] = { + items, + id: collectionId, + name: name, + itemCount: claim.value.claims.length, + type: isPlaylist ? 'playlist' : 'collection', + updatedAt: timestamp + }; + // clear any stale edits + if (editedCollection && timestamp > editedCollection['updatedAt']) { + dispatch({ + type: COLLECTION_DELETE, + data: { + id: collectionId, + collectionKey: 'edited' + } + }); + } + } + }); + const processedClaimsByUri = processClaims(flatResolvedCollectionItems); + + dispatch({ + type: RESOLVE_URIS_COMPLETED, + data: { resolveInfo: processedClaimsByUri } + }); + + dispatch({ + type: COLLECTION_ITEMS_RESOLVE_COMPLETED, + data: { + resolvedCollections: newCollectionItemsById, + failedCollectionIds: invalidCollectionIds + } + }); + }); + + return function (_x, _x2) { + return _ref.apply(this, arguments); + }; +})(); + +const doFetchItemsInCollection = (options, cb) => { + const { collectionId, pageSize } = options; + const newOptions = { + collectionIds: [collectionId] + }; + if (pageSize) newOptions.pageSize = pageSize; + return doFetchItemsInCollections(newOptions, cb); +}; + +const doCollectionEdit = (collectionId, params) => (() => { + var _ref3 = _asyncToGenerator$2(function* (dispatch, getState) { + const state = getState(); + const collection = makeSelectCollectionForId(collectionId)(state); + const editedCollection = makeSelectEditedCollectionForId(collectionId)(state); + const unpublishedCollection = makeSelectUnpublishedCollectionForId(collectionId)(state); + const publishedCollection = makeSelectPublishedCollectionForId(collectionId)(state); // needs to be published only + + const generateCollectionItemsFromSearchResult = function (results) { + return Object.values(results) + // $FlowFixMe + .reduce(function (acc, cur) { + let url; + if (cur.stream) { + url = cur.stream.permanent_url; + } else if (cur.channel) { + url = cur.channel.permanent_url; + } else if (cur.collection) { + url = cur.collection.permanent_url; + } else { + return acc; + } + acc.push(url); + return acc; + }, []); + }; + + if (!collection) { + return dispatch({ + type: COLLECTION_ERROR, + data: { + message: 'collection does not exist' + } + }); + } + + let currentItems = collection.items ? collection.items.concat() : []; + const { claims: passedClaims, order, claimIds, replace, remove, type } = params; + + const collectionType = type || collection.type; + let newItems = currentItems; + + if (passedClaims) { + if (remove) { + const passedUrls = passedClaims.map(function (claim) { + return claim.permanent_url; + }); + // $FlowFixMe // need this? + newItems = currentItems.filter(function (item) { + return !passedUrls.includes(item); + }); + } else { + passedClaims.forEach(function (claim) { + return newItems.push(claim.permanent_url); + }); + } + } + + if (claimIds) { + const batches = []; + if (claimIds.length > 50) { + for (let i = 0; i < Math.ceil(claimIds.length / 50); i++) { + batches[i] = claimIds.slice(i * 50, (i + 1) * 50); + } + } else { + batches[0] = claimIds; + } + const resultArray = yield Promise.all(batches.map(function (batch) { + let options = { claim_ids: batch, page: 1, page_size: 50 }; + return dispatch(doClaimSearch(options)); + })); + + const searchResults = Object.assign({}, ...resultArray); + + if (replace) { + newItems = generateCollectionItemsFromSearchResult(searchResults); + } else { + newItems = currentItems.concat(generateCollectionItemsFromSearchResult(searchResults)); + } + } + + if (order) { + const [movedItem] = currentItems.splice(order.from, 1); + currentItems.splice(order.to, 0, movedItem); + } + + // console.log('p&e', publishedCollection.items, newItems, publishedCollection.items.join(','), newItems.join(',')) + if (editedCollection) { + if (publishedCollection.items.join(',') === newItems.join(',')) { + // print these + + // delete edited if newItems are the same as publishedItems + dispatch({ + type: COLLECTION_DELETE, + data: { + id: collectionId, + collectionKey: 'edited' + } + }); + } else { + dispatch({ + type: COLLECTION_EDIT, + data: { + id: collectionId, + collectionKey: 'edited', + collection: { + items: newItems, + id: collectionId, + name: params.name || collection.name, + updatedAt: getTimestamp(), + type: collectionType + } + } + }); + } + } else if (publishedCollection) { + dispatch({ + type: COLLECTION_EDIT, + data: { + id: collectionId, + collectionKey: 'edited', + collection: { + items: newItems, + id: collectionId, + name: params.name || collection.name, + updatedAt: getTimestamp(), + type: collectionType + } + } + }); + } else if (BUILTIN_LISTS.includes(collectionId)) { + dispatch({ + type: COLLECTION_EDIT, + data: { + id: collectionId, + collectionKey: 'builtin', + collection: { + items: newItems, + id: collectionId, + name: params.name || collection.name, + updatedAt: getTimestamp(), + type: collectionType + } + } + }); + } else if (unpublishedCollection) { + dispatch({ + type: COLLECTION_EDIT, + data: { + id: collectionId, + collectionKey: 'unpublished', + collection: { + items: newItems, + id: collectionId, + name: params.name || collection.name, + updatedAt: getTimestamp(), + type: collectionType + } + } + }); + } + return true; + }); + + return function (_x6, _x7) { + return _ref3.apply(this, arguments); + }; +})(); + +const selectState$3 = state => state.fileInfo || {}; + +const selectFileInfosByOutpoint = reselect.createSelector(selectState$3, state => state.byOutpoint || {}); + +const selectIsFetchingFileList = reselect.createSelector(selectState$3, state => state.isFetchingFileList); const selectIsFetchingFileListDownloadedOrPublished = reselect.createSelector(selectIsFetchingFileList, selectIsFetchingClaimListMine, (isFetchingFileList, isFetchingClaimListMine) => isFetchingFileList || isFetchingClaimListMine); @@ -4213,14 +5003,14 @@ const makeSelectFileInfoForUri = uri => reselect.createSelector(selectClaimsByUr return outpoint ? byOutpoint[outpoint] : undefined; }); -const selectDownloadingByOutpoint = reselect.createSelector(selectState$2, state => state.downloadingByOutpoint || {}); +const selectDownloadingByOutpoint = reselect.createSelector(selectState$3, state => state.downloadingByOutpoint || {}); const makeSelectDownloadingForUri = uri => reselect.createSelector(selectDownloadingByOutpoint, makeSelectFileInfoForUri(uri), (byOutpoint, fileInfo) => { if (!fileInfo) return false; return byOutpoint[fileInfo.outpoint]; }); -const selectUrisLoading = reselect.createSelector(selectState$2, state => state.fetching || {}); +const selectUrisLoading = reselect.createSelector(selectState$3, state => state.fetching || {}); const makeSelectLoadingForUri = uri => reselect.createSelector(selectUrisLoading, makeSelectClaimForUri(uri), (fetchingByOutpoint, claim) => { if (!claim) { @@ -4274,9 +5064,9 @@ const selectTotalDownloadProgress = reselect.createSelector(selectDownloadingFil return -1; }); -const selectFileListPublishedSort = reselect.createSelector(selectState$2, state => state.fileListPublishedSort); +const selectFileListPublishedSort = reselect.createSelector(selectState$3, state => state.fileListPublishedSort); -const selectFileListDownloadedSort = reselect.createSelector(selectState$2, state => state.fileListDownloadedSort); +const selectFileListDownloadedSort = reselect.createSelector(selectState$3, state => state.fileListDownloadedSort); const selectDownloadedUris = reselect.createSelector(selectFileInfosDownloaded, // We should use permament_url but it doesn't exist in file_list @@ -4529,10 +5319,10 @@ var _extends$6 = Object.assign || function (target) { for (var i = 1; i < argume function _objectWithoutProperties$2(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } -const selectState$3 = state => state.publish || {}; +const selectState$4 = state => state.publish || {}; // Is the current uri the same as the uri they clicked "edit" on -const selectIsStillEditing = reselect.createSelector(selectState$3, publishState => { +const selectIsStillEditing = reselect.createSelector(selectState$4, publishState => { const { editingURI, uri } = publishState; if (!editingURI || !uri) { @@ -4557,7 +5347,7 @@ const selectIsStillEditing = reselect.createSelector(selectState$3, publishState return currentName === editName; }); -const selectPublishFormValues = reselect.createSelector(selectState$3, state => state.settings, selectIsStillEditing, (publishState, settingsState, isStillEditing) => { +const selectPublishFormValues = reselect.createSelector(selectState$4, state => state.settings, selectIsStillEditing, (publishState, settingsState, isStillEditing) => { const { pendingPublish, language } = publishState, formValues = _objectWithoutProperties$2(publishState, ['pendingPublish', 'language']); const { clientSettings } = settingsState; @@ -4573,7 +5363,7 @@ const selectPublishFormValues = reselect.createSelector(selectState$3, state => return _extends$6({}, formValues, { language: actualLanguage }); }); -const makeSelectPublishFormValue = item => reselect.createSelector(selectState$3, state => state[item]); +const makeSelectPublishFormValue = item => reselect.createSelector(selectState$4, state => state[item]); const selectMyClaimForUri = reselect.createSelector(selectPublishFormValues, selectIsStillEditing, selectClaimsById, selectMyClaimsWithoutChannels, ({ editingURI, uri }, isStillEditing, claimsById, myClaims) => { const { channelName: contentName, streamName: claimName } = parseURI(uri); @@ -4586,7 +5376,7 @@ const selectMyClaimForUri = reselect.createSelector(selectPublishFormValues, sel return isStillEditing ? claimsById[editClaimId] : myClaims.find(claim => !contentName ? claim.name === claimName : claim.name === contentName || claim.name === claimName); }); -const selectIsResolvingPublishUris = reselect.createSelector(selectState$3, selectResolvingUris, ({ uri, name }, resolvingUris) => { +const selectIsResolvingPublishUris = reselect.createSelector(selectState$4, selectResolvingUris, ({ uri, name }, resolvingUris) => { if (uri) { const isResolvingUri = resolvingUris.includes(uri); const { isChannel } = parseURI(uri); @@ -4603,7 +5393,7 @@ const selectIsResolvingPublishUris = reselect.createSelector(selectState$3, sele return false; }); -const selectTakeOverAmount = reselect.createSelector(selectState$3, selectMyClaimForUri, selectClaimsByUri, ({ name }, myClaimForUri, claimsByUri) => { +const selectTakeOverAmount = reselect.createSelector(selectState$4, selectMyClaimForUri, selectClaimsByUri, ({ name }, myClaimForUri, claimsByUri) => { if (!name) { return null; } @@ -4628,7 +5418,7 @@ const selectTakeOverAmount = reselect.createSelector(selectState$3, selectMyClai var _extends$7 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; -function _asyncToGenerator$2(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } +function _asyncToGenerator$3(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } const doResetThumbnailStatus = () => dispatch => { dispatch({ @@ -4973,7 +5763,7 @@ const doCheckReflectingFiles = () => (dispatch, getState) => { let reflectorCheckInterval; const checkFileList = (() => { - var _ref = _asyncToGenerator$2(function* () { + var _ref = _asyncToGenerator$3(function* () { const state = getState(); const reflectingById = selectReflectingById(state); const ids = Object.keys(reflectingById); @@ -5106,6 +5896,7 @@ const defaultState = { fetchingChannelClaims: {}, resolvingUris: [], myChannelClaims: undefined, + myCollectionClaims: [], myClaims: undefined, myPurchases: undefined, myPurchasesPageNumber: undefined, @@ -5114,6 +5905,7 @@ const defaultState = { fetchingMyPurchases: false, fetchingMyPurchasesError: undefined, fetchingMyChannels: false, + fetchingMyCollections: false, abandoningById: {}, pendingIds: [], reflectingById: {}, @@ -5122,9 +5914,13 @@ const defaultState = { claimSearchByQueryLastPageReached: {}, fetchingClaimSearchByQuery: {}, updateChannelError: '', + updateCollectionError: '', updatingChannel: false, creatingChannel: false, createChannelError: undefined, + updatingCollection: false, + creatingCollection: false, + createCollectionError: undefined, pendingChannelImport: false, repostLoading: false, repostError: undefined, @@ -5140,9 +5936,7 @@ const defaultState = { }; function handleClaimAction(state, action) { - const { - resolveInfo - } = action.data; + const { resolveInfo } = action.data; const byUri = Object.assign({}, state.claimsByUri); const byId = Object.assign({}, state.byId); @@ -5153,7 +5947,7 @@ function handleClaimAction(state, action) { Object.entries(resolveInfo).forEach(([url, resolveResponse]) => { // $FlowFixMe - const { claimsInChannel, stream, channel: channelFromResolve } = resolveResponse; + const { claimsInChannel, stream, channel: channelFromResolve, collection } = resolveResponse; const channel = channelFromResolve || stream && stream.signing_channel; if (stream) { @@ -5199,8 +5993,29 @@ function handleClaimAction(state, action) { newResolvingUrls.delete(channel.permanent_url); } + if (collection) { + if (pendingIds.includes(collection.claim_id)) { + byId[collection.claim_id] = mergeClaims(collection, byId[collection.claim_id]); + } else { + byId[collection.claim_id] = collection; + } + byUri[url] = collection.claim_id; + + // If url isn't a canonical_url, make sure that is added too + byUri[collection.canonical_url] = collection.claim_id; + + // Also add the permanent_url here until lighthouse returns canonical_url for search results + byUri[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 && !pendingIds.includes(byUri[url])) { + if (!stream && !channel && !collection && !pendingIds.includes(byUri[url])) { byUri[url] = null; } }); @@ -5335,6 +6150,51 @@ reducers[FETCH_CHANNEL_LIST_FAILED] = (state, action) => { }); }; +reducers[FETCH_COLLECTION_LIST_STARTED] = state => _extends$9({}, state, { + fetchingMyCollections: true +}); + +reducers[FETCH_COLLECTION_LIST_COMPLETED] = (state, action) => { + const { claims } = action.data; + const myClaims = state.myClaims || []; + let myClaimIds = new Set(myClaims); + const pendingIds = state.pendingIds || []; + let myCollectionClaimsSet = new Set([]); + const byId = Object.assign({}, state.byId); + const byUri = Object.assign({}, state.claimsByUri); + + if (claims.length) { + myCollectionClaimsSet = new Set(state.myCollectionClaims); + claims.forEach(claim => { + const { canonical_url: canonicalUrl, permanent_url: permanentUrl, claim_id: claimId } = claim; + // maybe add info about items in collection + + byUri[canonicalUrl] = claimId; + byUri[permanentUrl] = claimId; + + // $FlowFixMe + myCollectionClaimsSet.add(claimId); + // we don't want to overwrite a pending result with a resolve + if (!pendingIds.some(c => c === claimId)) { + byId[claimId] = claim; + } + myClaimIds.add(claimId); + }); + } + + return _extends$9({}, state, { + byId, + claimsByUri: byUri, + fetchingMyCollections: false, + myCollectionClaims: Array.from(myCollectionClaimsSet), + myClaims: myClaimIds ? Array.from(myClaimIds) : null + }); +}; + +reducers[FETCH_COLLECTION_LIST_FAILED] = state => { + return _extends$9({}, state, { fetchingMyCollections: false }); +}; + reducers[FETCH_CHANNEL_CLAIMS_STARTED] = (state, action) => { const { uri, page } = action.data; const fetchingChannelClaims = Object.assign({}, state.fetchingChannelClaims); @@ -5540,6 +6400,52 @@ reducers[UPDATE_CHANNEL_FAILED] = (state, action) => { }); }; +reducers[CLEAR_COLLECTION_ERRORS] = state => _extends$9({}, state, { + createCollectionError: null, + updateCollectionError: null +}); + +reducers[COLLECTION_PUBLISH_STARTED] = state => _extends$9({}, state, { + creatingCollection: true, + createCollectionError: null +}); + +reducers[COLLECTION_PUBLISH_COMPLETED] = (state, action) => { + return Object.assign({}, state, { + creatingCollection: false + }); +}; + +reducers[COLLECTION_PUBLISH_FAILED] = (state, action) => { + return Object.assign({}, state, { + creatingCollection: false, + createCollectionError: action.data.error + }); +}; + +reducers[COLLECTION_PUBLISH_UPDATE_STARTED] = (state, action) => { + return Object.assign({}, state, { + updateCollectionError: '', + updatingCollection: true + }); +}; + +reducers[COLLECTION_PUBLISH_UPDATE_COMPLETED] = (state, action) => { + return Object.assign({}, state, { + updateCollectionError: '', + updatingCollection: false + }); +}; + +reducers[COLLECTION_PUBLISH_UPDATE_FAILED] = (state, action) => { + return Object.assign({}, state, { + updateCollectionError: action.data.error, + updatingCollection: false + }); +}; + +// COLLECTION_PUBLISH_ABANDON_... + reducers[IMPORT_CHANNEL_STARTED] = state => Object.assign({}, state, { pendingChannelImports: true }); reducers[IMPORT_CHANNEL_COMPLETED] = state => Object.assign({}, state, { pendingChannelImports: false }); @@ -6565,11 +7471,224 @@ const walletReducer = handleActions({ }) }, defaultState$5); +var _extends$e = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +const getTimestamp$1 = () => { + return Math.floor(Date.now() / 1000); +}; + +const defaultState$6 = { + builtin: { + watchlater: { + items: ['lbry://why-wolves-determine-the-shape-of-rivers#d8a60a057ac9adb6b618be6985ca8361c730c02e'], + id: WATCH_LATER_ID, + name: 'Watch Later', + updatedAt: getTimestamp$1(), + type: COL_TYPE_PLAYLIST + }, + favorites: { + items: ['lbry://why-wolves-determine-the-shape-of-rivers#d8a60a057ac9adb6b618be6985ca8361c730c02e'], + id: FAVORITES_ID, + name: 'Favorites', + type: COL_TYPE_PLAYLIST, + updatedAt: getTimestamp$1() + } + }, + resolved: {}, + unpublished: {}, // sync + edited: {}, + pending: {}, + saved: [], + isResolvingCollectionById: {}, + error: null +}; + +const collectionsReducer = handleActions({ + [COLLECTION_NEW]: (state, action) => { + const { entry: params } = action.data; // { id:, items: Array} + // entry + const newListTemplate = { + id: params.id, + name: params.name, + items: [], + updatedAt: getTimestamp$1(), + type: params.type + }; + + const newList = Object.assign({}, newListTemplate, _extends$e({}, params)); + const { unpublished: lists } = state; + const newLists = Object.assign({}, lists, { [params.id]: newList }); + + return _extends$e({}, state, { + unpublished: newLists + }); + }, + + [COLLECTION_DELETE]: (state, action) => { + const { id, collectionKey } = action.data; + const { edited: editList, unpublished: unpublishedList, pending: pendingList } = state; + const newEditList = Object.assign({}, editList); + const newUnpublishedList = Object.assign({}, unpublishedList); + + const newPendingList = Object.assign({}, pendingList); + + if (collectionKey && state[collectionKey] && state[collectionKey][id]) { + const newList = Object.assign({}, state[collectionKey]); + delete newList[id]; + return _extends$e({}, state, { + [collectionKey]: newList + }); + } else { + if (newEditList[id]) { + delete newEditList[id]; + } else if (newUnpublishedList[id]) { + delete newUnpublishedList[id]; + } else if (newPendingList[id]) { + delete newPendingList[id]; + } + } + return _extends$e({}, state, { + edited: newEditList, + unpublished: newUnpublishedList, + pending: newPendingList + }); + }, + + [COLLECTION_PENDING]: (state, action) => { + const { localId, claimId } = action.data; + const { edited: editList, unpublished: unpublishedList, pending: pendingList } = state; + const newEditList = Object.assign({}, editList); + const newUnpublishedList = Object.assign({}, unpublishedList); + const newPendingList = Object.assign({}, pendingList); + + const isEdit = editList[localId]; + if (localId) { + // pending from unpublished -> published + // delete from local + newPendingList[claimId] = Object.assign({}, newEditList[localId] || newUnpublishedList[localId] || {}); + if (isEdit) { + delete newEditList[localId]; + } else { + delete newUnpublishedList[localId]; + } + } else { + // pending from edited published -> published + if (isEdit) { + newPendingList[claimId] = Object.assign({}, newEditList[claimId]); + delete newEditList[claimId]; + } + } + + return _extends$e({}, state, { + edited: newEditList, + unpublished: newUnpublishedList, + pending: newPendingList + }); + }, + + [COLLECTION_EDIT]: (state, action) => { + const { id, collectionKey, collection } = action.data; + + if (BUILTIN_LISTS.includes(id)) { + const { builtin: lists } = state; + return _extends$e({}, state, { + [collectionKey]: _extends$e({}, lists, { [id]: collection }) + }); + } + + if (collectionKey === 'edited') { + const { edited: lists } = state; + return _extends$e({}, state, { + edited: _extends$e({}, lists, { [id]: collection }) + }); + } + const { unpublished: lists } = state; + return _extends$e({}, state, { + unpublished: _extends$e({}, lists, { [id]: collection }) + }); + }, + + [COLLECTION_ERROR]: (state, action) => { + return Object.assign({}, state, { + error: action.data.message + }); + }, + + [COLLECTION_ITEMS_RESOLVE_STARTED]: (state, action) => { + const { ids } = action.data; + const { isResolvingCollectionById } = state; + const newResolving = Object.assign({}, isResolvingCollectionById); + ids.forEach(id => { + newResolving[id] = true; + }); + return Object.assign({}, state, _extends$e({}, state, { + error: '', + isResolvingCollectionById: newResolving + })); + }, + [USER_STATE_POPULATE]: (state, action) => { + const { builtinCollectionTest, savedCollectionTest, unpublishedCollectionTest } = action.data; + return _extends$e({}, state, { + unpublished: unpublishedCollectionTest || state.unpublished, + builtin: builtinCollectionTest || state.builtin, + saved: savedCollectionTest || state.saved + }); + }, + [COLLECTION_ITEMS_RESOLVE_COMPLETED]: (state, action) => { + const { resolvedCollections, failedCollectionIds } = action.data; + const { pending, edited, isResolvingCollectionById, resolved } = state; + const newPending = Object.assign({}, pending); + const newEdited = Object.assign({}, edited); + const newResolved = Object.assign({}, resolved, resolvedCollections); + + const resolvedIds = Object.keys(resolvedCollections); + const newResolving = Object.assign({}, isResolvingCollectionById); + if (resolvedCollections && Object.keys(resolvedCollections).length) { + resolvedIds.forEach(resolvedId => { + if (newEdited[resolvedId]) { + if (newEdited[resolvedId]['updatedAt'] < resolvedCollections[resolvedId]['updatedAt']) { + delete newEdited[resolvedId]; + } + } + delete newResolving[resolvedId]; + if (newPending[resolvedId]) { + delete newPending[resolvedId]; + } + }); + } + + if (failedCollectionIds && Object.keys(failedCollectionIds).length) { + failedCollectionIds.forEach(failedId => { + delete newResolving[failedId]; + }); + } + + return Object.assign({}, state, _extends$e({}, state, { + pending: newPending, + resolved: newResolved, + edited: newEdited, + isResolvingCollectionById: newResolving + })); + }, + [COLLECTION_ITEMS_RESOLVE_FAILED]: (state, action) => { + const { ids } = action.data; + const { isResolvingCollectionById } = state; + const newResolving = Object.assign({}, isResolvingCollectionById); + ids.forEach(id => { + delete newResolving[id]; + }); + return Object.assign({}, state, _extends$e({}, state, { + isResolvingCollectionById: newResolving, + error: action.data.message + })); + } +}, defaultState$6); + // -const selectState$4 = state => state.content || {}; +const selectState$5 = state => state.content || {}; -const makeSelectContentPositionForUri = uri => reselect.createSelector(selectState$4, makeSelectClaimForUri(uri), (state, claim) => { +const makeSelectContentPositionForUri = uri => reselect.createSelector(selectState$5, makeSelectClaimForUri(uri), (state, claim) => { if (!claim) { return null; } @@ -6578,14 +7697,14 @@ const makeSelectContentPositionForUri = uri => reselect.createSelector(selectSta return state.positions[id] ? state.positions[id][outpoint] : null; }); -var _extends$e = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; +var _extends$f = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; -const selectState$5 = state => state.notifications || {}; +const selectState$6 = state => state.notifications || {}; -const selectToast = reselect.createSelector(selectState$5, state => { +const selectToast = reselect.createSelector(selectState$6, state => { if (state.toasts.length) { const { id, params } = state.toasts[0]; - return _extends$e({ + return _extends$f({ id }, params); } @@ -6593,7 +7712,7 @@ const selectToast = reselect.createSelector(selectState$5, state => { return null; }); -const selectError = reselect.createSelector(selectState$5, state => { +const selectError = reselect.createSelector(selectState$6, state => { if (state.errors.length) { const { error } = state.errors[0]; return { @@ -6607,6 +7726,7 @@ const selectError = reselect.createSelector(selectState$5, state => { exports.ABANDON_STATES = abandon_states; exports.ACTIONS = action_types; exports.CLAIM_VALUES = claim; +exports.COLLECTIONS_CONSTS = collections; exports.DAEMON_SETTINGS = daemon_settings; exports.DEFAULT_FOLLOWED_TAGS = DEFAULT_FOLLOWED_TAGS; exports.DEFAULT_KNOWN_TAGS = DEFAULT_KNOWN_TAGS; @@ -6628,6 +7748,7 @@ exports.batchActions = batchActions; exports.buildSharedStateMiddleware = buildSharedStateMiddleware; exports.buildURI = buildURI; exports.claimsReducer = claimsReducer; +exports.collectionsReducer = collectionsReducer; exports.contentReducer = contentReducer; exports.convertToShareLink = convertToShareLink; exports.createNormalizedClaimSearchKey = createNormalizedClaimSearchKey; @@ -6645,6 +7766,9 @@ exports.doClearPublish = doClearPublish; exports.doClearPurchasedUriSuccess = doClearPurchasedUriSuccess; exports.doClearRepostError = doClearRepostError; exports.doClearSupport = doClearSupport; +exports.doCollectionEdit = doCollectionEdit; +exports.doCollectionPublish = doCollectionPublish; +exports.doCollectionPublishUpdate = doCollectionPublishUpdate; exports.doCreateChannel = doCreateChannel; exports.doDismissError = doDismissError; exports.doDismissToast = doDismissToast; @@ -6652,8 +7776,11 @@ exports.doError = doError; exports.doFetchChannelListMine = doFetchChannelListMine; exports.doFetchClaimListMine = doFetchClaimListMine; exports.doFetchClaimsByChannel = doFetchClaimsByChannel; +exports.doFetchCollectionListMine = doFetchCollectionListMine; exports.doFetchFileInfo = doFetchFileInfo; exports.doFetchFileInfos = doFetchFileInfos; +exports.doFetchItemsInCollection = doFetchItemsInCollection; +exports.doFetchItemsInCollections = doFetchItemsInCollections; exports.doFetchTransactions = doFetchTransactions; exports.doFetchTxoPage = doFetchTxoPage; exports.doFetchUtxoCounts = doFetchUtxoCounts; @@ -6661,6 +7788,8 @@ exports.doFileGet = doFileGet; exports.doFileList = doFileList; exports.doGetNewAddress = doGetNewAddress; exports.doImportChannel = doImportChannel; +exports.doLocalCollectionCreate = doLocalCollectionCreate; +exports.doLocalCollectionDelete = doLocalCollectionDelete; exports.doPopulateSharedUserState = doPopulateSharedUserState; exports.doPreferenceGet = doPreferenceGet; exports.doPreferenceSet = doPreferenceSet; @@ -6708,6 +7837,8 @@ exports.makeSelectChannelPermUrlForClaimUri = makeSelectChannelPermUrlForClaimUr exports.makeSelectClaimForClaimId = makeSelectClaimForClaimId; exports.makeSelectClaimForUri = makeSelectClaimForUri; exports.makeSelectClaimHasSource = makeSelectClaimHasSource; +exports.makeSelectClaimIdForUri = makeSelectClaimIdForUri; +exports.makeSelectClaimIdsForCollectionId = makeSelectClaimIdsForCollectionId; exports.makeSelectClaimIsMine = makeSelectClaimIsMine; exports.makeSelectClaimIsNsfw = makeSelectClaimIsNsfw; exports.makeSelectClaimIsPending = makeSelectClaimIsPending; @@ -6715,12 +7846,16 @@ exports.makeSelectClaimIsStreamPlaceholder = makeSelectClaimIsStreamPlaceholder; exports.makeSelectClaimWasPurchased = makeSelectClaimWasPurchased; exports.makeSelectClaimsInChannelForCurrentPageState = makeSelectClaimsInChannelForCurrentPageState; exports.makeSelectClaimsInChannelForPage = makeSelectClaimsInChannelForPage; +exports.makeSelectCollectionForId = makeSelectCollectionForId; +exports.makeSelectCollectionForIdHasClaimUrl = makeSelectCollectionForIdHasClaimUrl; +exports.makeSelectCollectionIsMine = makeSelectCollectionIsMine; exports.makeSelectContentPositionForUri = makeSelectContentPositionForUri; exports.makeSelectContentTypeForUri = makeSelectContentTypeForUri; exports.makeSelectCoverForUri = makeSelectCoverForUri; exports.makeSelectDateForUri = makeSelectDateForUri; exports.makeSelectDownloadPathForUri = makeSelectDownloadPathForUri; exports.makeSelectDownloadingForUri = makeSelectDownloadingForUri; +exports.makeSelectEditedCollectionForId = makeSelectEditedCollectionForId; exports.makeSelectEffectiveAmountForUri = makeSelectEffectiveAmountForUri; exports.makeSelectFetchingChannelClaims = makeSelectFetchingChannelClaims; exports.makeSelectFileInfoForUri = makeSelectFileInfoForUri; @@ -6728,6 +7863,7 @@ exports.makeSelectFileNameForUri = makeSelectFileNameForUri; exports.makeSelectFilePartlyDownloaded = makeSelectFilePartlyDownloaded; exports.makeSelectFilteredTransactionsForPage = makeSelectFilteredTransactionsForPage; exports.makeSelectIsAbandoningClaimForUri = makeSelectIsAbandoningClaimForUri; +exports.makeSelectIsResolvingCollectionForId = makeSelectIsResolvingCollectionForId; exports.makeSelectIsUriResolving = makeSelectIsUriResolving; exports.makeSelectLatestTransactions = makeSelectLatestTransactions; exports.makeSelectLoadingForUri = makeSelectLoadingForUri; @@ -6735,15 +7871,20 @@ exports.makeSelectMediaTypeForUri = makeSelectMediaTypeForUri; exports.makeSelectMetadataForUri = makeSelectMetadataForUri; exports.makeSelectMetadataItemForUri = makeSelectMetadataItemForUri; exports.makeSelectMyChannelPermUrlForName = makeSelectMyChannelPermUrlForName; +exports.makeSelectMyPublishedCollectionForId = makeSelectMyPublishedCollectionForId; exports.makeSelectMyPurchasesForPage = makeSelectMyPurchasesForPage; exports.makeSelectMyStreamUrlsForPage = makeSelectMyStreamUrlsForPage; +exports.makeSelectNameForCollectionId = makeSelectNameForCollectionId; +exports.makeSelectNextUrlForCollection = makeSelectNextUrlForCollection; exports.makeSelectNsfwCountForChannel = makeSelectNsfwCountForChannel; exports.makeSelectNsfwCountFromUris = makeSelectNsfwCountFromUris; exports.makeSelectOmittedCountForChannel = makeSelectOmittedCountForChannel; exports.makeSelectPendingAmountByUri = makeSelectPendingAmountByUri; exports.makeSelectPendingClaimForUri = makeSelectPendingClaimForUri; +exports.makeSelectPendingCollectionForId = makeSelectPendingCollectionForId; exports.makeSelectPermanentUrlForUri = makeSelectPermanentUrlForUri; exports.makeSelectPublishFormValue = makeSelectPublishFormValue; +exports.makeSelectPublishedCollectionForId = makeSelectPublishedCollectionForId; exports.makeSelectReflectingClaimForUri = makeSelectReflectingClaimForUri; exports.makeSelectSearchDownloadUrlsCount = makeSelectSearchDownloadUrlsCount; exports.makeSelectSearchDownloadUrlsForPage = makeSelectSearchDownloadUrlsForPage; @@ -6760,7 +7901,9 @@ exports.makeSelectTotalItemsForChannel = makeSelectTotalItemsForChannel; exports.makeSelectTotalPagesForChannel = makeSelectTotalPagesForChannel; exports.makeSelectTotalPagesInChannelSearch = makeSelectTotalPagesInChannelSearch; exports.makeSelectTotalStakedAmountForChannelUri = makeSelectTotalStakedAmountForChannelUri; +exports.makeSelectUnpublishedCollectionForId = makeSelectUnpublishedCollectionForId; exports.makeSelectUriIsStreamable = makeSelectUriIsStreamable; +exports.makeSelectUrlsForCollectionId = makeSelectUrlsForCollectionId; exports.normalizeURI = normalizeURI; exports.notificationsReducer = notificationsReducer; exports.parseQueryParams = parseQueryParams; @@ -6776,6 +7919,7 @@ exports.selectAllFetchingChannelClaims = selectAllFetchingChannelClaims; exports.selectAllMyClaimsByOutpoint = selectAllMyClaimsByOutpoint; exports.selectBalance = selectBalance; exports.selectBlocks = selectBlocks; +exports.selectBuiltinCollections = selectBuiltinCollections; exports.selectChannelClaimCounts = selectChannelClaimCounts; exports.selectChannelImportPending = selectChannelImportPending; exports.selectClaimIdsByUri = selectClaimIdsByUri; @@ -6785,7 +7929,9 @@ exports.selectClaimsBalance = selectClaimsBalance; exports.selectClaimsById = selectClaimsById; exports.selectClaimsByUri = selectClaimsByUri; exports.selectCreateChannelError = selectCreateChannelError; +exports.selectCreateCollectionError = selectCreateCollectionError; exports.selectCreatingChannel = selectCreatingChannel; +exports.selectCreatingCollection = selectCreatingCollection; exports.selectCurrentChannelPage = selectCurrentChannelPage; exports.selectDownloadUrlsCount = selectDownloadUrlsCount; exports.selectDownloadedUris = selectDownloadedUris; @@ -6800,6 +7946,7 @@ exports.selectFetchingClaimSearch = selectFetchingClaimSearch; exports.selectFetchingClaimSearchByQuery = selectFetchingClaimSearchByQuery; exports.selectFetchingMyChannels = selectFetchingMyChannels; exports.selectFetchingMyClaimsPageError = selectFetchingMyClaimsPageError; +exports.selectFetchingMyCollections = selectFetchingMyCollections; exports.selectFetchingMyPurchasesError = selectFetchingMyPurchasesError; exports.selectFetchingTxosError = selectFetchingTxosError; exports.selectFileInfosByOutpoint = selectFileInfosByOutpoint; @@ -6835,9 +7982,15 @@ exports.selectMyClaimsPageItemCount = selectMyClaimsPageItemCount; exports.selectMyClaimsPageNumber = selectMyClaimsPageNumber; exports.selectMyClaimsRaw = selectMyClaimsRaw; exports.selectMyClaimsWithoutChannels = selectMyClaimsWithoutChannels; +exports.selectMyCollectionIds = selectMyCollectionIds; +exports.selectMyEditedCollections = selectMyEditedCollections; +exports.selectMyPublishedCollections = selectMyPublishedCollections; +exports.selectMyPublishedMixedCollections = selectMyPublishedMixedCollections; +exports.selectMyPublishedPlaylistCollections = selectMyPublishedPlaylistCollections; exports.selectMyPurchases = selectMyPurchases; exports.selectMyPurchasesCount = selectMyPurchasesCount; exports.selectMyStreamUrlsCount = selectMyStreamUrlsCount; +exports.selectMyUnpublishedCollections = selectMyUnpublishedCollections; exports.selectPendingClaims = selectPendingClaims; exports.selectPendingConsolidateTxid = selectPendingConsolidateTxid; exports.selectPendingIds = selectPendingIds; @@ -6853,7 +8006,9 @@ exports.selectReflectingById = selectReflectingById; exports.selectRepostError = selectRepostError; exports.selectRepostLoading = selectRepostLoading; exports.selectReservedBalance = selectReservedBalance; +exports.selectResolvedCollections = selectResolvedCollections; exports.selectResolvingUris = selectResolvingUris; +exports.selectSavedCollectionIds = selectSavedCollectionIds; exports.selectSupportsBalance = selectSupportsBalance; exports.selectSupportsByOutpoint = selectSupportsByOutpoint; exports.selectTakeOverAmount = selectTakeOverAmount; @@ -6871,7 +8026,9 @@ exports.selectTxoPage = selectTxoPage; exports.selectTxoPageNumber = selectTxoPageNumber; exports.selectTxoPageParams = selectTxoPageParams; exports.selectUpdateChannelError = selectUpdateChannelError; +exports.selectUpdateCollectionError = selectUpdateCollectionError; exports.selectUpdatingChannel = selectUpdatingChannel; +exports.selectUpdatingCollection = selectUpdatingCollection; exports.selectUrisLoading = selectUrisLoading; exports.selectUtxoCounts = selectUtxoCounts; exports.selectWalletDecryptPending = selectWalletDecryptPending; diff --git a/src/constants/collections.js b/src/constants/collections.js index 6b6d2f1..d4c1739 100644 --- a/src/constants/collections.js +++ b/src/constants/collections.js @@ -1,5 +1,10 @@ export const COLLECTION_ID = 'colid'; export const COLLECTION_INDEX = 'colindex'; +export const COL_TYPE_PLAYLIST = 'playlist'; +export const COL_TYPE_CHANNELS = 'channelCollection'; + export const WATCH_LATER_ID = 'watchlater'; export const FAVORITES_ID = 'favorites'; +export const FAVORITE_CHANNELS_ID = 'favoriteChannels'; +export const BUILTIN_LISTS = [WATCH_LATER_ID, FAVORITES_ID, FAVORITE_CHANNELS_ID]; diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index 98cee29..50368ee 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -422,7 +422,7 @@ export function doCreateChannel(name: string, amount: number, optionalParams: an description?: string, website_url?: string, email?: string, - tags?: Array, + tags?: Array, languages?: Array, } = { name, @@ -801,11 +801,12 @@ export function doCollectionPublishUpdate(options: { bid?: string, blocking?: true, title?: string, - thumbnail_url?: string, + thumbnailUrl?: string, description?: string, claim_id: string, - tags?: Array, + tags?: Array, languages?: Array, + claims?: Array, }) { return (dispatch: Dispatch, getState: GetState) => { const state = getState(); @@ -813,12 +814,35 @@ export function doCollectionPublishUpdate(options: { // get publish params from claim // $FlowFixMe - const collectionClaim = makeSelectClaimForClaimId(options.claim_id)(state); - // TODO: add old claim entries to params - const editItems = makeSelectEditedCollectionForId(options.claim_id)(state); - const oldParams: CollectionUpdateParams = { - bid: collectionClaim.amount, + const updateParams: { + bid?: string, + blocking?: true, + title?: string, + thumbnail_url?: string, + description?: string, + claim_id: string, + tags?: Array, + languages?: Array, + claims?: Array, + } = { + bid: creditsToString(options.bid), + title: options.title, + thumbnail_url: options.thumbnailUrl, + description: options.description, + tags: [], + languages: options.languages || [], + locations: [], + blocking: true, + claim_id: options.claim_id, }; + + if (options.tags) { + updateParams['tags'] = options.tags.map(tag => tag.name); + } + + if (options.claims) { + updateParams['claims'] = options.claims; + } // $FlowFixMe return new Promise(resolve => { dispatch({ @@ -839,6 +863,7 @@ export function doCollectionPublishUpdate(options: { claims: [collectionClaim], }, }); + dispatch(doCheckPendingClaims()); dispatch(doFetchCollectionListMine(1, 10)); resolve(collectionClaim); } @@ -852,7 +877,7 @@ export function doCollectionPublishUpdate(options: { }); } - Lbry.collection_update(options).then(success, failure); + Lbry.collection_update(updateParams).then(success, failure); }); }; } diff --git a/src/redux/actions/collections.js b/src/redux/actions/collections.js index 7aca1e0..213d86c 100644 --- a/src/redux/actions/collections.js +++ b/src/redux/actions/collections.js @@ -12,10 +12,7 @@ import { makeSelectUnpublishedCollectionForId, makeSelectEditedCollectionForId, } from 'redux/selectors/collections'; -const WATCH_LATER_ID = 'watchlater'; -const FAVORITES_ID = 'favorites'; - -const BUILTIN_LISTS = [WATCH_LATER_ID, FAVORITES_ID]; +import * as COLS from 'constants/collections'; const getTimestamp = () => { return Math.floor(Date.now() / 1000); @@ -37,7 +34,7 @@ export const doLocalCollectionCreate = ( updatedAt: getTimestamp(), items: collectionItems || [], sourceId: sourceId, - type: type || 'collection', + type: type, }, }, }); @@ -343,7 +340,7 @@ export const doCollectionEdit = (collectionId: string, params: CollectionEditPar let currentItems = collection.items ? collection.items.concat() : []; const { claims: passedClaims, order, claimIds, replace, remove, type } = params; - const collectionType = type || 'collection'; + const collectionType = type || collection.type; let newItems: Array = currentItems; if (passedClaims) { @@ -430,7 +427,7 @@ export const doCollectionEdit = (collectionId: string, params: CollectionEditPar }, }, }); - } else if (BUILTIN_LISTS.includes(collectionId)) { + } else if (COLS.BUILTIN_LISTS.includes(collectionId)) { dispatch({ type: ACTIONS.COLLECTION_EDIT, data: { diff --git a/src/redux/reducers/claims.js b/src/redux/reducers/claims.js index 21b3c01..008ee4f 100644 --- a/src/redux/reducers/claims.js +++ b/src/redux/reducers/claims.js @@ -341,15 +341,12 @@ reducers[ACTIONS.FETCH_COLLECTION_LIST_COMPLETED] = (state: State, action: any): const myClaims = state.myClaims || []; let myClaimIds = new Set(myClaims); const pendingIds = state.pendingIds || []; - let myCollectionClaims; + let myCollectionClaimsSet = new Set([]); const byId = Object.assign({}, state.byId); const byUri = Object.assign({}, state.claimsByUri); - if (!claims.length) { - // $FlowFixMe - myCollectionClaims = null; - } else { - myCollectionClaims = new Set(state.myCollectionClaims); + if (claims.length) { + myCollectionClaimsSet = new Set(state.myCollectionClaims); claims.forEach(claim => { const { meta } = claim; const { canonical_url: canonicalUrl, permanent_url: permanentUrl, claim_id: claimId } = claim; @@ -359,7 +356,7 @@ reducers[ACTIONS.FETCH_COLLECTION_LIST_COMPLETED] = (state: State, action: any): byUri[permanentUrl] = claimId; // $FlowFixMe - myCollectionClaims.add(claimId); + myCollectionClaimsSet.add(claimId); // we don't want to overwrite a pending result with a resolve if (!pendingIds.some(c => c === claimId)) { byId[claimId] = claim; @@ -373,7 +370,7 @@ reducers[ACTIONS.FETCH_COLLECTION_LIST_COMPLETED] = (state: State, action: any): byId, claimsByUri: byUri, fetchingMyCollections: false, - myCollectionClaims: myCollectionClaims ? Array.from(myCollectionClaims) : null, + myCollectionClaims: Array.from(myCollectionClaimsSet), myClaims: myClaimIds ? Array.from(myClaimIds) : null, }; }; diff --git a/src/redux/reducers/collections.js b/src/redux/reducers/collections.js index 691a821..f38cb2a 100644 --- a/src/redux/reducers/collections.js +++ b/src/redux/reducers/collections.js @@ -1,12 +1,8 @@ // @flow import { handleActions } from 'util/redux-utils'; import * as ACTIONS from 'constants/action_types'; -import * as COLLECTION_CONSTS from 'constants/collections'; +import * as COLS from 'constants/collections'; -const WATCH_LATER_ID = 'watchlater'; -const FAVORITES_ID = 'favorites'; - -const BUILTIN_LISTS = [WATCH_LATER_ID, FAVORITES_ID]; const getTimestamp = () => { return Math.floor(Date.now() / 1000); }; @@ -14,17 +10,21 @@ const getTimestamp = () => { const defaultState: CollectionState = { builtin: { watchlater: { - items: ['lbry://seriouspublish#c1b740eb88f96b465f65e5f1542564539df1c62e'], - id: WATCH_LATER_ID, + items: [ + 'lbry://why-wolves-determine-the-shape-of-rivers#d8a60a057ac9adb6b618be6985ca8361c730c02e', + ], + id: COLS.WATCH_LATER_ID, name: 'Watch Later', updatedAt: getTimestamp(), - type: 'playlist', + type: COLS.COL_TYPE_PLAYLIST, }, favorites: { - items: ['lbry://seriouspublish#c1b740eb88f96b465f65e5f1542564539df1c62e'], - id: FAVORITES_ID, + items: [ + 'lbry://why-wolves-determine-the-shape-of-rivers#d8a60a057ac9adb6b618be6985ca8361c730c02e', + ], + id: COLS.FAVORITES_ID, name: 'Favorites', - type: 'collection', + type: COLS.COL_TYPE_PLAYLIST, updatedAt: getTimestamp(), }, }, @@ -47,7 +47,7 @@ const collectionsReducer = handleActions( name: params.name, items: [], updatedAt: getTimestamp(), - type: params.type || 'mixed', + type: params.type, }; const newList = Object.assign({}, newListTemplate, { ...params }); @@ -131,7 +131,7 @@ const collectionsReducer = handleActions( [ACTIONS.COLLECTION_EDIT]: (state, action) => { const { id, collectionKey, collection } = action.data; - if (BUILTIN_LISTS.includes(id)) { + if (COLS.BUILTIN_LISTS.includes(id)) { const { builtin: lists } = state; return { ...state, diff --git a/src/redux/selectors/collections.js b/src/redux/selectors/collections.js index e59fb7c..74537f7 100644 --- a/src/redux/selectors/collections.js +++ b/src/redux/selectors/collections.js @@ -162,7 +162,7 @@ export const makeSelectCollectionForId = (id: string) => export const makeSelectCollectionForIdHasClaimUrl = (id: string, url: string) => createSelector( makeSelectCollectionForId(id), - collection => collection.items.includes(url) + collection => collection && collection.items.includes(url) ); export const makeSelectUrlsForCollectionId = (id: string) => -- 2.45.3 From 3b853b6ddd1e55ca8e9f184e0d8c2702b68ddb3c Mon Sep 17 00:00:00 2001 From: infiinte-persistence Date: Tue, 11 May 2021 11:02:48 +0800 Subject: [PATCH 04/24] Define default value for 'thumbnailError' ## Issue "thumbnail is invalid" not reset with new thumbnail upload #6044 https://github.com/lbryio/lbry-desktop/issues/6044 ## Change Defining a default value will cover both CLEAR_PUBLISH and DO_PREPARE_EDIT --- dist/bundle.es.js | 1 + src/redux/reducers/publish.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 2af1339..d4b83f9 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -6056,6 +6056,7 @@ const defaultState$4 = { thumbnail_url: '', thumbnailPath: '', uploadThumbnailStatus: API_DOWN, + thumbnailError: undefined, description: '', language: '', releaseTime: undefined, diff --git a/src/redux/reducers/publish.js b/src/redux/reducers/publish.js index e89564d..39e40f5 100644 --- a/src/redux/reducers/publish.js +++ b/src/redux/reducers/publish.js @@ -22,6 +22,7 @@ type PublishState = { thumbnail_url: string, thumbnailPath: string, uploadThumbnailStatus: string, + thumbnailError: ?boolean, description: string, language: string, releaseTime: ?string, @@ -55,6 +56,7 @@ const defaultState: PublishState = { thumbnail_url: '', thumbnailPath: '', uploadThumbnailStatus: THUMBNAIL_STATUSES.API_DOWN, + thumbnailError: undefined, description: '', language: '', releaseTime: undefined, -- 2.45.3 From 15eebee6946426eb613791546357d1e368a8a75e Mon Sep 17 00:00:00 2001 From: zeppi Date: Wed, 12 May 2021 15:11:04 -0400 Subject: [PATCH 05/24] pending, edit fixes, support collectionCount --- dist/bundle.es.js | 44 ++++++++++++++++++++++-------- src/index.js | 3 +- src/redux/actions/claims.js | 19 +++++++++++-- src/redux/actions/collections.js | 7 ++++- src/redux/reducers/claims.js | 9 ++++++ src/redux/selectors/collections.js | 11 ++++++++ 6 files changed, 78 insertions(+), 15 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 85ec3f6..314a4eb 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -3706,6 +3706,13 @@ const makeSelectNameForCollectionId = id => reselect.createSelector(makeSelectCo return collection && collection.name || ''; }); +const makeSelectCountForCollectionId = id => reselect.createSelector(makeSelectCollectionForId(id), collection => { + if (collection.itemCount !== undefined) { + return collection.itemCount; + } + return collection.items.length; +}); + var _extends$5 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function _asyncToGenerator$1(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } @@ -4242,7 +4249,7 @@ function doFetchCollectionListMine(page = 1, pageSize = 99999) { }); }; - lbryProxy.collection_list({ page, page_size: pageSize, resolve_claims: 1 }).then(callback, failure); + lbryProxy.collection_list({ page, page_size: pageSize, resolve_claims: 1, resolve: true }).then(callback, failure); }; } @@ -4354,7 +4361,8 @@ function doCollectionPublish(options, localId) { function success(response) { const collectionClaim = response.outputs[0]; dispatch(batchActions({ - type: COLLECTION_PUBLISH_COMPLETED + type: COLLECTION_PUBLISH_COMPLETED, + data: { claimId: collectionClaim.claim_id } }, // shift unpublished collection to pending collection with new publish id // recent publish won't resolve this second. handle it in checkPending @@ -4401,13 +4409,11 @@ function doCollectionPublishUpdate(options) { tags: [], languages: options.languages || [], locations: [], - blocking: true + blocking: true, + claim_id: options.claim_id, + clear_claims: true }; - if (options.claim_id) { - updateParams['claim_id'] = options.claim_id; - } - if (options.tags) { updateParams['tags'] = options.tags.map(tag => tag.name); } @@ -4429,6 +4435,10 @@ function doCollectionPublishUpdate(options) { collectionClaim } }); + dispatch({ + type: COLLECTION_PENDING, + data: { claimId: collectionClaim.claim_id } + }); dispatch({ type: UPDATE_PENDING_CLAIMS, data: { @@ -4532,6 +4542,7 @@ const doCheckPendingClaims = onConfirmed => (dispatch, getState) => { pendingIdSet.delete(claimId); if (Object.keys(pendingCollections).includes(claim.claim_id)) { dispatch(doFetchItemsInCollection({ collectionId: claim.claim_id })); + dispatch(doCollectionDelete(claim.claim_id, 'pending')); } claimsToConfirm.push(claim); if (onConfirmed) { @@ -4583,11 +4594,12 @@ const doLocalCollectionCreate = (name, collectionItems, type, sourceId) => dispa }); }; -const doLocalCollectionDelete = id => dispatch => { +const doCollectionDelete = (id, colKey = undefined, keepLocal) => dispatch => { return dispatch({ type: COLLECTION_DELETE, data: { - id: id + id: id, + collectionKey: colKey } }); }; @@ -6411,8 +6423,17 @@ reducers[COLLECTION_PUBLISH_STARTED] = state => _extends$9({}, state, { }); reducers[COLLECTION_PUBLISH_COMPLETED] = (state, action) => { + 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 + creatingCollection: false, + myClaims: Array.from(myClaimIds), + myCollectionClaims: Array.from(myCollectionClaimsSet) }); }; @@ -7766,6 +7787,7 @@ exports.doClearPublish = doClearPublish; exports.doClearPurchasedUriSuccess = doClearPurchasedUriSuccess; exports.doClearRepostError = doClearRepostError; exports.doClearSupport = doClearSupport; +exports.doCollectionDelete = doCollectionDelete; exports.doCollectionEdit = doCollectionEdit; exports.doCollectionPublish = doCollectionPublish; exports.doCollectionPublishUpdate = doCollectionPublishUpdate; @@ -7789,7 +7811,6 @@ exports.doFileList = doFileList; exports.doGetNewAddress = doGetNewAddress; exports.doImportChannel = doImportChannel; exports.doLocalCollectionCreate = doLocalCollectionCreate; -exports.doLocalCollectionDelete = doLocalCollectionDelete; exports.doPopulateSharedUserState = doPopulateSharedUserState; exports.doPreferenceGet = doPreferenceGet; exports.doPreferenceSet = doPreferenceSet; @@ -7851,6 +7872,7 @@ exports.makeSelectCollectionForIdHasClaimUrl = makeSelectCollectionForIdHasClaim exports.makeSelectCollectionIsMine = makeSelectCollectionIsMine; exports.makeSelectContentPositionForUri = makeSelectContentPositionForUri; exports.makeSelectContentTypeForUri = makeSelectContentTypeForUri; +exports.makeSelectCountForCollectionId = makeSelectCountForCollectionId; exports.makeSelectCoverForUri = makeSelectCoverForUri; exports.makeSelectDateForUri = makeSelectDateForUri; exports.makeSelectDownloadPathForUri = makeSelectDownloadPathForUri; diff --git a/src/index.js b/src/index.js index d2acbad..6187fdf 100644 --- a/src/index.js +++ b/src/index.js @@ -64,7 +64,7 @@ export { doFetchItemsInCollection, doFetchItemsInCollections, doCollectionEdit, - doLocalCollectionDelete, + doCollectionDelete, } from 'redux/actions/collections'; export { @@ -177,6 +177,7 @@ export { makeSelectUrlsForCollectionId, makeSelectClaimIdsForCollectionId, makeSelectNameForCollectionId, + makeSelectCountForCollectionId, makeSelectIsResolvingCollectionForId, makeSelectNextUrlForCollection, makeSelectCollectionForIdHasClaimUrl, diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index 50368ee..525c63d 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -24,7 +24,11 @@ import { makeSelectEditedCollectionForId, selectPendingCollections, } from 'redux/selectors/collections'; -import { doFetchItemsInCollection, doFetchItemsInCollections } from 'redux/actions/collections'; +import { + doFetchItemsInCollection, + doFetchItemsInCollections, + doCollectionDelete, +} from 'redux/actions/collections'; type ResolveEntries = Array<[string, GenericClaim]>; @@ -624,7 +628,10 @@ export function doFetchCollectionListMine(page: number = 1, pageSize: number = 9 }); }; - Lbry.collection_list({ page, page_size: pageSize, resolve_claims: 1 }).then(callback, failure); + Lbry.collection_list({ page, page_size: pageSize, resolve_claims: 1, resolve: true }).then( + callback, + failure + ); }; } @@ -763,6 +770,7 @@ export function doCollectionPublish( batchActions( { type: ACTIONS.COLLECTION_PUBLISH_COMPLETED, + data: { claimId: collectionClaim.claim_id }, }, // shift unpublished collection to pending collection with new publish id // recent publish won't resolve this second. handle it in checkPending @@ -824,6 +832,7 @@ export function doCollectionPublishUpdate(options: { tags?: Array, languages?: Array, claims?: Array, + clear_claims: boolean, } = { bid: creditsToString(options.bid), title: options.title, @@ -834,6 +843,7 @@ export function doCollectionPublishUpdate(options: { locations: [], blocking: true, claim_id: options.claim_id, + clear_claims: true, }; if (options.tags) { @@ -857,6 +867,10 @@ export function doCollectionPublishUpdate(options: { collectionClaim, }, }); + dispatch({ + type: ACTIONS.COLLECTION_PENDING, + data: { claimId: collectionClaim.claim_id }, + }); dispatch({ type: ACTIONS.UPDATE_PENDING_CLAIMS, data: { @@ -964,6 +978,7 @@ export const doCheckPendingClaims = (onConfirmed: Function) => ( pendingIdSet.delete(claimId); if (Object.keys(pendingCollections).includes(claim.claim_id)) { dispatch(doFetchItemsInCollection({ collectionId: claim.claim_id })); + dispatch(doCollectionDelete(claim.claim_id, 'pending')); } claimsToConfirm.push(claim); if (onConfirmed) { diff --git a/src/redux/actions/collections.js b/src/redux/actions/collections.js index 213d86c..74b6a2d 100644 --- a/src/redux/actions/collections.js +++ b/src/redux/actions/collections.js @@ -40,11 +40,16 @@ export const doLocalCollectionCreate = ( }); }; -export const doLocalCollectionDelete = (id: string) => (dispatch: Dispatch) => { +export const doCollectionDelete = ( + id: string, + colKey: ?string = undefined, + keepLocal?: boolean +) => (dispatch: Dispatch) => { return dispatch({ type: ACTIONS.COLLECTION_DELETE, data: { id: id, + collectionKey: colKey, }, }); }; diff --git a/src/redux/reducers/claims.js b/src/redux/reducers/claims.js index 008ee4f..3b6ecb9 100644 --- a/src/redux/reducers/claims.js +++ b/src/redux/reducers/claims.js @@ -606,8 +606,17 @@ reducers[ACTIONS.COLLECTION_PUBLISH_STARTED] = (state: State): State => ({ }); 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), }); }; diff --git a/src/redux/selectors/collections.js b/src/redux/selectors/collections.js index 74537f7..f781b91 100644 --- a/src/redux/selectors/collections.js +++ b/src/redux/selectors/collections.js @@ -203,3 +203,14 @@ export const makeSelectNameForCollectionId = (id: string) => return (collection && collection.name) || ''; } ); + +export const makeSelectCountForCollectionId = (id: string) => + createSelector( + makeSelectCollectionForId(id), + collection => { + if (collection.itemCount !== undefined) { + return collection.itemCount; + } + return collection.items.length; + } + ); -- 2.45.3 From babfec7d4381382e10ff3df4571334fdb4c5fd27 Mon Sep 17 00:00:00 2001 From: infiinte-persistence Date: Fri, 30 Apr 2021 16:19:13 +0800 Subject: [PATCH 06/24] Complete rename of 'release_time' I believe this was missed out in c31161c4 --- src/redux/actions/publish.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/redux/actions/publish.js b/src/redux/actions/publish.js index 4732ebd..a17dfbc 100644 --- a/src/redux/actions/publish.js +++ b/src/redux/actions/publish.js @@ -106,12 +106,12 @@ export const doUploadThumbnail = ( .then(json => { return json.success ? dispatch({ - type: ACTIONS.UPDATE_PUBLISH_FORM, - data: { - uploadThumbnailStatus: THUMBNAIL_STATUSES.COMPLETE, - thumbnail: json.data.serveUrl, - }, - }) + type: ACTIONS.UPDATE_PUBLISH_FORM, + data: { + uploadThumbnailStatus: THUMBNAIL_STATUSES.COMPLETE, + thumbnail: json.data.serveUrl, + }, + }) : uploadError(json.message || downMessage); }) .catch(err => { @@ -200,7 +200,7 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileLis description, fee, languages, - release_time: release_time ? Number(release_time) * 1000 : undefined, + releaseTime: release_time ? Number(release_time) * 1000 : undefined, thumbnail: thumbnail ? thumbnail.url : null, title, uri, @@ -412,7 +412,7 @@ export const doCheckReflectingFiles = () => (dispatch: Dispatch, getState: GetSt const { checkingReflector } = state.claims; let reflectorCheckInterval; - const checkFileList = async () => { + const checkFileList = async() => { const state = getState(); const reflectingById = selectReflectingById(state); const ids = Object.keys(reflectingById); -- 2.45.3 From 35dd7650fb235109d4a7ab44d1f6dde63dfdf2f7 Mon Sep 17 00:00:00 2001 From: infiinte-persistence Date: Fri, 30 Apr 2021 16:22:28 +0800 Subject: [PATCH 07/24] Change how release_time is edited. - `releaseTime` is now a number instead of a string, matching `release_time`. It was getting confusing what the variable units were. - `releaseTime` will always match `release_time` for an edit. It will be used in the GUI to reset just the date to the original, instead of having to reset the entire form. - `releaseTimeEdited` will be used by `updatePublishForm` in the GUI to represent the desired new release time. Set to `undefined` if we don't want to change the date. --- dist/bundle.es.js | 10 ++++++---- src/redux/actions/publish.js | 9 +++++---- src/redux/reducers/publish.js | 4 +++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index d4b83f9..e292bea 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -4786,7 +4786,8 @@ const doPrepareEdit = (claim, uri, fileInfo, fs) => dispatch => { description, fee, languages, - release_time: release_time ? Number(release_time) * 1000 : undefined, + releaseTime: release_time, + releaseTimeEdited: undefined, thumbnail: thumbnail ? thumbnail.url : null, title, uri, @@ -4837,7 +4838,7 @@ const doPublish = (success, fail, preview) => (dispatch, getState) => { filePath, description, language, - releaseTime, + releaseTimeEdited, license, licenseUrl, useLBRYUploader, @@ -4909,8 +4910,8 @@ const doPublish = (success, fail, preview) => (dispatch, getState) => { } // Set release time to curret date. On edits, keep original release/transaction time as release_time - if (releaseTime) { - publishPayload.release_time = Number(Math.round(new Date(releaseTime) / 1000)); + if (releaseTimeEdited) { + publishPayload.release_time = releaseTimeEdited; } else if (myClaimForUriEditing && myClaimForUriEditing.value.release_time) { publishPayload.release_time = Number(myClaimForUri.value.release_time); } else if (myClaimForUriEditing && myClaimForUriEditing.timestamp) { @@ -6060,6 +6061,7 @@ const defaultState$4 = { description: '', language: '', releaseTime: undefined, + releaseTimeEdited: undefined, nsfw: false, channel: CHANNEL_ANONYMOUS, channelId: '', diff --git a/src/redux/actions/publish.js b/src/redux/actions/publish.js index a17dfbc..181a378 100644 --- a/src/redux/actions/publish.js +++ b/src/redux/actions/publish.js @@ -200,7 +200,8 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileLis description, fee, languages, - releaseTime: release_time ? Number(release_time) * 1000 : undefined, + releaseTime: release_time, + releaseTimeEdited: undefined, thumbnail: thumbnail ? thumbnail.url : null, title, uri, @@ -254,7 +255,7 @@ export const doPublish = (success: Function, fail: Function, preview: Function) filePath, description, language, - releaseTime, + releaseTimeEdited, license, licenseUrl, useLBRYUploader, @@ -347,8 +348,8 @@ export const doPublish = (success: Function, fail: Function, preview: Function) } // Set release time to curret date. On edits, keep original release/transaction time as release_time - if (releaseTime) { - publishPayload.release_time = Number(Math.round(new Date(releaseTime) / 1000)); + if (releaseTimeEdited) { + publishPayload.release_time = releaseTimeEdited; } else if (myClaimForUriEditing && myClaimForUriEditing.value.release_time) { publishPayload.release_time = Number(myClaimForUri.value.release_time); } else if (myClaimForUriEditing && myClaimForUriEditing.timestamp) { diff --git a/src/redux/reducers/publish.js b/src/redux/reducers/publish.js index 39e40f5..794c43d 100644 --- a/src/redux/reducers/publish.js +++ b/src/redux/reducers/publish.js @@ -25,7 +25,8 @@ type PublishState = { thumbnailError: ?boolean, description: string, language: string, - releaseTime: ?string, + releaseTime: ?number, + releaseTimeEdited: ?number, channel: string, channelId: ?string, name: string, @@ -60,6 +61,7 @@ const defaultState: PublishState = { description: '', language: '', releaseTime: undefined, + releaseTimeEdited: undefined, nsfw: false, channel: CHANNEL_ANONYMOUS, channelId: '', -- 2.45.3 From 79519a3ea19432fa9db368adf1b40dd5be0039a6 Mon Sep 17 00:00:00 2001 From: zeppi Date: Thu, 13 May 2021 14:06:45 -0400 Subject: [PATCH 08/24] prefer title for collection name on resolve --- src/redux/actions/collections.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/redux/actions/collections.js b/src/redux/actions/collections.js index 74b6a2d..3b44b5e 100644 --- a/src/redux/actions/collections.js +++ b/src/redux/actions/collections.js @@ -40,11 +40,9 @@ export const doLocalCollectionCreate = ( }); }; -export const doCollectionDelete = ( - id: string, - colKey: ?string = undefined, - keepLocal?: boolean -) => (dispatch: Dispatch) => { +export const doCollectionDelete = (id: string, colKey: ?string = undefined) => ( + dispatch: Dispatch +) => { return dispatch({ type: ACTIONS.COLLECTION_DELETE, data: { @@ -219,7 +217,8 @@ export const doFetchItemsInCollections = ( const claim = makeSelectClaimForClaimId(collectionId)(stateAfterClaimSearch); const editedCollection = makeSelectEditedCollectionForId(collectionId)(stateAfterClaimSearch); - const { name, timestamp } = claim || {}; + const { name, timestamp, value } = claim || {}; + const { title } = value; const valueTypes = new Set(); const streamTypes = new Set(); @@ -242,7 +241,7 @@ export const doFetchItemsInCollections = ( newCollectionItemsById[collectionId] = { items, id: collectionId, - name: name, + name: title || name, itemCount: claim.value.claims.length, type: isPlaylist ? 'playlist' : 'collection', updatedAt: timestamp, -- 2.45.3 From ecfcc95bebbdbe303b3ea065134457a5e168fb89 Mon Sep 17 00:00:00 2001 From: infiinte-persistence Date: Tue, 18 May 2021 01:23:37 +0800 Subject: [PATCH 09/24] Clear 'thumbnailError' when uploading new one ## Issue "thumbnail is invalid" not reset with new thumbnail upload #6044 https://github.com/lbryio/lbry-desktop/issues/6044 ## Change The previous PR only covered the scenario of changing between NEW and EDIT. This one covers "uploading new". --- dist/bundle.es.js | 10 +++++++++- src/redux/actions/publish.js | 22 +++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index e292bea..867e17b 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -4634,7 +4634,8 @@ const doResetThumbnailStatus = () => dispatch => { dispatch({ type: UPDATE_PUBLISH_FORM, data: { - thumbnailPath: '' + thumbnailPath: '', + thumbnailError: undefined } }); @@ -4691,6 +4692,13 @@ const doUploadThumbnail = (filePath, thumbnailBlob, fsAdapter, fs, path) => disp }, doError(error))); }; + dispatch({ + type: UPDATE_PUBLISH_FORM, + data: { + thumbnailError: undefined + } + }); + const doUpload = data => { return fetch(SPEECH_PUBLISH, { method: 'POST', diff --git a/src/redux/actions/publish.js b/src/redux/actions/publish.js index 181a378..36222c0 100644 --- a/src/redux/actions/publish.js +++ b/src/redux/actions/publish.js @@ -21,6 +21,7 @@ export const doResetThumbnailStatus = () => (dispatch: Dispatch) => { type: ACTIONS.UPDATE_PUBLISH_FORM, data: { thumbnailPath: '', + thumbnailError: undefined, }, }); @@ -96,6 +97,13 @@ export const doUploadThumbnail = ( ); }; + dispatch({ + type: ACTIONS.UPDATE_PUBLISH_FORM, + data: { + thumbnailError: undefined, + }, + }); + const doUpload = data => { return fetch(SPEECH_PUBLISH, { method: 'POST', @@ -106,12 +114,12 @@ export const doUploadThumbnail = ( .then(json => { return json.success ? dispatch({ - type: ACTIONS.UPDATE_PUBLISH_FORM, - data: { - uploadThumbnailStatus: THUMBNAIL_STATUSES.COMPLETE, - thumbnail: json.data.serveUrl, - }, - }) + type: ACTIONS.UPDATE_PUBLISH_FORM, + data: { + uploadThumbnailStatus: THUMBNAIL_STATUSES.COMPLETE, + thumbnail: json.data.serveUrl, + }, + }) : uploadError(json.message || downMessage); }) .catch(err => { @@ -413,7 +421,7 @@ export const doCheckReflectingFiles = () => (dispatch: Dispatch, getState: GetSt const { checkingReflector } = state.claims; let reflectorCheckInterval; - const checkFileList = async() => { + const checkFileList = async () => { const state = getState(); const reflectingById = selectReflectingById(state); const ids = Object.keys(reflectingById); -- 2.45.3 From d4fba29fc554e9aae6ab29fe5fead535a9b7a16f Mon Sep 17 00:00:00 2001 From: zeppi Date: Wed, 19 May 2021 11:36:11 -0400 Subject: [PATCH 10/24] collections length --- dist/bundle.es.js | 16 ++++++++++------ src/redux/actions/collections.js | 2 +- src/redux/selectors/collections.js | 9 ++++++--- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 314a4eb..30d0498 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -3707,10 +3707,13 @@ const makeSelectNameForCollectionId = id => reselect.createSelector(makeSelectCo }); const makeSelectCountForCollectionId = id => reselect.createSelector(makeSelectCollectionForId(id), collection => { - if (collection.itemCount !== undefined) { - return collection.itemCount; + if (collection) { + if (collection.itemCount !== undefined) { + return collection.itemCount; + } + return collection.items.length; } - return collection.items.length; + return null; }); var _extends$5 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; @@ -4594,7 +4597,7 @@ const doLocalCollectionCreate = (name, collectionItems, type, sourceId) => dispa }); }; -const doCollectionDelete = (id, colKey = undefined, keepLocal) => dispatch => { +const doCollectionDelete = (id, colKey = undefined) => dispatch => { return dispatch({ type: COLLECTION_DELETE, data: { @@ -4766,7 +4769,8 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) const claim = makeSelectClaimForClaimId(collectionId)(stateAfterClaimSearch); const editedCollection = makeSelectEditedCollectionForId(collectionId)(stateAfterClaimSearch); - const { name, timestamp } = claim || {}; + const { name, timestamp, value } = claim || {}; + const { title } = value; const valueTypes = new Set(); const streamTypes = new Set(); @@ -4785,7 +4789,7 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) newCollectionItemsById[collectionId] = { items, id: collectionId, - name: name, + name: title || name, itemCount: claim.value.claims.length, type: isPlaylist ? 'playlist' : 'collection', updatedAt: timestamp diff --git a/src/redux/actions/collections.js b/src/redux/actions/collections.js index 3b44b5e..202b516 100644 --- a/src/redux/actions/collections.js +++ b/src/redux/actions/collections.js @@ -21,7 +21,7 @@ const getTimestamp = () => { // maybe take items param export const doLocalCollectionCreate = ( name: string, - collectionItems: string, + collectionItems: Array, type: string, sourceId: string ) => (dispatch: Dispatch) => { diff --git a/src/redux/selectors/collections.js b/src/redux/selectors/collections.js index f781b91..68cdddf 100644 --- a/src/redux/selectors/collections.js +++ b/src/redux/selectors/collections.js @@ -208,9 +208,12 @@ export const makeSelectCountForCollectionId = (id: string) => createSelector( makeSelectCollectionForId(id), collection => { - if (collection.itemCount !== undefined) { - return collection.itemCount; + if (collection) { + if (collection.itemCount !== undefined) { + return collection.itemCount; + } + return collection.items.length; } - return collection.items.length; + return null; } ); -- 2.45.3 From bcd8fd1005493afaa9a581f9db045b697e042615 Mon Sep 17 00:00:00 2001 From: zeppi Date: Wed, 19 May 2021 15:47:23 -0400 Subject: [PATCH 11/24] thumb param --- dist/bundle.es.js | 2 +- src/redux/actions/claims.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 30d0498..2513ce0 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -4407,7 +4407,7 @@ function doCollectionPublishUpdate(options) { const updateParams = { bid: creditsToString(options.bid), title: options.title, - thumbnail_url: options.thumbnailUrl, + thumbnail_url: options.thumbnail_url, description: options.description, tags: [], languages: options.languages || [], diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index 525c63d..090e15f 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -809,7 +809,7 @@ export function doCollectionPublishUpdate(options: { bid?: string, blocking?: true, title?: string, - thumbnailUrl?: string, + thumbnail_url?: string, description?: string, claim_id: string, tags?: Array, @@ -836,7 +836,7 @@ export function doCollectionPublishUpdate(options: { } = { bid: creditsToString(options.bid), title: options.title, - thumbnail_url: options.thumbnailUrl, + thumbnail_url: options.thumbnail_url, description: options.description, tags: [], languages: options.languages || [], -- 2.45.3 From d2d9b037f0f654d7693321a58620b08c327714c5 Mon Sep 17 00:00:00 2001 From: zeppi Date: Tue, 25 May 2021 08:59:56 -0400 Subject: [PATCH 12/24] refactor fetch to fix pending --- dist/bundle.es.js | 179 ++++++++++++++++------------ src/redux/actions/collections.js | 189 +++++++++++++++++------------- src/redux/reducers/collections.js | 2 +- 3 files changed, 214 insertions(+), 156 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 2513ce0..67dbac0 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -4580,7 +4580,8 @@ const getTimestamp = () => { return Math.floor(Date.now() / 1000); }; -// maybe take items param +const FETCH_BATCH_SIZE = 10; + const doLocalCollectionCreate = (name, collectionItems, type, sourceId) => dispatch => { return dispatch({ type: COLLECTION_NEW, @@ -4633,11 +4634,26 @@ const doCollectionDelete = (id, colKey = undefined) => dispatch => { const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) => (() => { var _ref = _asyncToGenerator$2(function* (dispatch, getState) { - let resolveCollectionItems = (() => { - var _ref2 = _asyncToGenerator$2(function* (claimId, totalItems, pageSize) { + let fetchItemsForCollectionClaim = (() => { + var _ref2 = _asyncToGenerator$2(function* (claim, pageSize) { // take [ {}, {} ], return {} - // only need items [ Claim... ] and total_items - const mergeResults = function (arrayOfResults) { + // only need items [ url... ] and total_items + const totalItems = claim.value.claims && claim.value.claims.length; + const claimId = claim.claim_id; + const itemOrder = claim.value.claims; + + const sortResults = function (results, claimList) { + const newResults = []; + claimList.forEach(function (id) { + const item = results.pop(function (i) { + return i.claim_id === id; + }); + if (item) newResults.push(item); + }); + return newResults; + }; + + const mergeBatches = function (arrayOfResults, claimList) { const mergedResults = { items: [], total_items: 0 @@ -4646,35 +4662,42 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) mergedResults.items = mergedResults.items.concat(result.items); mergedResults.total_items = result.total_items; }); + + mergedResults.items = sortResults(mergedResults.items, claimList); return mergedResults; }; try { - const BATCH_SIZE = 10; // up batch size when sdk bug fixed + // sdk had a strange bug that would only return so many, so this had to be batched. + // otherwise large lists of, ~500 channels for a homepage category failed + const batchSize = pageSize || FETCH_BATCH_SIZE; const batches = []; - let fetchResult; - if (!pageSize) { - // batch all - for (let i = 0; i < Math.ceil(totalItems / BATCH_SIZE); i++) { - batches[i] = lbryProxy.collection_resolve({ + /* + // this was `collection_resolve` which returns claims for collection in order + // however, this fails when a claim is pending. :/ + for (let i = 0; i < Math.ceil(totalItems / batchSize); i++) { + batches[i] = Lbry.collection_resolve({ claim_id: claimId, page: i + 1, - page_size: BATCH_SIZE + page_size: batchSize, }); } - const resultArray = yield Promise.all(batches); - fetchResult = mergeResults(resultArray); - } else { - fetchResult = yield lbryProxy.collection_resolve({ - claim_id: claimId, - page: 1, - page_size: pageSize + */ + + for (let i = 0; i < Math.ceil(totalItems / batchSize); i++) { + batches[i] = lbryProxy.claim_search({ + claim_ids: claim.value.claims, + page: i + 1, + page_size: batchSize }); } + const itemsInBatches = yield Promise.all(batches); + const result = mergeBatches(itemsInBatches, itemOrder); + // $FlowFixMe const itemsById = { claimId: claimId }; - if (fetchResult.items) { - itemsById.items = fetchResult.items; + if (result.items) { + itemsById.items = result.items; } else { itemsById.items = null; } @@ -4687,15 +4710,19 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) } }); - return function resolveCollectionItems(_x3, _x4, _x5) { + return function fetchItemsForCollectionClaim(_x3, _x4) { return _ref2.apply(this, arguments); }; })(); + /* + 1) make sure all the collection claims are loaded into claims reducer, search/resolve if necessary. + 2) get the item claims for each + 3) format and make sure they're in the order as in the claim + 4) Build the collection objects and update collections reducer + 5) Update redux claims reducer + */ let state = getState(); - // for each collection id, - // make sure the collection is resolved, the items are resolved, and build the collection objects - const { collectionIds, pageSize } = resolveItemsOptions; dispatch({ @@ -4708,33 +4735,15 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) const collectionIdsToSearch = collectionIds.filter(function (claimId) { return !state.claims.byId[claimId]; }); + if (collectionIdsToSearch.length) { - let claimSearchOptions = { claim_ids: collectionIdsToSearch, page: 1, page_size: 9999 }; - yield dispatch(doClaimSearch(claimSearchOptions)); + yield dispatch(doClaimSearch({ claim_ids: collectionIdsToSearch, page: 1, page_size: 9999 })); } - const invalidCollectionIds = []; + const stateAfterClaimSearch = getState(); - const promises = []; - collectionIds.forEach(function (collectionId) { - const claim = makeSelectClaimForClaimId(collectionId)(stateAfterClaimSearch); - if (!claim) { - invalidCollectionIds.push(collectionId); - } else { - const claimCount = claim.value.claims && claim.value.claims.length; - if (pageSize) { - promises.push(resolveCollectionItems(collectionId, claimCount, pageSize)); - } else { - promises.push(resolveCollectionItems(collectionId, claimCount)); - } - } - }); - - // $FlowFixMe - const resolvedCollectionItemsById = yield Promise.all(promises); - - function processClaims(resultClaimsByUri) { - const processedClaims = {}; + function formatForClaimActions(resultClaimsByUri) { + const formattedClaims = {}; Object.entries(resultClaimsByUri).forEach(([uri, uriResolveInfo]) => { // Flow has terrible Object.entries support // https://github.com/facebook/flow/issues/2221 @@ -4745,6 +4754,8 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) // $FlowFixMe result.claimsInChannel = uriResolveInfo.meta.claims_in_channel; // ALSO SKIP COLLECTIONS + } else if (uriResolveInfo.value_type === 'collection') { + result.collection = uriResolveInfo; } else { result.stream = uriResolveInfo; if (uriResolveInfo.signing_channel) { @@ -4753,15 +4764,29 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) } } // $FlowFixMe - processedClaims[uri] = result; + formattedClaims[uri] = result; } }); - return processedClaims; + return formattedClaims; } - const newCollectionItemsById = {}; - const flatResolvedCollectionItems = {}; - resolvedCollectionItemsById.forEach(function (entry) { + const invalidCollectionIds = []; + const promisedCollectionItemFetches = []; + collectionIds.forEach(function (collectionId) { + const claim = makeSelectClaimForClaimId(collectionId)(stateAfterClaimSearch); + if (!claim) { + invalidCollectionIds.push(collectionId); + } else { + promisedCollectionItemFetches.push(fetchItemsForCollectionClaim(claim, pageSize)); + } + }); + + // $FlowFixMe + const collectionItemsById = yield Promise.all(promisedCollectionItemFetches); + + const newCollectionObjectsById = {}; + const resolvedItemsByUrl = {}; + collectionItemsById.forEach(function (entry) { // $FlowFixMe const collectionItems = entry.items; const collectionId = entry.claimId; @@ -4774,27 +4799,31 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) const valueTypes = new Set(); const streamTypes = new Set(); - let items = []; - collectionItems.forEach(function (collectionItem) { - // here's where we would just items.push(collectionItem.permanent_url - items.push(collectionItem.permanent_url); - valueTypes.add(collectionItem.value_type); - if (collectionItem.value.stream_type) { - streamTypes.add(collectionItem.value.stream_type); - } - flatResolvedCollectionItems[collectionItem.canonical_url] = collectionItem; - }); - const isPlaylist = valueTypes.size === 1 && valueTypes.has('stream') && (streamTypes.size === 1 && (streamTypes.has('audio') || streamTypes.has('video')) || streamTypes.size === 2 && streamTypes.has('audio') && streamTypes.has('video')); + let newItems = []; + let isPlaylist; - newCollectionItemsById[collectionId] = { - items, + if (collectionItems) { + collectionItems.forEach(function (collectionItem) { + // here's where we would just items.push(collectionItem.permanent_url + newItems.push(collectionItem.permanent_url); + valueTypes.add(collectionItem.value_type); + if (collectionItem.value.stream_type) { + streamTypes.add(collectionItem.value.stream_type); + } + resolvedItemsByUrl[collectionItem.canonical_url] = collectionItem; + }); + isPlaylist = valueTypes.size === 1 && valueTypes.has('stream') && (streamTypes.size === 1 && (streamTypes.has('audio') || streamTypes.has('video')) || streamTypes.size === 2 && streamTypes.has('audio') && streamTypes.has('video')); + } + + newCollectionObjectsById[collectionId] = { + items: newItems, id: collectionId, name: title || name, itemCount: claim.value.claims.length, type: isPlaylist ? 'playlist' : 'collection', updatedAt: timestamp }; - // clear any stale edits + if (editedCollection && timestamp > editedCollection['updatedAt']) { dispatch({ type: COLLECTION_DELETE, @@ -4804,19 +4833,21 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) } }); } + } else { + invalidCollectionIds.push(collectionId); } }); - const processedClaimsByUri = processClaims(flatResolvedCollectionItems); + const formattedClaimsByUri = formatForClaimActions(collectionItemsById); dispatch({ type: RESOLVE_URIS_COMPLETED, - data: { resolveInfo: processedClaimsByUri } + data: { resolveInfo: formattedClaimsByUri } }); dispatch({ type: COLLECTION_ITEMS_RESOLVE_COMPLETED, data: { - resolvedCollections: newCollectionItemsById, + resolvedCollections: newCollectionObjectsById, failedCollectionIds: invalidCollectionIds } }); @@ -4924,10 +4955,8 @@ const doCollectionEdit = (collectionId, params) => (() => { // console.log('p&e', publishedCollection.items, newItems, publishedCollection.items.join(','), newItems.join(',')) if (editedCollection) { + // delete edited if newItems are the same as publishedItems if (publishedCollection.items.join(',') === newItems.join(',')) { - // print these - - // delete edited if newItems are the same as publishedItems dispatch({ type: COLLECTION_DELETE, data: { @@ -5000,7 +5029,7 @@ const doCollectionEdit = (collectionId, params) => (() => { return true; }); - return function (_x6, _x7) { + return function (_x5, _x6) { return _ref3.apply(this, arguments); }; })(); @@ -7586,7 +7615,7 @@ const collectionsReducer = handleActions({ const newUnpublishedList = Object.assign({}, unpublishedList); const newPendingList = Object.assign({}, pendingList); - const isEdit = editList[localId]; + const isEdit = editList[localId || claimId]; if (localId) { // pending from unpublished -> published // delete from local diff --git a/src/redux/actions/collections.js b/src/redux/actions/collections.js index 202b516..705deb6 100644 --- a/src/redux/actions/collections.js +++ b/src/redux/actions/collections.js @@ -18,7 +18,8 @@ const getTimestamp = () => { return Math.floor(Date.now() / 1000); }; -// maybe take items param +const FETCH_BATCH_SIZE = 10; + export const doLocalCollectionCreate = ( name: string, collectionItems: Array, @@ -83,10 +84,14 @@ export const doFetchItemsInCollections = ( }, resolveStartedCallback?: () => void ) => async(dispatch: Dispatch, getState: GetState) => { + /* + 1) make sure all the collection claims are loaded into claims reducer, search/resolve if necessary. + 2) get the item claims for each + 3) format and make sure they're in the order as in the claim + 4) Build the collection objects and update collections reducer + 5) Update redux claims reducer + */ let state = getState(); - // for each collection id, - // make sure the collection is resolved, the items are resolved, and build the collection objects - const { collectionIds, pageSize } = resolveItemsOptions; dispatch({ @@ -97,18 +102,35 @@ export const doFetchItemsInCollections = ( if (resolveStartedCallback) resolveStartedCallback(); const collectionIdsToSearch = collectionIds.filter(claimId => !state.claims.byId[claimId]); + if (collectionIdsToSearch.length) { - let claimSearchOptions = { claim_ids: collectionIdsToSearch, page: 1, page_size: 9999 }; - await dispatch(doClaimSearch(claimSearchOptions)); + await dispatch(doClaimSearch({ claim_ids: collectionIdsToSearch, page: 1, page_size: 9999 })); } - const invalidCollectionIds = []; + const stateAfterClaimSearch = getState(); - async function resolveCollectionItems(claimId, totalItems, pageSize) { + async function fetchItemsForCollectionClaim(claim: CollectionClaim, pageSize?: number) { // take [ {}, {} ], return {} - // only need items [ Claim... ] and total_items - const mergeResults = (arrayOfResults: Array<{ items: any, total_items: number }>) => { - const mergedResults: { items: Array, total_items: number } = { + // only need items [ url... ] and total_items + const totalItems = claim.value.claims && claim.value.claims.length; + const claimId = claim.claim_id; + const itemOrder = claim.value.claims; + + const sortResults = (results: Array, claimList) => { + const newResults: Array = []; + claimList.forEach(id => { + const index = results.findIndex(i => i.claim_id === id); + const item = results.splice(index, 1); + if (item) newResults.push(item[0]); + }); + return newResults; + }; + + const mergeBatches = ( + arrayOfResults: Array<{ items: Array, total_items: number }>, + claimList: Array + ) => { + const mergedResults: { items: Array, total_items: number } = { items: [], total_items: 0, }; @@ -116,35 +138,42 @@ export const doFetchItemsInCollections = ( mergedResults.items = mergedResults.items.concat(result.items); mergedResults.total_items = result.total_items; }); + + mergedResults.items = sortResults(mergedResults.items, claimList); return mergedResults; }; try { - const BATCH_SIZE = 10; // up batch size when sdk bug fixed - const batches = []; - let fetchResult; - if (!pageSize) { - // batch all - for (let i = 0; i < Math.ceil(totalItems / BATCH_SIZE); i++) { + // sdk had a strange bug that would only return so many, so this had to be batched. + // otherwise large lists of, ~500 channels for a homepage category failed + const batchSize = pageSize || FETCH_BATCH_SIZE; + const batches: Array> = []; + /* + // this was `collection_resolve` which returns claims for collection in order + // however, this fails when a claim is pending. :/ + for (let i = 0; i < Math.ceil(totalItems / batchSize); i++) { batches[i] = Lbry.collection_resolve({ claim_id: claimId, page: i + 1, - page_size: BATCH_SIZE, + page_size: batchSize, }); } - const resultArray = await Promise.all(batches); - fetchResult = mergeResults(resultArray); - } else { - fetchResult = await Lbry.collection_resolve({ - claim_id: claimId, - page: 1, - page_size: pageSize, + */ + + for (let i = 0; i < Math.ceil(totalItems / batchSize); i++) { + batches[i] = Lbry.claim_search({ + claim_ids: claim.value.claims, + page: i + 1, + page_size: batchSize, }); } + const itemsInBatches = await Promise.all(batches); + const result = mergeBatches(itemsInBatches, itemOrder); + // $FlowFixMe const itemsById: { claimId: string, items?: ?Array } = { claimId: claimId }; - if (fetchResult.items) { - itemsById.items = fetchResult.items; + if (result.items) { + itemsById.items = result.items; } else { itemsById.items = null; } @@ -157,29 +186,8 @@ export const doFetchItemsInCollections = ( } } - const promises = []; - collectionIds.forEach(collectionId => { - const claim = makeSelectClaimForClaimId(collectionId)(stateAfterClaimSearch); - if (!claim) { - invalidCollectionIds.push(collectionId); - } else { - const claimCount = claim.value.claims && claim.value.claims.length; - if (pageSize) { - promises.push(resolveCollectionItems(collectionId, claimCount, pageSize)); - } else { - promises.push(resolveCollectionItems(collectionId, claimCount)); - } - } - }); - - // $FlowFixMe - const resolvedCollectionItemsById: Array<{ - claimId: string, - items: ?Array, - }> = await Promise.all(promises); - - function processClaims(resultClaimsByUri) { - const processedClaims = {}; + function formatForClaimActions(resultClaimsByUri) { + const formattedClaims = {}; Object.entries(resultClaimsByUri).forEach(([uri, uriResolveInfo]) => { // Flow has terrible Object.entries support // https://github.com/facebook/flow/issues/2221 @@ -190,6 +198,8 @@ export const doFetchItemsInCollections = ( // $FlowFixMe result.claimsInChannel = uriResolveInfo.meta.claims_in_channel; // ALSO SKIP COLLECTIONS + } else if (uriResolveInfo.value_type === 'collection') { + result.collection = uriResolveInfo; } else { result.stream = uriResolveInfo; if (uriResolveInfo.signing_channel) { @@ -201,15 +211,32 @@ export const doFetchItemsInCollections = ( } } // $FlowFixMe - processedClaims[uri] = result; + formattedClaims[uri] = result; } }); - return processedClaims; + return formattedClaims; } - const newCollectionItemsById = {}; - const flatResolvedCollectionItems = {}; - resolvedCollectionItemsById.forEach(entry => { + const invalidCollectionIds = []; + const promisedCollectionItemFetches = []; + collectionIds.forEach(collectionId => { + const claim = makeSelectClaimForClaimId(collectionId)(stateAfterClaimSearch); + if (!claim) { + invalidCollectionIds.push(collectionId); + } else { + promisedCollectionItemFetches.push(fetchItemsForCollectionClaim(claim, pageSize)); + } + }); + + // $FlowFixMe + const collectionItemsById: Array<{ + claimId: string, + items: ?Array, + }> = await Promise.all(promisedCollectionItemFetches); + + const newCollectionObjectsById = {}; + const resolvedItemsByUrl = {}; + collectionItemsById.forEach(entry => { // $FlowFixMe const collectionItems: Array = entry.items; const collectionId = entry.claimId; @@ -222,31 +249,35 @@ export const doFetchItemsInCollections = ( const valueTypes = new Set(); const streamTypes = new Set(); - let items = []; - collectionItems.forEach(collectionItem => { - // here's where we would just items.push(collectionItem.permanent_url - items.push(collectionItem.permanent_url); - valueTypes.add(collectionItem.value_type); - if (collectionItem.value.stream_type) { - streamTypes.add(collectionItem.value.stream_type); - } - flatResolvedCollectionItems[collectionItem.canonical_url] = collectionItem; - }); - const isPlaylist = - valueTypes.size === 1 && - valueTypes.has('stream') && - ((streamTypes.size === 1 && (streamTypes.has('audio') || streamTypes.has('video'))) || - (streamTypes.size === 2 && (streamTypes.has('audio') && streamTypes.has('video')))); + let newItems = []; + let isPlaylist; - newCollectionItemsById[collectionId] = { - items, + if (collectionItems) { + collectionItems.forEach(collectionItem => { + // here's where we would just items.push(collectionItem.permanent_url + newItems.push(collectionItem.permanent_url); + valueTypes.add(collectionItem.value_type); + if (collectionItem.value.stream_type) { + streamTypes.add(collectionItem.value.stream_type); + } + resolvedItemsByUrl[collectionItem.canonical_url] = collectionItem; + }); + isPlaylist = + valueTypes.size === 1 && + valueTypes.has('stream') && + ((streamTypes.size === 1 && (streamTypes.has('audio') || streamTypes.has('video'))) || + (streamTypes.size === 2 && (streamTypes.has('audio') && streamTypes.has('video')))); + } + + newCollectionObjectsById[collectionId] = { + items: newItems, id: collectionId, name: title || name, itemCount: claim.value.claims.length, type: isPlaylist ? 'playlist' : 'collection', updatedAt: timestamp, }; - // clear any stale edits + if (editedCollection && timestamp > editedCollection['updatedAt']) { dispatch({ type: ACTIONS.COLLECTION_DELETE, @@ -257,20 +288,20 @@ export const doFetchItemsInCollections = ( }); } } else { - // no collection items? probably in pending. + invalidCollectionIds.push(collectionId); } }); - const processedClaimsByUri = processClaims(flatResolvedCollectionItems); + const formattedClaimsByUri = formatForClaimActions(collectionItemsById); dispatch({ type: ACTIONS.RESOLVE_URIS_COMPLETED, - data: { resolveInfo: processedClaimsByUri }, + data: { resolveInfo: formattedClaimsByUri }, }); dispatch({ type: ACTIONS.COLLECTION_ITEMS_RESOLVE_COMPLETED, data: { - resolvedCollections: newCollectionItemsById, + resolvedCollections: newCollectionObjectsById, failedCollectionIds: invalidCollectionIds, }, }); @@ -389,10 +420,8 @@ export const doCollectionEdit = (collectionId: string, params: CollectionEditPar // console.log('p&e', publishedCollection.items, newItems, publishedCollection.items.join(','), newItems.join(',')) if (editedCollection) { + // delete edited if newItems are the same as publishedItems if (publishedCollection.items.join(',') === newItems.join(',')) { - // print these - - // delete edited if newItems are the same as publishedItems dispatch({ type: ACTIONS.COLLECTION_DELETE, data: { diff --git a/src/redux/reducers/collections.js b/src/redux/reducers/collections.js index f38cb2a..e9d72de 100644 --- a/src/redux/reducers/collections.js +++ b/src/redux/reducers/collections.js @@ -99,7 +99,7 @@ const collectionsReducer = handleActions( const newUnpublishedList = Object.assign({}, unpublishedList); const newPendingList = Object.assign({}, pendingList); - const isEdit = editList[localId]; + const isEdit = editList[localId || claimId]; if (localId) { // pending from unpublished -> published // delete from local -- 2.45.3 From 25acc1289ca460edf218e8e10663cf4b22afa690 Mon Sep 17 00:00:00 2001 From: zeppi Date: Wed, 26 May 2021 11:36:17 -0400 Subject: [PATCH 13/24] handle collection claim delete --- dist/bundle.es.js | 44 +++++++++++++++++-------------- src/redux/actions/collections.js | 27 ++++++++++++------- src/redux/reducers/claims.js | 3 +++ src/redux/reducers/collections.js | 32 +++++++--------------- 4 files changed, 54 insertions(+), 52 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 67dbac0..d6486b9 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -4598,14 +4598,21 @@ const doLocalCollectionCreate = (name, collectionItems, type, sourceId) => dispa }); }; -const doCollectionDelete = (id, colKey = undefined) => dispatch => { - return dispatch({ +const doCollectionDelete = (id, colKey = undefined) => (dispatch, getState) => { + const state = getState(); + const claim = makeSelectClaimForClaimId(id)(state); + const collectionDelete = () => dispatch({ type: COLLECTION_DELETE, data: { id: id, collectionKey: colKey } }); + if (claim) { + const { txid, nout } = claim; + return dispatch(doAbandonClaim(txid, nout, collectionDelete)); + } + return collectionDelete(); }; // Given a collection, save its collectionId to be resolved and displayed in Library @@ -4645,10 +4652,11 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) const sortResults = function (results, claimList) { const newResults = []; claimList.forEach(function (id) { - const item = results.pop(function (i) { + const index = results.findIndex(function (i) { return i.claim_id === id; }); - if (item) newResults.push(item); + const item = results.splice(index, 1); + if (item) newResults.push(item[0]); }); return newResults; }; @@ -6382,6 +6390,7 @@ reducers[ABANDON_CLAIM_SUCCEEDED] = (state, action) => { const newMyClaims = state.myClaims ? state.myClaims.slice() : []; const newMyChannelClaims = state.myChannelClaims ? state.myChannelClaims.slice() : []; const claimsByUri = Object.assign({}, state.claimsByUri); + const newMyCollectionClaims = state.myCollectionClaims ? state.myCollectionClaims.slice() : []; Object.keys(claimsByUri).forEach(uri => { if (claimsByUri[uri] === claimId) { @@ -6390,12 +6399,14 @@ reducers[ABANDON_CLAIM_SUCCEEDED] = (state, action) => { }); 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 }); @@ -7534,14 +7545,14 @@ const getTimestamp$1 = () => { const defaultState$6 = { builtin: { watchlater: { - items: ['lbry://why-wolves-determine-the-shape-of-rivers#d8a60a057ac9adb6b618be6985ca8361c730c02e'], + items: [], id: WATCH_LATER_ID, name: 'Watch Later', updatedAt: getTimestamp$1(), type: COL_TYPE_PLAYLIST }, favorites: { - items: ['lbry://why-wolves-determine-the-shape-of-rivers#d8a60a057ac9adb6b618be6985ca8361c730c02e'], + items: [], id: FAVORITES_ID, name: 'Favorites', type: COL_TYPE_PLAYLIST, @@ -7615,22 +7626,15 @@ const collectionsReducer = handleActions({ const newUnpublishedList = Object.assign({}, unpublishedList); const newPendingList = Object.assign({}, pendingList); - const isEdit = editList[localId || claimId]; + const isEdit = editList[claimId]; if (localId) { - // pending from unpublished -> published - // delete from local - newPendingList[claimId] = Object.assign({}, newEditList[localId] || newUnpublishedList[localId] || {}); - if (isEdit) { - delete newEditList[localId]; - } else { - delete newUnpublishedList[localId]; - } + // new publish + newPendingList[claimId] = Object.assign({}, newUnpublishedList[localId] || {}); + delete newUnpublishedList[localId]; } else { - // pending from edited published -> published - if (isEdit) { - newPendingList[claimId] = Object.assign({}, newEditList[claimId]); - delete newEditList[claimId]; - } + // edit update + newPendingList[claimId] = Object.assign({}, newEditList[claimId]); + delete newEditList[claimId]; } return _extends$e({}, state, { diff --git a/src/redux/actions/collections.js b/src/redux/actions/collections.js index 705deb6..b75d728 100644 --- a/src/redux/actions/collections.js +++ b/src/redux/actions/collections.js @@ -2,7 +2,7 @@ import * as ACTIONS from 'constants/action_types'; import { v4 as uuid } from 'uuid'; import Lbry from 'lbry'; -import { doClaimSearch } from 'redux/actions/claims'; +import { doClaimSearch, doAbandonClaim } from 'redux/actions/claims'; import { makeSelectClaimForClaimId } from 'redux/selectors/claims'; import { makeSelectCollectionForId, @@ -42,15 +42,24 @@ export const doLocalCollectionCreate = ( }; export const doCollectionDelete = (id: string, colKey: ?string = undefined) => ( - dispatch: Dispatch + dispatch: Dispatch, + getState: GetState ) => { - return dispatch({ - type: ACTIONS.COLLECTION_DELETE, - data: { - id: id, - collectionKey: colKey, - }, - }); + const state = getState(); + const claim = makeSelectClaimForClaimId(id)(state); + const collectionDelete = () => + dispatch({ + type: ACTIONS.COLLECTION_DELETE, + data: { + id: id, + collectionKey: colKey, + }, + }); + if (claim) { + const { txid, nout } = claim; + return dispatch(doAbandonClaim(txid, nout, collectionDelete)); + } + return collectionDelete(); }; // Given a collection, save its collectionId to be resolved and displayed in Library diff --git a/src/redux/reducers/claims.js b/src/redux/reducers/claims.js index 3b6ecb9..d321f30 100644 --- a/src/redux/reducers/claims.js +++ b/src/redux/reducers/claims.js @@ -528,6 +528,7 @@ reducers[ACTIONS.ABANDON_CLAIM_SUCCEEDED] = (state: State, action: any): State = const newMyClaims = state.myClaims ? state.myClaims.slice() : []; const newMyChannelClaims = state.myChannelClaims ? state.myChannelClaims.slice() : []; const claimsByUri = Object.assign({}, state.claimsByUri); + const newMyCollectionClaims = state.myCollectionClaims ? state.myCollectionClaims.slice() : []; Object.keys(claimsByUri).forEach(uri => { if (claimsByUri[uri] === claimId) { @@ -536,12 +537,14 @@ reducers[ACTIONS.ABANDON_CLAIM_SUCCEEDED] = (state: State, action: any): State = }); 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, }); diff --git a/src/redux/reducers/collections.js b/src/redux/reducers/collections.js index e9d72de..57f863b 100644 --- a/src/redux/reducers/collections.js +++ b/src/redux/reducers/collections.js @@ -10,18 +10,14 @@ const getTimestamp = () => { const defaultState: CollectionState = { builtin: { watchlater: { - items: [ - 'lbry://why-wolves-determine-the-shape-of-rivers#d8a60a057ac9adb6b618be6985ca8361c730c02e', - ], + items: [], id: COLS.WATCH_LATER_ID, name: 'Watch Later', updatedAt: getTimestamp(), type: COLS.COL_TYPE_PLAYLIST, }, favorites: { - items: [ - 'lbry://why-wolves-determine-the-shape-of-rivers#d8a60a057ac9adb6b618be6985ca8361c730c02e', - ], + items: [], id: COLS.FAVORITES_ID, name: 'Favorites', type: COLS.COL_TYPE_PLAYLIST, @@ -99,25 +95,15 @@ const collectionsReducer = handleActions( const newUnpublishedList = Object.assign({}, unpublishedList); const newPendingList = Object.assign({}, pendingList); - const isEdit = editList[localId || claimId]; + const isEdit = editList[claimId]; if (localId) { - // pending from unpublished -> published - // delete from local - newPendingList[claimId] = Object.assign( - {}, - newEditList[localId] || newUnpublishedList[localId] || {} - ); - if (isEdit) { - delete newEditList[localId]; - } else { - delete newUnpublishedList[localId]; - } + // new publish + newPendingList[claimId] = Object.assign({}, newUnpublishedList[localId] || {}); + delete newUnpublishedList[localId]; } else { - // pending from edited published -> published - if (isEdit) { - newPendingList[claimId] = Object.assign({}, newEditList[claimId]); - delete newEditList[claimId]; - } + // edit update + newPendingList[claimId] = Object.assign({}, newEditList[claimId]); + delete newEditList[claimId]; } return { -- 2.45.3 From 09699be25cac6ac23bb95fc105e4ece2e2f7740f Mon Sep 17 00:00:00 2001 From: zeppi Date: Fri, 28 May 2021 08:37:32 -0400 Subject: [PATCH 14/24] return new collection on publish --- dist/bundle.es.js | 7 ++++--- src/redux/actions/claims.js | 4 ++-- src/redux/actions/collections.js | 7 ++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index d6486b9..fc27ede 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -4380,7 +4380,7 @@ function doCollectionPublish(options, localId) { })); dispatch(doCheckPendingClaims()); dispatch(doFetchCollectionListMine(1, 10)); - resolve(collectionClaim); + return collectionClaim; } function failure(error) { @@ -4450,7 +4450,7 @@ function doCollectionPublishUpdate(options) { }); dispatch(doCheckPendingClaims()); dispatch(doFetchCollectionListMine(1, 10)); - resolve(collectionClaim); + return collectionClaim; } function failure(error) { @@ -4608,7 +4608,8 @@ const doCollectionDelete = (id, colKey = undefined) => (dispatch, getState) => { collectionKey: colKey } }); - if (claim) { + if (claim && !colKey) { + // could support "abandon, but keep" later const { txid, nout } = claim; return dispatch(doAbandonClaim(txid, nout, collectionDelete)); } diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index 090e15f..410a411 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -788,7 +788,7 @@ export function doCollectionPublish( ); dispatch(doCheckPendingClaims()); dispatch(doFetchCollectionListMine(1, 10)); - resolve(collectionClaim); + return collectionClaim; } function failure(error) { @@ -879,7 +879,7 @@ export function doCollectionPublishUpdate(options: { }); dispatch(doCheckPendingClaims()); dispatch(doFetchCollectionListMine(1, 10)); - resolve(collectionClaim); + return collectionClaim; } function failure(error) { diff --git a/src/redux/actions/collections.js b/src/redux/actions/collections.js index b75d728..39c10c3 100644 --- a/src/redux/actions/collections.js +++ b/src/redux/actions/collections.js @@ -55,7 +55,8 @@ export const doCollectionDelete = (id: string, colKey: ?string = undefined) => ( collectionKey: colKey, }, }); - if (claim) { + if (claim && !colKey) { + // could support "abandon, but keep" later const { txid, nout } = claim; return dispatch(doAbandonClaim(txid, nout, collectionDelete)); } @@ -92,7 +93,7 @@ export const doFetchItemsInCollections = ( pageSize?: number, }, resolveStartedCallback?: () => void -) => async(dispatch: Dispatch, getState: GetState) => { +) => async (dispatch: Dispatch, getState: GetState) => { /* 1) make sure all the collection claims are loaded into claims reducer, search/resolve if necessary. 2) get the item claims for each @@ -328,7 +329,7 @@ export const doFetchItemsInCollection = ( return doFetchItemsInCollections(newOptions, cb); }; -export const doCollectionEdit = (collectionId: string, params: CollectionEditParams) => async( +export const doCollectionEdit = (collectionId: string, params: CollectionEditParams) => async ( dispatch: Dispatch, getState: GetState ) => { -- 2.45.3 From 6228a2dce174c707af09da5079772b10571ff1b9 Mon Sep 17 00:00:00 2001 From: zeppi Date: Sun, 30 May 2021 18:26:53 -0400 Subject: [PATCH 15/24] fix tags bug, flow --- dist/bundle.es.js | 21 +++++++++++++++++++-- src/redux/actions/claims.js | 37 ++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index fc27ede..a3ac37d 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -4313,7 +4313,6 @@ function doClaimSearch(options = { function doRepost(options) { return dispatch => { - // $FlowFixMe return new Promise(resolve => { dispatch({ type: CLAIM_REPOST_STARTED @@ -4356,6 +4355,24 @@ function doRepost(options) { function doCollectionPublish(options, localId) { return dispatch => { // $FlowFixMe + + const params = { + name: options.name, + bid: creditsToString(options.bid), + title: options.title, + thumbnail_url: options.thumbnail_url, + description: options.description, + tags: [], + languages: options.languages || [], + locations: [], + blocking: true, + claims: options.claims + }; + + if (options.tags) { + params['tags'] = options.tags.map(tag => tag.name); + } + return new Promise(resolve => { dispatch({ type: COLLECTION_PUBLISH_STARTED @@ -4392,7 +4409,7 @@ function doCollectionPublish(options, localId) { }); } - lbryProxy.collection_create(options).then(success, failure); + lbryProxy.collection_create(params).then(success, failure); }); }; } diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index 410a411..c83dedc 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -701,8 +701,7 @@ export function doClaimSearch( } export function doRepost(options: StreamRepostOptions) { - return (dispatch: Dispatch) => { - // $FlowFixMe + return (dispatch: Dispatch): Promise => { return new Promise(resolve => { dispatch({ type: ACTIONS.CLAIM_REPOST_STARTED, @@ -751,14 +750,42 @@ export function doCollectionPublish( channel_id?: string, thumbnail_url?: string, description?: string, - tags?: Array, + tags?: Array, languages?: Array, claims: Array, }, localId: string ) { - return (dispatch: Dispatch) => { + return (dispatch: Dispatch): Promise => { // $FlowFixMe + + const params: { + name: string, + bid: string, + blocking?: true, + title?: string, + thumbnail_url?: string, + description?: string, + tags?: Array, + languages?: Array, + claims: Array, + } = { + name: options.name, + bid: creditsToString(options.bid), + title: options.title, + thumbnail_url: options.thumbnail_url, + description: options.description, + tags: [], + languages: options.languages || [], + locations: [], + blocking: true, + claims: options.claims, + }; + + if (options.tags) { + params['tags'] = options.tags.map(tag => tag.name); + } + return new Promise(resolve => { dispatch({ type: ACTIONS.COLLECTION_PUBLISH_STARTED, @@ -800,7 +827,7 @@ export function doCollectionPublish( }); } - Lbry.collection_create(options).then(success, failure); + Lbry.collection_create(params).then(success, failure); }); }; } -- 2.45.3 From ff16a0c8a409e6d11fd7892e9747c0bb8390cb9f Mon Sep 17 00:00:00 2001 From: zeppi Date: Tue, 1 Jun 2021 08:12:02 -0400 Subject: [PATCH 16/24] finalize collections sync keys --- dist/bundle.es.js | 18 +++++++----------- src/redux/actions/claims.js | 10 +++------- src/redux/reducers/collections.js | 8 ++++---- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index a3ac37d..a67535f 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -4384,7 +4384,7 @@ function doCollectionPublish(options, localId) { type: COLLECTION_PUBLISH_COMPLETED, data: { claimId: collectionClaim.claim_id } }, - // shift unpublished collection to pending collection with new publish id + // move unpublished collection to pending collection with new publish id // recent publish won't resolve this second. handle it in checkPending { type: COLLECTION_PENDING, @@ -4415,11 +4415,8 @@ function doCollectionPublish(options, localId) { } function doCollectionPublishUpdate(options) { - return (dispatch, getState) => { - const state = getState(); - // select claim forclaim_id - // get publish params from claim - // $FlowFixMe + return dispatch => { + // TODO: implement one click update const updateParams = { bid: creditsToString(options.bid), @@ -4441,7 +4438,6 @@ function doCollectionPublishUpdate(options) { if (options.claims) { updateParams['claims'] = options.claims; } - // $FlowFixMe return new Promise(resolve => { dispatch({ type: COLLECTION_PUBLISH_UPDATE_STARTED @@ -7703,11 +7699,11 @@ const collectionsReducer = handleActions({ })); }, [USER_STATE_POPULATE]: (state, action) => { - const { builtinCollectionTest, savedCollectionTest, unpublishedCollectionTest } = action.data; + const { builtinCollections, savedCollections, unpublishedCollections } = action.data; return _extends$e({}, state, { - unpublished: unpublishedCollectionTest || state.unpublished, - builtin: builtinCollectionTest || state.builtin, - saved: savedCollectionTest || state.saved + unpublished: unpublishedCollections || state.unpublished, + builtin: builtinCollections || state.builtin, + saved: savedCollections || state.saved }); }, [COLLECTION_ITEMS_RESOLVE_COMPLETED]: (state, action) => { diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index c83dedc..88ee81e 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -799,7 +799,7 @@ export function doCollectionPublish( type: ACTIONS.COLLECTION_PUBLISH_COMPLETED, data: { claimId: collectionClaim.claim_id }, }, - // shift unpublished collection to pending collection with new publish id + // move unpublished collection to pending collection with new publish id // recent publish won't resolve this second. handle it in checkPending { type: ACTIONS.COLLECTION_PENDING, @@ -843,11 +843,8 @@ export function doCollectionPublishUpdate(options: { languages?: Array, claims?: Array, }) { - return (dispatch: Dispatch, getState: GetState) => { - const state = getState(); - // select claim forclaim_id - // get publish params from claim - // $FlowFixMe + return (dispatch: Dispatch): Promise => { + // TODO: implement one click update const updateParams: { bid?: string, @@ -880,7 +877,6 @@ export function doCollectionPublishUpdate(options: { if (options.claims) { updateParams['claims'] = options.claims; } - // $FlowFixMe return new Promise(resolve => { dispatch({ type: ACTIONS.COLLECTION_PUBLISH_UPDATE_STARTED, diff --git a/src/redux/reducers/collections.js b/src/redux/reducers/collections.js index 57f863b..e228d6d 100644 --- a/src/redux/reducers/collections.js +++ b/src/redux/reducers/collections.js @@ -159,12 +159,12 @@ const collectionsReducer = handleActions( }); }, [ACTIONS.USER_STATE_POPULATE]: (state, action) => { - const { builtinCollectionTest, savedCollectionTest, unpublishedCollectionTest } = action.data; + const { builtinCollections, savedCollections, unpublishedCollections } = action.data; return { ...state, - unpublished: unpublishedCollectionTest || state.unpublished, - builtin: builtinCollectionTest || state.builtin, - saved: savedCollectionTest || state.saved, + unpublished: unpublishedCollections || state.unpublished, + builtin: builtinCollections || state.builtin, + saved: savedCollections || state.saved, }; }, [ACTIONS.COLLECTION_ITEMS_RESOLVE_COMPLETED]: (state, action) => { -- 2.45.3 From 9e802f47c479c5ee5a9307edfcf0003af8639a2b Mon Sep 17 00:00:00 2001 From: zeppi Date: Tue, 1 Jun 2021 09:09:21 -0400 Subject: [PATCH 17/24] fix sync --- dist/bundle.es.js | 20 ++++++++++---------- src/redux/actions/sync.js | 30 +++++++++++++++--------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index a67535f..dc85e2a 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -1829,12 +1829,12 @@ function extractUserState(rawObj) { settings, app_welcome_version, sharing_3P, - unpublishedCollectionTest, - builtinCollectionTest, - savedCollectionTest + unpublishedCollections, + builtinCollections, + savedCollections } = rawObj.value; - return _extends$1({}, subscriptions ? { subscriptions } : {}, following ? { following } : {}, tags ? { tags } : {}, blocked ? { blocked } : {}, coin_swap_codes ? { coin_swap_codes } : {}, settings ? { settings } : {}, app_welcome_version ? { app_welcome_version } : {}, sharing_3P ? { sharing_3P } : {}, unpublishedCollectionTest ? { unpublishedCollectionTest } : {}, builtinCollectionTest ? { builtinCollectionTest } : {}, savedCollectionTest ? { savedCollectionTest } : {}); + return _extends$1({}, subscriptions ? { subscriptions } : {}, following ? { following } : {}, tags ? { tags } : {}, blocked ? { blocked } : {}, coin_swap_codes ? { coin_swap_codes } : {}, settings ? { settings } : {}, app_welcome_version ? { app_welcome_version } : {}, sharing_3P ? { sharing_3P } : {}, unpublishedCollections ? { unpublishedCollections } : {}, builtinCollections ? { builtinCollections } : {}, savedCollections ? { savedCollections } : {}); } return {}; @@ -1851,9 +1851,9 @@ function doPopulateSharedUserState(sharedSettings) { settings, app_welcome_version, sharing_3P, - unpublishedCollectionTest, - builtinCollectionTest, - savedCollectionTest + unpublishedCollections, + builtinCollections, + savedCollections } = extractUserState(sharedSettings); dispatch({ type: USER_STATE_POPULATE, @@ -1866,9 +1866,9 @@ function doPopulateSharedUserState(sharedSettings) { settings, welcomeVersion: app_welcome_version, allowAnalytics: sharing_3P, - unpublishedCollectionTest, - builtinCollectionTest, - savedCollectionTest + unpublishedCollections, + builtinCollections, + savedCollections } }); }; diff --git a/src/redux/actions/sync.js b/src/redux/actions/sync.js index d1812a5..032a8ab 100644 --- a/src/redux/actions/sync.js +++ b/src/redux/actions/sync.js @@ -13,9 +13,9 @@ type SharedData = { settings?: any, app_welcome_version?: number, sharing_3P?: boolean, - unpublishedCollectionTest: CollectionGroup, - builtinCollectionTest: CollectionGroup, - savedCollectionTest: Array, + unpublishedCollections: CollectionGroup, + builtinCollections: CollectionGroup, + savedCollections: Array, }, }; @@ -30,9 +30,9 @@ function extractUserState(rawObj: SharedData) { settings, app_welcome_version, sharing_3P, - unpublishedCollectionTest, - builtinCollectionTest, - savedCollectionTest, + unpublishedCollections, + builtinCollections, + savedCollections, } = rawObj.value; return { @@ -44,9 +44,9 @@ function extractUserState(rawObj: SharedData) { ...(settings ? { settings } : {}), ...(app_welcome_version ? { app_welcome_version } : {}), ...(sharing_3P ? { sharing_3P } : {}), - ...(unpublishedCollectionTest ? { unpublishedCollectionTest } : {}), - ...(builtinCollectionTest ? { builtinCollectionTest } : {}), - ...(savedCollectionTest ? { savedCollectionTest } : {}), + ...(unpublishedCollections ? { unpublishedCollections } : {}), + ...(builtinCollections ? { builtinCollections } : {}), + ...(savedCollections ? { savedCollections } : {}), }; } @@ -64,9 +64,9 @@ export function doPopulateSharedUserState(sharedSettings: any) { settings, app_welcome_version, sharing_3P, - unpublishedCollectionTest, - builtinCollectionTest, - savedCollectionTest, + unpublishedCollections, + builtinCollections, + savedCollections, } = extractUserState(sharedSettings); dispatch({ type: ACTIONS.USER_STATE_POPULATE, @@ -79,9 +79,9 @@ export function doPopulateSharedUserState(sharedSettings: any) { settings, welcomeVersion: app_welcome_version, allowAnalytics: sharing_3P, - unpublishedCollectionTest, - builtinCollectionTest, - savedCollectionTest, + unpublishedCollections, + builtinCollections, + savedCollections, }, }); }; -- 2.45.3 From dd959162810655c8b464f3d94ee07aa13a48f8f6 Mon Sep 17 00:00:00 2001 From: zeppi Date: Tue, 1 Jun 2021 12:19:35 -0400 Subject: [PATCH 18/24] cleanup cleanup cleanup --- dist/bundle.es.js | 34 +++++-------------------------- dist/flow-typed/Collections.js | 7 ------- flow-typed/Collections.js | 7 ------- src/redux/actions/claims.js | 9 ++++---- src/redux/actions/collections.js | 20 ++---------------- src/redux/reducers/claims.js | 9 +------- src/redux/reducers/collections.js | 1 - 7 files changed, 13 insertions(+), 74 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index dc85e2a..39b6dac 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -3824,8 +3824,6 @@ function doResolveUris(uris, returnCachedClaims = false, resolveReposts = true) if (collectionIds.length) { dispatch(doFetchItemsInCollections({ collectionIds: collectionIds, pageSize: 5 })); } - // now collection claims are added, get their stuff - // if collections: doResolveCollections(claimIds) return result; }); @@ -4242,7 +4240,6 @@ function doFetchCollectionListMine(page = 1, pageSize = 99999) { collectionIds: items.map(claim => claim.claim_id), page_size: 5 })); - // update or fetch collections? }; const failure = error => { @@ -4287,7 +4284,6 @@ function doClaimSearch(options = { pageSize: options.page_size } }); - // was return true return resolveInfo; }; @@ -4373,6 +4369,10 @@ function doCollectionPublish(options, localId) { params['tags'] = options.tags.map(tag => tag.name); } + if (options.channel_id) { + params['channel_id'] = options.channel_id; + } + return new Promise(resolve => { dispatch({ type: COLLECTION_PUBLISH_STARTED @@ -4657,8 +4657,6 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) var _ref = _asyncToGenerator$2(function* (dispatch, getState) { let fetchItemsForCollectionClaim = (() => { var _ref2 = _asyncToGenerator$2(function* (claim, pageSize) { - // take [ {}, {} ], return {} - // only need items [ url... ] and total_items const totalItems = claim.value.claims && claim.value.claims.length; const claimId = claim.claim_id; const itemOrder = claim.value.claims; @@ -4690,21 +4688,8 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) }; try { - // sdk had a strange bug that would only return so many, so this had to be batched. - // otherwise large lists of, ~500 channels for a homepage category failed const batchSize = pageSize || FETCH_BATCH_SIZE; const batches = []; - /* - // this was `collection_resolve` which returns claims for collection in order - // however, this fails when a claim is pending. :/ - for (let i = 0; i < Math.ceil(totalItems / batchSize); i++) { - batches[i] = Lbry.collection_resolve({ - claim_id: claimId, - page: i + 1, - page_size: batchSize, - }); - } - */ for (let i = 0; i < Math.ceil(totalItems / batchSize); i++) { batches[i] = lbryProxy.claim_search({ @@ -4826,7 +4811,6 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) if (collectionItems) { collectionItems.forEach(function (collectionItem) { - // here's where we would just items.push(collectionItem.permanent_url newItems.push(collectionItem.permanent_url); valueTypes.add(collectionItem.value_type); if (collectionItem.value.stream_type) { @@ -6053,7 +6037,7 @@ function handleClaimAction(state, action) { } else { byId[channel.claim_id] = channel; } - // Also add the permanent_url here until lighthouse returns canonical_url for search results + byUri[channel.permanent_url] = channel.claim_id; byUri[channel.canonical_url] = channel.claim_id; newResolvingUrls.delete(channel.canonical_url); @@ -6067,11 +6051,7 @@ function handleClaimAction(state, action) { byId[collection.claim_id] = collection; } byUri[url] = collection.claim_id; - - // If url isn't a canonical_url, make sure that is added too byUri[collection.canonical_url] = collection.claim_id; - - // Also add the permanent_url here until lighthouse returns canonical_url for search results byUri[collection.permanent_url] = collection.claim_id; newResolvingUrls.delete(collection.canonical_url); newResolvingUrls.delete(collection.permanent_url); @@ -6234,7 +6214,6 @@ reducers[FETCH_COLLECTION_LIST_COMPLETED] = (state, action) => { myCollectionClaimsSet = new Set(state.myCollectionClaims); claims.forEach(claim => { const { canonical_url: canonicalUrl, permanent_url: permanentUrl, claim_id: claimId } = claim; - // maybe add info about items in collection byUri[canonicalUrl] = claimId; byUri[permanentUrl] = claimId; @@ -6523,8 +6502,6 @@ reducers[COLLECTION_PUBLISH_UPDATE_FAILED] = (state, action) => { }); }; -// COLLECTION_PUBLISH_ABANDON_... - reducers[IMPORT_CHANNEL_STARTED] = state => Object.assign({}, state, { pendingChannelImports: true }); reducers[IMPORT_CHANNEL_COMPLETED] = state => Object.assign({}, state, { pendingChannelImports: false }); @@ -7640,7 +7617,6 @@ const collectionsReducer = handleActions({ const newUnpublishedList = Object.assign({}, unpublishedList); const newPendingList = Object.assign({}, pendingList); - const isEdit = editList[claimId]; if (localId) { // new publish newPendingList[claimId] = Object.assign({}, newUnpublishedList[localId] || {}); diff --git a/dist/flow-typed/Collections.js b/dist/flow-typed/Collections.js index c2eba63..927cef7 100644 --- a/dist/flow-typed/Collections.js +++ b/dist/flow-typed/Collections.js @@ -1,10 +1,3 @@ -declare type CollectionUpdateParams = { - remove?: boolean, - claims?: Array, - name?: string, - order?: { from: number, to: number }, -} - declare type Collection = { id: string, items: Array, diff --git a/flow-typed/Collections.js b/flow-typed/Collections.js index c2eba63..927cef7 100644 --- a/flow-typed/Collections.js +++ b/flow-typed/Collections.js @@ -1,10 +1,3 @@ -declare type CollectionUpdateParams = { - remove?: boolean, - claims?: Array, - name?: string, - order?: { from: number, to: number }, -} - declare type Collection = { id: string, items: Array, diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index 88ee81e..e3a47af 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -151,8 +151,6 @@ export function doResolveUris( if (collectionIds.length) { dispatch(doFetchItemsInCollections({ collectionIds: collectionIds, pageSize: 5 })); } - // now collection claims are added, get their stuff - // if collections: doResolveCollections(claimIds) return result; } @@ -618,7 +616,6 @@ export function doFetchCollectionListMine(page: number = 1, pageSize: number = 9 page_size: 5, }) ); - // update or fetch collections? }; const failure = error => { @@ -680,7 +677,6 @@ export function doClaimSearch( pageSize: options.page_size, }, }); - // was return true return resolveInfo; }; @@ -762,6 +758,7 @@ export function doCollectionPublish( const params: { name: string, bid: string, + channel_id?: string, blocking?: true, title?: string, thumbnail_url?: string, @@ -786,6 +783,10 @@ export function doCollectionPublish( params['tags'] = options.tags.map(tag => tag.name); } + if (options.channel_id) { + params['channel_id'] = options.channel_id; + } + return new Promise(resolve => { dispatch({ type: ACTIONS.COLLECTION_PUBLISH_STARTED, diff --git a/src/redux/actions/collections.js b/src/redux/actions/collections.js index 39c10c3..a8b7066 100644 --- a/src/redux/actions/collections.js +++ b/src/redux/actions/collections.js @@ -93,7 +93,7 @@ export const doFetchItemsInCollections = ( pageSize?: number, }, resolveStartedCallback?: () => void -) => async (dispatch: Dispatch, getState: GetState) => { +) => async(dispatch: Dispatch, getState: GetState) => { /* 1) make sure all the collection claims are loaded into claims reducer, search/resolve if necessary. 2) get the item claims for each @@ -120,8 +120,6 @@ export const doFetchItemsInCollections = ( const stateAfterClaimSearch = getState(); async function fetchItemsForCollectionClaim(claim: CollectionClaim, pageSize?: number) { - // take [ {}, {} ], return {} - // only need items [ url... ] and total_items const totalItems = claim.value.claims && claim.value.claims.length; const claimId = claim.claim_id; const itemOrder = claim.value.claims; @@ -154,21 +152,8 @@ export const doFetchItemsInCollections = ( }; try { - // sdk had a strange bug that would only return so many, so this had to be batched. - // otherwise large lists of, ~500 channels for a homepage category failed const batchSize = pageSize || FETCH_BATCH_SIZE; const batches: Array> = []; - /* - // this was `collection_resolve` which returns claims for collection in order - // however, this fails when a claim is pending. :/ - for (let i = 0; i < Math.ceil(totalItems / batchSize); i++) { - batches[i] = Lbry.collection_resolve({ - claim_id: claimId, - page: i + 1, - page_size: batchSize, - }); - } - */ for (let i = 0; i < Math.ceil(totalItems / batchSize); i++) { batches[i] = Lbry.claim_search({ @@ -264,7 +249,6 @@ export const doFetchItemsInCollections = ( if (collectionItems) { collectionItems.forEach(collectionItem => { - // here's where we would just items.push(collectionItem.permanent_url newItems.push(collectionItem.permanent_url); valueTypes.add(collectionItem.value_type); if (collectionItem.value.stream_type) { @@ -329,7 +313,7 @@ export const doFetchItemsInCollection = ( return doFetchItemsInCollections(newOptions, cb); }; -export const doCollectionEdit = (collectionId: string, params: CollectionEditParams) => async ( +export const doCollectionEdit = (collectionId: string, params: CollectionEditParams) => async( dispatch: Dispatch, getState: GetState ) => { diff --git a/src/redux/reducers/claims.js b/src/redux/reducers/claims.js index d321f30..e167aac 100644 --- a/src/redux/reducers/claims.js +++ b/src/redux/reducers/claims.js @@ -162,7 +162,7 @@ function handleClaimAction(state: State, action: any): State { } else { byId[channel.claim_id] = channel; } - // Also add the permanent_url here until lighthouse returns canonical_url for search results + byUri[channel.permanent_url] = channel.claim_id; byUri[channel.canonical_url] = channel.claim_id; newResolvingUrls.delete(channel.canonical_url); @@ -176,11 +176,7 @@ function handleClaimAction(state: State, action: any): State { byId[collection.claim_id] = collection; } byUri[url] = collection.claim_id; - - // If url isn't a canonical_url, make sure that is added too byUri[collection.canonical_url] = collection.claim_id; - - // Also add the permanent_url here until lighthouse returns canonical_url for search results byUri[collection.permanent_url] = collection.claim_id; newResolvingUrls.delete(collection.canonical_url); newResolvingUrls.delete(collection.permanent_url); @@ -350,7 +346,6 @@ reducers[ACTIONS.FETCH_COLLECTION_LIST_COMPLETED] = (state: State, action: any): claims.forEach(claim => { const { meta } = claim; const { canonical_url: canonicalUrl, permanent_url: permanentUrl, claim_id: claimId } = claim; - // maybe add info about items in collection byUri[canonicalUrl] = claimId; byUri[permanentUrl] = claimId; @@ -651,8 +646,6 @@ reducers[ACTIONS.COLLECTION_PUBLISH_UPDATE_FAILED] = (state: State, action: any) }); }; -// COLLECTION_PUBLISH_ABANDON_... - reducers[ACTIONS.IMPORT_CHANNEL_STARTED] = (state: State): State => Object.assign({}, state, { pendingChannelImports: true }); diff --git a/src/redux/reducers/collections.js b/src/redux/reducers/collections.js index e228d6d..acd4fa7 100644 --- a/src/redux/reducers/collections.js +++ b/src/redux/reducers/collections.js @@ -95,7 +95,6 @@ const collectionsReducer = handleActions( const newUnpublishedList = Object.assign({}, unpublishedList); const newPendingList = Object.assign({}, pendingList); - const isEdit = editList[claimId]; if (localId) { // new publish newPendingList[claimId] = Object.assign({}, newUnpublishedList[localId] || {}); -- 2.45.3 From dc19ecb77e39ebd1cfeabfa869be8f8a16661deb Mon Sep 17 00:00:00 2001 From: zeppi Date: Wed, 2 Jun 2021 15:20:55 -0400 Subject: [PATCH 19/24] bugfix --- dist/bundle.es.js | 10 +++++----- src/constants/collections.js | 2 +- src/redux/actions/claims.js | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 39b6dac..10ea0e8 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -1044,7 +1044,7 @@ const COLLECTION_ID = 'colid'; const COLLECTION_INDEX = 'colindex'; const COL_TYPE_PLAYLIST = 'playlist'; -const COL_TYPE_CHANNELS = 'channelCollection'; +const COL_TYPE_CHANNELS = 'channelList'; const WATCH_LATER_ID = 'watchlater'; const FAVORITES_ID = 'favorites'; @@ -4397,7 +4397,7 @@ function doCollectionPublish(options, localId) { })); dispatch(doCheckPendingClaims()); dispatch(doFetchCollectionListMine(1, 10)); - return collectionClaim; + return resolve(collectionClaim); } function failure(error) { @@ -4409,7 +4409,7 @@ function doCollectionPublish(options, localId) { }); } - lbryProxy.collection_create(params).then(success, failure); + return lbryProxy.collection_create(params).then(success, failure); }); }; } @@ -4463,7 +4463,7 @@ function doCollectionPublishUpdate(options) { }); dispatch(doCheckPendingClaims()); dispatch(doFetchCollectionListMine(1, 10)); - return collectionClaim; + return resolve(collectionClaim); } function failure(error) { @@ -4475,7 +4475,7 @@ function doCollectionPublishUpdate(options) { }); } - lbryProxy.collection_update(updateParams).then(success, failure); + return lbryProxy.collection_update(updateParams).then(success, failure); }); }; } diff --git a/src/constants/collections.js b/src/constants/collections.js index d4c1739..0d53c94 100644 --- a/src/constants/collections.js +++ b/src/constants/collections.js @@ -2,7 +2,7 @@ export const COLLECTION_ID = 'colid'; export const COLLECTION_INDEX = 'colindex'; export const COL_TYPE_PLAYLIST = 'playlist'; -export const COL_TYPE_CHANNELS = 'channelCollection'; +export const COL_TYPE_CHANNELS = 'channelList'; export const WATCH_LATER_ID = 'watchlater'; export const FAVORITES_ID = 'favorites'; diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index e3a47af..56cc212 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -816,7 +816,7 @@ export function doCollectionPublish( ); dispatch(doCheckPendingClaims()); dispatch(doFetchCollectionListMine(1, 10)); - return collectionClaim; + return resolve(collectionClaim); } function failure(error) { @@ -828,7 +828,7 @@ export function doCollectionPublish( }); } - Lbry.collection_create(params).then(success, failure); + return Lbry.collection_create(params).then(success, failure); }); }; } @@ -903,7 +903,7 @@ export function doCollectionPublishUpdate(options: { }); dispatch(doCheckPendingClaims()); dispatch(doFetchCollectionListMine(1, 10)); - return collectionClaim; + return resolve(collectionClaim); } function failure(error) { @@ -915,7 +915,7 @@ export function doCollectionPublishUpdate(options: { }); } - Lbry.collection_update(updateParams).then(success, failure); + return Lbry.collection_update(updateParams).then(success, failure); }); }; } -- 2.45.3 From d71b9a351ca8a834bbc03f581ba5ce5d9f1f7daf Mon Sep 17 00:00:00 2001 From: zeppi Date: Thu, 3 Jun 2021 14:03:33 -0400 Subject: [PATCH 20/24] fix sync bringing back unpublished --- dist/bundle.es.js | 7 ++++--- src/redux/actions/claims.js | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 10ea0e8..7839fd2 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -4387,14 +4387,15 @@ function doCollectionPublish(options, localId) { // move unpublished collection to pending collection with new publish id // recent publish won't resolve this second. handle it in checkPending { - type: COLLECTION_PENDING, - data: { localId: localId, claimId: collectionClaim.claim_id } - }, { type: UPDATE_PENDING_CLAIMS, data: { claims: [collectionClaim] } })); + dispatch({ + type: COLLECTION_PENDING, + data: { localId: localId, claimId: collectionClaim.claim_id } + }); dispatch(doCheckPendingClaims()); dispatch(doFetchCollectionListMine(1, 10)); return resolve(collectionClaim); diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index 56cc212..ca39046 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -79,7 +79,7 @@ export function doResolveUris( const collectionIds: Array = []; return Lbry.resolve({ urls: urisToResolve, ...options }).then( - async(result: ResolveResponse) => { + async (result: ResolveResponse) => { let repostedResults = {}; const collectionClaimIdsToResolve = []; const repostsToResolve = []; @@ -653,7 +653,7 @@ export function doClaimSearch( } ) { const query = createNormalizedClaimSearchKey(options); - return async(dispatch: Dispatch) => { + return async (dispatch: Dispatch) => { dispatch({ type: ACTIONS.CLAIM_SEARCH_STARTED, data: { query: query }, @@ -802,10 +802,6 @@ export function doCollectionPublish( }, // move unpublished collection to pending collection with new publish id // recent publish won't resolve this second. handle it in checkPending - { - type: ACTIONS.COLLECTION_PENDING, - data: { localId: localId, claimId: collectionClaim.claim_id }, - }, { type: ACTIONS.UPDATE_PENDING_CLAIMS, data: { @@ -814,6 +810,10 @@ export function doCollectionPublish( } ) ); + dispatch({ + type: ACTIONS.COLLECTION_PENDING, + data: { localId: localId, claimId: collectionClaim.claim_id }, + }); dispatch(doCheckPendingClaims()); dispatch(doFetchCollectionListMine(1, 10)); return resolve(collectionClaim); -- 2.45.3 From e4fc01fc72dd2f437661b00cd84b2f8ac224cdab Mon Sep 17 00:00:00 2001 From: zeppi Date: Fri, 4 Jun 2021 09:22:54 -0400 Subject: [PATCH 21/24] fix crash on abandoned collection claim --- dist/bundle.es.js | 21 +++++++++++++-------- src/constants/collections.js | 4 ++-- src/redux/actions/collections.js | 21 +++++++++++++-------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 7839fd2..991c050 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -1040,8 +1040,8 @@ var shared_preferences = /*#__PURE__*/Object.freeze({ CLIENT_SYNC_KEYS: CLIENT_SYNC_KEYS }); -const COLLECTION_ID = 'colid'; -const COLLECTION_INDEX = 'colindex'; +const COLLECTION_ID = 'lid'; +const COLLECTION_INDEX = 'linx'; const COL_TYPE_PLAYLIST = 'playlist'; const COL_TYPE_CHANNELS = 'channelList'; @@ -4662,16 +4662,21 @@ const doFetchItemsInCollections = (resolveItemsOptions, resolveStartedCallback) const claimId = claim.claim_id; const itemOrder = claim.value.claims; - const sortResults = function (results, claimList) { - const newResults = []; + const sortResults = function (items, claimList) { + const newItems = []; claimList.forEach(function (id) { - const index = results.findIndex(function (i) { + const index = items.findIndex(function (i) { return i.claim_id === id; }); - const item = results.splice(index, 1); - if (item) newResults.push(item[0]); + if (index >= 0) { + newItems.push(items[index]); + } }); - return newResults; + /* + This will return newItems[] of length less than total_items below + if one or more of the claims has been abandoned. That's ok for now. + */ + return newItems; }; const mergeBatches = function (arrayOfResults, claimList) { diff --git a/src/constants/collections.js b/src/constants/collections.js index 0d53c94..6c002e6 100644 --- a/src/constants/collections.js +++ b/src/constants/collections.js @@ -1,5 +1,5 @@ -export const COLLECTION_ID = 'colid'; -export const COLLECTION_INDEX = 'colindex'; +export const COLLECTION_ID = 'lid'; +export const COLLECTION_INDEX = 'linx'; export const COL_TYPE_PLAYLIST = 'playlist'; export const COL_TYPE_CHANNELS = 'channelList'; diff --git a/src/redux/actions/collections.js b/src/redux/actions/collections.js index a8b7066..43176e3 100644 --- a/src/redux/actions/collections.js +++ b/src/redux/actions/collections.js @@ -93,7 +93,7 @@ export const doFetchItemsInCollections = ( pageSize?: number, }, resolveStartedCallback?: () => void -) => async(dispatch: Dispatch, getState: GetState) => { +) => async (dispatch: Dispatch, getState: GetState) => { /* 1) make sure all the collection claims are loaded into claims reducer, search/resolve if necessary. 2) get the item claims for each @@ -124,14 +124,19 @@ export const doFetchItemsInCollections = ( const claimId = claim.claim_id; const itemOrder = claim.value.claims; - const sortResults = (results: Array, claimList) => { - const newResults: Array = []; + const sortResults = (items: Array, claimList) => { + const newItems: Array = []; claimList.forEach(id => { - const index = results.findIndex(i => i.claim_id === id); - const item = results.splice(index, 1); - if (item) newResults.push(item[0]); + const index = items.findIndex(i => i.claim_id === id); + if (index >= 0) { + newItems.push(items[index]); + } }); - return newResults; + /* + This will return newItems[] of length less than total_items below + if one or more of the claims has been abandoned. That's ok for now. + */ + return newItems; }; const mergeBatches = ( @@ -313,7 +318,7 @@ export const doFetchItemsInCollection = ( return doFetchItemsInCollections(newOptions, cb); }; -export const doCollectionEdit = (collectionId: string, params: CollectionEditParams) => async( +export const doCollectionEdit = (collectionId: string, params: CollectionEditParams) => async ( dispatch: Dispatch, getState: GetState ) => { -- 2.45.3 From 6d160b3d141b9abff532579ac7ac2b12382217fb Mon Sep 17 00:00:00 2001 From: zeppi Date: Fri, 4 Jun 2021 10:08:43 -0400 Subject: [PATCH 22/24] cleanup --- dist/bundle.es.js | 3 +-- dist/flow-typed/Collections.js | 2 +- flow-typed/Collections.js | 2 +- src/redux/actions/claims.js | 13 +++---------- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index 991c050..a9041ea 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -4153,7 +4153,6 @@ function doUpdateChannel(params, cb) { } // we'll need to remove these once we add locations/channels to channel page edit/create options - if (channelClaim && channelClaim.value && channelClaim.value.locations) { updateParams.locations = channelClaim.value.locations; } @@ -4187,7 +4186,7 @@ function doImportChannel(certificate) { type: IMPORT_CHANNEL_STARTED }); - return lbryProxy.channel_import({ channel_data: certificate }).then(result => { + return lbryProxy.channel_import({ channel_data: certificate }).then(() => { dispatch({ type: IMPORT_CHANNEL_COMPLETED }); diff --git a/dist/flow-typed/Collections.js b/dist/flow-typed/Collections.js index 927cef7..f70825a 100644 --- a/dist/flow-typed/Collections.js +++ b/dist/flow-typed/Collections.js @@ -5,7 +5,7 @@ declare type Collection = { type: string, updatedAt: number, totalItems?: number, - sourceid?: string, // if copied, claimId of original collection + sourceId?: string, // if copied, claimId of original collection }; declare type CollectionState = { diff --git a/flow-typed/Collections.js b/flow-typed/Collections.js index 927cef7..f70825a 100644 --- a/flow-typed/Collections.js +++ b/flow-typed/Collections.js @@ -5,7 +5,7 @@ declare type Collection = { type: string, updatedAt: number, totalItems?: number, - sourceid?: string, // if copied, claimId of original collection + sourceId?: string, // if copied, claimId of original collection }; declare type CollectionState = { diff --git a/src/redux/actions/claims.js b/src/redux/actions/claims.js index ca39046..cee7c13 100644 --- a/src/redux/actions/claims.js +++ b/src/redux/actions/claims.js @@ -10,8 +10,6 @@ import { selectClaimsByUri, selectMyChannelClaims, selectPendingIds, - selectClaimsById, - makeSelectClaimForClaimId, } from 'redux/selectors/claims'; import { doFetchTxoPage } from 'redux/actions/wallet'; @@ -21,7 +19,6 @@ import { batchActions } from 'util/batch-actions'; import { createNormalizedClaimSearchKey } from 'util/claim'; import { PAGE_SIZE } from 'constants/claim'; import { - makeSelectEditedCollectionForId, selectPendingCollections, } from 'redux/selectors/collections'; import { @@ -30,8 +27,6 @@ import { doCollectionDelete, } from 'redux/actions/collections'; -type ResolveEntries = Array<[string, GenericClaim]>; - export function doResolveUris( uris: Array, returnCachedClaims: boolean = false, @@ -79,9 +74,8 @@ export function doResolveUris( const collectionIds: Array = []; return Lbry.resolve({ urls: urisToResolve, ...options }).then( - async (result: ResolveResponse) => { + async(result: ResolveResponse) => { let repostedResults = {}; - const collectionClaimIdsToResolve = []; const repostsToResolve = []; const fallbackResolveInfo = { stream: null, @@ -518,7 +512,6 @@ export function doUpdateChannel(params: any, cb: any) { } // we'll need to remove these once we add locations/channels to channel page edit/create options - if (channelClaim && channelClaim.value && channelClaim.value.locations) { updateParams.locations = channelClaim.value.locations; } @@ -556,7 +549,7 @@ export function doImportChannel(certificate: string) { }); return Lbry.channel_import({ channel_data: certificate }) - .then((result: string) => { + .then(() => { dispatch({ type: ACTIONS.IMPORT_CHANNEL_COMPLETED, }); @@ -653,7 +646,7 @@ export function doClaimSearch( } ) { const query = createNormalizedClaimSearchKey(options); - return async (dispatch: Dispatch) => { + return async(dispatch: Dispatch) => { dispatch({ type: ACTIONS.CLAIM_SEARCH_STARTED, data: { query: query }, -- 2.45.3 From 97cd2dbd6a71c225efef3da1233335c10254adb3 Mon Sep 17 00:00:00 2001 From: zeppi Date: Mon, 7 Jun 2021 16:21:31 -0400 Subject: [PATCH 23/24] refactor select collection index / next --- dist/bundle.es.js | 21 ++++++++++++++++----- src/index.js | 3 ++- src/redux/selectors/collections.js | 23 +++++++++++++++++++---- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index a9041ea..a144d9c 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -3694,10 +3694,20 @@ const makeSelectClaimIdsForCollectionId = id => reselect.createSelector(makeSele return ids; }); -const makeSelectNextUrlForCollection = (id, index) => reselect.createSelector(makeSelectUrlsForCollectionId(id), urls => { - const url = urls[index + 1]; - if (url) { - return url; +const makeSelectIndexForUrlInCollection = (url, id) => reselect.createSelector(makeSelectUrlsForCollectionId(id), urls => { + const index = urls.findIndex(u => u === url); + if (index > -1) { + return index; + } + return null; +}); + +const makeSelectNextUrlForCollectionAndUrl = (id, url) => reselect.createSelector(makeSelectIndexForUrlInCollection(url, id), makeSelectUrlsForCollectionId(id), (index, urls) => { + if (urls && index >= -1) { + const url = urls[index + 1]; + if (url) { + return url; + } } return null; }); @@ -7916,6 +7926,7 @@ exports.makeSelectFileInfoForUri = makeSelectFileInfoForUri; exports.makeSelectFileNameForUri = makeSelectFileNameForUri; exports.makeSelectFilePartlyDownloaded = makeSelectFilePartlyDownloaded; exports.makeSelectFilteredTransactionsForPage = makeSelectFilteredTransactionsForPage; +exports.makeSelectIndexForUrlInCollection = makeSelectIndexForUrlInCollection; exports.makeSelectIsAbandoningClaimForUri = makeSelectIsAbandoningClaimForUri; exports.makeSelectIsResolvingCollectionForId = makeSelectIsResolvingCollectionForId; exports.makeSelectIsUriResolving = makeSelectIsUriResolving; @@ -7929,7 +7940,7 @@ exports.makeSelectMyPublishedCollectionForId = makeSelectMyPublishedCollectionFo exports.makeSelectMyPurchasesForPage = makeSelectMyPurchasesForPage; exports.makeSelectMyStreamUrlsForPage = makeSelectMyStreamUrlsForPage; exports.makeSelectNameForCollectionId = makeSelectNameForCollectionId; -exports.makeSelectNextUrlForCollection = makeSelectNextUrlForCollection; +exports.makeSelectNextUrlForCollectionAndUrl = makeSelectNextUrlForCollectionAndUrl; exports.makeSelectNsfwCountForChannel = makeSelectNsfwCountForChannel; exports.makeSelectNsfwCountFromUris = makeSelectNsfwCountFromUris; exports.makeSelectOmittedCountForChannel = makeSelectOmittedCountForChannel; diff --git a/src/index.js b/src/index.js index 6187fdf..db1ef86 100644 --- a/src/index.js +++ b/src/index.js @@ -179,7 +179,8 @@ export { makeSelectNameForCollectionId, makeSelectCountForCollectionId, makeSelectIsResolvingCollectionForId, - makeSelectNextUrlForCollection, + makeSelectIndexForUrlInCollection, + makeSelectNextUrlForCollectionAndUrl, makeSelectCollectionForIdHasClaimUrl, } from 'redux/selectors/collections'; diff --git a/src/redux/selectors/collections.js b/src/redux/selectors/collections.js index 68cdddf..b55fa40 100644 --- a/src/redux/selectors/collections.js +++ b/src/redux/selectors/collections.js @@ -184,13 +184,28 @@ export const makeSelectClaimIdsForCollectionId = (id: string) => } ); -export const makeSelectNextUrlForCollection = (id: string, index: number) => +export const makeSelectIndexForUrlInCollection = (url: string, id: string) => createSelector( makeSelectUrlsForCollectionId(id), urls => { - const url = urls[index + 1]; - if (url) { - return url; + const index = urls.findIndex(u => u === url); + if (index > -1) { + return index; + } + return null; + } + ); + +export const makeSelectNextUrlForCollectionAndUrl = (id: string, url: string) => + createSelector( + makeSelectIndexForUrlInCollection(url, id), + makeSelectUrlsForCollectionId(id), + (index, urls) => { + if (urls && index >= -1) { + const url = urls[index + 1]; + if (url) { + return url; + } } return null; } -- 2.45.3 From 5cd9e7601cdc1c81b8d0e2d2f02e9b9d2fb86575 Mon Sep 17 00:00:00 2001 From: zeppi Date: Tue, 8 Jun 2021 10:14:36 -0400 Subject: [PATCH 24/24] bugfix --- dist/bundle.es.js | 2 +- src/redux/selectors/collections.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/bundle.es.js b/dist/bundle.es.js index a144d9c..1f957de 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -3695,7 +3695,7 @@ const makeSelectClaimIdsForCollectionId = id => reselect.createSelector(makeSele }); const makeSelectIndexForUrlInCollection = (url, id) => reselect.createSelector(makeSelectUrlsForCollectionId(id), urls => { - const index = urls.findIndex(u => u === url); + const index = urls && urls.findIndex(u => u === url); if (index > -1) { return index; } diff --git a/src/redux/selectors/collections.js b/src/redux/selectors/collections.js index b55fa40..92e2cf7 100644 --- a/src/redux/selectors/collections.js +++ b/src/redux/selectors/collections.js @@ -188,7 +188,7 @@ export const makeSelectIndexForUrlInCollection = (url: string, id: string) => createSelector( makeSelectUrlsForCollectionId(id), urls => { - const index = urls.findIndex(u => u === url); + const index = urls && urls.findIndex(u => u === url); if (index > -1) { return index; } -- 2.45.3