Compare commits
189 commits
Author | SHA1 | Date | |
---|---|---|---|
|
0f930c4a7b | ||
|
32b5787071 | ||
|
129b0ea3fa | ||
|
e3bc848263 | ||
|
372e559cae | ||
|
49b9db5aae | ||
|
12a2ffc708 | ||
|
95fa26f836 | ||
|
dc264ec50c | ||
|
aeb1f533b5 | ||
|
d016d8057b | ||
|
0302a2f8d6 | ||
|
8fa92d872d | ||
|
e4d0662100 | ||
|
036aa59086 | ||
|
c76dfbde27 | ||
|
7cc9923ed9 | ||
|
60bd918d5e | ||
|
9ebfc927d0 | ||
|
54ca8c4320 | ||
|
bee9bf38dd | ||
|
aabae5ce59 | ||
|
a327385cdf | ||
|
64ce7aa99c | ||
|
34dfd384e4 | ||
|
707c60b813 | ||
|
8f66a2fe7c | ||
|
729a4831ad | ||
|
88370997b4 | ||
|
b93598b0ff | ||
|
4cbb9a35c3 | ||
|
04ce1df03d | ||
|
347fe25e85 | ||
|
e66698eadc | ||
|
0b505fb0f4 | ||
|
508e8d36fd | ||
|
0758827e6d | ||
|
166c3b2832 | ||
|
d298c00f24 | ||
|
06b09f5a81 | ||
|
4dfc4689c6 | ||
|
85ad697e0a | ||
|
609f13991f | ||
|
503e18be1b | ||
|
32a85a9ff3 | ||
|
40fc75320d | ||
|
e20baa0683 | ||
|
a1cb16400d | ||
|
9461cf1bee | ||
|
7e049487c3 | ||
|
f7775fd837 | ||
|
06531c6b48 | ||
|
b280d66f5d | ||
|
bfb50ebeb5 | ||
|
06ce8c623c | ||
|
f8ff4cfc8f | ||
|
e34f451025 | ||
|
d3c045b037 | ||
|
63946a0a6d | ||
|
dd697ed70e | ||
|
fd2551e764 | ||
|
8d0f9c18fd | ||
|
757e8c24ec | ||
|
ecfcc95beb | ||
|
b2ad71fb74 | ||
|
35dd7650fb | ||
|
babfec7d43 | ||
|
6fc11454eb | ||
|
3b853b6ddd | ||
|
41ef1117e5 | ||
|
66c77fc39b | ||
|
e5c0b5f0a6 | ||
|
7e17344683 | ||
|
b511282c35 | ||
|
eb37009a98 | ||
|
c2e03fa71d | ||
|
4e37ab6580 | ||
|
a0bfbee958 | ||
|
3ca0c8d204 | ||
|
5f3a40a420 | ||
|
8335c9d2de | ||
|
9f7902aa0b | ||
|
9a17013728 | ||
|
35088a6d10 | ||
|
8e74e3137a | ||
|
2cf645ab14 | ||
|
c494c92505 | ||
|
e9712dc954 | ||
|
5416b6bc42 | ||
|
86c7741d4c | ||
|
a5a326e73a | ||
|
4e2a6c8201 | ||
|
629c3273f5 | ||
|
638a78695a | ||
|
d75e7725fe | ||
|
d91ec1773c | ||
|
e5b79a8400 | ||
|
f449d7916c | ||
|
87e67aa714 | ||
|
74ab5bbf84 | ||
|
bf728f8716 | ||
|
bf3645df44 | ||
|
0d2d64aca7 | ||
|
c31161c41a | ||
|
251c646851 | ||
|
5ea369ee76 | ||
|
37f17fae0c | ||
|
f8a4264307 | ||
|
f37fd9bf92 | ||
|
bcaedbcd9c | ||
|
f0849b4ce1 | ||
|
a2f8646f95 | ||
|
92acb9a6c9 | ||
|
5d41b0656c | ||
|
7c926ad8de | ||
|
9e2d80909f | ||
|
7bd6ae1824 | ||
|
746acef66a | ||
|
0274f7e13e | ||
|
acc54f157f | ||
|
3b523980de | ||
|
b5cc1f8818 | ||
|
f80c71a2ab | ||
|
ce9f720bbd | ||
|
e3c05268e5 | ||
|
a37f195d73 | ||
|
8adf3dada3 | ||
|
664df6237d | ||
|
4f57992762 | ||
|
f683af3b99 | ||
|
612acc6e7f | ||
|
034838a23c | ||
|
92a4263c90 | ||
|
eb40d2c058 | ||
|
70c52e42e8 | ||
|
88dbef2cd0 | ||
|
8344379bfd | ||
|
8093d69807 | ||
|
c86810038c | ||
|
dc70da1be2 | ||
|
4d11f31914 | ||
|
04789190b0 | ||
|
1fc5afa0c4 | ||
|
3cb3859baf | ||
|
823197af37 | ||
|
fb4bcdb4f5 | ||
|
15737f9b09 | ||
|
77b27fea99 | ||
|
16c6ba1a24 | ||
|
3f14b93cbc | ||
|
22a26be26f | ||
|
a13ddadba4 | ||
|
7fbb87d38f | ||
|
fee1834bbc | ||
|
ba5d6b84be | ||
|
90012bf47c | ||
|
3bfdde4629 | ||
|
437c54f164 | ||
|
3eee65146b | ||
|
01df9522d5 | ||
|
1c02ca2b6b | ||
|
3df916548f | ||
|
316dfcf06a | ||
|
5c1a00b103 | ||
|
7d90cba8a0 | ||
|
aca15fe3e9 | ||
|
12ba291c3b | ||
|
903d425188 | ||
|
0dbe6efc75 | ||
|
210bb80f2c | ||
|
05b949d470 | ||
|
7df96d4767 | ||
|
27da80083e | ||
|
04e3ca8250 | ||
|
a1d5ce7e7e | ||
|
8c29c7e912 | ||
|
3a140c2318 | ||
|
5547f53f48 | ||
|
3ce73c6646 | ||
|
11a43bae79 | ||
|
7847224c89 | ||
|
3dfbb7de0f | ||
|
6306639c34 | ||
|
7a7a1aad32 | ||
|
c57d5ea664 | ||
|
83e20d3e6e | ||
|
10a508aaa5 | ||
|
9c68623047 | ||
|
f21f797ae3 |
65 changed files with 9190 additions and 3235 deletions
|
@ -1,5 +1,6 @@
|
|||
[ignore]
|
||||
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017-2020 LBRY Inc
|
||||
Copyright (c) 2017-2021 LBRY Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
|
|
|
@ -20,6 +20,9 @@ yarn link lbry-redux
|
|||
### Build
|
||||
Run `$ yarn build`. If the symlink does not work, just build the file and move the `bundle.js` file into the `node_modules/` folder.
|
||||
|
||||
### Tests
|
||||
Run `$ yarn test`.
|
||||
|
||||
## Contributing
|
||||
We :heart: contributions from everyone! We welcome [bug reports](https://github.com/lbryio/lbry-redux/issues/), [bug fixes](https://github.com/lbryio/lbry-redux/pulls) and feedback on the module is always appreciated.
|
||||
|
||||
|
|
3704
dist/bundle.es.js
vendored
3704
dist/bundle.es.js
vendored
File diff suppressed because one or more lines are too long
83
dist/flow-typed/Claim.js
vendored
83
dist/flow-typed/Claim.js
vendored
|
@ -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,9 +34,12 @@ 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,
|
||||
repost_url?: string,
|
||||
repost_bid_amount?: string,
|
||||
purchase_receipt?: PurchaseReceipt,
|
||||
meta: {
|
||||
activation_height: number,
|
||||
|
@ -71,6 +78,10 @@ declare type ChannelMetadata = GenericMetadata & {
|
|||
featured?: Array<string>,
|
||||
};
|
||||
|
||||
declare type CollectionMetadata = GenericMetadata & {
|
||||
claims: Array<string>,
|
||||
}
|
||||
|
||||
declare type StreamMetadata = GenericMetadata & {
|
||||
license?: string, // License "title" ex: Creative Commons, Custom copyright
|
||||
license_url?: string, // Link to full license
|
||||
|
@ -133,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<string>,
|
||||
replace?: boolean,
|
||||
languages?: Array<string>,
|
||||
locations?: Array<string>,
|
||||
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<string>,
|
||||
languages?: Array<string>,
|
||||
}
|
||||
|
||||
declare type CollectionUpdateParams = {
|
||||
claim_id: string,
|
||||
claim_ids?: Array<string>,
|
||||
bid?: string,
|
||||
title?: string,
|
||||
cover_url?: string,
|
||||
thumbnail_url?: string,
|
||||
description?: string,
|
||||
website_url?: string,
|
||||
email?: string,
|
||||
tags?: Array<string>,
|
||||
replace?: boolean,
|
||||
languages?: Array<string>,
|
||||
locations?: Array<string>,
|
||||
blocking?: boolean,
|
||||
}
|
||||
|
||||
declare type CollectionPublishParams = {
|
||||
name: string,
|
||||
bid: string,
|
||||
claim_ids: Array<string>,
|
||||
blocking?: true,
|
||||
title?: string,
|
||||
thumbnail_url?: string,
|
||||
description?: string,
|
||||
tags?: Array<string>,
|
||||
languages?: Array<string>,
|
||||
}
|
||||
|
|
29
dist/flow-typed/CoinSwap.js
vendored
Normal file
29
dist/flow-typed/CoinSwap.js
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
declare type CoinSwapInfo = {
|
||||
chargeCode: string,
|
||||
coins: Array<string>,
|
||||
sendAddresses: { [string]: string},
|
||||
sendAmounts: { [string]: any },
|
||||
lbcAmount: number,
|
||||
status?: {
|
||||
status: string,
|
||||
receiptCurrency: string,
|
||||
receiptTxid: string,
|
||||
lbcTxid: string,
|
||||
},
|
||||
}
|
||||
|
||||
declare type CoinSwapState = {
|
||||
coinSwaps: Array<CoinSwapInfo>,
|
||||
};
|
||||
|
||||
declare type CoinSwapAddAction = {
|
||||
type: string,
|
||||
data: CoinSwapInfo,
|
||||
};
|
||||
|
||||
declare type CoinSwapRemoveAction = {
|
||||
type: string,
|
||||
data: {
|
||||
chargeCode: string,
|
||||
},
|
||||
};
|
34
dist/flow-typed/Collections.js
vendored
Normal file
34
dist/flow-typed/Collections.js
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
declare type Collection = {
|
||||
id: string,
|
||||
items: Array<?string>,
|
||||
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<string>,
|
||||
isResolvingCollectionById: { [string]: boolean },
|
||||
error?: string | null,
|
||||
};
|
||||
|
||||
declare type CollectionGroup = {
|
||||
[string]: Collection,
|
||||
}
|
||||
|
||||
declare type CollectionEditParams = {
|
||||
claims?: Array<Claim>,
|
||||
remove?: boolean,
|
||||
claimIds?: Array<string>,
|
||||
replace?: boolean,
|
||||
order?: { from: number, to: number },
|
||||
type?: string,
|
||||
name?: string,
|
||||
}
|
23
dist/flow-typed/Comment.js
vendored
23
dist/flow-typed/Comment.js
vendored
|
@ -1,23 +0,0 @@
|
|||
declare type Comment = {
|
||||
comment: string, // comment body
|
||||
comment_id: string, // sha256 digest
|
||||
claim_id: string, // id linking to the claim this comment
|
||||
timestamp: number, // integer representing unix-time
|
||||
is_hidden: boolean, // claim owner may enable/disable this
|
||||
channel_id?: string, // claimId of channel signing this comment
|
||||
channel_name?: string, // name of channel claim
|
||||
channel_url?: string, // full lbry url to signing channel
|
||||
signature?: string, // signature of comment by originating channel
|
||||
signing_ts?: string, // timestamp used when signing this comment
|
||||
is_channel_signature_valid?: boolean, // whether or not the signature could be validated
|
||||
parent_id?: number, // comment_id of comment this is in reply to
|
||||
};
|
||||
|
||||
// todo: relate individual comments to their commentId
|
||||
declare type CommentsState = {
|
||||
commentsByUri: { [string]: string },
|
||||
byId: { [string]: Array<string> },
|
||||
commentById: { [string]: Comment },
|
||||
isLoading: boolean,
|
||||
myComments: ?Set<string>,
|
||||
};
|
80
dist/flow-typed/Lbry.js
vendored
80
dist/flow-typed/Lbry.js
vendored
|
@ -7,10 +7,6 @@ declare type StatusResponse = {
|
|||
download_progress: number,
|
||||
downloading_headers: boolean,
|
||||
},
|
||||
connection_status: {
|
||||
code: string,
|
||||
message: string,
|
||||
},
|
||||
dht: {
|
||||
node_id: string,
|
||||
peers_in_routing_table: number,
|
||||
|
@ -45,6 +41,7 @@ declare type StatusResponse = {
|
|||
redirects: {},
|
||||
},
|
||||
wallet: ?{
|
||||
connected: string,
|
||||
best_blockhash: string,
|
||||
blocks: number,
|
||||
blocks_behind: number,
|
||||
|
@ -78,7 +75,7 @@ declare type BalanceResponse = {
|
|||
|
||||
declare type ResolveResponse = {
|
||||
// Keys are the url(s) passed to resolve
|
||||
[string]: { error?: {}, stream?: StreamClaim, channel?: ChannelClaim, claimsInChannel?: number },
|
||||
[string]: { error?: {}, stream?: StreamClaim, channel?: ChannelClaim, collection?: CollectionClaim, claimsInChannel?: number },
|
||||
};
|
||||
|
||||
declare type GetResponse = FileListItem & { error?: string };
|
||||
|
@ -127,12 +124,22 @@ declare type ChannelUpdateResponse = GenericTxResponse & {
|
|||
declare type CommentCreateResponse = Comment;
|
||||
declare type CommentUpdateResponse = Comment;
|
||||
|
||||
declare type CommentListResponse = {
|
||||
items: Array<Comment>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
declare type MyReactions = {
|
||||
// Keys are the commentId
|
||||
[string]: Array<string>,
|
||||
};
|
||||
|
||||
declare type OthersReactions = {
|
||||
// Keys are the commentId
|
||||
[string]: {
|
||||
// Keys are the reaction_type, e.g. 'like'
|
||||
[string]: number,
|
||||
},
|
||||
};
|
||||
|
||||
declare type CommentReactListResponse = {
|
||||
my_reactions: Array<MyReactions>,
|
||||
others_reactions: Array<OthersReactions>,
|
||||
};
|
||||
|
||||
declare type CommentHideResponse = {
|
||||
|
@ -140,6 +147,11 @@ declare type CommentHideResponse = {
|
|||
[string]: { hidden: boolean },
|
||||
};
|
||||
|
||||
declare type CommentPinResponse = {
|
||||
// keyed by the CommentIds entered
|
||||
items: Comment,
|
||||
};
|
||||
|
||||
declare type CommentAbandonResponse = {
|
||||
// keyed by the CommentId given
|
||||
abandoned: boolean,
|
||||
|
@ -153,6 +165,42 @@ declare type ChannelListResponse = {
|
|||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type ChannelSignResponse = {
|
||||
signature: string,
|
||||
signing_ts: string,
|
||||
};
|
||||
|
||||
declare type CollectionCreateResponse = {
|
||||
outputs: Array<Claim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
}
|
||||
|
||||
declare type CollectionListResponse = {
|
||||
items: Array<Claim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type CollectionResolveResponse = {
|
||||
items: Array<Claim>,
|
||||
total_items: number,
|
||||
};
|
||||
|
||||
declare type CollectionResolveOptions = {
|
||||
claim_id: string,
|
||||
};
|
||||
|
||||
declare type CollectionListOptions = {
|
||||
page: number,
|
||||
page_size: number,
|
||||
resolve?: boolean,
|
||||
};
|
||||
|
||||
declare type FileListResponse = {
|
||||
items: Array<FileListItem>,
|
||||
page: number,
|
||||
|
@ -209,7 +257,7 @@ declare type StreamRepostOptions = {
|
|||
name: string,
|
||||
bid: string,
|
||||
claim_id: string,
|
||||
channel_id: string,
|
||||
channel_id?: string,
|
||||
};
|
||||
|
||||
declare type StreamRepostResponse = GenericTxResponse;
|
||||
|
@ -262,6 +310,7 @@ declare type LbryTypes = {
|
|||
channel_update: (params: {}) => Promise<ChannelUpdateResponse>,
|
||||
channel_import: (params: {}) => Promise<string>,
|
||||
channel_list: (params: {}) => Promise<ChannelListResponse>,
|
||||
channel_sign: (params: {}) => Promise<ChannelSignResponse>,
|
||||
stream_abandon: (params: {}) => Promise<GenericTxResponse>,
|
||||
stream_list: (params: {}) => Promise<StreamListResponse>,
|
||||
channel_abandon: (params: {}) => Promise<GenericTxResponse>,
|
||||
|
@ -270,6 +319,10 @@ declare type LbryTypes = {
|
|||
support_abandon: (params: {}) => Promise<SupportAbandonResponse>,
|
||||
stream_repost: (params: StreamRepostOptions) => Promise<StreamRepostResponse>,
|
||||
purchase_list: (params: PurchaseListOptions) => Promise<PurchaseListResponse>,
|
||||
collection_resolve: (params: CollectionResolveOptions) => Promise<CollectionResolveResponse>,
|
||||
collection_list: (params: CollectionListOptions) => Promise<CollectionListResponse>,
|
||||
collection_create: (params: {}) => Promise<CollectionCreateResponse>,
|
||||
collection_update: (params: {}) => Promise<CollectionCreateResponse>,
|
||||
|
||||
// File fetching and manipulation
|
||||
file_list: (params: {}) => Promise<FileListResponse>,
|
||||
|
@ -282,8 +335,6 @@ declare type LbryTypes = {
|
|||
preference_set: (params: {}) => Promise<any>,
|
||||
|
||||
// Commenting
|
||||
comment_list: (params: {}) => Promise<CommentListResponse>,
|
||||
comment_create: (params: {}) => Promise<CommentCreateResponse>,
|
||||
comment_update: (params: {}) => Promise<CommentUpdateResponse>,
|
||||
comment_hide: (params: {}) => Promise<CommentHideResponse>,
|
||||
comment_abandon: (params: {}) => Promise<CommentAbandonResponse>,
|
||||
|
@ -300,6 +351,7 @@ declare type LbryTypes = {
|
|||
address_unused: (params: {}) => Promise<string>, // New address
|
||||
address_list: (params: {}) => Promise<string>,
|
||||
transaction_list: (params: {}) => Promise<TxListResponse>,
|
||||
txo_list: (params: {}) => Promise<any>,
|
||||
|
||||
// Sync
|
||||
sync_hash: (params: {}) => Promise<string>,
|
||||
|
|
4
dist/flow-typed/LbryFirst.js
vendored
4
dist/flow-typed/LbryFirst.js
vendored
|
@ -1,12 +1,12 @@
|
|||
// @flow
|
||||
declare type StatusResponse = {
|
||||
declare type LbryFirstStatusResponse = {
|
||||
Version: string,
|
||||
Message: string,
|
||||
Running: boolean,
|
||||
Commit: string,
|
||||
};
|
||||
|
||||
declare type VersionResponse = {
|
||||
declare type LbryFirstVersionResponse = {
|
||||
build: string,
|
||||
lbrynet_version: string,
|
||||
os_release: string,
|
||||
|
|
84
dist/flow-typed/Search.js
vendored
84
dist/flow-typed/Search.js
vendored
|
@ -1,84 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
declare type SearchSuggestion = {
|
||||
value: string,
|
||||
shorthand: string,
|
||||
type: string,
|
||||
};
|
||||
|
||||
declare type SearchOptions = {
|
||||
// :(
|
||||
// https://github.com/facebook/flow/issues/6492
|
||||
RESULT_COUNT: number,
|
||||
CLAIM_TYPE: string,
|
||||
INCLUDE_FILES: string,
|
||||
INCLUDE_CHANNELS: string,
|
||||
INCLUDE_FILES_AND_CHANNELS: string,
|
||||
MEDIA_AUDIO: string,
|
||||
MEDIA_VIDEO: string,
|
||||
MEDIA_TEXT: string,
|
||||
MEDIA_IMAGE: string,
|
||||
MEDIA_APPLICATION: string,
|
||||
};
|
||||
|
||||
declare type SearchState = {
|
||||
isActive: boolean,
|
||||
searchQuery: string,
|
||||
options: SearchOptions,
|
||||
suggestions: { [string]: Array<SearchSuggestion> },
|
||||
urisByQuery: {},
|
||||
resolvedResultsByQuery: {},
|
||||
resolvedResultsByQueryLastPageReached: {},
|
||||
};
|
||||
|
||||
declare type SearchSuccess = {
|
||||
type: ACTIONS.SEARCH_SUCCESS,
|
||||
data: {
|
||||
query: string,
|
||||
uris: Array<string>,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchQuery = {
|
||||
type: ACTIONS.UPDATE_SEARCH_QUERY,
|
||||
data: {
|
||||
query: string,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchSuggestions = {
|
||||
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
|
||||
data: {
|
||||
query: string,
|
||||
suggestions: Array<SearchSuggestion>,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchOptions = {
|
||||
type: ACTIONS.UPDATE_SEARCH_OPTIONS,
|
||||
data: SearchOptions,
|
||||
};
|
||||
|
||||
declare type ResolvedSearchResult = {
|
||||
channel: string,
|
||||
channel_claim_id: string,
|
||||
claimId: string,
|
||||
duration: number,
|
||||
fee: number,
|
||||
name: string,
|
||||
nsfw: boolean,
|
||||
release_time: string,
|
||||
thumbnail_url: string,
|
||||
title: string,
|
||||
};
|
||||
|
||||
declare type ResolvedSearchSuccess = {
|
||||
type: ACTIONS.RESOLVED_SEARCH_SUCCESS,
|
||||
data: {
|
||||
append: boolean,
|
||||
pageSize: number,
|
||||
results: Array<ResolvedSearchResult>,
|
||||
query: string,
|
||||
},
|
||||
};
|
5
dist/flow-typed/Txo.js
vendored
5
dist/flow-typed/Txo.js
vendored
|
@ -10,6 +10,9 @@ declare type Txo = {
|
|||
is_my_output: boolean,
|
||||
is_my_input: boolean,
|
||||
is_spent: boolean,
|
||||
signing_channel?: {
|
||||
channel_id: string,
|
||||
},
|
||||
};
|
||||
|
||||
declare type TxoListParams = {
|
||||
|
@ -21,4 +24,4 @@ declare type TxoListParams = {
|
|||
is_not_my_input?: boolean,
|
||||
is_not_my_output?: boolean,
|
||||
is_spent?: boolean,
|
||||
}
|
||||
};
|
||||
|
|
5
dist/flow-typed/npm/from-entries.js
vendored
Normal file
5
dist/flow-typed/npm/from-entries.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
// @flow
|
||||
|
||||
declare module '@ungap/from-entries' {
|
||||
declare module.exports: any;
|
||||
}
|
5
dist/flow-typed/npm/uuid.js
vendored
Normal file
5
dist/flow-typed/npm/uuid.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
// @flow
|
||||
|
||||
declare module 'uuid' {
|
||||
declare module.exports: any;
|
||||
}
|
102
dist/flow-typed/npm/uuid_v3.x.x.js
vendored
102
dist/flow-typed/npm/uuid_v3.x.x.js
vendored
|
@ -1,102 +0,0 @@
|
|||
// flow-typed signature: 3cf668e64747095cab0bb360cf2fb34f
|
||||
// flow-typed version: d659bd0cb8/uuid_v3.x.x/flow_>=v0.32.x
|
||||
|
||||
declare module "uuid" {
|
||||
declare class uuid {
|
||||
static (
|
||||
options?: {|
|
||||
random?: number[],
|
||||
rng?: () => number[] | Buffer
|
||||
|},
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string,
|
||||
|
||||
static v1(
|
||||
options?: {|
|
||||
node?: number[],
|
||||
clockseq?: number,
|
||||
msecs?: number | Date,
|
||||
nsecs?: number
|
||||
|},
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string,
|
||||
|
||||
static v4(
|
||||
options?: {|
|
||||
random?: number[],
|
||||
rng?: () => number[] | Buffer
|
||||
|},
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string
|
||||
}
|
||||
declare module.exports: Class<uuid>;
|
||||
}
|
||||
|
||||
declare module "uuid/v1" {
|
||||
declare class v1 {
|
||||
static (
|
||||
options?: {|
|
||||
node?: number[],
|
||||
clockseq?: number,
|
||||
msecs?: number | Date,
|
||||
nsecs?: number
|
||||
|},
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string
|
||||
}
|
||||
|
||||
declare module.exports: Class<v1>;
|
||||
}
|
||||
|
||||
declare module "uuid/v3" {
|
||||
declare class v3 {
|
||||
static (
|
||||
name?: string | number[],
|
||||
namespace?: string | number[],
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string,
|
||||
|
||||
static name: string,
|
||||
static DNS: string,
|
||||
static URL: string
|
||||
}
|
||||
|
||||
declare module.exports: Class<v3>;
|
||||
}
|
||||
|
||||
declare module "uuid/v4" {
|
||||
declare class v4 {
|
||||
static (
|
||||
options?: {|
|
||||
random?: number[],
|
||||
rng?: () => number[] | Buffer
|
||||
|},
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string
|
||||
}
|
||||
|
||||
declare module.exports: Class<v4>;
|
||||
}
|
||||
|
||||
declare module "uuid/v5" {
|
||||
declare class v5 {
|
||||
static (
|
||||
name?: string | number[],
|
||||
namespace?: string | number[],
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string,
|
||||
|
||||
static name: string,
|
||||
static DNS: string,
|
||||
static URL: string
|
||||
}
|
||||
|
||||
declare module.exports: Class<v5>;
|
||||
}
|
83
flow-typed/Claim.js
vendored
83
flow-typed/Claim.js
vendored
|
@ -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,9 +34,12 @@ 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,
|
||||
repost_url?: string,
|
||||
repost_bid_amount?: string,
|
||||
purchase_receipt?: PurchaseReceipt,
|
||||
meta: {
|
||||
activation_height: number,
|
||||
|
@ -71,6 +78,10 @@ declare type ChannelMetadata = GenericMetadata & {
|
|||
featured?: Array<string>,
|
||||
};
|
||||
|
||||
declare type CollectionMetadata = GenericMetadata & {
|
||||
claims: Array<string>,
|
||||
}
|
||||
|
||||
declare type StreamMetadata = GenericMetadata & {
|
||||
license?: string, // License "title" ex: Creative Commons, Custom copyright
|
||||
license_url?: string, // Link to full license
|
||||
|
@ -133,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<string>,
|
||||
replace?: boolean,
|
||||
languages?: Array<string>,
|
||||
locations?: Array<string>,
|
||||
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<string>,
|
||||
languages?: Array<string>,
|
||||
}
|
||||
|
||||
declare type CollectionUpdateParams = {
|
||||
claim_id: string,
|
||||
claim_ids?: Array<string>,
|
||||
bid?: string,
|
||||
title?: string,
|
||||
cover_url?: string,
|
||||
thumbnail_url?: string,
|
||||
description?: string,
|
||||
website_url?: string,
|
||||
email?: string,
|
||||
tags?: Array<string>,
|
||||
replace?: boolean,
|
||||
languages?: Array<string>,
|
||||
locations?: Array<string>,
|
||||
blocking?: boolean,
|
||||
}
|
||||
|
||||
declare type CollectionPublishParams = {
|
||||
name: string,
|
||||
bid: string,
|
||||
claim_ids: Array<string>,
|
||||
blocking?: true,
|
||||
title?: string,
|
||||
thumbnail_url?: string,
|
||||
description?: string,
|
||||
tags?: Array<string>,
|
||||
languages?: Array<string>,
|
||||
}
|
||||
|
|
29
flow-typed/CoinSwap.js
vendored
Normal file
29
flow-typed/CoinSwap.js
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
declare type CoinSwapInfo = {
|
||||
chargeCode: string,
|
||||
coins: Array<string>,
|
||||
sendAddresses: { [string]: string},
|
||||
sendAmounts: { [string]: any },
|
||||
lbcAmount: number,
|
||||
status?: {
|
||||
status: string,
|
||||
receiptCurrency: string,
|
||||
receiptTxid: string,
|
||||
lbcTxid: string,
|
||||
},
|
||||
}
|
||||
|
||||
declare type CoinSwapState = {
|
||||
coinSwaps: Array<CoinSwapInfo>,
|
||||
};
|
||||
|
||||
declare type CoinSwapAddAction = {
|
||||
type: string,
|
||||
data: CoinSwapInfo,
|
||||
};
|
||||
|
||||
declare type CoinSwapRemoveAction = {
|
||||
type: string,
|
||||
data: {
|
||||
chargeCode: string,
|
||||
},
|
||||
};
|
34
flow-typed/Collections.js
vendored
Normal file
34
flow-typed/Collections.js
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
declare type Collection = {
|
||||
id: string,
|
||||
items: Array<?string>,
|
||||
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<string>,
|
||||
isResolvingCollectionById: { [string]: boolean },
|
||||
error?: string | null,
|
||||
};
|
||||
|
||||
declare type CollectionGroup = {
|
||||
[string]: Collection,
|
||||
}
|
||||
|
||||
declare type CollectionEditParams = {
|
||||
claims?: Array<Claim>,
|
||||
remove?: boolean,
|
||||
claimIds?: Array<string>,
|
||||
replace?: boolean,
|
||||
order?: { from: number, to: number },
|
||||
type?: string,
|
||||
name?: string,
|
||||
}
|
23
flow-typed/Comment.js
vendored
23
flow-typed/Comment.js
vendored
|
@ -1,23 +0,0 @@
|
|||
declare type Comment = {
|
||||
comment: string, // comment body
|
||||
comment_id: string, // sha256 digest
|
||||
claim_id: string, // id linking to the claim this comment
|
||||
timestamp: number, // integer representing unix-time
|
||||
is_hidden: boolean, // claim owner may enable/disable this
|
||||
channel_id?: string, // claimId of channel signing this comment
|
||||
channel_name?: string, // name of channel claim
|
||||
channel_url?: string, // full lbry url to signing channel
|
||||
signature?: string, // signature of comment by originating channel
|
||||
signing_ts?: string, // timestamp used when signing this comment
|
||||
is_channel_signature_valid?: boolean, // whether or not the signature could be validated
|
||||
parent_id?: number, // comment_id of comment this is in reply to
|
||||
};
|
||||
|
||||
// todo: relate individual comments to their commentId
|
||||
declare type CommentsState = {
|
||||
commentsByUri: { [string]: string },
|
||||
byId: { [string]: Array<string> },
|
||||
commentById: { [string]: Comment },
|
||||
isLoading: boolean,
|
||||
myComments: ?Set<string>,
|
||||
};
|
80
flow-typed/Lbry.js
vendored
80
flow-typed/Lbry.js
vendored
|
@ -7,10 +7,6 @@ declare type StatusResponse = {
|
|||
download_progress: number,
|
||||
downloading_headers: boolean,
|
||||
},
|
||||
connection_status: {
|
||||
code: string,
|
||||
message: string,
|
||||
},
|
||||
dht: {
|
||||
node_id: string,
|
||||
peers_in_routing_table: number,
|
||||
|
@ -45,6 +41,7 @@ declare type StatusResponse = {
|
|||
redirects: {},
|
||||
},
|
||||
wallet: ?{
|
||||
connected: string,
|
||||
best_blockhash: string,
|
||||
blocks: number,
|
||||
blocks_behind: number,
|
||||
|
@ -78,7 +75,7 @@ declare type BalanceResponse = {
|
|||
|
||||
declare type ResolveResponse = {
|
||||
// Keys are the url(s) passed to resolve
|
||||
[string]: { error?: {}, stream?: StreamClaim, channel?: ChannelClaim, claimsInChannel?: number },
|
||||
[string]: { error?: {}, stream?: StreamClaim, channel?: ChannelClaim, collection?: CollectionClaim, claimsInChannel?: number },
|
||||
};
|
||||
|
||||
declare type GetResponse = FileListItem & { error?: string };
|
||||
|
@ -127,12 +124,22 @@ declare type ChannelUpdateResponse = GenericTxResponse & {
|
|||
declare type CommentCreateResponse = Comment;
|
||||
declare type CommentUpdateResponse = Comment;
|
||||
|
||||
declare type CommentListResponse = {
|
||||
items: Array<Comment>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
declare type MyReactions = {
|
||||
// Keys are the commentId
|
||||
[string]: Array<string>,
|
||||
};
|
||||
|
||||
declare type OthersReactions = {
|
||||
// Keys are the commentId
|
||||
[string]: {
|
||||
// Keys are the reaction_type, e.g. 'like'
|
||||
[string]: number,
|
||||
},
|
||||
};
|
||||
|
||||
declare type CommentReactListResponse = {
|
||||
my_reactions: Array<MyReactions>,
|
||||
others_reactions: Array<OthersReactions>,
|
||||
};
|
||||
|
||||
declare type CommentHideResponse = {
|
||||
|
@ -140,6 +147,11 @@ declare type CommentHideResponse = {
|
|||
[string]: { hidden: boolean },
|
||||
};
|
||||
|
||||
declare type CommentPinResponse = {
|
||||
// keyed by the CommentIds entered
|
||||
items: Comment,
|
||||
};
|
||||
|
||||
declare type CommentAbandonResponse = {
|
||||
// keyed by the CommentId given
|
||||
abandoned: boolean,
|
||||
|
@ -153,6 +165,42 @@ declare type ChannelListResponse = {
|
|||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type ChannelSignResponse = {
|
||||
signature: string,
|
||||
signing_ts: string,
|
||||
};
|
||||
|
||||
declare type CollectionCreateResponse = {
|
||||
outputs: Array<Claim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
}
|
||||
|
||||
declare type CollectionListResponse = {
|
||||
items: Array<Claim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type CollectionResolveResponse = {
|
||||
items: Array<Claim>,
|
||||
total_items: number,
|
||||
};
|
||||
|
||||
declare type CollectionResolveOptions = {
|
||||
claim_id: string,
|
||||
};
|
||||
|
||||
declare type CollectionListOptions = {
|
||||
page: number,
|
||||
page_size: number,
|
||||
resolve?: boolean,
|
||||
};
|
||||
|
||||
declare type FileListResponse = {
|
||||
items: Array<FileListItem>,
|
||||
page: number,
|
||||
|
@ -209,7 +257,7 @@ declare type StreamRepostOptions = {
|
|||
name: string,
|
||||
bid: string,
|
||||
claim_id: string,
|
||||
channel_id: string,
|
||||
channel_id?: string,
|
||||
};
|
||||
|
||||
declare type StreamRepostResponse = GenericTxResponse;
|
||||
|
@ -262,6 +310,7 @@ declare type LbryTypes = {
|
|||
channel_update: (params: {}) => Promise<ChannelUpdateResponse>,
|
||||
channel_import: (params: {}) => Promise<string>,
|
||||
channel_list: (params: {}) => Promise<ChannelListResponse>,
|
||||
channel_sign: (params: {}) => Promise<ChannelSignResponse>,
|
||||
stream_abandon: (params: {}) => Promise<GenericTxResponse>,
|
||||
stream_list: (params: {}) => Promise<StreamListResponse>,
|
||||
channel_abandon: (params: {}) => Promise<GenericTxResponse>,
|
||||
|
@ -270,6 +319,10 @@ declare type LbryTypes = {
|
|||
support_abandon: (params: {}) => Promise<SupportAbandonResponse>,
|
||||
stream_repost: (params: StreamRepostOptions) => Promise<StreamRepostResponse>,
|
||||
purchase_list: (params: PurchaseListOptions) => Promise<PurchaseListResponse>,
|
||||
collection_resolve: (params: CollectionResolveOptions) => Promise<CollectionResolveResponse>,
|
||||
collection_list: (params: CollectionListOptions) => Promise<CollectionListResponse>,
|
||||
collection_create: (params: {}) => Promise<CollectionCreateResponse>,
|
||||
collection_update: (params: {}) => Promise<CollectionCreateResponse>,
|
||||
|
||||
// File fetching and manipulation
|
||||
file_list: (params: {}) => Promise<FileListResponse>,
|
||||
|
@ -282,8 +335,6 @@ declare type LbryTypes = {
|
|||
preference_set: (params: {}) => Promise<any>,
|
||||
|
||||
// Commenting
|
||||
comment_list: (params: {}) => Promise<CommentListResponse>,
|
||||
comment_create: (params: {}) => Promise<CommentCreateResponse>,
|
||||
comment_update: (params: {}) => Promise<CommentUpdateResponse>,
|
||||
comment_hide: (params: {}) => Promise<CommentHideResponse>,
|
||||
comment_abandon: (params: {}) => Promise<CommentAbandonResponse>,
|
||||
|
@ -300,6 +351,7 @@ declare type LbryTypes = {
|
|||
address_unused: (params: {}) => Promise<string>, // New address
|
||||
address_list: (params: {}) => Promise<string>,
|
||||
transaction_list: (params: {}) => Promise<TxListResponse>,
|
||||
txo_list: (params: {}) => Promise<any>,
|
||||
|
||||
// Sync
|
||||
sync_hash: (params: {}) => Promise<string>,
|
||||
|
|
4
flow-typed/LbryFirst.js
vendored
4
flow-typed/LbryFirst.js
vendored
|
@ -1,12 +1,12 @@
|
|||
// @flow
|
||||
declare type StatusResponse = {
|
||||
declare type LbryFirstStatusResponse = {
|
||||
Version: string,
|
||||
Message: string,
|
||||
Running: boolean,
|
||||
Commit: string,
|
||||
};
|
||||
|
||||
declare type VersionResponse = {
|
||||
declare type LbryFirstVersionResponse = {
|
||||
build: string,
|
||||
lbrynet_version: string,
|
||||
os_release: string,
|
||||
|
|
84
flow-typed/Search.js
vendored
84
flow-typed/Search.js
vendored
|
@ -1,84 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
declare type SearchSuggestion = {
|
||||
value: string,
|
||||
shorthand: string,
|
||||
type: string,
|
||||
};
|
||||
|
||||
declare type SearchOptions = {
|
||||
// :(
|
||||
// https://github.com/facebook/flow/issues/6492
|
||||
RESULT_COUNT: number,
|
||||
CLAIM_TYPE: string,
|
||||
INCLUDE_FILES: string,
|
||||
INCLUDE_CHANNELS: string,
|
||||
INCLUDE_FILES_AND_CHANNELS: string,
|
||||
MEDIA_AUDIO: string,
|
||||
MEDIA_VIDEO: string,
|
||||
MEDIA_TEXT: string,
|
||||
MEDIA_IMAGE: string,
|
||||
MEDIA_APPLICATION: string,
|
||||
};
|
||||
|
||||
declare type SearchState = {
|
||||
isActive: boolean,
|
||||
searchQuery: string,
|
||||
options: SearchOptions,
|
||||
suggestions: { [string]: Array<SearchSuggestion> },
|
||||
urisByQuery: {},
|
||||
resolvedResultsByQuery: {},
|
||||
resolvedResultsByQueryLastPageReached: {},
|
||||
};
|
||||
|
||||
declare type SearchSuccess = {
|
||||
type: ACTIONS.SEARCH_SUCCESS,
|
||||
data: {
|
||||
query: string,
|
||||
uris: Array<string>,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchQuery = {
|
||||
type: ACTIONS.UPDATE_SEARCH_QUERY,
|
||||
data: {
|
||||
query: string,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchSuggestions = {
|
||||
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
|
||||
data: {
|
||||
query: string,
|
||||
suggestions: Array<SearchSuggestion>,
|
||||
},
|
||||
};
|
||||
|
||||
declare type UpdateSearchOptions = {
|
||||
type: ACTIONS.UPDATE_SEARCH_OPTIONS,
|
||||
data: SearchOptions,
|
||||
};
|
||||
|
||||
declare type ResolvedSearchResult = {
|
||||
channel: string,
|
||||
channel_claim_id: string,
|
||||
claimId: string,
|
||||
duration: number,
|
||||
fee: number,
|
||||
name: string,
|
||||
nsfw: boolean,
|
||||
release_time: string,
|
||||
thumbnail_url: string,
|
||||
title: string,
|
||||
};
|
||||
|
||||
declare type ResolvedSearchSuccess = {
|
||||
type: ACTIONS.RESOLVED_SEARCH_SUCCESS,
|
||||
data: {
|
||||
append: boolean,
|
||||
pageSize: number,
|
||||
results: Array<ResolvedSearchResult>,
|
||||
query: string,
|
||||
},
|
||||
};
|
5
flow-typed/Txo.js
vendored
5
flow-typed/Txo.js
vendored
|
@ -10,6 +10,9 @@ declare type Txo = {
|
|||
is_my_output: boolean,
|
||||
is_my_input: boolean,
|
||||
is_spent: boolean,
|
||||
signing_channel?: {
|
||||
channel_id: string,
|
||||
},
|
||||
};
|
||||
|
||||
declare type TxoListParams = {
|
||||
|
@ -21,4 +24,4 @@ declare type TxoListParams = {
|
|||
is_not_my_input?: boolean,
|
||||
is_not_my_output?: boolean,
|
||||
is_spent?: boolean,
|
||||
}
|
||||
};
|
||||
|
|
5
flow-typed/npm/from-entries.js
vendored
Normal file
5
flow-typed/npm/from-entries.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
// @flow
|
||||
|
||||
declare module '@ungap/from-entries' {
|
||||
declare module.exports: any;
|
||||
}
|
5
flow-typed/npm/uuid.js
vendored
Normal file
5
flow-typed/npm/uuid.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
// @flow
|
||||
|
||||
declare module 'uuid' {
|
||||
declare module.exports: any;
|
||||
}
|
102
flow-typed/npm/uuid_v3.x.x.js
vendored
102
flow-typed/npm/uuid_v3.x.x.js
vendored
|
@ -1,102 +0,0 @@
|
|||
// flow-typed signature: 3cf668e64747095cab0bb360cf2fb34f
|
||||
// flow-typed version: d659bd0cb8/uuid_v3.x.x/flow_>=v0.32.x
|
||||
|
||||
declare module "uuid" {
|
||||
declare class uuid {
|
||||
static (
|
||||
options?: {|
|
||||
random?: number[],
|
||||
rng?: () => number[] | Buffer
|
||||
|},
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string,
|
||||
|
||||
static v1(
|
||||
options?: {|
|
||||
node?: number[],
|
||||
clockseq?: number,
|
||||
msecs?: number | Date,
|
||||
nsecs?: number
|
||||
|},
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string,
|
||||
|
||||
static v4(
|
||||
options?: {|
|
||||
random?: number[],
|
||||
rng?: () => number[] | Buffer
|
||||
|},
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string
|
||||
}
|
||||
declare module.exports: Class<uuid>;
|
||||
}
|
||||
|
||||
declare module "uuid/v1" {
|
||||
declare class v1 {
|
||||
static (
|
||||
options?: {|
|
||||
node?: number[],
|
||||
clockseq?: number,
|
||||
msecs?: number | Date,
|
||||
nsecs?: number
|
||||
|},
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string
|
||||
}
|
||||
|
||||
declare module.exports: Class<v1>;
|
||||
}
|
||||
|
||||
declare module "uuid/v3" {
|
||||
declare class v3 {
|
||||
static (
|
||||
name?: string | number[],
|
||||
namespace?: string | number[],
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string,
|
||||
|
||||
static name: string,
|
||||
static DNS: string,
|
||||
static URL: string
|
||||
}
|
||||
|
||||
declare module.exports: Class<v3>;
|
||||
}
|
||||
|
||||
declare module "uuid/v4" {
|
||||
declare class v4 {
|
||||
static (
|
||||
options?: {|
|
||||
random?: number[],
|
||||
rng?: () => number[] | Buffer
|
||||
|},
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string
|
||||
}
|
||||
|
||||
declare module.exports: Class<v4>;
|
||||
}
|
||||
|
||||
declare module "uuid/v5" {
|
||||
declare class v5 {
|
||||
static (
|
||||
name?: string | number[],
|
||||
namespace?: string | number[],
|
||||
buffer?: number[] | Buffer,
|
||||
offset?: number
|
||||
): string,
|
||||
|
||||
static name: string,
|
||||
static DNS: string,
|
||||
static URL: string
|
||||
}
|
||||
|
||||
declare module.exports: Class<v5>;
|
||||
}
|
8
jest.config.js
Normal file
8
jest.config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
collectCoverageFrom: ["src/**/*.{js,jsx,mjs}"],
|
||||
testMatch: ["<rootDir>/tests/**/*.test.js"],
|
||||
transform: {
|
||||
"^.+\\.(js|jsx|mjs)$": "<rootDir>/tests/config/jest-transformer.js",
|
||||
},
|
||||
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$"]
|
||||
};
|
12
package.json
12
package.json
|
@ -25,14 +25,21 @@
|
|||
"dev": "rollup --config --watch",
|
||||
"precommit": "flow check && lint-staged",
|
||||
"lint": "eslint 'src/**/*.js' --fix",
|
||||
"format": "prettier 'src/**/*.{js,json}' --write"
|
||||
"format": "prettier 'src/**/*.{js,json}' --write",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ungap/from-entries": "^0.2.1",
|
||||
"proxy-polyfill": "0.1.6",
|
||||
"reselect": "^3.0.0",
|
||||
"uuid": "^3.3.2"
|
||||
"uuid": "^8.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.10.5",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.10.4",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-eslint": "^8.0.3",
|
||||
"babel-loader": "^7.1.4",
|
||||
|
@ -53,6 +60,7 @@
|
|||
"flow-bin": "^0.97.0",
|
||||
"flow-typed": "^2.5.1",
|
||||
"husky": "^0.14.3",
|
||||
"jest": "^26.4.2",
|
||||
"lint-staged": "^7.0.4",
|
||||
"prettier": "^1.4.2",
|
||||
"rollup": "^1.8.0",
|
||||
|
|
|
@ -79,6 +79,16 @@ export const SET_TRANSACTION_LIST_FILTER = 'SET_TRANSACTION_LIST_FILTER';
|
|||
export const UPDATE_CURRENT_HEIGHT = 'UPDATE_CURRENT_HEIGHT';
|
||||
export const SET_DRAFT_TRANSACTION_AMOUNT = 'SET_DRAFT_TRANSACTION_AMOUNT';
|
||||
export const SET_DRAFT_TRANSACTION_ADDRESS = 'SET_DRAFT_TRANSACTION_ADDRESS';
|
||||
export const FETCH_UTXO_COUNT_STARTED = 'FETCH_UTXO_COUNT_STARTED';
|
||||
export const FETCH_UTXO_COUNT_COMPLETED = 'FETCH_UTXO_COUNT_COMPLETED';
|
||||
export const FETCH_UTXO_COUNT_FAILED = 'FETCH_UTXO_COUNT_FAILED';
|
||||
export const TIP_CLAIM_MASS_STARTED = 'TIP_CLAIM_MASS_STARTED';
|
||||
export const TIP_CLAIM_MASS_COMPLETED = 'TIP_CLAIM_MASS_COMPLETED';
|
||||
export const TIP_CLAIM_MASS_FAILED = 'TIP_CLAIM_MASS_FAILED';
|
||||
export const DO_UTXO_CONSOLIDATE_STARTED = 'DO_UTXO_CONSOLIDATE_STARTED';
|
||||
export const DO_UTXO_CONSOLIDATE_COMPLETED = 'DO_UTXO_CONSOLIDATE_COMPLETED';
|
||||
export const DO_UTXO_CONSOLIDATE_FAILED = 'DO_UTXO_CONSOLIDATE_FAILED';
|
||||
export const PENDING_CONSOLIDATED_TXOS_UPDATED = 'PENDING_CONSOLIDATED_TXOS_UPDATED';
|
||||
|
||||
// Claims
|
||||
export const RESOLVE_URIS_STARTED = 'RESOLVE_URIS_STARTED';
|
||||
|
@ -92,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';
|
||||
|
@ -101,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';
|
||||
|
@ -119,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';
|
||||
|
@ -132,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';
|
||||
|
@ -172,19 +206,6 @@ export const PURCHASE_URI_COMPLETED = 'PURCHASE_URI_COMPLETED';
|
|||
export const PURCHASE_URI_FAILED = 'PURCHASE_URI_FAILED';
|
||||
export const CLEAR_PURCHASED_URI_SUCCESS = 'CLEAR_PURCHASED_URI_SUCCESS';
|
||||
|
||||
// Search
|
||||
export const SEARCH_START = 'SEARCH_START';
|
||||
export const SEARCH_SUCCESS = 'SEARCH_SUCCESS';
|
||||
export const SEARCH_FAIL = 'SEARCH_FAIL';
|
||||
export const RESOLVED_SEARCH_START = 'RESOLVED_SEARCH_START';
|
||||
export const RESOLVED_SEARCH_SUCCESS = 'RESOLVED_SEARCH_SUCCESS';
|
||||
export const RESOLVED_SEARCH_FAIL = 'RESOLVED_SEARCH_FAIL';
|
||||
export const UPDATE_SEARCH_QUERY = 'UPDATE_SEARCH_QUERY';
|
||||
export const UPDATE_SEARCH_OPTIONS = 'UPDATE_SEARCH_OPTIONS';
|
||||
export const UPDATE_SEARCH_SUGGESTIONS = 'UPDATE_SEARCH_SUGGESTIONS';
|
||||
export const SEARCH_FOCUS = 'SEARCH_FOCUS';
|
||||
export const SEARCH_BLUR = 'SEARCH_BLUR';
|
||||
|
||||
// Settings
|
||||
export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED';
|
||||
export const DAEMON_STATUS_RECEIVED = 'DAEMON_STATUS_RECEIVED';
|
||||
|
@ -279,13 +300,6 @@ export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED';
|
|||
export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED';
|
||||
export const FETCH_COST_INFO_FAILED = 'FETCH_COST_INFO_FAILED';
|
||||
|
||||
// Tags
|
||||
export const TOGGLE_TAG_FOLLOW = 'TOGGLE_TAG_FOLLOW';
|
||||
export const TAG_ADD = 'TAG_ADD';
|
||||
export const TAG_DELETE = 'TAG_DELETE';
|
||||
|
||||
// Blocked Channels
|
||||
export const TOGGLE_BLOCK_CHANNEL = 'TOGGLE_BLOCK_CHANNEL';
|
||||
|
||||
// Sync
|
||||
export const USER_STATE_POPULATE = 'USER_STATE_POPULATE';
|
||||
export const SYNC_FATAL_ERROR = 'SYNC_FATAL_ERROR';
|
||||
|
|
|
@ -3,3 +3,9 @@ export const MINIMUM_PUBLISH_BID = 0.00000001;
|
|||
export const CHANNEL_ANONYMOUS = 'anonymous';
|
||||
export const CHANNEL_NEW = 'new';
|
||||
export const PAGE_SIZE = 20;
|
||||
|
||||
export const LEVEL_1_STAKED_AMOUNT = 0;
|
||||
export const LEVEL_2_STAKED_AMOUNT = 1;
|
||||
export const LEVEL_3_STAKED_AMOUNT = 50;
|
||||
export const LEVEL_4_STAKED_AMOUNT = 250;
|
||||
export const LEVEL_5_STAKED_AMOUNT = 1000;
|
||||
|
|
15
src/constants/collections.js
Normal file
15
src/constants/collections.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
export const COLLECTION_ID = 'lid';
|
||||
export const COLLECTION_INDEX = 'linx';
|
||||
|
||||
export const COL_TYPE_PLAYLIST = 'playlist';
|
||||
export const COL_TYPE_CHANNELS = 'channelList';
|
||||
|
||||
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];
|
||||
|
||||
export const COL_KEY_EDITED = 'edited';
|
||||
export const COL_KEY_UNPUBLISHED = 'unpublished';
|
||||
export const COL_KEY_PENDING = 'pending';
|
||||
export const COL_KEY_SAVED = 'saved';
|
|
@ -1,19 +0,0 @@
|
|||
export const SEARCH_TYPES = {
|
||||
FILE: 'file',
|
||||
CHANNEL: 'channel',
|
||||
SEARCH: 'search',
|
||||
TAG: 'tag',
|
||||
};
|
||||
|
||||
export const SEARCH_OPTIONS = {
|
||||
RESULT_COUNT: 'size',
|
||||
CLAIM_TYPE: 'claimType',
|
||||
INCLUDE_FILES: 'file',
|
||||
INCLUDE_CHANNELS: 'channel',
|
||||
INCLUDE_FILES_AND_CHANNELS: 'file,channel',
|
||||
MEDIA_AUDIO: 'audio',
|
||||
MEDIA_VIDEO: 'video',
|
||||
MEDIA_TEXT: 'text',
|
||||
MEDIA_IMAGE: 'image',
|
||||
MEDIA_APPLICATION: 'application',
|
||||
};
|
|
@ -6,9 +6,15 @@ export const SHOW_NSFW = 'showNsfw';
|
|||
export const CREDIT_REQUIRED_ACKNOWLEDGED = 'credit_required_acknowledged';
|
||||
export const NEW_USER_ACKNOWLEDGED = 'welcome_acknowledged';
|
||||
export const EMAIL_COLLECTION_ACKNOWLEDGED = 'email_collection_acknowledged';
|
||||
export const FIRST_RUN_STARTED = 'first_run_started';
|
||||
export const INVITE_ACKNOWLEDGED = 'invite_acknowledged';
|
||||
export const FOLLOWING_ACKNOWLEDGED = 'following_acknowledged';
|
||||
export const TAGS_ACKNOWLEDGED = 'tags_acknowledged';
|
||||
export const REWARDS_ACKNOWLEDGED = 'rewards_acknowledged';
|
||||
export const LANGUAGE = 'language';
|
||||
export const SEARCH_IN_LANGUAGE = 'search_in_language';
|
||||
export const SHOW_MATURE = 'show_mature';
|
||||
export const HOMEPAGE = 'homepage';
|
||||
export const HIDE_REPOSTS = 'hide_reposts';
|
||||
export const SHOW_ANONYMOUS = 'show_anonymous';
|
||||
export const SHOW_UNAVAILABLE = 'show_unavailable';
|
||||
|
@ -17,16 +23,24 @@ export const INSTANT_PURCHASE_MAX = 'instant_purchase_max';
|
|||
export const THEME = 'theme';
|
||||
export const THEMES = 'themes';
|
||||
export const AUTOMATIC_DARK_MODE_ENABLED = 'automatic_dark_mode_enabled';
|
||||
export const AUTOPLAY = 'autoplay';
|
||||
export const AUTOPLAY_MEDIA = 'autoplay';
|
||||
export const AUTOPLAY_NEXT = 'autoplay_next';
|
||||
export const OS_NOTIFICATIONS_ENABLED = 'os_notifications_enabled';
|
||||
export const AUTO_DOWNLOAD = 'auto_download';
|
||||
export const AUTO_LAUNCH = 'auto_launch';
|
||||
export const TO_TRAY_WHEN_CLOSED = 'to_tray_when_closed';
|
||||
export const SUPPORT_OPTION = 'support_option';
|
||||
export const HIDE_BALANCE = 'hide_balance';
|
||||
export const HIDE_SPLASH_ANIMATION = 'hide_splash_animation';
|
||||
export const FLOATING_PLAYER = 'floating_player';
|
||||
export const DARK_MODE_TIMES = 'dark_mode_times';
|
||||
export const ENABLE_SYNC = 'enable_sync';
|
||||
export const ENABLE_PUBLISH_PREVIEW = 'enable-publish-preview';
|
||||
export const TILE_LAYOUT = 'tile_layout';
|
||||
export const VIDEO_THEATER_MODE = 'video_theater_mode';
|
||||
export const VIDEO_PLAYBACK_RATE = 'video_playback_rate';
|
||||
export const CUSTOM_COMMENTS_SERVER_ENABLED = 'custom_comments_server_enabled';
|
||||
export const CUSTOM_COMMENTS_SERVER_URL = 'custom_comments_server_url';
|
||||
|
||||
// mobile settings
|
||||
export const BACKGROUND_PLAY_ENABLED = 'backgroundPlayEnabled';
|
||||
|
|
|
@ -8,6 +8,25 @@
|
|||
*/
|
||||
|
||||
import * as DAEMON_SETTINGS from './daemon_settings';
|
||||
import * as SETTINGS from './settings';
|
||||
|
||||
export const WALLET_SERVERS = DAEMON_SETTINGS.LBRYUM_SERVERS;
|
||||
export const SHARE_USAGE_DATA = DAEMON_SETTINGS.SHARE_USAGE_DATA;
|
||||
// DAEMON
|
||||
export const SDK_SYNC_KEYS = [DAEMON_SETTINGS.LBRYUM_SERVERS, DAEMON_SETTINGS.SHARE_USAGE_DATA];
|
||||
|
||||
// CLIENT
|
||||
export const CLIENT_SYNC_KEYS = [
|
||||
SETTINGS.SHOW_MATURE,
|
||||
SETTINGS.HIDE_REPOSTS,
|
||||
SETTINGS.SHOW_ANONYMOUS,
|
||||
SETTINGS.INSTANT_PURCHASE_ENABLED,
|
||||
SETTINGS.INSTANT_PURCHASE_MAX,
|
||||
SETTINGS.THEME,
|
||||
SETTINGS.AUTOPLAY_MEDIA,
|
||||
SETTINGS.AUTOPLAY_NEXT,
|
||||
SETTINGS.HIDE_BALANCE,
|
||||
SETTINGS.HIDE_SPLASH_ANIMATION,
|
||||
SETTINGS.FLOATING_PLAYER,
|
||||
SETTINGS.DARK_MODE_TIMES,
|
||||
SETTINGS.AUTOMATIC_DARK_MODE_ENABLED,
|
||||
SETTINGS.LANGUAGE,
|
||||
];
|
||||
|
|
|
@ -520,6 +520,8 @@ const DEFAULT_ENGLISH_KNOWN_TAGS = [
|
|||
'2020protests',
|
||||
'covidcuts',
|
||||
'covid-19',
|
||||
'LBRYFoundationBoardCandidacy',
|
||||
'helplbrysavecrypto'
|
||||
];
|
||||
|
||||
const DEFAULT_SPANISH_KNOWN_TAGS = [
|
||||
|
|
130
src/index.js
130
src/index.js
|
@ -12,11 +12,10 @@ 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 { SEARCH_TYPES, SEARCH_OPTIONS } from 'constants/search';
|
||||
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';
|
||||
import { selectState as selectSearchState } from 'redux/selectors/search';
|
||||
|
||||
// constants
|
||||
export {
|
||||
|
@ -24,8 +23,6 @@ export {
|
|||
CLAIM_VALUES,
|
||||
LICENSES,
|
||||
THUMBNAIL_STATUSES,
|
||||
SEARCH_TYPES,
|
||||
SEARCH_OPTIONS,
|
||||
SETTINGS,
|
||||
DAEMON_SETTINGS,
|
||||
TRANSACTIONS,
|
||||
|
@ -39,6 +36,7 @@ export {
|
|||
MATURE_TAGS,
|
||||
SPEECH_URLS,
|
||||
SHARED_PREFERENCES,
|
||||
COLLECTIONS_CONSTS,
|
||||
};
|
||||
|
||||
// common
|
||||
|
@ -54,6 +52,8 @@ export {
|
|||
isURIClaimable,
|
||||
isNameValid,
|
||||
convertToShareLink,
|
||||
splitBySeparator,
|
||||
isURIEqual,
|
||||
} from 'lbryURI';
|
||||
|
||||
// middlware
|
||||
|
@ -61,6 +61,13 @@ export { buildSharedStateMiddleware } from 'redux/middleware/shared-state';
|
|||
|
||||
// actions
|
||||
export { doToast, doDismissToast, doError, doDismissError } from 'redux/actions/notifications';
|
||||
export {
|
||||
doLocalCollectionCreate,
|
||||
doFetchItemsInCollection,
|
||||
doFetchItemsInCollections,
|
||||
doCollectionEdit,
|
||||
doCollectionDelete,
|
||||
} from 'redux/actions/collections';
|
||||
|
||||
export {
|
||||
doFetchClaimsByChannel,
|
||||
|
@ -70,6 +77,7 @@ export {
|
|||
doResolveUris,
|
||||
doResolveUri,
|
||||
doFetchChannelListMine,
|
||||
doFetchCollectionListMine,
|
||||
doCreateChannel,
|
||||
doUpdateChannel,
|
||||
doClaimSearch,
|
||||
|
@ -80,6 +88,8 @@ export {
|
|||
doCheckPublishNameAvailability,
|
||||
doPurchaseList,
|
||||
doCheckPendingClaims,
|
||||
doCollectionPublish,
|
||||
doCollectionPublishUpdate,
|
||||
} from 'redux/actions/claims';
|
||||
|
||||
export { doClearPurchasedUriSuccess, doPurchaseUri, doFileGet } from 'redux/actions/file';
|
||||
|
@ -101,16 +111,6 @@ export {
|
|||
doCheckReflectingFiles,
|
||||
} from 'redux/actions/publish';
|
||||
|
||||
export {
|
||||
doSearch,
|
||||
doResolvedSearch,
|
||||
doUpdateSearchQuery,
|
||||
doFocusSearchInput,
|
||||
doBlurSearchInput,
|
||||
setSearchApi,
|
||||
doUpdateSearchOptions,
|
||||
} from 'redux/actions/search';
|
||||
|
||||
export { savePosition } from 'redux/actions/content';
|
||||
|
||||
export {
|
||||
|
@ -134,12 +134,11 @@ export {
|
|||
doUpdateBlockHeight,
|
||||
doClearSupport,
|
||||
doSupportAbandonForClaim,
|
||||
doFetchUtxoCounts,
|
||||
doUtxoConsolidate,
|
||||
doTipClaimMass,
|
||||
} from 'redux/actions/wallet';
|
||||
|
||||
export { doToggleTagFollow, doAddTag, doDeleteTag } from 'redux/actions/tags';
|
||||
|
||||
export { doToggleBlockChannel } from 'redux/actions/blocked';
|
||||
|
||||
export { doPopulateSharedUserState, doPreferenceGet, doPreferenceSet } from 'redux/actions/sync';
|
||||
|
||||
// utils
|
||||
|
@ -154,15 +153,40 @@ export { contentReducer } from 'redux/reducers/content';
|
|||
export { fileInfoReducer } from 'redux/reducers/file_info';
|
||||
export { notificationsReducer } from 'redux/reducers/notifications';
|
||||
export { publishReducer } from 'redux/reducers/publish';
|
||||
export { searchReducer } from 'redux/reducers/search';
|
||||
export { tagsReducer } from 'redux/reducers/tags';
|
||||
export { blockedReducer } from 'redux/reducers/blocked';
|
||||
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,
|
||||
makeSelectClaimUrlInCollection,
|
||||
makeSelectUrlsForCollectionId,
|
||||
makeSelectClaimIdsForCollectionId,
|
||||
makeSelectNameForCollectionId,
|
||||
makeSelectCountForCollectionId,
|
||||
makeSelectIsResolvingCollectionForId,
|
||||
makeSelectIndexForUrlInCollection,
|
||||
makeSelectPreviousUrlForCollectionAndUrl,
|
||||
makeSelectNextUrlForCollectionAndUrl,
|
||||
makeSelectCollectionForIdHasClaimUrl,
|
||||
} from 'redux/selectors/collections';
|
||||
|
||||
export {
|
||||
makeSelectClaimForUri,
|
||||
|
@ -178,29 +202,37 @@ export {
|
|||
makeSelectTitleForUri,
|
||||
makeSelectDateForUri,
|
||||
makeSelectAmountForUri,
|
||||
makeSelectEffectiveAmountForUri,
|
||||
makeSelectTagsForUri,
|
||||
makeSelectTagInClaimOrChannelForUri,
|
||||
makeSelectTotalStakedAmountForChannelUri,
|
||||
makeSelectStakedLevelForChannelUri,
|
||||
makeSelectContentTypeForUri,
|
||||
makeSelectIsUriResolving,
|
||||
makeSelectPendingClaimForUri,
|
||||
makeSelectTotalItemsForChannel,
|
||||
makeSelectTotalPagesForChannel,
|
||||
makeSelectNsfwCountFromUris,
|
||||
makeSelectNsfwCountForChannel,
|
||||
makeSelectOmittedCountForChannel,
|
||||
makeSelectClaimIsNsfw,
|
||||
makeSelectRecommendedContentForUri,
|
||||
makeSelectResolvedRecommendedContentForUri,
|
||||
makeSelectFirstRecommendedFileForUri,
|
||||
makeSelectChannelForClaimUri,
|
||||
makeSelectChannelPermUrlForClaimUri,
|
||||
makeSelectMyChannelPermUrlForName,
|
||||
makeSelectClaimIsPending,
|
||||
makeSelectReflectingClaimForUri,
|
||||
makeSelectClaimsInChannelForCurrentPageState,
|
||||
makeSelectShortUrlForUri,
|
||||
makeSelectCanonicalUrlForUri,
|
||||
makeSelectPermanentUrlForUri,
|
||||
makeSelectSupportsForUri,
|
||||
makeSelectMyPurchasesForPage,
|
||||
makeSelectClaimWasPurchased,
|
||||
makeSelectAbandoningClaimById,
|
||||
makeSelectIsAbandoningClaimForUri,
|
||||
makeSelectClaimHasSource,
|
||||
makeSelectClaimIsStreamPlaceholder,
|
||||
selectPendingIds,
|
||||
selectReflectingById,
|
||||
makeSelectClaimForClaimId,
|
||||
selectClaimsById,
|
||||
selectClaimsByUri,
|
||||
selectAllClaimsByChannel,
|
||||
|
@ -210,12 +242,15 @@ export {
|
|||
selectAllFetchingChannelClaims,
|
||||
selectIsFetchingClaimListMine,
|
||||
selectMyClaims,
|
||||
selectPendingClaims,
|
||||
selectMyClaimsWithoutChannels,
|
||||
selectMyChannelUrls,
|
||||
selectMyClaimUrisWithoutChannels,
|
||||
selectAllMyClaimsByOutpoint,
|
||||
selectMyClaimsOutpoints,
|
||||
selectFetchingMyChannels,
|
||||
selectFetchingMyCollections,
|
||||
selectMyCollectionIds,
|
||||
selectMyChannelClaims,
|
||||
selectResolvingUris,
|
||||
selectPlayingUri,
|
||||
|
@ -244,6 +279,12 @@ export {
|
|||
selectFetchingMyPurchasesError,
|
||||
selectMyPurchasesCount,
|
||||
selectPurchaseUriSuccess,
|
||||
makeSelectClaimIdForUri,
|
||||
selectUpdatingCollection,
|
||||
selectUpdateCollectionError,
|
||||
selectCreatingCollection,
|
||||
selectCreateCollectionError,
|
||||
makeSelectClaimIdIsPending,
|
||||
} from 'redux/selectors/claims';
|
||||
|
||||
export {
|
||||
|
@ -281,22 +322,6 @@ export {
|
|||
selectTakeOverAmount,
|
||||
} from 'redux/selectors/publish';
|
||||
|
||||
export { selectSearchState };
|
||||
export {
|
||||
makeSelectSearchUris,
|
||||
makeSelectResolvedSearchResults,
|
||||
makeSelectResolvedSearchResultsLastPageReached,
|
||||
selectSearchValue,
|
||||
selectSearchOptions,
|
||||
selectIsSearching,
|
||||
selectResolvedSearchResultsByQuery,
|
||||
selectResolvedSearchResultsByQueryLastPageReached,
|
||||
selectSearchUrisByQuery,
|
||||
selectSearchBarFocused,
|
||||
selectSearchSuggestions,
|
||||
makeSelectQueryWithOptions,
|
||||
} from 'redux/selectors/search';
|
||||
|
||||
export {
|
||||
selectBalance,
|
||||
selectTotalBalance,
|
||||
|
@ -308,6 +333,7 @@ export {
|
|||
selectSupportsByOutpoint,
|
||||
selectTotalSupports,
|
||||
selectTransactionItems,
|
||||
selectTransactionsFile,
|
||||
selectRecentTransactions,
|
||||
selectHasTransactions,
|
||||
selectIsFetchingTransactions,
|
||||
|
@ -345,17 +371,11 @@ export {
|
|||
selectPendingSupportTransactions,
|
||||
selectAbandonClaimSupportError,
|
||||
makeSelectPendingAmountByUri,
|
||||
selectIsFetchingUtxoCounts,
|
||||
selectIsConsolidatingUtxos,
|
||||
selectIsMassClaimingTips,
|
||||
selectUtxoCounts,
|
||||
selectPendingOtherTransactions,
|
||||
selectPendingConsolidateTxid,
|
||||
selectPendingMassClaimTxid,
|
||||
} from 'redux/selectors/wallet';
|
||||
|
||||
export {
|
||||
selectFollowedTags,
|
||||
selectFollowedTagsList,
|
||||
selectUnfollowedTags,
|
||||
makeSelectIsFollowingTag,
|
||||
} from 'redux/selectors/tags';
|
||||
|
||||
export {
|
||||
selectBlockedChannels,
|
||||
selectChannelIsBlocked,
|
||||
selectBlockedChannelsCount,
|
||||
} from 'redux/selectors/blocked';
|
||||
|
|
|
@ -86,9 +86,14 @@ const Lbry: LbryTypes = {
|
|||
stream_abandon: params => daemonCallWithResult('stream_abandon', params),
|
||||
stream_list: params => daemonCallWithResult('stream_list', params),
|
||||
channel_abandon: params => daemonCallWithResult('channel_abandon', params),
|
||||
channel_sign: params => daemonCallWithResult('channel_sign', params),
|
||||
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),
|
||||
|
@ -112,6 +117,7 @@ const Lbry: LbryTypes = {
|
|||
utxo_release: (params = {}) => daemonCallWithResult('utxo_release', params),
|
||||
support_abandon: (params = {}) => daemonCallWithResult('support_abandon', params),
|
||||
purchase_list: (params = {}) => daemonCallWithResult('purchase_list', params),
|
||||
txo_list: (params = {}) => daemonCallWithResult('txo_list', params),
|
||||
|
||||
sync_hash: (params = {}) => daemonCallWithResult('sync_hash', params),
|
||||
sync_apply: (params = {}) => daemonCallWithResult('sync_apply', params),
|
||||
|
|
|
@ -4,7 +4,7 @@ const channelNameMinLength = 1;
|
|||
const claimIdMaxLength = 40;
|
||||
|
||||
// see https://spec.lbry.com/#urls
|
||||
export const regexInvalidURI = /[ =&#:$@%?;/\\"<>%\{\}|^~[\]`\u{0000}-\u{0008}\u{000b}-\u{000c}\u{000e}-\u{001F}\u{D800}-\u{DFFF}\u{FFFE}-\u{FFFF}]/u;
|
||||
export const regexInvalidURI = /[ =&#:$@%?;/\\"<>%{}|^~[\]`\u{0000}-\u{0008}\u{000b}-\u{000c}\u{000e}-\u{001F}\u{D800}-\u{DFFF}\u{FFFE}-\u{FFFF}]/u;
|
||||
export const regexAddress = /^(b|r)(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/;
|
||||
const regexPartProtocol = '^((?:lbry://)?)';
|
||||
const regexPartStreamOrChannelName = '([^:$#/]*)';
|
||||
|
@ -12,6 +12,11 @@ const regexPartModifierSeparator = '([:$#]?)([^/]*)';
|
|||
const queryStringBreaker = '^([\\S]+)([?][\\S]*)';
|
||||
const separateQuerystring = new RegExp(queryStringBreaker);
|
||||
|
||||
const MOD_SEQUENCE_SEPARATOR = '*';
|
||||
const MOD_CLAIM_ID_SEPARATOR_OLD = '#';
|
||||
const MOD_CLAIM_ID_SEPARATOR = ':';
|
||||
const MOD_BID_POSITION_SEPARATOR = '$';
|
||||
|
||||
/**
|
||||
* Parses a LBRY name into its component parts. Throws errors with user-friendly
|
||||
* messages for invalid names.
|
||||
|
@ -29,7 +34,7 @@ const separateQuerystring = new RegExp(queryStringBreaker);
|
|||
* - secondaryBidPosition (int, if present)
|
||||
*/
|
||||
|
||||
export function parseURI(URL: string, requireProto: boolean = false): LbryUrlObj {
|
||||
export function parseURI(url: string, requireProto: boolean = false): LbryUrlObj {
|
||||
// Break into components. Empty sub-matches are converted to null
|
||||
|
||||
const componentsRegex = new RegExp(
|
||||
|
@ -42,12 +47,12 @@ export function parseURI(URL: string, requireProto: boolean = false): LbryUrlObj
|
|||
);
|
||||
// chop off the querystring first
|
||||
let QSStrippedURL, qs;
|
||||
const qsRegexResult = separateQuerystring.exec(URL);
|
||||
const qsRegexResult = separateQuerystring.exec(url);
|
||||
if (qsRegexResult) {
|
||||
[QSStrippedURL, qs] = qsRegexResult.slice(1).map(match => match || null);
|
||||
}
|
||||
|
||||
const cleanURL = QSStrippedURL || URL;
|
||||
const cleanURL = QSStrippedURL || url;
|
||||
const regexMatch = componentsRegex.exec(cleanURL) || [];
|
||||
const [proto, ...rest] = regexMatch.slice(1).map(match => match || null);
|
||||
const path = rest.join('');
|
||||
|
@ -75,7 +80,7 @@ export function parseURI(URL: string, requireProto: boolean = false): LbryUrlObj
|
|||
|
||||
rest.forEach(urlPiece => {
|
||||
if (urlPiece && urlPiece.includes(' ')) {
|
||||
console.error('URL can not include a space');
|
||||
throw new Error(__('URL can not include a space'));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -144,11 +149,11 @@ function parseURIModifier(modSeperator: ?string, modValue: ?string) {
|
|||
throw new Error(__(`No modifier provided after separator %modSeperator%.`, { modSeperator }));
|
||||
}
|
||||
|
||||
if (modSeperator === '#') {
|
||||
if (modSeperator === MOD_CLAIM_ID_SEPARATOR || MOD_CLAIM_ID_SEPARATOR_OLD) {
|
||||
claimId = modValue;
|
||||
} else if (modSeperator === ':') {
|
||||
} else if (modSeperator === MOD_SEQUENCE_SEPARATOR) {
|
||||
claimSequence = modValue;
|
||||
} else if (modSeperator === '$') {
|
||||
} else if (modSeperator === MOD_BID_POSITION_SEPARATOR) {
|
||||
bidPosition = modValue;
|
||||
}
|
||||
}
|
||||
|
@ -320,3 +325,22 @@ export function convertToShareLink(URL: string) {
|
|||
'https://open.lbry.com/'
|
||||
);
|
||||
}
|
||||
|
||||
export function splitBySeparator(uri: string) {
|
||||
const protocolLength = 7;
|
||||
return uri.startsWith('lbry://') ? uri.slice(protocolLength).split(/[#:*]/) : uri.split(/#:\*\$/);
|
||||
}
|
||||
|
||||
export function isURIEqual(uriA: string, uriB: string) {
|
||||
const parseA = parseURI(normalizeURI(uriA));
|
||||
const parseB = parseURI(normalizeURI(uriB));
|
||||
if (parseA.isChannel) {
|
||||
if (parseB.isChannel && parseA.channelClaimId === parseB.channelClaimId) {
|
||||
return true;
|
||||
}
|
||||
} else if (parseA.streamClaimId === parseB.streamClaimId) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,15 +10,33 @@ import {
|
|||
selectClaimsByUri,
|
||||
selectMyChannelClaims,
|
||||
selectPendingIds,
|
||||
selectPendingClaimsById,
|
||||
} 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 {
|
||||
selectPendingCollections,
|
||||
makeSelectClaimIdsForCollectionId,
|
||||
} from 'redux/selectors/collections';
|
||||
import {
|
||||
doFetchItemsInCollection,
|
||||
doFetchItemsInCollections,
|
||||
doCollectionDelete,
|
||||
} from 'redux/actions/collections';
|
||||
|
||||
export function doResolveUris(uris: Array<string>, returnCachedClaims: boolean = false) {
|
||||
let onChannelConfirmCallback;
|
||||
let checkPendingInterval;
|
||||
|
||||
export function doResolveUris(
|
||||
uris: Array<string>,
|
||||
returnCachedClaims: boolean = false,
|
||||
resolveReposts: boolean = true
|
||||
) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const normalizedUris = uris.map(normalizeURI);
|
||||
const state = getState();
|
||||
|
@ -54,28 +72,49 @@ export function doResolveUris(uris: Array<string>, returnCachedClaims: boolean =
|
|||
stream: ?StreamClaim,
|
||||
channel: ?ChannelClaim,
|
||||
claimsInChannel: ?number,
|
||||
collection: ?CollectionClaim,
|
||||
},
|
||||
} = {};
|
||||
|
||||
return Lbry.resolve({ urls: urisToResolve, ...options }).then((result: ResolveResponse) => {
|
||||
Object.entries(result).forEach(([uri, uriResolveInfo]) => {
|
||||
const collectionIds: Array<string> = [];
|
||||
|
||||
return Lbry.resolve({ urls: urisToResolve, ...options }).then(
|
||||
async(result: ResolveResponse) => {
|
||||
let repostedResults = {};
|
||||
const repostsToResolve = [];
|
||||
const fallbackResolveInfo = {
|
||||
stream: null,
|
||||
claimsInChannel: null,
|
||||
channel: null,
|
||||
};
|
||||
|
||||
function processResult(result, resolveInfo = {}, checkReposts = false) {
|
||||
Object.entries(result).forEach(([uri, uriResolveInfo]) => {
|
||||
// Flow has terrible Object.entries support
|
||||
// https://github.com/facebook/flow/issues/2221
|
||||
if (uriResolveInfo) {
|
||||
if (uriResolveInfo.error) {
|
||||
// $FlowFixMe
|
||||
resolveInfo[uri] = { ...fallbackResolveInfo };
|
||||
} else {
|
||||
if (checkReposts) {
|
||||
if (uriResolveInfo.reposted_claim) {
|
||||
// $FlowFixMe
|
||||
const repostUrl = uriResolveInfo.reposted_claim.permanent_url;
|
||||
if (!resolvingUris.includes(repostUrl)) {
|
||||
repostsToResolve.push(repostUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = {};
|
||||
if (uriResolveInfo.value_type === 'channel') {
|
||||
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) {
|
||||
|
@ -91,13 +130,30 @@ export function doResolveUris(uris: Array<string>, returnCachedClaims: boolean =
|
|||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
processResult(result, resolveInfo, resolveReposts);
|
||||
|
||||
if (repostsToResolve.length) {
|
||||
dispatch({
|
||||
type: ACTIONS.RESOLVE_URIS_STARTED,
|
||||
data: { uris: repostsToResolve, debug: 'reposts' },
|
||||
});
|
||||
repostedResults = await Lbry.resolve({ urls: repostsToResolve, ...options });
|
||||
}
|
||||
processResult(repostedResults, resolveInfo);
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.RESOLVE_URIS_COMPLETED,
|
||||
data: { resolveInfo },
|
||||
});
|
||||
|
||||
if (collectionIds.length) {
|
||||
dispatch(doFetchItemsInCollections({ collectionIds: collectionIds, pageSize: 5 }));
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -108,18 +164,24 @@ export function doResolveUri(uri: string) {
|
|||
export function doFetchClaimListMine(
|
||||
page: number = 1,
|
||||
pageSize: number = 99999,
|
||||
resolve: boolean = true
|
||||
resolve: boolean = true,
|
||||
filterBy: Array<string> = []
|
||||
) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_CLAIM_LIST_MINE_STARTED,
|
||||
});
|
||||
|
||||
let claimTypes = ['stream', 'repost'];
|
||||
if (filterBy && filterBy.length !== 0) {
|
||||
claimTypes = claimTypes.filter(t => filterBy.includes(t));
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
Lbry.claim_list({
|
||||
page: page,
|
||||
page_size: pageSize,
|
||||
claim_type: ['stream', 'repost'],
|
||||
claim_type: claimTypes,
|
||||
resolve,
|
||||
}).then((result: StreamListResponse) => {
|
||||
dispatch({
|
||||
|
@ -345,7 +407,7 @@ export function doClearChannelErrors() {
|
|||
};
|
||||
}
|
||||
|
||||
export function doCreateChannel(name: string, amount: number, optionalParams: any, cb: any) {
|
||||
export function doCreateChannel(name: string, amount: number, optionalParams: any, onConfirm: any) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({
|
||||
type: ACTIONS.CREATE_CHANNEL_STARTED,
|
||||
|
@ -361,7 +423,8 @@ export function doCreateChannel(name: string, amount: number, optionalParams: an
|
|||
description?: string,
|
||||
website_url?: string,
|
||||
email?: string,
|
||||
tags?: Array<string>,
|
||||
tags?: Array<Tag>,
|
||||
languages?: Array<string>,
|
||||
} = {
|
||||
name,
|
||||
bid: creditsToString(amount),
|
||||
|
@ -390,6 +453,9 @@ export function doCreateChannel(name: string, amount: number, optionalParams: an
|
|||
if (optionalParams.tags) {
|
||||
createParams.tags = optionalParams.tags.map(tag => tag.name);
|
||||
}
|
||||
if (optionalParams.languages) {
|
||||
createParams.languages = optionalParams.languages;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -408,7 +474,7 @@ export function doCreateChannel(name: string, amount: number, optionalParams: an
|
|||
claims: [channelClaim],
|
||||
},
|
||||
});
|
||||
dispatch(doCheckPendingClaims(cb));
|
||||
dispatch(doCheckPendingClaims(onConfirm));
|
||||
return channelClaim;
|
||||
})
|
||||
.catch(error => {
|
||||
|
@ -441,7 +507,7 @@ export function doUpdateChannel(params: any, cb: any) {
|
|||
email: params.email,
|
||||
tags: [],
|
||||
replace: true,
|
||||
languages: [],
|
||||
languages: params.languages || [],
|
||||
locations: [],
|
||||
blocking: true,
|
||||
};
|
||||
|
@ -451,15 +517,10 @@ 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;
|
||||
}
|
||||
|
||||
if (channelClaim && channelClaim.value && channelClaim.value.languages) {
|
||||
updateParams.languages = channelClaim.value.languages;
|
||||
}
|
||||
|
||||
return Lbry.channel_update(updateParams)
|
||||
.then((result: ChannelUpdateResponse) => {
|
||||
const channelClaim = result.outputs[0];
|
||||
|
@ -493,7 +554,7 @@ export function doImportChannel(certificate: string) {
|
|||
});
|
||||
|
||||
return Lbry.channel_import({ channel_data: certificate })
|
||||
.then((result: string) => {
|
||||
.then(() => {
|
||||
dispatch({
|
||||
type: ACTIONS.IMPORT_CHANNEL_COMPLETED,
|
||||
});
|
||||
|
@ -535,17 +596,54 @@ 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,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const failure = error => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_COLLECTION_LIST_FAILED,
|
||||
data: error,
|
||||
});
|
||||
};
|
||||
|
||||
Lbry.collection_list({ page, page_size: pageSize, resolve_claims: 1, resolve: true }).then(
|
||||
callback,
|
||||
failure
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export function doClaimSearch(
|
||||
options: {
|
||||
page_size: number,
|
||||
page: number,
|
||||
no_totals: boolean,
|
||||
no_totals?: boolean,
|
||||
any_tags?: Array<string>,
|
||||
claim_ids?: Array<string>,
|
||||
channel_ids?: Array<string>,
|
||||
not_channel_ids?: Array<string>,
|
||||
not_tags?: Array<string>,
|
||||
order_by?: Array<string>,
|
||||
release_time?: string,
|
||||
has_source?: boolean,
|
||||
has_no_souce?: boolean,
|
||||
} = {
|
||||
no_totals: true,
|
||||
page_size: 10,
|
||||
|
@ -553,7 +651,7 @@ export function doClaimSearch(
|
|||
}
|
||||
) {
|
||||
const query = createNormalizedClaimSearchKey(options);
|
||||
return (dispatch: Dispatch) => {
|
||||
return async(dispatch: Dispatch) => {
|
||||
dispatch({
|
||||
type: ACTIONS.CLAIM_SEARCH_STARTED,
|
||||
data: { query: query },
|
||||
|
@ -577,6 +675,7 @@ export function doClaimSearch(
|
|||
pageSize: options.page_size,
|
||||
},
|
||||
});
|
||||
return resolveInfo;
|
||||
};
|
||||
|
||||
const failure = err => {
|
||||
|
@ -585,9 +684,10 @@ export function doClaimSearch(
|
|||
data: { query },
|
||||
error: err,
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
Lbry.claim_search({
|
||||
return await Lbry.claim_search({
|
||||
...options,
|
||||
include_purchase_receipt: true,
|
||||
}).then(success, failure);
|
||||
|
@ -595,8 +695,7 @@ export function doClaimSearch(
|
|||
}
|
||||
|
||||
export function doRepost(options: StreamRepostOptions) {
|
||||
return (dispatch: Dispatch) => {
|
||||
// $FlowFixMe
|
||||
return (dispatch: Dispatch): Promise<any> => {
|
||||
return new Promise(resolve => {
|
||||
dispatch({
|
||||
type: ACTIONS.CLAIM_REPOST_STARTED,
|
||||
|
@ -611,6 +710,12 @@ export function doRepost(options: StreamRepostOptions) {
|
|||
repostClaim,
|
||||
},
|
||||
});
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_PENDING_CLAIMS,
|
||||
data: {
|
||||
claims: [repostClaim],
|
||||
},
|
||||
});
|
||||
|
||||
dispatch(doFetchClaimListMine(1, 10));
|
||||
resolve(repostClaim);
|
||||
|
@ -630,6 +735,209 @@ 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<Tag>,
|
||||
languages?: Array<string>,
|
||||
claims: Array<string>,
|
||||
},
|
||||
localId: string
|
||||
) {
|
||||
return (dispatch: Dispatch): Promise<any> => {
|
||||
// $FlowFixMe
|
||||
|
||||
const params: {
|
||||
name: string,
|
||||
bid: string,
|
||||
channel_id?: string,
|
||||
blocking?: true,
|
||||
title?: string,
|
||||
thumbnail_url?: string,
|
||||
description?: string,
|
||||
tags?: Array<string>,
|
||||
languages?: Array<string>,
|
||||
claims: Array<string>,
|
||||
} = {
|
||||
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);
|
||||
}
|
||||
|
||||
if (options.channel_id) {
|
||||
params['channel_id'] = options.channel_id;
|
||||
}
|
||||
|
||||
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,
|
||||
data: { claimId: collectionClaim.claim_id },
|
||||
},
|
||||
// move unpublished collection to pending collection with new publish id
|
||||
// recent publish won't resolve this second. handle it in checkPending
|
||||
{
|
||||
type: ACTIONS.UPDATE_PENDING_CLAIMS,
|
||||
data: {
|
||||
claims: [collectionClaim],
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
dispatch({
|
||||
type: ACTIONS.COLLECTION_PENDING,
|
||||
data: { localId: localId, claimId: collectionClaim.claim_id },
|
||||
});
|
||||
dispatch(doCheckPendingClaims());
|
||||
dispatch(doFetchCollectionListMine(1, 10));
|
||||
return resolve(collectionClaim);
|
||||
}
|
||||
|
||||
function failure(error) {
|
||||
dispatch({
|
||||
type: ACTIONS.COLLECTION_PUBLISH_FAILED,
|
||||
data: {
|
||||
error: error.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return Lbry.collection_create(params).then(success, failure);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doCollectionPublishUpdate(
|
||||
options: {
|
||||
bid?: string,
|
||||
blocking?: true,
|
||||
title?: string,
|
||||
thumbnail_url?: string,
|
||||
description?: string,
|
||||
claim_id: string,
|
||||
tags?: Array<Tag>,
|
||||
languages?: Array<string>,
|
||||
claims?: Array<string>,
|
||||
channel_id?: string,
|
||||
},
|
||||
isBackgroundUpdate?: boolean
|
||||
) {
|
||||
return (dispatch: Dispatch, getState: GetState): Promise<any> => {
|
||||
// TODO: implement one click update
|
||||
|
||||
const updateParams: {
|
||||
bid?: string,
|
||||
blocking?: true,
|
||||
title?: string,
|
||||
thumbnail_url?: string,
|
||||
channel_id?: string,
|
||||
description?: string,
|
||||
claim_id: string,
|
||||
tags?: Array<string>,
|
||||
languages?: Array<string>,
|
||||
claims?: Array<string>,
|
||||
clear_claims: boolean,
|
||||
replace?: boolean,
|
||||
} = isBackgroundUpdate
|
||||
? {
|
||||
blocking: true,
|
||||
claim_id: options.claim_id,
|
||||
clear_claims: true,
|
||||
}
|
||||
: {
|
||||
bid: creditsToString(options.bid),
|
||||
title: options.title,
|
||||
thumbnail_url: options.thumbnail_url,
|
||||
description: options.description,
|
||||
tags: [],
|
||||
languages: options.languages || [],
|
||||
locations: [],
|
||||
blocking: true,
|
||||
claim_id: options.claim_id,
|
||||
clear_claims: true,
|
||||
replace: true,
|
||||
};
|
||||
|
||||
if (isBackgroundUpdate && updateParams.claim_id) {
|
||||
const state = getState();
|
||||
updateParams['claims'] = makeSelectClaimIdsForCollectionId(updateParams.claim_id)(state);
|
||||
} else if (options.claims) {
|
||||
updateParams['claims'] = options.claims;
|
||||
}
|
||||
|
||||
if (options.tags) {
|
||||
updateParams['tags'] = options.tags.map(tag => tag.name);
|
||||
}
|
||||
|
||||
if (options.channel_id) {
|
||||
updateParams['channel_id'] = options.channel_id;
|
||||
}
|
||||
|
||||
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.COLLECTION_PENDING,
|
||||
data: { claimId: collectionClaim.claim_id },
|
||||
});
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_PENDING_CLAIMS,
|
||||
data: {
|
||||
claims: [collectionClaim],
|
||||
},
|
||||
});
|
||||
dispatch(doCheckPendingClaims());
|
||||
return resolve(collectionClaim);
|
||||
}
|
||||
|
||||
function failure(error) {
|
||||
dispatch({
|
||||
type: ACTIONS.COLLECTION_PUBLISH_UPDATE_FAILED,
|
||||
data: {
|
||||
error: error.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return Lbry.collection_update(updateParams).then(success, failure);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doCheckPublishNameAvailability(name: string) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({
|
||||
|
@ -692,47 +1000,71 @@ export function doPurchaseList(page: number = 1, pageSize: number = PAGE_SIZE) {
|
|||
};
|
||||
}
|
||||
|
||||
export const doCheckPendingClaims = (onConfirmed: Function) => (
|
||||
export const doCheckPendingClaims = (onChannelConfirmed: Function) => (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
) => {
|
||||
let claimCheckInterval;
|
||||
|
||||
const checkClaimList = () => {
|
||||
const state = getState();
|
||||
const pendingIdSet = new Set(selectPendingIds(state));
|
||||
Lbry.claim_list({ page: 1, page_size: 10 })
|
||||
.then(result => {
|
||||
const claims = result.items;
|
||||
const claimsToConfirm = [];
|
||||
claims.forEach(claim => {
|
||||
const { claim_id: claimId } = claim;
|
||||
if (claim.confirmations > 0 && pendingIdSet.has(claimId)) {
|
||||
pendingIdSet.delete(claimId);
|
||||
claimsToConfirm.push(claim);
|
||||
if (onConfirmed) {
|
||||
onConfirmed(claim);
|
||||
if (onChannelConfirmed) {
|
||||
onChannelConfirmCallback = onChannelConfirmed;
|
||||
}
|
||||
clearInterval(checkPendingInterval);
|
||||
const checkTxoList = () => {
|
||||
const state = getState();
|
||||
const pendingById = Object.assign({}, selectPendingClaimsById(state));
|
||||
const pendingTxos = (Object.values(pendingById): any).map(p => p.txid);
|
||||
// use collections
|
||||
const pendingCollections = selectPendingCollections(state);
|
||||
if (pendingTxos.length) {
|
||||
Lbry.txo_list({ txid: pendingTxos })
|
||||
.then(result => {
|
||||
const txos = result.items;
|
||||
const idsToConfirm = [];
|
||||
txos.forEach(txo => {
|
||||
if (txo.claim_id && txo.confirmations > 0) {
|
||||
idsToConfirm.push(txo.claim_id);
|
||||
delete pendingById[txo.claim_id];
|
||||
}
|
||||
});
|
||||
if (claimsToConfirm.length) {
|
||||
return { idsToConfirm, pendingById };
|
||||
})
|
||||
.then(results => {
|
||||
const { idsToConfirm, pendingById } = results;
|
||||
if (idsToConfirm.length) {
|
||||
return Lbry.claim_list({ claim_id: idsToConfirm, resolve: true }).then(results => {
|
||||
const claims = results.items;
|
||||
const collectionIds = claims
|
||||
.filter(c => c.value_type === 'collection')
|
||||
.map(c => c.claim_id);
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_CONFIRMED_CLAIMS,
|
||||
data: {
|
||||
claims: claimsToConfirm,
|
||||
claims: claims,
|
||||
pending: pendingById,
|
||||
},
|
||||
});
|
||||
}
|
||||
return pendingIdSet.size;
|
||||
if (collectionIds.length) {
|
||||
dispatch(
|
||||
doFetchItemsInCollections({
|
||||
collectionIds,
|
||||
})
|
||||
.then(len => {
|
||||
if (!len) {
|
||||
clearInterval(claimCheckInterval);
|
||||
);
|
||||
}
|
||||
const channelClaims = claims.filter(claim => claim.value_type === 'channel');
|
||||
if (channelClaims.length && onChannelConfirmCallback) {
|
||||
channelClaims.forEach(claim => onChannelConfirmCallback(claim));
|
||||
}
|
||||
if (Object.keys(pendingById).length === 0) {
|
||||
clearInterval(checkPendingInterval);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
clearInterval(checkPendingInterval);
|
||||
}
|
||||
};
|
||||
|
||||
claimCheckInterval = setInterval(() => {
|
||||
checkClaimList();
|
||||
// do something with onConfirmed (typically get blocklist for channel)
|
||||
checkPendingInterval = setInterval(() => {
|
||||
checkTxoList();
|
||||
}, 30000);
|
||||
};
|
||||
|
|
495
src/redux/actions/collections.js
Normal file
495
src/redux/actions/collections.js
Normal file
|
@ -0,0 +1,495 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import Lbry from 'lbry';
|
||||
import { doClaimSearch, doAbandonClaim } from 'redux/actions/claims';
|
||||
import { makeSelectClaimForClaimId } from 'redux/selectors/claims';
|
||||
import {
|
||||
makeSelectCollectionForId,
|
||||
// makeSelectPublishedCollectionForId, // for "save" or "copy" action
|
||||
makeSelectMyPublishedCollectionForId,
|
||||
makeSelectPublishedCollectionForId,
|
||||
makeSelectUnpublishedCollectionForId,
|
||||
makeSelectEditedCollectionForId,
|
||||
} from 'redux/selectors/collections';
|
||||
import * as COLS from 'constants/collections';
|
||||
|
||||
const getTimestamp = () => {
|
||||
return Math.floor(Date.now() / 1000);
|
||||
};
|
||||
|
||||
const FETCH_BATCH_SIZE = 50;
|
||||
|
||||
export const doLocalCollectionCreate = (
|
||||
name: string,
|
||||
collectionItems: Array<string>,
|
||||
type: 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,
|
||||
type: type,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const doCollectionDelete = (id: string, colKey: ?string = undefined) => (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
) => {
|
||||
const state = getState();
|
||||
const claim = makeSelectClaimForClaimId(id)(state);
|
||||
const collectionDelete = () =>
|
||||
dispatch({
|
||||
type: ACTIONS.COLLECTION_DELETE,
|
||||
data: {
|
||||
id: id,
|
||||
collectionKey: colKey,
|
||||
},
|
||||
});
|
||||
if (claim && !colKey) {
|
||||
// could support "abandon, but keep" later
|
||||
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
|
||||
// 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<string>,
|
||||
pageSize?: number,
|
||||
},
|
||||
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();
|
||||
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) {
|
||||
await dispatch(doClaimSearch({ claim_ids: collectionIdsToSearch, page: 1, page_size: 9999 }));
|
||||
}
|
||||
|
||||
const stateAfterClaimSearch = getState();
|
||||
|
||||
async function fetchItemsForCollectionClaim(claim: CollectionClaim, pageSize?: number) {
|
||||
const totalItems = claim.value.claims && claim.value.claims.length;
|
||||
const claimId = claim.claim_id;
|
||||
const itemOrder = claim.value.claims;
|
||||
|
||||
const sortResults = (items: Array<Claim>, claimList) => {
|
||||
const newItems: Array<Claim> = [];
|
||||
claimList.forEach(id => {
|
||||
const index = items.findIndex(i => i.claim_id === id);
|
||||
if (index >= 0) {
|
||||
newItems.push(items[index]);
|
||||
}
|
||||
});
|
||||
/*
|
||||
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 = (
|
||||
arrayOfResults: Array<{ items: Array<Claim>, total_items: number }>,
|
||||
claimList: Array<string>
|
||||
) => {
|
||||
const mergedResults: { items: Array<Claim>, total_items: number } = {
|
||||
items: [],
|
||||
total_items: 0,
|
||||
};
|
||||
arrayOfResults.forEach(result => {
|
||||
mergedResults.items = mergedResults.items.concat(result.items);
|
||||
mergedResults.total_items = result.total_items;
|
||||
});
|
||||
|
||||
mergedResults.items = sortResults(mergedResults.items, claimList);
|
||||
return mergedResults;
|
||||
};
|
||||
|
||||
try {
|
||||
const batchSize = pageSize || FETCH_BATCH_SIZE;
|
||||
const batches: Array<Promise<any>> = [];
|
||||
|
||||
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,
|
||||
no_totals: true,
|
||||
});
|
||||
}
|
||||
const itemsInBatches = await Promise.all(batches);
|
||||
const result = mergeBatches(itemsInBatches, itemOrder);
|
||||
|
||||
// $FlowFixMe
|
||||
const itemsById: { claimId: string, items?: ?Array<GenericClaim> } = { claimId: claimId };
|
||||
if (result.items) {
|
||||
itemsById.items = result.items;
|
||||
} else {
|
||||
itemsById.items = null;
|
||||
}
|
||||
return itemsById;
|
||||
} catch (e) {
|
||||
return {
|
||||
claimId: claimId,
|
||||
items: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function formatForClaimActions(resultClaimsByUri) {
|
||||
const formattedClaims = {};
|
||||
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 if (uriResolveInfo.value_type === 'collection') {
|
||||
result.collection = uriResolveInfo;
|
||||
} 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
|
||||
formattedClaims[uri] = result;
|
||||
}
|
||||
});
|
||||
return formattedClaims;
|
||||
}
|
||||
|
||||
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<GenericClaim>,
|
||||
}> = await Promise.all(promisedCollectionItemFetches);
|
||||
|
||||
const newCollectionObjectsById = {};
|
||||
const resolvedItemsByUrl = {};
|
||||
collectionItemsById.forEach(entry => {
|
||||
// $FlowFixMe
|
||||
const collectionItems: Array<any> = entry.items;
|
||||
const collectionId = entry.claimId;
|
||||
if (collectionItems) {
|
||||
const claim = makeSelectClaimForClaimId(collectionId)(stateAfterClaimSearch);
|
||||
|
||||
const editedCollection = makeSelectEditedCollectionForId(collectionId)(stateAfterClaimSearch);
|
||||
const { name, timestamp, value } = claim || {};
|
||||
const { title } = value;
|
||||
const valueTypes = new Set();
|
||||
const streamTypes = new Set();
|
||||
|
||||
let newItems = [];
|
||||
let isPlaylist;
|
||||
|
||||
if (collectionItems) {
|
||||
collectionItems.forEach(collectionItem => {
|
||||
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,
|
||||
};
|
||||
|
||||
if (editedCollection && timestamp > editedCollection['updatedAt']) {
|
||||
dispatch({
|
||||
type: ACTIONS.COLLECTION_DELETE,
|
||||
data: {
|
||||
id: collectionId,
|
||||
collectionKey: 'edited',
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
invalidCollectionIds.push(collectionId);
|
||||
}
|
||||
});
|
||||
const formattedClaimsByUri = formatForClaimActions(collectionItemsById);
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.RESOLVE_URIS_COMPLETED,
|
||||
data: { resolveInfo: formattedClaimsByUri },
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.COLLECTION_ITEMS_RESOLVE_COMPLETED,
|
||||
data: {
|
||||
resolvedCollections: newCollectionObjectsById,
|
||||
failedCollectionIds: invalidCollectionIds,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const doFetchItemsInCollection = (
|
||||
options: { collectionId: string, pageSize?: number },
|
||||
cb?: () => void
|
||||
) => {
|
||||
const { collectionId, pageSize } = options;
|
||||
const newOptions: { collectionIds: Array<string>, 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 = makeSelectPublishedCollectionForId(collectionId)(state); // needs to be published only
|
||||
|
||||
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.type;
|
||||
let newItems: Array<?string> = 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);
|
||||
}
|
||||
|
||||
// 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(',')) {
|
||||
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 (COLS.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;
|
||||
};
|
|
@ -104,7 +104,10 @@ export function doPurchaseUri(
|
|||
data: { uri, error: `Already fetching uri: ${uri}` },
|
||||
});
|
||||
|
||||
Promise.resolve();
|
||||
if (onSuccess) {
|
||||
onSuccess(fileInfo);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import uuid from 'uuid/v4';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
export function doToast(params: ToastParams) {
|
||||
if (!params) {
|
||||
|
|
|
@ -21,6 +21,7 @@ export const doResetThumbnailStatus = () => (dispatch: Dispatch) => {
|
|||
type: ACTIONS.UPDATE_PUBLISH_FORM,
|
||||
data: {
|
||||
thumbnailPath: '',
|
||||
thumbnailError: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -68,8 +69,10 @@ export const doUploadThumbnail = (
|
|||
thumbnailBlob?: File,
|
||||
fsAdapter?: any,
|
||||
fs?: any,
|
||||
path?: any
|
||||
path?: any,
|
||||
cb?: (string) => void
|
||||
) => (dispatch: Dispatch) => {
|
||||
const downMessage = __('Thumbnail upload service may be down, try again later.');
|
||||
let thumbnail, fileExt, fileName, fileType;
|
||||
|
||||
const makeid = () => {
|
||||
|
@ -95,6 +98,13 @@ export const doUploadThumbnail = (
|
|||
);
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_PUBLISH_FORM,
|
||||
data: {
|
||||
thumbnailError: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
const doUpload = data => {
|
||||
return fetch(SPEECH_PUBLISH, {
|
||||
method: 'POST',
|
||||
|
@ -103,20 +113,27 @@ export const doUploadThumbnail = (
|
|||
.then(res => res.text())
|
||||
.then(text => (text.length ? JSON.parse(text) : {}))
|
||||
.then(json => {
|
||||
return json.success
|
||||
? dispatch({
|
||||
if (!json.success) return uploadError(json.message || downMessage);
|
||||
if (cb) {
|
||||
cb(json.data.serveUrl);
|
||||
}
|
||||
return dispatch({
|
||||
type: ACTIONS.UPDATE_PUBLISH_FORM,
|
||||
data: {
|
||||
uploadThumbnailStatus: THUMBNAIL_STATUSES.COMPLETE,
|
||||
thumbnail: json.data.serveUrl,
|
||||
},
|
||||
})
|
||||
: uploadError(
|
||||
json.message || __('Thumbnail upload service may be down, try again later.')
|
||||
);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
uploadError(err.message);
|
||||
let message = err.message;
|
||||
|
||||
// This sucks but ¯\_(ツ)_/¯
|
||||
if (message === 'Failed to fetch') {
|
||||
message = downMessage;
|
||||
}
|
||||
|
||||
uploadError(message);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -178,6 +195,7 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileLis
|
|||
currency: 'LBC',
|
||||
},
|
||||
languages,
|
||||
release_time,
|
||||
license,
|
||||
license_url: licenseUrl,
|
||||
thumbnail,
|
||||
|
@ -193,6 +211,8 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileLis
|
|||
description,
|
||||
fee,
|
||||
languages,
|
||||
releaseTime: release_time,
|
||||
releaseTimeEdited: undefined,
|
||||
thumbnail: thumbnail ? thumbnail.url : null,
|
||||
title,
|
||||
uri,
|
||||
|
@ -224,11 +244,13 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileLis
|
|||
dispatch({ type: ACTIONS.DO_PREPARE_EDIT, data: publishData });
|
||||
};
|
||||
|
||||
export const doPublish = (success: Function, fail: Function) => (
|
||||
export const doPublish = (success: Function, fail: Function, preview: Function) => (
|
||||
dispatch: Dispatch,
|
||||
getState: () => {}
|
||||
) => {
|
||||
if (!preview) {
|
||||
dispatch({ type: ACTIONS.PUBLISH_START });
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const myClaimForUri = selectMyClaimForUri(state);
|
||||
|
@ -244,6 +266,7 @@ export const doPublish = (success: Function, fail: Function) => (
|
|||
filePath,
|
||||
description,
|
||||
language,
|
||||
releaseTimeEdited,
|
||||
license,
|
||||
licenseUrl,
|
||||
useLBRYUploader,
|
||||
|
@ -258,7 +281,10 @@ export const doPublish = (success: Function, fail: Function) => (
|
|||
tags,
|
||||
locations,
|
||||
optimize,
|
||||
isLivestreamPublish,
|
||||
remoteFileUrl,
|
||||
} = publishData;
|
||||
|
||||
// Handle scenario where we have a claim that has the same name as a channel we are publishing with.
|
||||
const myClaimForUriEditing = myClaimForUri && myClaimForUri.name === name ? myClaimForUri : null;
|
||||
|
||||
|
@ -284,7 +310,6 @@ export const doPublish = (success: Function, fail: Function) => (
|
|||
description?: string,
|
||||
channel_id?: string,
|
||||
file_path?: string,
|
||||
|
||||
license_url?: string,
|
||||
license?: string,
|
||||
thumbnail_url?: string,
|
||||
|
@ -296,6 +321,8 @@ export const doPublish = (success: Function, fail: Function) => (
|
|||
locations?: Array<any>,
|
||||
blocking: boolean,
|
||||
optimize_file?: boolean,
|
||||
preview?: boolean,
|
||||
remote_url?: string,
|
||||
} = {
|
||||
name,
|
||||
title,
|
||||
|
@ -306,10 +333,14 @@ export const doPublish = (success: Function, fail: Function) => (
|
|||
tags: tags && tags.map(tag => tag.name),
|
||||
thumbnail_url: thumbnail,
|
||||
blocking: true,
|
||||
preview: false,
|
||||
};
|
||||
// Temporary solution to keep the same publish flow with the new tags api
|
||||
// Eventually we will allow users to enter their own tags on publish
|
||||
// `nsfw` will probably be removed
|
||||
if (remoteFileUrl) {
|
||||
publishPayload.remote_url = remoteFileUrl;
|
||||
}
|
||||
|
||||
if (publishingLicense) {
|
||||
publishPayload.license = publishingLicense;
|
||||
|
@ -328,7 +359,9 @@ export const doPublish = (success: Function, fail: Function) => (
|
|||
}
|
||||
|
||||
// Set release time to curret date. On edits, keep original release/transaction time as release_time
|
||||
if (myClaimForUriEditing && myClaimForUriEditing.value.release_time) {
|
||||
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) {
|
||||
publishPayload.release_time = Number(myClaimForUriEditing.timestamp);
|
||||
|
@ -355,7 +388,16 @@ export const doPublish = (success: Function, fail: Function) => (
|
|||
|
||||
// Only pass file on new uploads, not metadata only edits.
|
||||
// The sdk will figure it out
|
||||
if (filePath) publishPayload.file_path = filePath;
|
||||
if (filePath && !isLivestreamPublish) publishPayload.file_path = filePath;
|
||||
|
||||
if (preview) {
|
||||
publishPayload.preview = true;
|
||||
publishPayload.optimize_file = false;
|
||||
|
||||
return Lbry.publish(publishPayload).then((previewResponse: PublishResponse) => {
|
||||
return preview(previewResponse);
|
||||
}, fail);
|
||||
}
|
||||
|
||||
return Lbry.publish(publishPayload).then((response: PublishResponse) => {
|
||||
if (!useLBRYUploader) {
|
||||
|
@ -382,7 +424,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);
|
||||
|
|
|
@ -1,278 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import { buildURI } from 'lbryURI';
|
||||
import { doResolveUri } from 'redux/actions/claims';
|
||||
import {
|
||||
makeSelectSearchUris,
|
||||
makeSelectResolvedSearchResults,
|
||||
selectSuggestions,
|
||||
makeSelectQueryWithOptions,
|
||||
selectSearchValue,
|
||||
} from 'redux/selectors/search';
|
||||
import { batchActions } from 'util/batch-actions';
|
||||
import debounce from 'util/debounce';
|
||||
import handleFetchResponse from 'util/handle-fetch';
|
||||
|
||||
const DEBOUNCED_SEARCH_SUGGESTION_MS = 300;
|
||||
type Dispatch = (action: any) => any;
|
||||
type GetState = () => { search: SearchState };
|
||||
|
||||
type SearchOptions = {
|
||||
size?: number,
|
||||
from?: number,
|
||||
related_to?: string,
|
||||
nsfw?: boolean,
|
||||
isBackgroundSearch?: boolean,
|
||||
resolveResults?: boolean,
|
||||
};
|
||||
|
||||
// We can't use env's because they aren't passed into node_modules
|
||||
let CONNECTION_STRING = 'https://lighthouse.lbry.com/';
|
||||
|
||||
export const setSearchApi = (endpoint: string) => {
|
||||
CONNECTION_STRING = endpoint.replace(/\/*$/, '/'); // exactly one slash at the end;
|
||||
};
|
||||
|
||||
export const getSearchSuggestions = (value: string) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const query = value.trim();
|
||||
|
||||
// strip out any basic stuff for more accurate search results
|
||||
let searchValue = query.replace(/lbry:\/\//g, '').replace(/-/g, ' ');
|
||||
if (searchValue.includes('#')) {
|
||||
// This should probably be more robust, but I think it's fine for now
|
||||
// Remove everything after # to get rid of the claim id
|
||||
searchValue = searchValue.substring(0, searchValue.indexOf('#'));
|
||||
}
|
||||
|
||||
const suggestions = selectSuggestions(getState());
|
||||
if (suggestions[searchValue]) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`${CONNECTION_STRING}autocomplete?s=${searchValue}`)
|
||||
.then(handleFetchResponse)
|
||||
.then(apiSuggestions => {
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_SEARCH_SUGGESTIONS,
|
||||
data: {
|
||||
query: searchValue,
|
||||
suggestions: apiSuggestions,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// If the fetch fails, do nothing
|
||||
// Basic search suggestions are already populated at this point
|
||||
});
|
||||
};
|
||||
|
||||
const throttledSearchSuggestions = debounce((dispatch, query) => {
|
||||
dispatch(getSearchSuggestions(query));
|
||||
}, DEBOUNCED_SEARCH_SUGGESTION_MS);
|
||||
|
||||
export const doUpdateSearchQuery = (query: string, shouldSkipSuggestions: ?boolean) => (
|
||||
dispatch: Dispatch
|
||||
) => {
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_SEARCH_QUERY,
|
||||
data: { query },
|
||||
});
|
||||
|
||||
// Don't fetch new suggestions if the user just added a space
|
||||
if (!query.endsWith(' ') || !shouldSkipSuggestions) {
|
||||
throttledSearchSuggestions(dispatch, query);
|
||||
}
|
||||
};
|
||||
|
||||
export const doSearch = (rawQuery: string, searchOptions: SearchOptions) => (
|
||||
dispatch: Dispatch,
|
||||
getState: GetState
|
||||
) => {
|
||||
const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
|
||||
const resolveResults = searchOptions && searchOptions.resolveResults;
|
||||
const isBackgroundSearch = (searchOptions && searchOptions.isBackgroundSearch) || false;
|
||||
|
||||
if (!query) {
|
||||
dispatch({
|
||||
type: ACTIONS.SEARCH_FAIL,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
|
||||
let queryWithOptions = makeSelectQueryWithOptions(query, searchOptions)(state);
|
||||
|
||||
// If we have already searched for something, we don't need to do anything
|
||||
const urisForQuery = makeSelectSearchUris(queryWithOptions)(state);
|
||||
if (urisForQuery && !!urisForQuery.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.SEARCH_START,
|
||||
});
|
||||
|
||||
// If the user is on the file page with a pre-populated uri and they select
|
||||
// the search option without typing anything, searchQuery will be empty
|
||||
// We need to populate it so the input is filled on the search page
|
||||
// isBackgroundSearch means the search is happening in the background, don't update the search query
|
||||
if (!state.search.searchQuery && !isBackgroundSearch) {
|
||||
dispatch(doUpdateSearchQuery(query));
|
||||
}
|
||||
|
||||
fetch(`${CONNECTION_STRING}search?${queryWithOptions}`)
|
||||
.then(handleFetchResponse)
|
||||
.then((data: Array<{ name: string, claimId: string }>) => {
|
||||
const uris = [];
|
||||
const actions = [];
|
||||
|
||||
data.forEach(result => {
|
||||
if (result) {
|
||||
const { name, claimId } = result;
|
||||
const urlObj: LbryUrlObj = {};
|
||||
|
||||
if (name.startsWith('@')) {
|
||||
urlObj.channelName = name;
|
||||
urlObj.channelClaimId = claimId;
|
||||
} else {
|
||||
urlObj.streamName = name;
|
||||
urlObj.streamClaimId = claimId;
|
||||
}
|
||||
|
||||
const url = buildURI(urlObj);
|
||||
if (resolveResults) {
|
||||
actions.push(doResolveUri(url));
|
||||
}
|
||||
uris.push(url);
|
||||
}
|
||||
});
|
||||
|
||||
actions.push({
|
||||
type: ACTIONS.SEARCH_SUCCESS,
|
||||
data: {
|
||||
query: queryWithOptions,
|
||||
uris,
|
||||
},
|
||||
});
|
||||
dispatch(batchActions(...actions));
|
||||
})
|
||||
.catch(e => {
|
||||
dispatch({
|
||||
type: ACTIONS.SEARCH_FAIL,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const doResolvedSearch = (
|
||||
rawQuery: string,
|
||||
size: ?number, // only pass in if you don't want to use the users setting (ex: related content)
|
||||
from: ?number,
|
||||
isBackgroundSearch: boolean = false,
|
||||
options: {
|
||||
related_to?: string,
|
||||
} = {},
|
||||
nsfw: boolean
|
||||
) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const query = rawQuery.replace(/^lbry:\/\//i, '').replace(/\//, ' ');
|
||||
|
||||
if (!query) {
|
||||
dispatch({
|
||||
type: ACTIONS.RESOLVED_SEARCH_FAIL,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const optionsWithFrom: SearchOptions = {
|
||||
...(size ? { size } : {}),
|
||||
...(from ? { from } : {}),
|
||||
isBackgroundSearch,
|
||||
...options,
|
||||
};
|
||||
|
||||
const optionsWithoutFrom: SearchOptions = {
|
||||
...(size ? { size } : {}),
|
||||
isBackgroundSearch,
|
||||
...options,
|
||||
};
|
||||
|
||||
const state = getState();
|
||||
|
||||
let queryWithOptions = makeSelectQueryWithOptions(query, optionsWithFrom)(state);
|
||||
|
||||
// make from null so that we can maintain a reference to the same query for multiple pages and simply append the found results
|
||||
let queryWithoutFrom = makeSelectQueryWithOptions(query, optionsWithoutFrom)(state);
|
||||
|
||||
// If we have already searched for something, we don't need to do anything
|
||||
// TODO: Tweak this check for multiple page results
|
||||
/* const resultsForQuery = makeSelectResolvedSearchResults(queryWithOptions)(state);
|
||||
if (resultsForQuery && resultsForQuery.length && resultsForQuery.length > (from * size)) {
|
||||
return;
|
||||
} */
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.RESOLVED_SEARCH_START,
|
||||
});
|
||||
|
||||
if (!state.search.searchQuery && !isBackgroundSearch) {
|
||||
dispatch(doUpdateSearchQuery(query));
|
||||
}
|
||||
|
||||
const fetchUrl = nsfw
|
||||
? `${CONNECTION_STRING}search?resolve=true&${queryWithOptions}`
|
||||
: `${CONNECTION_STRING}search?resolve=true&nsfw=false&${queryWithOptions}`;
|
||||
fetch(fetchUrl)
|
||||
.then(handleFetchResponse)
|
||||
.then((data: Array<ResolvedSearchResult>) => {
|
||||
const results = [];
|
||||
|
||||
data.forEach(result => {
|
||||
if (result) {
|
||||
results.push(result);
|
||||
}
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.RESOLVED_SEARCH_SUCCESS,
|
||||
data: {
|
||||
query: queryWithoutFrom,
|
||||
results,
|
||||
pageSize: size,
|
||||
append: parseInt(from, 10) > parseInt(size, 10) - 1,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
dispatch({
|
||||
type: ACTIONS.RESOLVED_SEARCH_FAIL,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const doFocusSearchInput = () => (dispatch: Dispatch) =>
|
||||
dispatch({
|
||||
type: ACTIONS.SEARCH_FOCUS,
|
||||
});
|
||||
|
||||
export const doBlurSearchInput = () => (dispatch: Dispatch) =>
|
||||
dispatch({
|
||||
type: ACTIONS.SEARCH_BLUR,
|
||||
});
|
||||
|
||||
export const doUpdateSearchOptions = (
|
||||
newOptions: SearchOptions,
|
||||
additionalOptions: SearchOptions
|
||||
) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const searchValue = selectSearchValue(state);
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.UPDATE_SEARCH_OPTIONS,
|
||||
data: newOptions,
|
||||
});
|
||||
|
||||
if (searchValue) {
|
||||
// After updating, perform a search with the new options
|
||||
dispatch(doSearch(searchValue, additionalOptions));
|
||||
}
|
||||
};
|
|
@ -6,11 +6,17 @@ type SharedData = {
|
|||
version: '0.1',
|
||||
value: {
|
||||
subscriptions?: Array<string>,
|
||||
following?: Array<{ uri: string, notificationsDisabled: boolean }>,
|
||||
tags?: Array<string>,
|
||||
blocked?: Array<string>,
|
||||
coin_swap_codes?: Array<string>,
|
||||
settings?: any,
|
||||
app_welcome_version?: number,
|
||||
sharing_3P?: boolean,
|
||||
unpublishedCollections: CollectionGroup,
|
||||
editedCollections: CollectionGroup,
|
||||
builtinCollections: CollectionGroup,
|
||||
savedCollections: Array<string>,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -18,20 +24,32 @@ function extractUserState(rawObj: SharedData) {
|
|||
if (rawObj && rawObj.version === '0.1' && rawObj.value) {
|
||||
const {
|
||||
subscriptions,
|
||||
following,
|
||||
tags,
|
||||
blocked,
|
||||
coin_swap_codes,
|
||||
settings,
|
||||
app_welcome_version,
|
||||
sharing_3P,
|
||||
unpublishedCollections,
|
||||
editedCollections,
|
||||
builtinCollections,
|
||||
savedCollections,
|
||||
} = rawObj.value;
|
||||
|
||||
return {
|
||||
...(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 } : {}),
|
||||
...(editedCollections ? { editedCollections } : {}),
|
||||
...(builtinCollections ? { builtinCollections } : {}),
|
||||
...(savedCollections ? { savedCollections } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -42,21 +60,33 @@ export function doPopulateSharedUserState(sharedSettings: any) {
|
|||
return (dispatch: Dispatch) => {
|
||||
const {
|
||||
subscriptions,
|
||||
following,
|
||||
tags,
|
||||
blocked,
|
||||
coin_swap_codes,
|
||||
settings,
|
||||
app_welcome_version,
|
||||
sharing_3P,
|
||||
unpublishedCollections,
|
||||
editedCollections,
|
||||
builtinCollections,
|
||||
savedCollections,
|
||||
} = extractUserState(sharedSettings);
|
||||
dispatch({
|
||||
type: ACTIONS.USER_STATE_POPULATE,
|
||||
data: {
|
||||
subscriptions,
|
||||
following,
|
||||
tags,
|
||||
blocked,
|
||||
coinSwapCodes: coin_swap_codes,
|
||||
settings,
|
||||
welcomeVersion: app_welcome_version,
|
||||
allowAnalytics: sharing_3P,
|
||||
unpublishedCollections,
|
||||
editedCollections,
|
||||
builtinCollections,
|
||||
savedCollections,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -69,6 +99,7 @@ export function doPreferenceSet(
|
|||
success: Function,
|
||||
fail: Function
|
||||
) {
|
||||
return (dispatch: Dispatch) => {
|
||||
const preference = {
|
||||
type: typeof value,
|
||||
version,
|
||||
|
@ -82,21 +113,30 @@ export function doPreferenceSet(
|
|||
|
||||
Lbry.preference_set(options)
|
||||
.then(() => {
|
||||
if (success) {
|
||||
success(preference);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(err => {
|
||||
dispatch({
|
||||
type: ACTIONS.SYNC_FATAL_ERROR,
|
||||
error: err,
|
||||
});
|
||||
|
||||
if (fail) {
|
||||
fail();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doPreferenceGet(key: string, success: Function, fail?: Function) {
|
||||
return (dispatch: Dispatch) => {
|
||||
const options = {
|
||||
key,
|
||||
};
|
||||
|
||||
Lbry.preference_get(options)
|
||||
return Lbry.preference_get(options)
|
||||
.then(result => {
|
||||
if (result) {
|
||||
const preference = result[key];
|
||||
|
@ -106,8 +146,14 @@ export function doPreferenceGet(key: string, success: Function, fail?: Function)
|
|||
return success(null);
|
||||
})
|
||||
.catch(err => {
|
||||
dispatch({
|
||||
type: ACTIONS.SYNC_FATAL_ERROR,
|
||||
error: err,
|
||||
});
|
||||
|
||||
if (fail) {
|
||||
fail(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import Lbry from 'lbry';
|
||||
|
||||
export const doToggleTagFollow = (name: string) => ({
|
||||
type: ACTIONS.TOGGLE_TAG_FOLLOW,
|
||||
data: {
|
||||
name,
|
||||
},
|
||||
});
|
||||
|
||||
export const doAddTag = (name: string) => ({
|
||||
type: ACTIONS.TAG_ADD,
|
||||
data: {
|
||||
name,
|
||||
},
|
||||
});
|
||||
|
||||
export const doDeleteTag = (name: string) => ({
|
||||
type: ACTIONS.TAG_DELETE,
|
||||
data: {
|
||||
name,
|
||||
},
|
||||
});
|
|
@ -5,12 +5,17 @@ import {
|
|||
selectBalance,
|
||||
selectPendingSupportTransactions,
|
||||
selectTxoPageParams,
|
||||
selectPendingOtherTransactions,
|
||||
selectPendingConsolidateTxid,
|
||||
selectPendingMassClaimTxid,
|
||||
} from 'redux/selectors/wallet';
|
||||
import { creditsToString } from 'util/format-credits';
|
||||
import { selectMyClaimsRaw } from 'redux/selectors/claims';
|
||||
import { doFetchChannelListMine, doFetchClaimListMine } from 'redux/actions/claims';
|
||||
import { selectMyClaimsRaw, selectClaimsById } from 'redux/selectors/claims';
|
||||
import { doFetchChannelListMine, doFetchClaimListMine, doClaimSearch } from 'redux/actions/claims';
|
||||
|
||||
const FIFTEEN_SECONDS = 15000;
|
||||
let walletBalancePromise = null;
|
||||
|
||||
export function doUpdateBalance() {
|
||||
return (dispatch, getState) => {
|
||||
const {
|
||||
|
@ -52,20 +57,17 @@ export function doUpdateBalance() {
|
|||
export function doBalanceSubscribe() {
|
||||
return dispatch => {
|
||||
dispatch(doUpdateBalance());
|
||||
setInterval(() => dispatch(doUpdateBalance()), 5000);
|
||||
setInterval(() => dispatch(doUpdateBalance()), 10000);
|
||||
};
|
||||
}
|
||||
|
||||
export function doFetchTransactions(page = 1, pageSize = 99999) {
|
||||
export function doFetchTransactions(page = 1, pageSize = 999999) {
|
||||
return dispatch => {
|
||||
dispatch(doFetchSupports());
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_TRANSACTIONS_STARTED,
|
||||
});
|
||||
|
||||
Lbry.utxo_release()
|
||||
.then(() => Lbry.transaction_list({ page, page_size: pageSize }))
|
||||
.then(result => {
|
||||
Lbry.transaction_list({ page, page_size: pageSize }).then(result => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_TRANSACTIONS_COMPLETED,
|
||||
data: {
|
||||
|
@ -78,25 +80,63 @@ export function doFetchTransactions(page = 1, pageSize = 99999) {
|
|||
|
||||
export function doFetchTxoPage() {
|
||||
return (dispatch, getState) => {
|
||||
const fetchId = Math.random()
|
||||
.toString(36)
|
||||
.substr(2, 9);
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_TXO_PAGE_STARTED,
|
||||
data: fetchId,
|
||||
});
|
||||
|
||||
const state = getState();
|
||||
const queryParams = selectTxoPageParams(state);
|
||||
|
||||
Lbry.utxo_release()
|
||||
.then(() => Lbry.txo_list(queryParams))
|
||||
Lbry.txo_list(queryParams)
|
||||
.then(res => {
|
||||
const items = res.items || [];
|
||||
const claimsById = selectClaimsById(state);
|
||||
|
||||
const channelIds = items.reduce((acc, cur) => {
|
||||
if (
|
||||
cur.type === 'support' &&
|
||||
cur.signing_channel &&
|
||||
!claimsById[cur.signing_channel.channel_id]
|
||||
) {
|
||||
acc.push(cur.signing_channel.channel_id);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (channelIds.length) {
|
||||
const searchParams = {
|
||||
page_size: 9999,
|
||||
page: 1,
|
||||
no_totals: true,
|
||||
claim_ids: channelIds,
|
||||
};
|
||||
// make sure redux has these channels resolved
|
||||
dispatch(doClaimSearch(searchParams));
|
||||
}
|
||||
|
||||
return res;
|
||||
})
|
||||
.then(res => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_TXO_PAGE_COMPLETED,
|
||||
data: res,
|
||||
data: {
|
||||
result: res,
|
||||
fetchId: fetchId,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_TXO_PAGE_COMPLETED,
|
||||
data: e.message,
|
||||
data: {
|
||||
error: e.message,
|
||||
fetchId: fetchId,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -130,6 +170,74 @@ export function doFetchSupports(page = 1, pageSize = 99999) {
|
|||
};
|
||||
}
|
||||
|
||||
export function doFetchUtxoCounts() {
|
||||
return async dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_UTXO_COUNT_STARTED,
|
||||
});
|
||||
|
||||
let resultSets = await Promise.all([
|
||||
Lbry.txo_list({ type: 'other', is_not_spent: true, page: 1, page_size: 1 }),
|
||||
Lbry.txo_list({ type: 'support', is_not_spent: true, page: 1, page_size: 1 }),
|
||||
]);
|
||||
const counts = {};
|
||||
const paymentCount = resultSets[0]['total_items'];
|
||||
const supportCount = resultSets[1]['total_items'];
|
||||
counts['other'] = typeof paymentCount === 'number' ? paymentCount : 0;
|
||||
counts['support'] = typeof supportCount === 'number' ? supportCount : 0;
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_UTXO_COUNT_COMPLETED,
|
||||
data: counts,
|
||||
debug: { resultSets },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doUtxoConsolidate() {
|
||||
return async dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.DO_UTXO_CONSOLIDATE_STARTED,
|
||||
});
|
||||
|
||||
const results = await Lbry.txo_spend({ type: 'other' });
|
||||
const result = results[0];
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.PENDING_CONSOLIDATED_TXOS_UPDATED,
|
||||
data: { txids: [result.txid] },
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.DO_UTXO_CONSOLIDATE_COMPLETED,
|
||||
data: { txid: result.txid },
|
||||
});
|
||||
dispatch(doCheckPendingTxs());
|
||||
};
|
||||
}
|
||||
|
||||
export function doTipClaimMass() {
|
||||
return async dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.TIP_CLAIM_MASS_STARTED,
|
||||
});
|
||||
|
||||
const results = await Lbry.txo_spend({ type: 'support', is_not_my_input: true });
|
||||
const result = results[0];
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.PENDING_CONSOLIDATED_TXOS_UPDATED,
|
||||
data: { txids: [result.txid] },
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.TIP_CLAIM_MASS_COMPLETED,
|
||||
data: { txid: result.txid },
|
||||
});
|
||||
dispatch(doCheckPendingTxs());
|
||||
};
|
||||
}
|
||||
|
||||
export function doGetNewAddress() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
|
@ -187,7 +295,7 @@ export function doSendDraftTransaction(address, amount) {
|
|||
});
|
||||
dispatch(
|
||||
doToast({
|
||||
message: __(`You sent ${amount} LBC`),
|
||||
message: __('You sent %amount% LBRY Credits', { amount: amount }),
|
||||
linkText: __('History'),
|
||||
linkTarget: '/wallet',
|
||||
})
|
||||
|
@ -240,7 +348,7 @@ export function doSetDraftTransactionAddress(address) {
|
|||
};
|
||||
}
|
||||
|
||||
export function doSendTip(params, isSupport, successCallback, errorCallback) {
|
||||
export function doSendTip(params, isSupport, successCallback, errorCallback, shouldNotify = true) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const balance = selectBalance(state);
|
||||
|
@ -259,23 +367,25 @@ export function doSendTip(params, isSupport, successCallback, errorCallback) {
|
|||
return;
|
||||
}
|
||||
|
||||
const success = () => {
|
||||
const success = response => {
|
||||
if (shouldNotify) {
|
||||
dispatch(
|
||||
doToast({
|
||||
message: shouldSupport
|
||||
? __('You deposited %amount% LBC as a support!', { amount: params.amount })
|
||||
: __('You sent %amount% LBC as a tip, Mahalo!', { amount: params.amount }),
|
||||
? __('You deposited %amount% LBRY Credits as a support!', { amount: params.amount })
|
||||
: __('You sent %amount% LBRY Credits as a tip, Mahalo!', { amount: params.amount }),
|
||||
linkText: __('History'),
|
||||
linkTarget: '/wallet',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.SUPPORT_TRANSACTION_COMPLETED,
|
||||
});
|
||||
|
||||
if (successCallback) {
|
||||
successCallback();
|
||||
successCallback(response);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -384,6 +494,7 @@ export function doWalletLock() {
|
|||
};
|
||||
}
|
||||
|
||||
// Collect all tips for a claim
|
||||
export function doSupportAbandonForClaim(claimId, claimType, keep, preview) {
|
||||
return dispatch => {
|
||||
if (preview) {
|
||||
|
@ -404,7 +515,7 @@ export function doSupportAbandonForClaim(claimId, claimType, keep, preview) {
|
|||
if (!preview) {
|
||||
dispatch({
|
||||
type: ACTIONS.ABANDON_CLAIM_SUPPORT_COMPLETED,
|
||||
data: { claimId, txid: res.txid, effective: res.outputs[0].amount, type: claimType }, // add to pendingSupportTransactions,
|
||||
data: { claimId, txid: res.txid, effective: res.outputs[0].amount, type: claimType },
|
||||
});
|
||||
dispatch(doCheckPendingTxs());
|
||||
}
|
||||
|
@ -424,15 +535,30 @@ export function doWalletReconnect() {
|
|||
dispatch({
|
||||
type: ACTIONS.WALLET_RESTART,
|
||||
});
|
||||
let failed = false;
|
||||
// this basically returns null when it's done. :(
|
||||
// might be good to dispatch ACTIONS.WALLET_RESTARTED
|
||||
Lbry.wallet_reconnect().then(() =>
|
||||
const walletTimeout = setTimeout(() => {
|
||||
failed = true;
|
||||
dispatch({
|
||||
type: ACTIONS.WALLET_RESTART_COMPLETED,
|
||||
});
|
||||
dispatch(
|
||||
doToast({
|
||||
message: __(
|
||||
'Your servers were not available. Check your url and port, or switch back to defaults.'
|
||||
),
|
||||
isError: true,
|
||||
})
|
||||
);
|
||||
}, FIFTEEN_SECONDS);
|
||||
Lbry.wallet_reconnect().then(() => {
|
||||
clearTimeout(walletTimeout);
|
||||
if (!failed) dispatch({ type: ACTIONS.WALLET_RESTART_COMPLETED });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doWalletDecrypt() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
|
@ -495,35 +621,50 @@ export function doUpdateBlockHeight() {
|
|||
export const doCheckPendingTxs = () => (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const pendingTxsById = selectPendingSupportTransactions(state); // {}
|
||||
if (!Object.keys(pendingTxsById).length) {
|
||||
const pendingOtherTxes = selectPendingOtherTransactions(state);
|
||||
|
||||
if (!Object.keys(pendingTxsById).length && !pendingOtherTxes.length) {
|
||||
return;
|
||||
}
|
||||
let txCheckInterval;
|
||||
const checkTxList = () => {
|
||||
const state = getState();
|
||||
const pendingTxs = selectPendingSupportTransactions(state); // {}
|
||||
const pendingSupportTxs = selectPendingSupportTransactions(state); // {}
|
||||
const pendingConsolidateTxes = selectPendingOtherTransactions(state);
|
||||
const pendingConsTxid = selectPendingConsolidateTxid(state);
|
||||
const pendingMassCLaimTxid = selectPendingMassClaimTxid(state);
|
||||
|
||||
const promises = [];
|
||||
const newPendingTxes = {};
|
||||
const noLongerPendingConsolidate = [];
|
||||
const types = new Set([]);
|
||||
let changed = false;
|
||||
Object.entries(pendingTxs).forEach(([claim, data]) => {
|
||||
// { claimId: {txid: 123, amount 12.3}, }
|
||||
const entries = Object.entries(pendingSupportTxs);
|
||||
entries.forEach(([claim, data]) => {
|
||||
promises.push(Lbry.transaction_show({ txid: data.txid }));
|
||||
types.add(data.type);
|
||||
});
|
||||
if (pendingConsolidateTxes.length) {
|
||||
pendingConsolidateTxes.forEach(txid => promises.push(Lbry.transaction_show({ txid })));
|
||||
}
|
||||
|
||||
Promise.all(promises)
|
||||
.then(txShows => {
|
||||
Promise.all(promises).then(txShows => {
|
||||
let changed = false;
|
||||
txShows.forEach(result => {
|
||||
if (pendingConsolidateTxes.includes(result.txid)) {
|
||||
if (result.height > 0) {
|
||||
noLongerPendingConsolidate.push(result.txid);
|
||||
}
|
||||
} else {
|
||||
if (result.height <= 0) {
|
||||
const entries = Object.entries(pendingTxs);
|
||||
const match = entries.find(entry => entry[1].txid === result.txid);
|
||||
newPendingTxes[match[0]] = match[1];
|
||||
} else {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
if (changed) {
|
||||
dispatch({
|
||||
type: ACTIONS.PENDING_SUPPORTS_UPDATED,
|
||||
|
@ -536,12 +677,31 @@ export const doCheckPendingTxs = () => (dispatch, getState) => {
|
|||
dispatch(doFetchClaimListMine());
|
||||
}
|
||||
}
|
||||
if (Object.keys(newPendingTxes).length === 0) clearInterval(txCheckInterval);
|
||||
if (noLongerPendingConsolidate.length) {
|
||||
if (noLongerPendingConsolidate.includes(pendingConsTxid)) {
|
||||
dispatch(
|
||||
doToast({
|
||||
message: __('Your wallet is finished consolidating'),
|
||||
})
|
||||
);
|
||||
}
|
||||
if (noLongerPendingConsolidate.includes(pendingMassCLaimTxid)) {
|
||||
dispatch(
|
||||
doToast({
|
||||
message: __('Your tips have been collected'),
|
||||
})
|
||||
);
|
||||
}
|
||||
dispatch({
|
||||
type: ACTIONS.PENDING_CONSOLIDATED_TXOS_UPDATED,
|
||||
data: { txids: noLongerPendingConsolidate, remove: true },
|
||||
});
|
||||
}
|
||||
|
||||
if (!Object.keys(pendingTxsById).length) {
|
||||
if (!Object.keys(pendingTxsById).length && !pendingOtherTxes.length) {
|
||||
clearInterval(txCheckInterval);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
txCheckInterval = setInterval(() => {
|
||||
|
|
|
@ -2,27 +2,39 @@
|
|||
import isEqual from 'util/deep-equal';
|
||||
import { doPreferenceSet } from 'redux/actions/sync';
|
||||
|
||||
const SHARED_PREFERENCE_KEY = 'shared';
|
||||
const RUN_PREFERENCES_DELAY_MS = 2000;
|
||||
const SHARED_PREFERENCE_VERSION = '0.1';
|
||||
let oldShared = {};
|
||||
|
||||
let timeout;
|
||||
export const buildSharedStateMiddleware = (
|
||||
actions: Array<string>,
|
||||
sharedStateFilters: {},
|
||||
sharedStateCb?: any => void
|
||||
) => ({ getState, dispatch }: { getState: () => {}, dispatch: any => void }) => (
|
||||
next: ({}) => void
|
||||
) => (action: { type: string, data: any }) => {
|
||||
) => ({
|
||||
getState,
|
||||
dispatch,
|
||||
}: {
|
||||
getState: () => { user: any, settings: any },
|
||||
dispatch: any => void,
|
||||
}) => (next: ({}) => void) => (action: { type: string, data: any }) => {
|
||||
const currentState = getState();
|
||||
|
||||
// We don't care if sync is disabled here, we always want to backup preferences to the wallet
|
||||
if (!actions.includes(action.type)) {
|
||||
if (!actions.includes(action.type) || typeof action === 'function') {
|
||||
return next(action);
|
||||
}
|
||||
|
||||
clearTimeout(timeout);
|
||||
const actionResult = next(action);
|
||||
// Call `getState` after calling `next` to ensure the state has updated in response to the action
|
||||
const nextState = getState();
|
||||
function runPreferences() {
|
||||
const nextState: { user: any, settings: any } = getState();
|
||||
const syncEnabled =
|
||||
nextState.settings &&
|
||||
nextState.settings.clientSettings &&
|
||||
nextState.settings.clientSettings.enable_sync;
|
||||
const hasVerifiedEmail =
|
||||
nextState.user && nextState.user.user && nextState.user.user.has_verified_email;
|
||||
const preferenceKey = syncEnabled && hasVerifiedEmail ? 'shared' : 'local';
|
||||
const shared = {};
|
||||
|
||||
Object.keys(sharedStateFilters).forEach(key => {
|
||||
|
@ -39,13 +51,15 @@ export const buildSharedStateMiddleware = (
|
|||
if (!isEqual(oldShared, shared)) {
|
||||
// only update if the preference changed from last call in the same session
|
||||
oldShared = shared;
|
||||
doPreferenceSet(SHARED_PREFERENCE_KEY, shared, SHARED_PREFERENCE_VERSION);
|
||||
dispatch(doPreferenceSet(preferenceKey, shared, SHARED_PREFERENCE_VERSION));
|
||||
}
|
||||
|
||||
if (sharedStateCb) {
|
||||
// Pass dispatch to the callback to consumers can dispatch actions in response to preference set
|
||||
sharedStateCb({ dispatch, getState });
|
||||
}
|
||||
|
||||
clearTimeout(timeout);
|
||||
return actionResult;
|
||||
}
|
||||
timeout = setTimeout(runPreferences, RUN_PREFERENCES_DELAY_MS);
|
||||
};
|
||||
|
|
|
@ -13,17 +13,20 @@ import mergeClaim from 'util/merge-claim';
|
|||
|
||||
type State = {
|
||||
createChannelError: ?string,
|
||||
createCollectionError: ?string,
|
||||
channelClaimCounts: { [string]: number },
|
||||
claimsByUri: { [string]: string },
|
||||
byId: { [string]: Claim },
|
||||
pendingById: { [string]: Claim }, // keep pending claims
|
||||
resolvingUris: Array<string>,
|
||||
pendingIds: Array<string>,
|
||||
reflectingById: { [string]: ReflectingUpdate },
|
||||
myClaims: ?Array<string>,
|
||||
myChannelClaims: ?Array<string>,
|
||||
myCollectionClaims: ?Array<string>,
|
||||
abandoningById: { [string]: boolean },
|
||||
fetchingChannelClaims: { [string]: number },
|
||||
fetchingMyChannels: boolean,
|
||||
fetchingMyCollections: boolean,
|
||||
fetchingClaimSearchByQuery: { [string]: boolean },
|
||||
purchaseUriSuccess: boolean,
|
||||
myPurchases: ?Array<string>,
|
||||
|
@ -34,6 +37,7 @@ type State = {
|
|||
claimSearchByQuery: { [string]: Array<string> },
|
||||
claimSearchByQueryLastPageReached: { [string]: Array<boolean> },
|
||||
creatingChannel: boolean,
|
||||
creatingCollection: boolean,
|
||||
paginatedClaimsByChannel: {
|
||||
[string]: {
|
||||
all: Array<string>,
|
||||
|
@ -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,17 +81,22 @@ const defaultState = {
|
|||
fetchingMyPurchases: false,
|
||||
fetchingMyPurchasesError: undefined,
|
||||
fetchingMyChannels: false,
|
||||
fetchingMyCollections: false,
|
||||
abandoningById: {},
|
||||
pendingIds: [],
|
||||
pendingById: {},
|
||||
reflectingById: {},
|
||||
claimSearchError: false,
|
||||
claimSearchByQuery: {},
|
||||
claimSearchByQueryLastPageReached: {},
|
||||
fetchingClaimSearchByQuery: {},
|
||||
updateChannelError: '',
|
||||
updateCollectionError: '',
|
||||
updatingChannel: false,
|
||||
creatingChannel: false,
|
||||
createChannelError: undefined,
|
||||
updatingCollection: false,
|
||||
creatingCollection: false,
|
||||
createCollectionError: undefined,
|
||||
pendingChannelImport: false,
|
||||
repostLoading: false,
|
||||
repostError: undefined,
|
||||
|
@ -100,28 +112,22 @@ 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);
|
||||
const channelClaimCounts = Object.assign({}, state.channelClaimCounts);
|
||||
const pendingIds = state.pendingIds;
|
||||
const pendingById = state.pendingById;
|
||||
let newResolvingUrls = new Set(state.resolvingUris);
|
||||
let myClaimIds = new Set(state.myClaims);
|
||||
|
||||
Object.entries(resolveInfo).forEach(([url: string, resolveResponse: ResolveResponse]) => {
|
||||
// $FlowFixMe
|
||||
const { claimsInChannel, stream, channel } = resolveResponse;
|
||||
const { claimsInChannel, stream, channel: channelFromResolve, collection } = resolveResponse;
|
||||
const channel = channelFromResolve || (stream && stream.signing_channel);
|
||||
|
||||
if (stream) {
|
||||
if (pendingIds.includes(stream.claim_id)) {
|
||||
if (pendingById[stream.claim_id]) {
|
||||
byId[stream.claim_id] = mergeClaim(stream, byId[stream.claim_id]);
|
||||
} else {
|
||||
byId[stream.claim_id] = stream;
|
||||
|
@ -135,6 +141,10 @@ function handleClaimAction(state: State, action: any): State {
|
|||
byUri[stream.permanent_url] = stream.claim_id;
|
||||
newResolvingUrls.delete(stream.canonical_url);
|
||||
newResolvingUrls.delete(stream.permanent_url);
|
||||
|
||||
if (stream.is_my_output) {
|
||||
myClaimIds.add(stream.claim_id);
|
||||
}
|
||||
}
|
||||
|
||||
if (channel && channel.claim_id) {
|
||||
|
@ -147,20 +157,37 @@ function handleClaimAction(state: State, action: any): State {
|
|||
channelClaimCounts[channel.canonical_url] = claimsInChannel;
|
||||
}
|
||||
|
||||
if (pendingIds.includes(channel.claim_id)) {
|
||||
if (pendingById[channel.claim_id]) {
|
||||
byId[channel.claim_id] = mergeClaim(channel, byId[channel.claim_id]);
|
||||
} 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);
|
||||
newResolvingUrls.delete(channel.permanent_url);
|
||||
}
|
||||
|
||||
if (collection) {
|
||||
if (pendingById[collection.claim_id]) {
|
||||
byId[collection.claim_id] = mergeClaim(collection, byId[collection.claim_id]);
|
||||
} else {
|
||||
byId[collection.claim_id] = collection;
|
||||
}
|
||||
byUri[url] = collection.claim_id;
|
||||
byUri[collection.canonical_url] = collection.claim_id;
|
||||
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 && !pendingById[byUri[url]]) {
|
||||
byUri[url] = null;
|
||||
}
|
||||
});
|
||||
|
@ -170,6 +197,7 @@ function handleClaimAction(state: State, action: any): State {
|
|||
claimsByUri: byUri,
|
||||
channelClaimCounts,
|
||||
resolvingUris: Array.from(newResolvingUrls),
|
||||
myClaims: Array.from(myClaimIds),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -202,34 +230,33 @@ reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_STARTED] = (state: State): State =>
|
|||
});
|
||||
|
||||
reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED] = (state: State, action: any): State => {
|
||||
const { result, resolve }: { result: ClaimListResponse, resolve: boolean } = action.data;
|
||||
const { result }: { result: ClaimListResponse } = action.data;
|
||||
const claims = result.items;
|
||||
const page = result.page;
|
||||
const totalItems = result.total_items;
|
||||
|
||||
const byId = Object.assign({}, state.byId);
|
||||
const byUri = Object.assign({}, state.claimsByUri);
|
||||
const pendingIds = state.pendingIds || [];
|
||||
const pendingById = Object.assign({}, state.pendingById);
|
||||
let myClaimIds = new Set(state.myClaims);
|
||||
let urlsForCurrentPage = [];
|
||||
|
||||
const pendingIdSet = new Set(pendingIds);
|
||||
|
||||
claims.forEach((claim: Claim) => {
|
||||
const { permanent_url: permanentUri, claim_id: claimId } = claim;
|
||||
const { permanent_url: permanentUri, claim_id: claimId, canonical_url: canonicalUri } = claim;
|
||||
if (claim.type && claim.type.match(/claim|update/)) {
|
||||
urlsForCurrentPage.push(permanentUri);
|
||||
if (claim.confirmations < 1) {
|
||||
pendingIdSet.add(claimId);
|
||||
} else if (!resolve && pendingIdSet.has(claimId) && claim.confirmations > 0) {
|
||||
pendingIdSet.delete(claimId);
|
||||
}
|
||||
if (pendingIds.includes(claimId)) {
|
||||
pendingById[claimId] = claim;
|
||||
if (byId[claimId]) {
|
||||
byId[claimId] = mergeClaim(claim, byId[claimId]);
|
||||
} else {
|
||||
byId[claimId] = claim;
|
||||
}
|
||||
} else {
|
||||
byId[claimId] = claim;
|
||||
}
|
||||
byUri[permanentUri] = claimId;
|
||||
byUri[canonicalUri] = claimId;
|
||||
myClaimIds.add(claimId);
|
||||
}
|
||||
});
|
||||
|
@ -238,7 +265,7 @@ reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED] = (state: State, action: any):
|
|||
isFetchingClaimListMine: false,
|
||||
myClaims: Array.from(myClaimIds),
|
||||
byId,
|
||||
pendingIds: Array.from(pendingIdSet),
|
||||
pendingById,
|
||||
claimsByUri: byUri,
|
||||
myClaimsPageResults: urlsForCurrentPage,
|
||||
myClaimsPageNumber: page,
|
||||
|
@ -251,9 +278,8 @@ reducers[ACTIONS.FETCH_CHANNEL_LIST_STARTED] = (state: State): State =>
|
|||
|
||||
reducers[ACTIONS.FETCH_CHANNEL_LIST_COMPLETED] = (state: State, action: any): State => {
|
||||
const { claims }: { claims: Array<ChannelClaim> } = action.data;
|
||||
const myClaims = state.myClaims || [];
|
||||
let myClaimIds = new Set(state.myClaims);
|
||||
const pendingIds = state.pendingIds || [];
|
||||
const pendingById = Object.assign({}, state.pendingById);
|
||||
let myChannelClaims;
|
||||
const byId = Object.assign({}, state.byId);
|
||||
const byUri = Object.assign({}, state.claimsByUri);
|
||||
|
@ -267,7 +293,12 @@ reducers[ACTIONS.FETCH_CHANNEL_LIST_COMPLETED] = (state: State, action: any): St
|
|||
claims.forEach(claim => {
|
||||
const { meta } = claim;
|
||||
const { claims_in_channel: claimsInChannel } = claim.meta;
|
||||
const { canonical_url: canonicalUrl, permanent_url: permanentUrl, claim_id: claimId } = claim;
|
||||
const {
|
||||
canonical_url: canonicalUrl,
|
||||
permanent_url: permanentUrl,
|
||||
claim_id: claimId,
|
||||
confirmations,
|
||||
} = claim;
|
||||
|
||||
byUri[canonicalUrl] = claimId;
|
||||
byUri[permanentUrl] = claimId;
|
||||
|
@ -276,7 +307,14 @@ reducers[ACTIONS.FETCH_CHANNEL_LIST_COMPLETED] = (state: State, action: any): St
|
|||
|
||||
// $FlowFixMe
|
||||
myChannelClaims.add(claimId);
|
||||
if (!pendingIds.some(c => c === claimId)) {
|
||||
if (confirmations < 1) {
|
||||
pendingById[claimId] = claim;
|
||||
if (byId[claimId]) {
|
||||
byId[claimId] = mergeClaim(claim, byId[claimId]);
|
||||
} else {
|
||||
byId[claimId] = claim;
|
||||
}
|
||||
} else {
|
||||
byId[claimId] = claim;
|
||||
}
|
||||
myClaimIds.add(claimId);
|
||||
|
@ -285,6 +323,7 @@ reducers[ACTIONS.FETCH_CHANNEL_LIST_COMPLETED] = (state: State, action: any): St
|
|||
|
||||
return Object.assign({}, state, {
|
||||
byId,
|
||||
pendingById,
|
||||
claimsByUri: byUri,
|
||||
channelClaimCounts,
|
||||
fetchingMyChannels: false,
|
||||
|
@ -299,6 +338,66 @@ 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<CollectionClaim> } = action.data;
|
||||
const myClaims = state.myClaims || [];
|
||||
let myClaimIds = new Set(myClaims);
|
||||
const pendingById = Object.assign({}, state.pendingById);
|
||||
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 { meta } = claim;
|
||||
const {
|
||||
canonical_url: canonicalUrl,
|
||||
permanent_url: permanentUrl,
|
||||
claim_id: claimId,
|
||||
confirmations,
|
||||
} = claim;
|
||||
|
||||
byUri[canonicalUrl] = claimId;
|
||||
byUri[permanentUrl] = claimId;
|
||||
|
||||
// $FlowFixMe
|
||||
myCollectionClaimsSet.add(claimId);
|
||||
// we don't want to overwrite a pending result with a resolve
|
||||
if (confirmations < 1) {
|
||||
pendingById[claimId] = claim;
|
||||
if (byId[claimId]) {
|
||||
byId[claimId] = mergeClaim(claim, byId[claimId]);
|
||||
} else {
|
||||
byId[claimId] = claim;
|
||||
}
|
||||
} else {
|
||||
byId[claimId] = claim;
|
||||
}
|
||||
myClaimIds.add(claimId);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
byId,
|
||||
pendingById,
|
||||
claimsByUri: byUri,
|
||||
fetchingMyCollections: false,
|
||||
myCollectionClaims: Array.from(myCollectionClaimsSet),
|
||||
myClaims: myClaimIds ? Array.from(myClaimIds) : null,
|
||||
};
|
||||
};
|
||||
|
||||
reducers[ACTIONS.FETCH_COLLECTION_LIST_FAILED] = (state: State): State => {
|
||||
return { ...state, fetchingMyCollections: false };
|
||||
};
|
||||
|
||||
reducers[ACTIONS.FETCH_CHANNEL_CLAIMS_STARTED] = (state: State, action: any): State => {
|
||||
const { uri, page } = action.data;
|
||||
const fetchingChannelClaims = Object.assign({}, state.fetchingChannelClaims);
|
||||
|
@ -380,9 +479,8 @@ reducers[ACTIONS.ABANDON_CLAIM_STARTED] = (state: State, action: any): State =>
|
|||
reducers[ACTIONS.UPDATE_PENDING_CLAIMS] = (state: State, action: any): State => {
|
||||
const { claims: pendingClaims }: { claims: Array<Claim> } = action.data;
|
||||
const byId = Object.assign({}, state.byId);
|
||||
const pendingById = Object.assign({}, state.pendingById);
|
||||
const byUri = Object.assign({}, state.claimsByUri);
|
||||
const pendingIds = state.pendingIds;
|
||||
const pendingIdSet = new Set(pendingIds);
|
||||
let myClaimIds = new Set(state.myClaims);
|
||||
const myChannelClaims = new Set(state.myChannelClaims);
|
||||
|
||||
|
@ -390,7 +488,7 @@ reducers[ACTIONS.UPDATE_PENDING_CLAIMS] = (state: State, action: any): State =>
|
|||
pendingClaims.forEach((claim: Claim) => {
|
||||
let newClaim;
|
||||
const { permanent_url: uri, claim_id: claimId, type, value_type: valueType } = claim;
|
||||
pendingIdSet.add(claimId);
|
||||
pendingById[claimId] = claim; // make sure we don't need to merge?
|
||||
const oldClaim = byId[claimId];
|
||||
if (oldClaim && oldClaim.canonical_url) {
|
||||
newClaim = mergeClaim(oldClaim, claim);
|
||||
|
@ -410,21 +508,22 @@ reducers[ACTIONS.UPDATE_PENDING_CLAIMS] = (state: State, action: any): State =>
|
|||
return Object.assign({}, state, {
|
||||
myClaims: Array.from(myClaimIds),
|
||||
byId,
|
||||
pendingById,
|
||||
myChannelClaims: Array.from(myChannelClaims),
|
||||
claimsByUri: byUri,
|
||||
pendingIds: Array.from(pendingIdSet),
|
||||
});
|
||||
};
|
||||
|
||||
reducers[ACTIONS.UPDATE_CONFIRMED_CLAIMS] = (state: State, action: any): State => {
|
||||
const { claims: confirmedClaims }: { claims: Array<Claim> } = action.data;
|
||||
const {
|
||||
claims: confirmedClaims,
|
||||
pending: pendingClaims,
|
||||
}: { claims: Array<Claim>, pending: { [string]: Claim } } = action.data;
|
||||
const byId = Object.assign({}, state.byId);
|
||||
const byUri = Object.assign({}, state.claimsByUri);
|
||||
const pendingIds = state.pendingIds;
|
||||
const pendingIdSet = new Set(pendingIds);
|
||||
|
||||
//
|
||||
confirmedClaims.forEach((claim: GenericClaim) => {
|
||||
const { permanent_url: permanentUri, claim_id: claimId, type } = claim;
|
||||
const { claim_id: claimId, type } = claim;
|
||||
let newClaim = claim;
|
||||
const oldClaim = byId[claimId];
|
||||
if (oldClaim && oldClaim.canonical_url) {
|
||||
|
@ -432,11 +531,10 @@ reducers[ACTIONS.UPDATE_CONFIRMED_CLAIMS] = (state: State, action: any): State =
|
|||
}
|
||||
if (type && type.match(/claim|update|channel/)) {
|
||||
byId[claimId] = newClaim;
|
||||
pendingIdSet.delete(claimId);
|
||||
}
|
||||
});
|
||||
return Object.assign({}, state, {
|
||||
pendingIds: Array.from(pendingIdSet),
|
||||
pendingById: pendingClaims,
|
||||
byId,
|
||||
claimsByUri: byUri,
|
||||
});
|
||||
|
@ -448,6 +546,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) {
|
||||
|
@ -456,12 +555,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,
|
||||
});
|
||||
|
@ -513,6 +614,61 @@ 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 => {
|
||||
const myCollections = state.myCollectionClaims || [];
|
||||
const myClaims = state.myClaims || [];
|
||||
const { claimId } = action.data;
|
||||
let myClaimIds = new Set(myClaims);
|
||||
let myCollectionClaimsSet = new Set(myCollections);
|
||||
myClaimIds.add(claimId);
|
||||
myCollectionClaimsSet.add(claimId);
|
||||
return Object.assign({}, state, {
|
||||
creatingCollection: false,
|
||||
myClaims: Array.from(myClaimIds),
|
||||
myCollectionClaims: Array.from(myCollectionClaimsSet),
|
||||
});
|
||||
};
|
||||
|
||||
reducers[ACTIONS.COLLECTION_PUBLISH_FAILED] = (state: State, action: any): State => {
|
||||
return Object.assign({}, state, {
|
||||
creatingCollection: false,
|
||||
createCollectionError: action.data.error,
|
||||
});
|
||||
};
|
||||
|
||||
reducers[ACTIONS.COLLECTION_PUBLISH_UPDATE_STARTED] = (state: State, action: any): State => {
|
||||
return Object.assign({}, state, {
|
||||
updateCollectionError: '',
|
||||
updatingCollection: true,
|
||||
});
|
||||
};
|
||||
|
||||
reducers[ACTIONS.COLLECTION_PUBLISH_UPDATE_COMPLETED] = (state: State, action: any): State => {
|
||||
return Object.assign({}, state, {
|
||||
updateCollectionError: '',
|
||||
updatingCollection: false,
|
||||
});
|
||||
};
|
||||
|
||||
reducers[ACTIONS.COLLECTION_PUBLISH_UPDATE_FAILED] = (state: State, action: any): State => {
|
||||
return Object.assign({}, state, {
|
||||
updateCollectionError: action.data.error,
|
||||
updatingCollection: false,
|
||||
});
|
||||
};
|
||||
|
||||
reducers[ACTIONS.IMPORT_CHANNEL_STARTED] = (state: State): State =>
|
||||
Object.assign({}, state, { pendingChannelImports: true });
|
||||
|
||||
|
@ -564,13 +720,23 @@ reducers[ACTIONS.CLAIM_SEARCH_FAILED] = (state: State, action: any): State => {
|
|||
const { query } = action.data;
|
||||
const claimSearchByQuery = Object.assign({}, state.claimSearchByQuery);
|
||||
const fetchingClaimSearchByQuery = Object.assign({}, state.fetchingClaimSearchByQuery);
|
||||
const claimSearchByQueryLastPageReached = Object.assign(
|
||||
{},
|
||||
state.claimSearchByQueryLastPageReached
|
||||
);
|
||||
|
||||
delete fetchingClaimSearchByQuery[query];
|
||||
|
||||
if (claimSearchByQuery[query] && claimSearchByQuery[query].length !== 0) {
|
||||
claimSearchByQueryLastPageReached[query] = true;
|
||||
} else {
|
||||
claimSearchByQuery[query] = null;
|
||||
}
|
||||
|
||||
return Object.assign({}, state, {
|
||||
fetchingClaimSearchByQuery,
|
||||
claimSearchByQuery,
|
||||
claimSearchByQueryLastPageReached,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
239
src/redux/reducers/collections.js
Normal file
239
src/redux/reducers/collections.js
Normal file
|
@ -0,0 +1,239 @@
|
|||
// @flow
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as COLS from 'constants/collections';
|
||||
|
||||
const getTimestamp = () => {
|
||||
return Math.floor(Date.now() / 1000);
|
||||
};
|
||||
|
||||
const defaultState: CollectionState = {
|
||||
builtin: {
|
||||
watchlater: {
|
||||
items: [],
|
||||
id: COLS.WATCH_LATER_ID,
|
||||
name: 'Watch Later',
|
||||
updatedAt: getTimestamp(),
|
||||
type: COLS.COL_TYPE_PLAYLIST,
|
||||
},
|
||||
favorites: {
|
||||
items: [],
|
||||
id: COLS.FAVORITES_ID,
|
||||
name: 'Favorites',
|
||||
type: COLS.COL_TYPE_PLAYLIST,
|
||||
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<string>}
|
||||
// entry
|
||||
const newListTemplate = {
|
||||
id: params.id,
|
||||
name: params.name,
|
||||
items: [],
|
||||
updatedAt: getTimestamp(),
|
||||
type: params.type,
|
||||
};
|
||||
|
||||
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]) {
|
||||
const newList = Object.assign({}, state[collectionKey]);
|
||||
delete newList[id];
|
||||
return {
|
||||
...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 {
|
||||
...state,
|
||||
edited: newEditList,
|
||||
unpublished: newUnpublishedList,
|
||||
pending: newPendingList,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.COLLECTION_PENDING]: (state, action) => {
|
||||
const { localId, claimId } = action.data;
|
||||
const {
|
||||
resolved: resolvedList,
|
||||
edited: editList,
|
||||
unpublished: unpublishedList,
|
||||
pending: pendingList,
|
||||
} = state;
|
||||
|
||||
const newEditList = Object.assign({}, editList);
|
||||
const newResolvedList = Object.assign({}, resolvedList);
|
||||
const newUnpublishedList = Object.assign({}, unpublishedList);
|
||||
const newPendingList = Object.assign({}, pendingList);
|
||||
|
||||
if (localId) {
|
||||
// new publish
|
||||
newPendingList[claimId] = Object.assign({}, newUnpublishedList[localId] || {});
|
||||
delete newUnpublishedList[localId];
|
||||
} else {
|
||||
// edit update
|
||||
newPendingList[claimId] = Object.assign(
|
||||
{},
|
||||
newEditList[claimId] || newResolvedList[claimId]
|
||||
);
|
||||
delete newEditList[claimId];
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
edited: newEditList,
|
||||
unpublished: newUnpublishedList,
|
||||
pending: newPendingList,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.COLLECTION_EDIT]: (state, action) => {
|
||||
const { id, collectionKey, collection } = action.data;
|
||||
|
||||
if (COLS.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 {
|
||||
builtinCollections,
|
||||
savedCollections,
|
||||
unpublishedCollections,
|
||||
editedCollections,
|
||||
} = action.data;
|
||||
return {
|
||||
...state,
|
||||
edited: editedCollections || state.edited,
|
||||
unpublished: unpublishedCollections || state.unpublished,
|
||||
builtin: builtinCollections || state.builtin,
|
||||
saved: savedCollections || state.saved,
|
||||
};
|
||||
},
|
||||
[ACTIONS.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, {
|
||||
...state,
|
||||
pending: newPending,
|
||||
resolved: newResolved,
|
||||
edited: newEdited,
|
||||
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 };
|
|
@ -9,6 +9,7 @@ type PublishState = {
|
|||
editingURI: ?string,
|
||||
fileText: ?string,
|
||||
filePath: ?string,
|
||||
remoteFileUrl: ?string,
|
||||
contentIsFree: boolean,
|
||||
fileDur: number,
|
||||
fileSize: number,
|
||||
|
@ -21,8 +22,11 @@ type PublishState = {
|
|||
thumbnail_url: string,
|
||||
thumbnailPath: string,
|
||||
uploadThumbnailStatus: string,
|
||||
thumbnailError: ?boolean,
|
||||
description: string,
|
||||
language: string,
|
||||
releaseTime: ?number,
|
||||
releaseTimeEdited: ?number,
|
||||
channel: string,
|
||||
channelId: ?string,
|
||||
name: string,
|
||||
|
@ -43,6 +47,7 @@ const defaultState: PublishState = {
|
|||
fileDur: 0,
|
||||
fileSize: 0,
|
||||
fileVid: false,
|
||||
remoteFileUrl: undefined,
|
||||
contentIsFree: true,
|
||||
fee: {
|
||||
amount: 1,
|
||||
|
@ -52,14 +57,17 @@ const defaultState: PublishState = {
|
|||
thumbnail_url: '',
|
||||
thumbnailPath: '',
|
||||
uploadThumbnailStatus: THUMBNAIL_STATUSES.API_DOWN,
|
||||
thumbnailError: undefined,
|
||||
description: '',
|
||||
language: '',
|
||||
releaseTime: undefined,
|
||||
releaseTimeEdited: undefined,
|
||||
nsfw: false,
|
||||
channel: CHANNEL_ANONYMOUS,
|
||||
channelId: '',
|
||||
name: '',
|
||||
nameError: undefined,
|
||||
bid: 0.1,
|
||||
bid: 0.01,
|
||||
bidError: undefined,
|
||||
licenseType: 'None',
|
||||
otherLicenseDescription: 'All rights reserved',
|
||||
|
@ -83,8 +91,11 @@ export const publishReducer = handleActions(
|
|||
},
|
||||
[ACTIONS.CLEAR_PUBLISH]: (state: PublishState): PublishState => ({
|
||||
...defaultState,
|
||||
uri: undefined,
|
||||
channel: state.channel,
|
||||
bid: state.bid,
|
||||
optimize: state.optimize,
|
||||
language: state.language,
|
||||
}),
|
||||
[ACTIONS.PUBLISH_START]: (state: PublishState): PublishState => ({
|
||||
...state,
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
import { SEARCH_OPTIONS } from 'constants/search';
|
||||
|
||||
const defaultState = {
|
||||
isActive: false, // does the user have any typed text in the search input
|
||||
focused: false, // is the search input focused
|
||||
searchQuery: '', // needs to be an empty string for input focusing
|
||||
options: {
|
||||
[SEARCH_OPTIONS.RESULT_COUNT]: 30,
|
||||
[SEARCH_OPTIONS.CLAIM_TYPE]: SEARCH_OPTIONS.INCLUDE_FILES_AND_CHANNELS,
|
||||
[SEARCH_OPTIONS.MEDIA_AUDIO]: true,
|
||||
[SEARCH_OPTIONS.MEDIA_VIDEO]: true,
|
||||
[SEARCH_OPTIONS.MEDIA_TEXT]: true,
|
||||
[SEARCH_OPTIONS.MEDIA_IMAGE]: true,
|
||||
[SEARCH_OPTIONS.MEDIA_APPLICATION]: true,
|
||||
},
|
||||
suggestions: {},
|
||||
urisByQuery: {},
|
||||
resolvedResultsByQuery: {},
|
||||
resolvedResultsByQueryLastPageReached: {},
|
||||
};
|
||||
|
||||
export const searchReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.SEARCH_START]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
searching: true,
|
||||
}),
|
||||
[ACTIONS.SEARCH_SUCCESS]: (state: SearchState, action: SearchSuccess): SearchState => {
|
||||
const { query, uris } = action.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
searching: false,
|
||||
urisByQuery: Object.assign({}, state.urisByQuery, { [query]: uris }),
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.SEARCH_FAIL]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
searching: false,
|
||||
}),
|
||||
|
||||
[ACTIONS.RESOLVED_SEARCH_START]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
searching: true,
|
||||
}),
|
||||
[ACTIONS.RESOLVED_SEARCH_SUCCESS]: (
|
||||
state: SearchState,
|
||||
action: ResolvedSearchSuccess
|
||||
): SearchState => {
|
||||
const resolvedResultsByQuery = Object.assign({}, state.resolvedResultsByQuery);
|
||||
const resolvedResultsByQueryLastPageReached = Object.assign(
|
||||
{},
|
||||
state.resolvedResultsByQueryLastPageReached
|
||||
);
|
||||
const { append, query, results, pageSize } = action.data;
|
||||
|
||||
if (append) {
|
||||
// todo: check for duplicates when concatenating?
|
||||
resolvedResultsByQuery[query] =
|
||||
resolvedResultsByQuery[query] && resolvedResultsByQuery[query].length
|
||||
? resolvedResultsByQuery[query].concat(results)
|
||||
: results;
|
||||
} else {
|
||||
resolvedResultsByQuery[query] = results;
|
||||
}
|
||||
|
||||
// the returned number of urls is less than the page size, so we're on the last page
|
||||
resolvedResultsByQueryLastPageReached[query] = results.length < pageSize;
|
||||
|
||||
return {
|
||||
...state,
|
||||
searching: false,
|
||||
resolvedResultsByQuery,
|
||||
resolvedResultsByQueryLastPageReached,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.RESOLVED_SEARCH_FAIL]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
searching: false,
|
||||
}),
|
||||
|
||||
[ACTIONS.UPDATE_SEARCH_QUERY]: (
|
||||
state: SearchState,
|
||||
action: UpdateSearchQuery
|
||||
): SearchState => ({
|
||||
...state,
|
||||
searchQuery: action.data.query,
|
||||
isActive: true,
|
||||
}),
|
||||
|
||||
[ACTIONS.UPDATE_SEARCH_SUGGESTIONS]: (
|
||||
state: SearchState,
|
||||
action: UpdateSearchSuggestions
|
||||
): SearchState => ({
|
||||
...state,
|
||||
suggestions: {
|
||||
...state.suggestions,
|
||||
[action.data.query]: action.data.suggestions,
|
||||
},
|
||||
}),
|
||||
|
||||
// sets isActive to false so the uri will be populated correctly if the
|
||||
// user is on a file page. The search query will still be present on any
|
||||
// other page
|
||||
[ACTIONS.DISMISS_NOTIFICATION]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
isActive: false,
|
||||
}),
|
||||
|
||||
[ACTIONS.SEARCH_FOCUS]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
focused: true,
|
||||
}),
|
||||
[ACTIONS.SEARCH_BLUR]: (state: SearchState): SearchState => ({
|
||||
...state,
|
||||
focused: false,
|
||||
}),
|
||||
[ACTIONS.UPDATE_SEARCH_OPTIONS]: (
|
||||
state: SearchState,
|
||||
action: UpdateSearchOptions
|
||||
): SearchState => {
|
||||
const { options: oldOptions } = state;
|
||||
const newOptions = action.data;
|
||||
const options = { ...oldOptions, ...newOptions };
|
||||
return {
|
||||
...state,
|
||||
options,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
|
@ -1,86 +0,0 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
import { DEFAULT_KNOWN_TAGS, DEFAULT_FOLLOWED_TAGS } from 'constants/tags';
|
||||
|
||||
function getDefaultKnownTags() {
|
||||
return DEFAULT_FOLLOWED_TAGS.concat(DEFAULT_KNOWN_TAGS).reduce(
|
||||
(tagsMap, tag) => ({
|
||||
...tagsMap,
|
||||
[tag]: { name: tag },
|
||||
}),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
const defaultState: TagState = {
|
||||
followedTags: [],
|
||||
knownTags: getDefaultKnownTags(),
|
||||
};
|
||||
|
||||
export const tagsReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.TOGGLE_TAG_FOLLOW]: (state: TagState, action: TagAction): TagState => {
|
||||
const { followedTags } = state;
|
||||
const { name } = action.data;
|
||||
|
||||
let newFollowedTags = followedTags.slice();
|
||||
|
||||
if (newFollowedTags.includes(name)) {
|
||||
newFollowedTags = newFollowedTags.filter(tag => tag !== name);
|
||||
} else {
|
||||
newFollowedTags.push(name);
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
followedTags: newFollowedTags,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.TAG_ADD]: (state: TagState, action: TagAction) => {
|
||||
const { knownTags } = state;
|
||||
const { name } = action.data;
|
||||
|
||||
let newKnownTags = { ...knownTags };
|
||||
newKnownTags[name] = { name };
|
||||
|
||||
return {
|
||||
...state,
|
||||
knownTags: newKnownTags,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.TAG_DELETE]: (state: TagState, action: TagAction) => {
|
||||
const { knownTags, followedTags } = state;
|
||||
const { name } = action.data;
|
||||
|
||||
let newKnownTags = { ...knownTags };
|
||||
delete newKnownTags[name];
|
||||
const newFollowedTags = followedTags.filter(tag => tag !== name);
|
||||
|
||||
return {
|
||||
...state,
|
||||
knownTags: newKnownTags,
|
||||
followedTags: newFollowedTags,
|
||||
};
|
||||
},
|
||||
[ACTIONS.USER_STATE_POPULATE]: (
|
||||
state: TagState,
|
||||
action: { data: { tags: ?Array<string> } }
|
||||
) => {
|
||||
const { tags } = action.data;
|
||||
if (Array.isArray(tags)) {
|
||||
return {
|
||||
...state,
|
||||
followedTags: tags,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
|
@ -45,10 +45,17 @@ type WalletState = {
|
|||
walletLockResult: ?boolean,
|
||||
walletReconnecting: boolean,
|
||||
txoFetchParams: {},
|
||||
utxoCounts: {},
|
||||
txoPage: any,
|
||||
fetchId: string,
|
||||
fetchingTxos: boolean,
|
||||
fetchingTxosError?: string,
|
||||
consolidatingUtxos: boolean,
|
||||
pendingConsolidateTxid?: string,
|
||||
massClaimingTips: boolean,
|
||||
pendingMassClaimTxid?: string,
|
||||
pendingSupportTransactions: {}, // { claimId: {txid: 123, amount 12.3}, }
|
||||
pendingTxos: Array<string>,
|
||||
abandonClaimSupportError?: string,
|
||||
};
|
||||
|
||||
|
@ -85,10 +92,20 @@ const defaultState = {
|
|||
transactionListFilter: 'all',
|
||||
walletReconnecting: false,
|
||||
txoFetchParams: {},
|
||||
utxoCounts: {},
|
||||
fetchingUtxoCounts: false,
|
||||
fetchingUtxoError: undefined,
|
||||
consolidatingUtxos: false,
|
||||
pendingConsolidateTxid: null,
|
||||
massClaimingTips: false,
|
||||
pendingMassClaimTxid: null,
|
||||
txoPage: {},
|
||||
fetchId: '',
|
||||
fetchingTxos: false,
|
||||
fetchingTxosError: undefined,
|
||||
pendingSupportTransactions: {},
|
||||
pendingTxos: [],
|
||||
|
||||
abandonClaimSupportError: undefined,
|
||||
};
|
||||
|
||||
|
@ -114,18 +131,26 @@ export const walletReducer = handleActions(
|
|||
};
|
||||
},
|
||||
|
||||
[ACTIONS.FETCH_TXO_PAGE_STARTED]: (state: WalletState) => {
|
||||
[ACTIONS.FETCH_TXO_PAGE_STARTED]: (state: WalletState, action) => {
|
||||
return {
|
||||
...state,
|
||||
fetchId: action.data,
|
||||
fetchingTxos: true,
|
||||
fetchingTxosError: undefined,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.FETCH_TXO_PAGE_COMPLETED]: (state: WalletState, action) => {
|
||||
if (state.fetchId !== action.data.fetchId) {
|
||||
// Leave 'state' and 'fetchingTxos' alone. The latter would ensure
|
||||
// the spiner would continue spinning for the latest transaction.
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
txoPage: action.data,
|
||||
txoPage: action.data.result,
|
||||
fetchId: '',
|
||||
fetchingTxos: false,
|
||||
};
|
||||
},
|
||||
|
@ -134,10 +159,104 @@ export const walletReducer = handleActions(
|
|||
return {
|
||||
...state,
|
||||
txoPage: {},
|
||||
fetchId: '',
|
||||
fetchingTxos: false,
|
||||
fetchingTxosError: action.data,
|
||||
};
|
||||
},
|
||||
[ACTIONS.FETCH_UTXO_COUNT_STARTED]: (state: WalletState) => {
|
||||
return {
|
||||
...state,
|
||||
fetchingUtxoCounts: true,
|
||||
fetchingUtxoError: undefined,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.FETCH_UTXO_COUNT_COMPLETED]: (state: WalletState, action) => {
|
||||
return {
|
||||
...state,
|
||||
utxoCounts: action.data,
|
||||
fetchingUtxoCounts: false,
|
||||
};
|
||||
},
|
||||
[ACTIONS.FETCH_UTXO_COUNT_FAILED]: (state: WalletState, action) => {
|
||||
return {
|
||||
...state,
|
||||
utxoCounts: {},
|
||||
fetchingUtxoCounts: false,
|
||||
fetchingUtxoError: action.data,
|
||||
};
|
||||
},
|
||||
[ACTIONS.DO_UTXO_CONSOLIDATE_STARTED]: (state: WalletState) => {
|
||||
return {
|
||||
...state,
|
||||
consolidatingUtxos: true,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.DO_UTXO_CONSOLIDATE_COMPLETED]: (state: WalletState, action) => {
|
||||
const { txid } = action.data;
|
||||
return {
|
||||
...state,
|
||||
consolidatingUtxos: false,
|
||||
pendingConsolidateTxid: txid,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.DO_UTXO_CONSOLIDATE_FAILED]: (state: WalletState, action) => {
|
||||
return {
|
||||
...state,
|
||||
consolidatingUtxos: false,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.TIP_CLAIM_MASS_STARTED]: (state: WalletState) => {
|
||||
return {
|
||||
...state,
|
||||
massClaimingTips: true,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.TIP_CLAIM_MASS_COMPLETED]: (state: WalletState, action) => {
|
||||
const { txid } = action.data;
|
||||
return {
|
||||
...state,
|
||||
massClaimingTips: false,
|
||||
pendingMassClaimTxid: txid,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.TIP_CLAIM_MASS_FAILED]: (state: WalletState, action) => {
|
||||
return {
|
||||
...state,
|
||||
massClaimingTips: false,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.PENDING_CONSOLIDATED_TXOS_UPDATED]: (state: WalletState, action) => {
|
||||
const { pendingTxos, pendingMassClaimTxid, pendingConsolidateTxid } = state;
|
||||
|
||||
const { txids, remove } = action.data;
|
||||
|
||||
if (remove) {
|
||||
const newTxos = pendingTxos.filter(txo => !txids.includes(txo));
|
||||
const newPendingMassClaimTxid = txids.includes(pendingMassClaimTxid)
|
||||
? undefined
|
||||
: pendingMassClaimTxid;
|
||||
const newPendingConsolidateTxid = txids.includes(pendingConsolidateTxid)
|
||||
? undefined
|
||||
: pendingConsolidateTxid;
|
||||
return {
|
||||
...state,
|
||||
pendingTxos: newTxos,
|
||||
pendingMassClaimTxid: newPendingMassClaimTxid,
|
||||
pendingConsolidateTxid: newPendingConsolidateTxid,
|
||||
};
|
||||
} else {
|
||||
const newPendingSet = new Set([...pendingTxos, ...txids]);
|
||||
return { ...state, pendingTxos: Array.from(newPendingSet) };
|
||||
}
|
||||
},
|
||||
|
||||
[ACTIONS.UPDATE_TXO_FETCH_PARAMS]: (state: WalletState, action) => {
|
||||
return {
|
||||
|
@ -186,7 +305,7 @@ export const walletReducer = handleActions(
|
|||
return {
|
||||
...state,
|
||||
supports: byOutpoint,
|
||||
abandoningSupportsById: currentlyAbandoning,
|
||||
abandoningSupportsByOutpoint: currentlyAbandoning,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -205,10 +324,15 @@ export const walletReducer = handleActions(
|
|||
},
|
||||
|
||||
[ACTIONS.ABANDON_CLAIM_SUPPORT_COMPLETED]: (state: WalletState, action: any): WalletState => {
|
||||
const { claimId, type, txid, effective }: { claimId: string, type: string, txid: string, effective: string } = action.data;
|
||||
const {
|
||||
claimId,
|
||||
type,
|
||||
txid,
|
||||
effective,
|
||||
}: { claimId: string, type: string, txid: string, effective: string } = action.data;
|
||||
const pendingtxs = Object.assign({}, state.pendingSupportTransactions);
|
||||
|
||||
pendingtxs[claimId] = {txid, type, effective};
|
||||
pendingtxs[claimId] = { txid, type, effective };
|
||||
|
||||
return {
|
||||
...state,
|
||||
|
@ -225,7 +349,6 @@ export const walletReducer = handleActions(
|
|||
},
|
||||
|
||||
[ACTIONS.PENDING_SUPPORTS_UPDATED]: (state: WalletState, action: any): WalletState => {
|
||||
|
||||
return {
|
||||
...state,
|
||||
pendingSupportTransactions: action.data,
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
// @flow
|
||||
import { normalizeURI, buildURI, parseURI } from 'lbryURI';
|
||||
import {
|
||||
selectResolvedSearchResultsByQuery,
|
||||
selectSearchUrisByQuery,
|
||||
} from 'redux/selectors/search';
|
||||
import { normalizeURI, parseURI } from 'lbryURI';
|
||||
import { selectSupportsByOutpoint } from 'redux/selectors/wallet';
|
||||
import { createSelector } from 'reselect';
|
||||
import { isClaimNsfw, filterClaims } from 'util/claim';
|
||||
import { getSearchQueryString } from 'util/query-params';
|
||||
import { PAGE_SIZE } from 'constants/claim';
|
||||
import * as CLAIM from 'constants/claim';
|
||||
|
||||
const selectState = state => state.claims || {};
|
||||
|
||||
export const selectClaimsById = createSelector(
|
||||
export const selectById = createSelector(
|
||||
selectState,
|
||||
state => state.byId || {}
|
||||
);
|
||||
|
||||
export const selectPendingClaimsById = createSelector(
|
||||
selectState,
|
||||
state => state.pendingById || {}
|
||||
);
|
||||
|
||||
export const selectClaimsById = createSelector(
|
||||
selectById,
|
||||
selectPendingClaimsById,
|
||||
(byId, pendingById) => {
|
||||
return Object.assign(byId, pendingById); // do I need merged to keep metadata?
|
||||
}
|
||||
);
|
||||
|
||||
export const selectClaimIdsByUri = createSelector(
|
||||
selectState,
|
||||
state => state.claimsByUri || {}
|
||||
|
@ -77,28 +85,53 @@ export const selectAllClaimsByChannel = createSelector(
|
|||
|
||||
export const selectPendingIds = createSelector(
|
||||
selectState,
|
||||
state => state.pendingIds || []
|
||||
state => Object.keys(state.pendingById) || []
|
||||
);
|
||||
|
||||
export const selectPendingClaims = createSelector(
|
||||
selectPendingClaimsById,
|
||||
pendingById => Object.values(pendingById)
|
||||
);
|
||||
|
||||
export const makeSelectClaimIsPending = (uri: string) =>
|
||||
createSelector(
|
||||
selectClaimIdsByUri,
|
||||
selectPendingIds,
|
||||
(idsByUri, pendingIds) => {
|
||||
selectPendingClaimsById,
|
||||
(idsByUri, pendingById) => {
|
||||
const claimId = idsByUri[normalizeURI(uri)];
|
||||
|
||||
if (claimId) {
|
||||
return pendingIds.some(i => i === claimId);
|
||||
return Boolean(pendingById[claimId]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectClaimIdIsPending = (claimId: string) =>
|
||||
createSelector(
|
||||
selectPendingClaimsById,
|
||||
pendingById => {
|
||||
return Boolean(pendingById[claimId]);
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectClaimIdForUri = (uri: string) =>
|
||||
createSelector(
|
||||
selectClaimIdsByUri,
|
||||
claimIds => claimIds[uri]
|
||||
);
|
||||
|
||||
export const selectReflectingById = createSelector(
|
||||
selectState,
|
||||
state => state.reflectingById
|
||||
);
|
||||
|
||||
export const makeSelectClaimForClaimId = (claimId: string) =>
|
||||
createSelector(
|
||||
selectClaimsById,
|
||||
byId => byId[claimId]
|
||||
);
|
||||
|
||||
export const makeSelectClaimForUri = (uri: string, returnRepost: boolean = true) =>
|
||||
createSelector(
|
||||
selectClaimIdsByUri,
|
||||
|
@ -124,14 +157,17 @@ export const makeSelectClaimForUri = (uri: string, returnRepost: boolean = true)
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const repostedClaim = claim.reposted_claim;
|
||||
const repostedClaim = claim && claim.reposted_claim;
|
||||
if (repostedClaim && returnRepost) {
|
||||
const channelUrl = claim.signing_channel && claim.signing_channel.canonical_url;
|
||||
const channelUrl =
|
||||
claim.signing_channel &&
|
||||
(claim.signing_channel.canonical_url || claim.signing_channel.permanent_url);
|
||||
|
||||
return {
|
||||
...repostedClaim,
|
||||
repost_url: uri,
|
||||
repost_url: normalizeURI(uri),
|
||||
repost_channel_url: channelUrl,
|
||||
repost_bid_amount: claim && claim.meta && claim.meta.effective_amount,
|
||||
};
|
||||
} else {
|
||||
return claim;
|
||||
|
@ -165,6 +201,22 @@ export const selectAbandoningIds = createSelector(
|
|||
state => Object.keys(state.abandoningById || {})
|
||||
);
|
||||
|
||||
export const makeSelectAbandoningClaimById = (claimId: string) =>
|
||||
createSelector(
|
||||
selectAbandoningIds,
|
||||
ids => ids.includes(claimId)
|
||||
);
|
||||
|
||||
export const makeSelectIsAbandoningClaimForUri = (uri: string) =>
|
||||
createSelector(
|
||||
selectClaimIdsByUri,
|
||||
selectAbandoningIds,
|
||||
(claimIdsByUri, abandoningById) => {
|
||||
const claimId = claimIdsByUri[normalizeURI(uri)];
|
||||
return abandoningById.indexOf(claimId) >= 0;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectMyActiveClaims = createSelector(
|
||||
selectMyClaimsRaw,
|
||||
selectAbandoningIds,
|
||||
|
@ -243,8 +295,8 @@ export const makeSelectMyPurchasesForPage = (query: ?string, page: number = 1) =
|
|||
|
||||
const fileInfos = myPurchases.map(uri => claimsByUri[uri]);
|
||||
const matchingFileInfos = filterClaims(fileInfos, query);
|
||||
const start = (Number(page) - 1) * Number(PAGE_SIZE);
|
||||
const end = Number(page) * Number(PAGE_SIZE);
|
||||
const start = (Number(page) - 1) * Number(CLAIM.PAGE_SIZE);
|
||||
const end = Number(page) * Number(CLAIM.PAGE_SIZE);
|
||||
return matchingFileInfos && matchingFileInfos.length
|
||||
? matchingFileInfos
|
||||
.slice(start, end)
|
||||
|
@ -286,6 +338,7 @@ export const makeSelectClaimsInChannelForPage = (uri: string, page?: number) =>
|
|||
}
|
||||
);
|
||||
|
||||
// THIS IS LEFT OVER FROM ONE TAB CHANNEL_CONTENT
|
||||
export const makeSelectTotalClaimsInChannelSearch = (uri: string) =>
|
||||
createSelector(
|
||||
selectClaimsById,
|
||||
|
@ -296,6 +349,7 @@ export const makeSelectTotalClaimsInChannelSearch = (uri: string) =>
|
|||
}
|
||||
);
|
||||
|
||||
// THIS IS LEFT OVER FROM ONE_TAB CHANNEL CONTENT
|
||||
export const makeSelectTotalPagesInChannelSearch = (uri: string) =>
|
||||
createSelector(
|
||||
selectClaimsById,
|
||||
|
@ -306,21 +360,6 @@ export const makeSelectTotalPagesInChannelSearch = (uri: string) =>
|
|||
}
|
||||
);
|
||||
|
||||
export const makeSelectClaimsInChannelForCurrentPageState = (uri: string) =>
|
||||
createSelector(
|
||||
selectClaimsById,
|
||||
selectAllClaimsByChannel,
|
||||
selectCurrentChannelPage,
|
||||
(byId, allClaims, page) => {
|
||||
const byChannel = allClaims[uri] || {};
|
||||
const claimIds = byChannel[page || 1];
|
||||
|
||||
if (!claimIds) return claimIds;
|
||||
|
||||
return claimIds.map(claimId => byId[claimId]);
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectMetadataForUri = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
|
@ -372,6 +411,19 @@ export const makeSelectAmountForUri = (uri: string) =>
|
|||
}
|
||||
);
|
||||
|
||||
export const makeSelectEffectiveAmountForUri = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri, false),
|
||||
claim => {
|
||||
return (
|
||||
claim &&
|
||||
claim.meta &&
|
||||
typeof claim.meta.effective_amount === 'string' &&
|
||||
Number(claim.meta.effective_amount)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectContentTypeForUri = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
|
@ -448,7 +500,9 @@ export const selectMyClaims = createSelector(
|
|||
export const selectMyClaimsWithoutChannels = createSelector(
|
||||
selectMyClaims,
|
||||
myClaims =>
|
||||
myClaims.filter(claim => !claim.name.match(/^@/)).sort((a, b) => a.timestamp - b.timestamp)
|
||||
myClaims
|
||||
.filter(claim => claim && !claim.name.match(/^@/))
|
||||
.sort((a, b) => a.timestamp - b.timestamp)
|
||||
);
|
||||
|
||||
export const selectMyClaimUrisWithoutChannels = createSelector(
|
||||
|
@ -492,6 +546,11 @@ export const selectFetchingMyChannels = createSelector(
|
|||
state => state.fetchingMyChannels
|
||||
);
|
||||
|
||||
export const selectFetchingMyCollections = createSelector(
|
||||
selectState,
|
||||
state => state.fetchingMyCollections
|
||||
);
|
||||
|
||||
export const selectMyChannelClaims = createSelector(
|
||||
selectState,
|
||||
selectClaimsById,
|
||||
|
@ -518,6 +577,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 || []
|
||||
|
@ -544,16 +608,35 @@ export const selectChannelClaimCounts = createSelector(
|
|||
state => state.channelClaimCounts || {}
|
||||
);
|
||||
|
||||
export const makeSelectPendingClaimForUri = (uri: string) =>
|
||||
createSelector(
|
||||
selectPendingClaimsById,
|
||||
pendingById => {
|
||||
let uriStreamName;
|
||||
let uriChannelName;
|
||||
try {
|
||||
({ streamName: uriStreamName, channelName: uriChannelName } = parseURI(uri));
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
const pendingClaims = (Object.values(pendingById): any);
|
||||
const matchingClaim = pendingClaims.find((claim: GenericClaim) => {
|
||||
return claim.normalized_name === uriChannelName || claim.normalized_name === uriStreamName;
|
||||
});
|
||||
return matchingClaim || null;
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectTotalItemsForChannel = (uri: string) =>
|
||||
createSelector(
|
||||
selectChannelClaimCounts,
|
||||
byUri => byUri && byUri[uri]
|
||||
byUri => byUri && byUri[normalizeURI(uri)]
|
||||
);
|
||||
|
||||
export const makeSelectTotalPagesForChannel = (uri: string, pageSize: number = 10) =>
|
||||
createSelector(
|
||||
selectChannelClaimCounts,
|
||||
byUri => byUri && byUri[uri] && Math.ceil(byUri[uri] / pageSize)
|
||||
byUri => byUri && byUri[uri] && Math.ceil(byUri[normalizeURI(uri)] / pageSize)
|
||||
);
|
||||
|
||||
export const makeSelectNsfwCountFromUris = (uris: Array<string>) =>
|
||||
|
@ -569,27 +652,6 @@ export const makeSelectNsfwCountFromUris = (uris: Array<string>) =>
|
|||
}, 0)
|
||||
);
|
||||
|
||||
export const makeSelectNsfwCountForChannel = (uri: string) =>
|
||||
createSelector(
|
||||
selectClaimsById,
|
||||
selectAllClaimsByChannel,
|
||||
selectCurrentChannelPage,
|
||||
(byId, allClaims, page) => {
|
||||
const byChannel = allClaims[uri] || {};
|
||||
const claimIds = byChannel[page || 1];
|
||||
|
||||
if (!claimIds) return 0;
|
||||
|
||||
return claimIds.reduce((acc, claimId) => {
|
||||
const claim = byId[claimId];
|
||||
if (isClaimNsfw(claim)) {
|
||||
return acc + 1;
|
||||
}
|
||||
return acc;
|
||||
}, 0);
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectOmittedCountForChannel = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectTotalItemsForChannel(uri),
|
||||
|
@ -617,53 +679,6 @@ export const makeSelectClaimIsNsfw = (uri: string): boolean =>
|
|||
}
|
||||
);
|
||||
|
||||
export const makeSelectRecommendedContentForUri = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
selectSearchUrisByQuery,
|
||||
makeSelectClaimIsNsfw(uri),
|
||||
(claim, searchUrisByQuery, isMature) => {
|
||||
const atVanityURI = !uri.includes('#');
|
||||
|
||||
let recommendedContent;
|
||||
if (claim) {
|
||||
// always grab full URL - this can change once search returns canonical
|
||||
const currentUri = buildURI({ streamClaimId: claim.claim_id, streamName: claim.name });
|
||||
|
||||
const { title } = claim.value;
|
||||
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options: {
|
||||
related_to?: string,
|
||||
nsfw?: boolean,
|
||||
isBackgroundSearch?: boolean,
|
||||
} = { related_to: claim.claim_id, isBackgroundSearch: true };
|
||||
|
||||
if (!isMature) {
|
||||
options['nsfw'] = false;
|
||||
}
|
||||
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options);
|
||||
|
||||
let searchUris = searchUrisByQuery[searchQuery];
|
||||
if (searchUris) {
|
||||
searchUris = searchUris.filter(searchUri => searchUri !== currentUri);
|
||||
recommendedContent = searchUris;
|
||||
}
|
||||
}
|
||||
|
||||
return recommendedContent;
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectFirstRecommendedFileForUri = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectRecommendedContentForUri(uri),
|
||||
recommendedContent => (recommendedContent ? recommendedContent[0] : null)
|
||||
);
|
||||
|
||||
// Returns the associated channel uri for a given claim uri
|
||||
// accepts a regular claim uri lbry://something
|
||||
// returns the channel uri that created this claim lbry://@channel
|
||||
|
@ -685,6 +700,29 @@ export const makeSelectChannelForClaimUri = (uri: string, includePrefix: boolean
|
|||
}
|
||||
);
|
||||
|
||||
export const makeSelectChannelPermUrlForClaimUri = (uri: string, includePrefix: boolean = false) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
(claim: ?Claim) => {
|
||||
if (claim && claim.value_type === 'channel') {
|
||||
return claim.permanent_url;
|
||||
}
|
||||
if (!claim || !claim.signing_channel || !claim.is_channel_signature_valid) {
|
||||
return null;
|
||||
}
|
||||
return claim.signing_channel.permanent_url;
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectMyChannelPermUrlForName = (name: string) =>
|
||||
createSelector(
|
||||
selectMyChannelClaims,
|
||||
claims => {
|
||||
const matchingClaim = claims && claims.find(claim => claim.name === name);
|
||||
return matchingClaim ? matchingClaim.permanent_url : null;
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectTagsForUri = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectMetadataForUri(uri),
|
||||
|
@ -777,8 +815,8 @@ export const makeSelectMyStreamUrlsForPage = (page: number = 1) =>
|
|||
createSelector(
|
||||
selectMyClaimUrisWithoutChannels,
|
||||
urls => {
|
||||
const start = (Number(page) - 1) * Number(PAGE_SIZE);
|
||||
const end = Number(page) * Number(PAGE_SIZE);
|
||||
const start = (Number(page) - 1) * Number(CLAIM.PAGE_SIZE);
|
||||
const end = Number(page) * Number(CLAIM.PAGE_SIZE);
|
||||
|
||||
return urls && urls.length ? urls.slice(start, end) : [];
|
||||
}
|
||||
|
@ -789,53 +827,96 @@ export const selectMyStreamUrlsCount = createSelector(
|
|||
channels => channels.length
|
||||
);
|
||||
|
||||
export const makeSelectResolvedRecommendedContentForUri = (
|
||||
uri: string,
|
||||
size: number,
|
||||
claimId: string,
|
||||
claimName: string,
|
||||
claimTitle: string
|
||||
) =>
|
||||
export const makeSelectTagInClaimOrChannelForUri = (uri: string, tag: string) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
selectResolvedSearchResultsByQuery,
|
||||
makeSelectClaimIsNsfw(uri),
|
||||
(claim, resolvedResultsByQuery, isMature) => {
|
||||
const atVanityURI = !uri.includes('#');
|
||||
|
||||
let currentUri;
|
||||
let recommendedContent;
|
||||
let title;
|
||||
if (claim) {
|
||||
// always grab full URL - this can change once search returns canonical
|
||||
currentUri = buildURI({ streamClaimId: claim.claim_id, streamName: claim.name });
|
||||
title = claim.value ? claim.value.title : null;
|
||||
} else {
|
||||
// for cases on mobile where the claim may not have been resolved ()
|
||||
currentUri = buildURI({ streamClaimId: claimId, streamName: claimName });
|
||||
title = claimTitle;
|
||||
}
|
||||
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options: {
|
||||
related_to?: string,
|
||||
nsfw?: boolean,
|
||||
isBackgroundSearch?: boolean,
|
||||
} = { related_to: claim ? claim.claim_id : claimId, size, isBackgroundSearch: false };
|
||||
|
||||
const searchQuery = getSearchQueryString(title.replace(/\//, ' '), options);
|
||||
let results = resolvedResultsByQuery[searchQuery];
|
||||
if (results) {
|
||||
results = results.filter(
|
||||
result =>
|
||||
buildURI({ streamClaimId: result.claimId, streamName: result.name }) !== currentUri
|
||||
);
|
||||
recommendedContent = results;
|
||||
}
|
||||
|
||||
return recommendedContent;
|
||||
claim => {
|
||||
const claimTags = (claim && claim.value && claim.value.tags) || [];
|
||||
const channelTags =
|
||||
(claim &&
|
||||
claim.signing_channel &&
|
||||
claim.signing_channel.value &&
|
||||
claim.signing_channel.value.tags) ||
|
||||
[];
|
||||
return claimTags.includes(tag) || channelTags.includes(tag);
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectClaimHasSource = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
claim => {
|
||||
if (!claim) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(claim.value.source);
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectClaimIsStreamPlaceholder = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
claim => {
|
||||
if (!claim) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(claim.value_type === 'stream' && !claim.value.source);
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectTotalStakedAmountForChannelUri = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectClaimForUri(uri),
|
||||
claim => {
|
||||
if (!claim || !claim.amount || !claim.meta || !claim.meta.support_amount) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return parseFloat(claim.amount) + parseFloat(claim.meta.support_amount) || 0;
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectStakedLevelForChannelUri = (uri: string) =>
|
||||
createSelector(
|
||||
makeSelectTotalStakedAmountForChannelUri(uri),
|
||||
amount => {
|
||||
let level = 1;
|
||||
switch (true) {
|
||||
case amount >= CLAIM.LEVEL_2_STAKED_AMOUNT && amount < CLAIM.LEVEL_3_STAKED_AMOUNT:
|
||||
level = 2;
|
||||
break;
|
||||
case amount >= CLAIM.LEVEL_3_STAKED_AMOUNT && amount < CLAIM.LEVEL_4_STAKED_AMOUNT:
|
||||
level = 3;
|
||||
break;
|
||||
case amount >= CLAIM.LEVEL_4_STAKED_AMOUNT && amount < CLAIM.LEVEL_5_STAKED_AMOUNT:
|
||||
level = 4;
|
||||
break;
|
||||
case amount >= CLAIM.LEVEL_5_STAKED_AMOUNT:
|
||||
level = 5;
|
||||
break;
|
||||
}
|
||||
return level;
|
||||
}
|
||||
);
|
||||
|
||||
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
|
||||
);
|
||||
|
|
311
src/redux/selectors/collections.js
Normal file
311
src/redux/selectors/collections.js
Normal file
|
@ -0,0 +1,311 @@
|
|||
// @flow
|
||||
import fromEntries from '@ungap/from-entries';
|
||||
import { createSelector } from 'reselect';
|
||||
import {
|
||||
selectMyCollectionIds,
|
||||
makeSelectClaimForUri,
|
||||
selectClaimsByUri,
|
||||
} 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]);
|
||||
}
|
||||
);
|
||||
|
||||
export const selectMyPublishedCollections = createSelector(
|
||||
selectResolvedCollections,
|
||||
selectPendingCollections,
|
||||
selectMyEditedCollections,
|
||||
selectMyCollectionIds,
|
||||
(resolved, pending, edited, myIds) => {
|
||||
// all resolved in myIds, plus those in pending and edited
|
||||
const myPublishedCollections = 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;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectMyPublishedMixedCollections = createSelector(
|
||||
selectMyPublishedCollections,
|
||||
published => {
|
||||
const myCollections = fromEntries(
|
||||
// $FlowFixMe
|
||||
Object.entries(published).filter(([key, collection]) => {
|
||||
// $FlowFixMe
|
||||
return collection.type === 'collection';
|
||||
})
|
||||
);
|
||||
return myCollections;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectMyPublishedPlaylistCollections = createSelector(
|
||||
selectMyPublishedCollections,
|
||||
published => {
|
||||
const myCollections = 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 = 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] || pLists[id] || rLists[id];
|
||||
return collection;
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectClaimUrlInCollection = (url: string) =>
|
||||
createSelector(
|
||||
selectBuiltinCollections,
|
||||
selectMyPublishedCollections,
|
||||
selectMyUnpublishedCollections,
|
||||
selectMyEditedCollections,
|
||||
selectPendingCollections,
|
||||
(bLists, myRLists, uLists, eLists, pLists) => {
|
||||
const collections = [bLists, uLists, eLists, myRLists, pLists];
|
||||
const itemsInCollections = [];
|
||||
collections.map(list => {
|
||||
Object.entries(list).forEach(([key, value]) => {
|
||||
// $FlowFixMe
|
||||
value.items.map(item => {
|
||||
itemsInCollections.push(item);
|
||||
});
|
||||
});
|
||||
});
|
||||
return itemsInCollections.includes(url);
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectCollectionForIdHasClaimUrl = (id: string, url: string) =>
|
||||
createSelector(
|
||||
makeSelectCollectionForId(id),
|
||||
collection => collection && collection.items.includes(url)
|
||||
);
|
||||
|
||||
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 makeSelectIndexForUrlInCollection = (url: string, id: string) =>
|
||||
createSelector(
|
||||
state => state.content.shuffleList,
|
||||
makeSelectUrlsForCollectionId(id),
|
||||
makeSelectClaimForUri(url),
|
||||
(shuffleState, urls, claim) => {
|
||||
const shuffleUrls = shuffleState && shuffleState.collectionId === id && shuffleState.newUrls;
|
||||
const listUrls = shuffleUrls || urls;
|
||||
|
||||
const index = listUrls && listUrls.findIndex(u => u === url);
|
||||
if (index > -1) {
|
||||
return index;
|
||||
} else if (claim) {
|
||||
const index = listUrls && listUrls.findIndex(u => u === claim.permanent_url);
|
||||
if (index > -1) return index;
|
||||
return claim;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectPreviousUrlForCollectionAndUrl = (id: string, url: string) =>
|
||||
createSelector(
|
||||
state => state.content.shuffleList,
|
||||
state => state.content.loopList,
|
||||
makeSelectIndexForUrlInCollection(url, id),
|
||||
makeSelectUrlsForCollectionId(id),
|
||||
(shuffleState, loopState, index, urls) => {
|
||||
const loopList = loopState && loopState.collectionId === id && loopState.loop;
|
||||
const shuffleUrls = shuffleState && shuffleState.collectionId === id && shuffleState.newUrls;
|
||||
|
||||
if (index > -1) {
|
||||
const listUrls = shuffleUrls || urls;
|
||||
let nextUrl;
|
||||
if (index === 0 && loopList) {
|
||||
nextUrl = listUrls[listUrls.length - 1];
|
||||
} else {
|
||||
nextUrl = listUrls[index - 1];
|
||||
}
|
||||
return nextUrl || null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectNextUrlForCollectionAndUrl = (id: string, url: string) =>
|
||||
createSelector(
|
||||
state => state.content.shuffleList,
|
||||
state => state.content.loopList,
|
||||
makeSelectIndexForUrlInCollection(url, id),
|
||||
makeSelectUrlsForCollectionId(id),
|
||||
(shuffleState, loopState, index, urls) => {
|
||||
const loopList = loopState && loopState.collectionId === id && loopState.loop;
|
||||
const shuffleUrls = shuffleState && shuffleState.collectionId === id && shuffleState.newUrls;
|
||||
|
||||
if (index > -1) {
|
||||
const listUrls = shuffleUrls || urls;
|
||||
// We'll get the next playble url
|
||||
let remainingUrls = listUrls.slice(index + 1);
|
||||
if (!remainingUrls.length && loopList) {
|
||||
remainingUrls = listUrls.slice(0);
|
||||
}
|
||||
const nextUrl = remainingUrls && remainingUrls[0];
|
||||
return nextUrl || null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectNameForCollectionId = (id: string) =>
|
||||
createSelector(
|
||||
makeSelectCollectionForId(id),
|
||||
collection => {
|
||||
return (collection && collection.name) || '';
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectCountForCollectionId = (id: string) =>
|
||||
createSelector(
|
||||
makeSelectCollectionForId(id),
|
||||
collection => {
|
||||
if (collection) {
|
||||
if (collection.itemCount !== undefined) {
|
||||
return collection.itemCount;
|
||||
}
|
||||
let itemCount = 0;
|
||||
collection.items.map(item => {
|
||||
if (item) {
|
||||
itemCount += 1;
|
||||
}
|
||||
});
|
||||
return itemCount;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
);
|
|
@ -40,17 +40,22 @@ export const selectIsStillEditing = createSelector(
|
|||
|
||||
export const selectPublishFormValues = createSelector(
|
||||
selectState,
|
||||
state => state.settings,
|
||||
selectIsStillEditing,
|
||||
(state, isStillEditing) => {
|
||||
const { pendingPublish, language, languages, ...formValues } = state;
|
||||
(publishState, settingsState, isStillEditing) => {
|
||||
const { languages, ...formValues } = publishState;
|
||||
const language = languages && languages.length && languages[0];
|
||||
const { clientSettings } = settingsState;
|
||||
const { language: languageSet } = clientSettings;
|
||||
|
||||
let actualLanguage;
|
||||
// Sets default if editing a claim with a set language
|
||||
if (!language && isStillEditing && languages && languages[0]) {
|
||||
actualLanguage = languages[0];
|
||||
if (!language && isStillEditing && languageSet) {
|
||||
actualLanguage = languageSet;
|
||||
} else {
|
||||
actualLanguage = language || 'en';
|
||||
actualLanguage = language || languageSet || 'en';
|
||||
}
|
||||
|
||||
return { ...formValues, language: actualLanguage };
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,187 +0,0 @@
|
|||
// @flow
|
||||
import { SEARCH_TYPES, SEARCH_OPTIONS } from 'constants/search';
|
||||
import { getSearchQueryString } from 'util/query-params';
|
||||
import { normalizeURI, parseURI } from 'lbryURI';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
type State = { search: SearchState };
|
||||
|
||||
export const selectState = (state: State): SearchState => state.search;
|
||||
|
||||
export const selectSearchValue: (state: State) => string = createSelector(
|
||||
selectState,
|
||||
state => state.searchQuery
|
||||
);
|
||||
|
||||
export const selectSearchOptions: (state: State) => SearchOptions = createSelector(
|
||||
selectState,
|
||||
state => state.options
|
||||
);
|
||||
|
||||
export const selectSuggestions: (
|
||||
state: State
|
||||
) => { [string]: Array<SearchSuggestion> } = createSelector(
|
||||
selectState,
|
||||
state => state.suggestions
|
||||
);
|
||||
|
||||
export const selectIsSearching: (state: State) => boolean = createSelector(
|
||||
selectState,
|
||||
state => state.searching
|
||||
);
|
||||
|
||||
export const selectSearchUrisByQuery: (
|
||||
state: State
|
||||
) => { [string]: Array<string> } = createSelector(
|
||||
selectState,
|
||||
state => state.urisByQuery
|
||||
);
|
||||
|
||||
export const makeSelectSearchUris = (query: string): ((state: State) => Array<string>) =>
|
||||
// replace statement below is kind of ugly, and repeated in doSearch action
|
||||
createSelector(
|
||||
selectSearchUrisByQuery,
|
||||
byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]
|
||||
);
|
||||
|
||||
export const selectResolvedSearchResultsByQuery: (
|
||||
state: State
|
||||
) => { [string]: Array<ResolvedSearchResult> } = createSelector(
|
||||
selectState,
|
||||
state => state.resolvedResultsByQuery
|
||||
);
|
||||
|
||||
export const selectResolvedSearchResultsByQueryLastPageReached: (
|
||||
state: State
|
||||
) => { [string]: Array<boolean> } = createSelector(
|
||||
selectState,
|
||||
state => state.resolvedResultsByQueryLastPageReached
|
||||
);
|
||||
|
||||
export const makeSelectResolvedSearchResults = (
|
||||
query: string
|
||||
): ((state: State) => Array<ResolvedSearchResult>) =>
|
||||
// replace statement below is kind of ugly, and repeated in doSearch action
|
||||
createSelector(
|
||||
selectResolvedSearchResultsByQuery,
|
||||
byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]
|
||||
);
|
||||
|
||||
export const makeSelectResolvedSearchResultsLastPageReached = (
|
||||
query: string
|
||||
): ((state: State) => boolean) =>
|
||||
// replace statement below is kind of ugly, and repeated in doSearch action
|
||||
createSelector(
|
||||
selectResolvedSearchResultsByQueryLastPageReached,
|
||||
byQuery => byQuery[query ? query.replace(/^lbry:\/\//i, '').replace(/\//, ' ') : query]
|
||||
);
|
||||
|
||||
export const selectSearchBarFocused: boolean = createSelector(
|
||||
selectState,
|
||||
state => state.focused
|
||||
);
|
||||
|
||||
export const selectSearchSuggestions: Array<SearchSuggestion> = createSelector(
|
||||
selectSearchValue,
|
||||
selectSuggestions,
|
||||
(query: string, suggestions: { [string]: Array<string> }) => {
|
||||
if (!query) {
|
||||
return [];
|
||||
}
|
||||
const queryIsPrefix =
|
||||
query === 'lbry:' || query === 'lbry:/' || query === 'lbry://' || query === 'lbry://@';
|
||||
|
||||
if (queryIsPrefix) {
|
||||
// If it is a prefix, wait until something else comes to figure out what to do
|
||||
return [];
|
||||
} else if (query.startsWith('lbry://')) {
|
||||
// If it starts with a prefix, don't show any autocomplete results
|
||||
// They are probably typing/pasting in a lbry uri
|
||||
return [
|
||||
{
|
||||
value: query,
|
||||
type: query[7] === '@' ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
let searchSuggestions = [];
|
||||
try {
|
||||
const uri = normalizeURI(query);
|
||||
const { channelName, streamName, isChannel } = parseURI(uri);
|
||||
searchSuggestions.push(
|
||||
{
|
||||
value: query,
|
||||
type: SEARCH_TYPES.SEARCH,
|
||||
},
|
||||
{
|
||||
value: uri,
|
||||
shorthand: isChannel ? channelName : streamName,
|
||||
type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE,
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
searchSuggestions.push({
|
||||
value: query,
|
||||
type: SEARCH_TYPES.SEARCH,
|
||||
});
|
||||
}
|
||||
|
||||
searchSuggestions.push({
|
||||
value: query,
|
||||
type: SEARCH_TYPES.TAG,
|
||||
});
|
||||
|
||||
const apiSuggestions = suggestions[query] || [];
|
||||
if (apiSuggestions.length) {
|
||||
searchSuggestions = searchSuggestions.concat(
|
||||
apiSuggestions
|
||||
.filter(suggestion => suggestion !== query)
|
||||
.map(suggestion => {
|
||||
// determine if it's a channel
|
||||
try {
|
||||
const uri = normalizeURI(suggestion);
|
||||
const { channelName, streamName, isChannel } = parseURI(uri);
|
||||
|
||||
return {
|
||||
value: uri,
|
||||
shorthand: isChannel ? channelName : streamName,
|
||||
type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE,
|
||||
};
|
||||
} catch (e) {
|
||||
// search result includes some character that isn't valid in claim names
|
||||
return {
|
||||
value: suggestion,
|
||||
type: SEARCH_TYPES.SEARCH,
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return searchSuggestions;
|
||||
}
|
||||
);
|
||||
|
||||
// Creates a query string based on the state in the search reducer
|
||||
// Can be overrided by passing in custom sizes/from values for other areas pagination
|
||||
|
||||
type CustomOptions = {
|
||||
isBackgroundSearch?: boolean,
|
||||
size?: number,
|
||||
from?: number,
|
||||
related_to?: string,
|
||||
nsfw?: boolean,
|
||||
};
|
||||
|
||||
export const makeSelectQueryWithOptions = (customQuery: ?string, options: CustomOptions) =>
|
||||
createSelector(
|
||||
selectSearchValue,
|
||||
selectSearchOptions,
|
||||
(query, defaultOptions) => {
|
||||
const searchOptions = { ...defaultOptions, ...options };
|
||||
const queryString = getSearchQueryString(customQuery || query, searchOptions);
|
||||
|
||||
return queryString;
|
||||
}
|
||||
);
|
|
@ -1,47 +0,0 @@
|
|||
// @flow
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
const selectState = (state: { tags: TagState }) => state.tags || {};
|
||||
|
||||
export const selectKnownTagsByName = createSelector(
|
||||
selectState,
|
||||
(state: TagState): KnownTags => state.knownTags
|
||||
);
|
||||
|
||||
export const selectFollowedTagsList = createSelector(
|
||||
selectState,
|
||||
(state: TagState): Array<string> => state.followedTags.filter(tag => typeof tag === 'string')
|
||||
);
|
||||
|
||||
export const selectFollowedTags = createSelector(
|
||||
selectFollowedTagsList,
|
||||
(followedTags: Array<string>): Array<Tag> =>
|
||||
followedTags
|
||||
.map(tag => ({ name: tag.toLowerCase() }))
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
);
|
||||
|
||||
export const selectUnfollowedTags = createSelector(
|
||||
selectKnownTagsByName,
|
||||
selectFollowedTagsList,
|
||||
(tagsByName: KnownTags, followedTags: Array<string>): Array<Tag> => {
|
||||
const followedTagsSet = new Set(followedTags);
|
||||
let tagsToReturn = [];
|
||||
Object.keys(tagsByName).forEach(key => {
|
||||
if (!followedTagsSet.has(key)) {
|
||||
const { name } = tagsByName[key];
|
||||
tagsToReturn.push({ name: name.toLowerCase() });
|
||||
}
|
||||
});
|
||||
|
||||
return tagsToReturn;
|
||||
}
|
||||
);
|
||||
|
||||
export const makeSelectIsFollowingTag = (tag: string) =>
|
||||
createSelector(
|
||||
selectFollowedTags,
|
||||
followedTags => {
|
||||
return followedTags.some(followedTag => followedTag.name === tag.toLowerCase());
|
||||
}
|
||||
);
|
|
@ -2,6 +2,7 @@ import { createSelector } from 'reselect';
|
|||
import * as TRANSACTIONS from 'constants/transaction_types';
|
||||
import { PAGE_SIZE, LATEST_PAGE_SIZE } from 'constants/transaction_list';
|
||||
import { selectClaimIdsByUri } from 'redux/selectors/claims';
|
||||
import parseData from 'util/parse-data';
|
||||
export const selectState = state => state.wallet || {};
|
||||
|
||||
export const selectWalletState = selectState;
|
||||
|
@ -26,12 +27,18 @@ export const selectPendingSupportTransactions = createSelector(
|
|||
state => state.pendingSupportTransactions
|
||||
);
|
||||
|
||||
export const selectPendingOtherTransactions = createSelector(
|
||||
selectState,
|
||||
state => state.pendingTxos
|
||||
);
|
||||
|
||||
export const selectAbandonClaimSupportError = createSelector(
|
||||
selectState,
|
||||
state => state.abandonClaimSupportError
|
||||
);
|
||||
|
||||
export const makeSelectPendingAmountByUri = (uri) => createSelector(
|
||||
export const makeSelectPendingAmountByUri = uri =>
|
||||
createSelector(
|
||||
selectClaimIdsByUri,
|
||||
selectPendingSupportTransactions,
|
||||
(claimIdsByUri, pendingSupports) => {
|
||||
|
@ -40,7 +47,7 @@ export const makeSelectPendingAmountByUri = (uri) => createSelector(
|
|||
const pendingSupport = claimId && pendingSupports[claimId];
|
||||
return pendingSupport ? pendingSupport.effective : undefined;
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
export const selectWalletEncryptResult = createSelector(
|
||||
selectState,
|
||||
|
@ -261,6 +268,27 @@ export const selectIsFetchingTransactions = createSelector(
|
|||
state => state.fetchingTransactions
|
||||
);
|
||||
|
||||
/**
|
||||
* CSV of 'selectTransactionItems'.
|
||||
*/
|
||||
export const selectTransactionsFile = createSelector(
|
||||
selectTransactionItems,
|
||||
transactions => {
|
||||
if (!transactions || transactions.length === 0) {
|
||||
// No data.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parsed = parseData(transactions, 'csv');
|
||||
if (!parsed) {
|
||||
// Invalid data, or failed to parse.
|
||||
return null;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectIsSendingSupport = createSelector(
|
||||
selectState,
|
||||
state => state.sendingSupport
|
||||
|
@ -328,27 +356,27 @@ export const selectTxoPageParams = createSelector(
|
|||
|
||||
export const selectTxoPage = createSelector(
|
||||
selectState,
|
||||
state => (state.txoPage && state.txoPage.items) || [],
|
||||
state => (state.txoPage && state.txoPage.items) || []
|
||||
);
|
||||
|
||||
export const selectTxoPageNumber = createSelector(
|
||||
selectState,
|
||||
state => (state.txoPage && state.txoPage.page) || 1,
|
||||
state => (state.txoPage && state.txoPage.page) || 1
|
||||
);
|
||||
|
||||
export const selectTxoItemCount = createSelector(
|
||||
selectState,
|
||||
state => (state.txoPage && state.txoPage.total_items) || 1,
|
||||
state => (state.txoPage && state.txoPage.total_items) || 1
|
||||
);
|
||||
|
||||
export const selectFetchingTxosError = createSelector(
|
||||
selectState,
|
||||
state => state.fetchingTxosError,
|
||||
state => state.fetchingTxosError
|
||||
);
|
||||
|
||||
export const selectIsFetchingTxos = createSelector(
|
||||
selectState,
|
||||
state => state.fetchingTxos,
|
||||
state => state.fetchingTxos
|
||||
);
|
||||
|
||||
export const makeSelectFilteredTransactionsForPage = (page = 1) =>
|
||||
|
@ -379,3 +407,33 @@ export const selectIsWalletReconnecting = createSelector(
|
|||
selectState,
|
||||
state => state.walletReconnecting
|
||||
);
|
||||
|
||||
export const selectIsFetchingUtxoCounts = createSelector(
|
||||
selectState,
|
||||
state => state.fetchingUtxoCounts
|
||||
);
|
||||
|
||||
export const selectIsConsolidatingUtxos = createSelector(
|
||||
selectState,
|
||||
state => state.consolidatingUtxos
|
||||
);
|
||||
|
||||
export const selectIsMassClaimingTips = createSelector(
|
||||
selectState,
|
||||
state => state.massClaimingTips
|
||||
);
|
||||
|
||||
export const selectPendingConsolidateTxid = createSelector(
|
||||
selectState,
|
||||
state => state.pendingConsolidateTxid
|
||||
);
|
||||
|
||||
export const selectPendingMassClaimTxid = createSelector(
|
||||
selectState,
|
||||
state => state.pendingMassClaimTxid
|
||||
);
|
||||
|
||||
export const selectUtxoCounts = createSelector(
|
||||
selectState,
|
||||
state => state.utxoCounts
|
||||
);
|
||||
|
|
61
src/util/parse-data.js
Normal file
61
src/util/parse-data.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
// JSON parser
|
||||
const parseJson = (data, filters = []) => {
|
||||
const list = data.map(item => {
|
||||
const temp = {};
|
||||
// Apply filters
|
||||
Object.entries(item).forEach(([key, value]) => {
|
||||
if (!filters.includes(key)) temp[key] = value;
|
||||
});
|
||||
return temp;
|
||||
});
|
||||
// Beautify JSON
|
||||
return JSON.stringify(list, null, '\t');
|
||||
};
|
||||
|
||||
// CSV Parser
|
||||
// No need for an external module:
|
||||
// https://gist.github.com/btzr-io/55c3450ea3d709fc57540e762899fb85
|
||||
const parseCsv = (data, filters = []) => {
|
||||
// Get items for header
|
||||
const getHeaders = item => {
|
||||
const list = [];
|
||||
// Apply filters
|
||||
Object.entries(item).forEach(([key]) => {
|
||||
if (!filters.includes(key)) list.push(key);
|
||||
});
|
||||
// return headers
|
||||
return list.join(',');
|
||||
};
|
||||
|
||||
// Get rows content
|
||||
const getData = list =>
|
||||
list
|
||||
.map(item => {
|
||||
const row = [];
|
||||
// Apply filters
|
||||
Object.entries(item).forEach(([key, value]) => {
|
||||
if (!filters.includes(key)) row.push(value);
|
||||
});
|
||||
// return rows
|
||||
return row.join(',');
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
// Return CSV string
|
||||
return `${getHeaders(data[0])} \n ${getData(data)}`;
|
||||
};
|
||||
|
||||
const parseData = (data, format, filters = []) => {
|
||||
// Check for validation
|
||||
const valid = data && data[0] && format;
|
||||
// Pick a format
|
||||
const formats = {
|
||||
csv: list => parseCsv(list, filters),
|
||||
json: list => parseJson(list, filters),
|
||||
};
|
||||
|
||||
// Return parsed data: JSON || CSV
|
||||
return valid && formats[format] ? formats[format](data) : undefined;
|
||||
};
|
||||
|
||||
export default parseData;
|
|
@ -1,8 +1,4 @@
|
|||
// @flow
|
||||
import { SEARCH_OPTIONS } from 'constants/search';
|
||||
|
||||
const DEFAULT_SEARCH_RESULT_FROM = 0;
|
||||
const DEFAULT_SEARCH_SIZE = 20;
|
||||
|
||||
export function parseQueryParams(queryString: string) {
|
||||
if (queryString === '') return {};
|
||||
|
@ -32,54 +28,3 @@ export function toQueryString(params: { [string]: string | number }) {
|
|||
|
||||
return parts.join('&');
|
||||
}
|
||||
|
||||
export const getSearchQueryString = (query: string, options: any = {}) => {
|
||||
const encodedQuery = encodeURIComponent(query);
|
||||
const queryParams = [
|
||||
`s=${encodedQuery}`,
|
||||
`size=${options.size || DEFAULT_SEARCH_SIZE}`,
|
||||
`from=${options.from || DEFAULT_SEARCH_RESULT_FROM}`,
|
||||
];
|
||||
const { isBackgroundSearch } = options;
|
||||
const includeUserOptions =
|
||||
typeof isBackgroundSearch === 'undefined' ? false : !isBackgroundSearch;
|
||||
|
||||
if (includeUserOptions) {
|
||||
const claimType = options[SEARCH_OPTIONS.CLAIM_TYPE];
|
||||
if (claimType) {
|
||||
queryParams.push(`claimType=${claimType}`);
|
||||
|
||||
// If they are only searching for channels, strip out the media info
|
||||
if (!claimType.includes(SEARCH_OPTIONS.INCLUDE_CHANNELS)) {
|
||||
queryParams.push(
|
||||
`mediaType=${[
|
||||
SEARCH_OPTIONS.MEDIA_FILE,
|
||||
SEARCH_OPTIONS.MEDIA_AUDIO,
|
||||
SEARCH_OPTIONS.MEDIA_VIDEO,
|
||||
SEARCH_OPTIONS.MEDIA_TEXT,
|
||||
SEARCH_OPTIONS.MEDIA_IMAGE,
|
||||
SEARCH_OPTIONS.MEDIA_APPLICATION,
|
||||
].reduce(
|
||||
(acc, currentOption) => (options[currentOption] ? `${acc}${currentOption},` : acc),
|
||||
''
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const additionalOptions = {};
|
||||
const { related_to } = options;
|
||||
const { nsfw } = options;
|
||||
if (related_to) additionalOptions['related_to'] = related_to;
|
||||
if (typeof nsfw !== 'undefined') additionalOptions['nsfw'] = nsfw;
|
||||
|
||||
if (additionalOptions) {
|
||||
Object.keys(additionalOptions).forEach(key => {
|
||||
const option = additionalOptions[key];
|
||||
queryParams.push(`${key}=${option}`);
|
||||
});
|
||||
}
|
||||
|
||||
return queryParams.join('&');
|
||||
};
|
||||
|
|
19
tests/config/jest-transformer.js
Normal file
19
tests/config/jest-transformer.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
const config = {
|
||||
babelrc: false,
|
||||
presets: [
|
||||
[
|
||||
"@babel/env",
|
||||
{
|
||||
modules: false
|
||||
}
|
||||
],
|
||||
"@babel/react"
|
||||
],
|
||||
plugins: [
|
||||
["@babel/plugin-proposal-decorators", { legacy: true }],
|
||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
||||
"@babel/plugin-transform-flow-strip-types",
|
||||
"transform-es2015-modules-commonjs"
|
||||
]
|
||||
};
|
||||
module.exports = require("babel-jest").createTransformer(config);
|
44
tests/parseURI.test.js
Normal file
44
tests/parseURI.test.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import * as lbryURI from '../src/lbryURI.js';
|
||||
import {describe, test} from "@jest/globals";
|
||||
|
||||
describe('parseURI tests', () => {
|
||||
|
||||
test('Correctly parses channel URI', () => {
|
||||
let result = lbryURI.parseURI('lbry://@ChannelName');
|
||||
expect(result.isChannel).toBeTruthy();
|
||||
expect(result.path).toStrictEqual("@ChannelName");
|
||||
expect(result.channelName).toStrictEqual("ChannelName");
|
||||
expect(result.claimName).toStrictEqual("@ChannelName");
|
||||
});
|
||||
|
||||
test('Correctly parses test case channel/stream lbry URI', () => {
|
||||
let result = lbryURI.parseURI('lbry://@CryptoGnome#1/whale-pool-how-to#e');
|
||||
expect(result.isChannel).toStrictEqual(false);;
|
||||
expect(result.path).toStrictEqual("@CryptoGnome#1/whale-pool-how-to#e");
|
||||
expect(result.claimId).toStrictEqual("1");
|
||||
expect(result.streamClaimId).toStrictEqual("e");
|
||||
expect(result.streamName).toStrictEqual("whale-pool-how-to");
|
||||
expect(result.channelName).toStrictEqual("CryptoGnome");
|
||||
expect(result.contentName).toStrictEqual("whale-pool-how-to");
|
||||
});
|
||||
|
||||
test('Correctly parses lbry URI without protocol', () => {
|
||||
let result = lbryURI.parseURI('@CryptoGnome#1/whale-pool-how-to#e');
|
||||
expect(result.isChannel).toStrictEqual(false);;
|
||||
expect(result.streamName).toStrictEqual("whale-pool-how-to");
|
||||
expect(result.channelName).toStrictEqual("CryptoGnome");
|
||||
});
|
||||
|
||||
test('Throws error for http protocol', () => {
|
||||
// TODO - this catches wrong type of error..
|
||||
let uri = 'http://@CryptoGnome#1/whale-pool-how-to#e';
|
||||
expect(() => lbryURI.parseURI(uri)).toThrowError();
|
||||
});
|
||||
|
||||
test('Correctly parses search', () => {
|
||||
let result = lbryURI.parseURI('CryptoGn%ome');
|
||||
expect(result.isChannel).toStrictEqual(false);
|
||||
expect(result.path).toStrictEqual("CryptoGn%ome");
|
||||
expect(result.contentName).toStrictEqual("CryptoGn%ome");
|
||||
});
|
||||
})
|
Loading…
Reference in a new issue