Compare commits

...

280 commits

Author SHA1 Message Date
zeppi
0f930c4a7b sync-language 2021-10-08 16:12:32 -04:00
zeppi
32b5787071 pass channel_id on list update 2021-09-13 12:05:57 -04:00
Thomas Zarebczan
129b0ea3fa
Merge pull request #431 from saltrafael/list-thumb
Changes for list thumbnail upload
2021-09-13 11:24:58 -04:00
saltrafael
e3bc848263
Add cb to thumbnail upload 2021-09-13 07:38:21 -03:00
zeppi
372e559cae use replace for list updates 2021-09-11 13:18:41 -04:00
saltrafael
49b9db5aae Fix autoplay not saving 2021-09-06 13:17:07 -04:00
Thomas Zarebczan
12a2ffc708
Merge pull request #426 from saltrafael/playback-controls
Playback and List control changes
2021-09-01 14:08:36 -04:00
Thomas Zarebczan
95fa26f836
Merge pull request #428 from lbryio/ip/from.entries.poly
Fix Object.fromEntries crash on some browsers
2021-09-01 12:04:46 -04:00
infiinte-persistence
dc264ec50c
Fix Object.fromEntries crash on some browsers
## Issue
6985 fromentries app crash - fix or add polyfill
2021-09-01 10:20:40 +08:00
saltrafael
aeb1f533b5
Playback and List control changes 2021-08-25 09:01:50 -03:00
zeppi
d016d8057b cleanup 2021-08-23 10:20:22 -04:00
zeppi
0302a2f8d6 fix background collection update 2021-08-23 10:20:22 -04:00
zeppi
8fa92d872d add isBackgroundUpdate to collection update 2021-08-23 10:20:22 -04:00
zeppi
e4d0662100 build 2021-08-06 12:33:10 -04:00
zeppi
036aa59086 fix collection edit 2021-08-06 12:33:10 -04:00
Thomas Zarebczan
c76dfbde27
Merge pull request #423 from lbryio/playlist-fetch-changes
change collection fetch params
2021-08-03 14:18:20 -04:00
zeppi
7cc9923ed9 change collection fetch params 2021-08-03 14:05:03 -04:00
zeppi
60bd918d5e U_S_P edited collection
bugfix

sync edited
2021-08-02 09:45:43 -04:00
saltrafael
9ebfc927d0 Change rLists selector 2021-07-30 11:01:18 -04:00
saltrafael
54ca8c4320 Filter rLists 2021-07-30 11:01:18 -04:00
saltrafael
bee9bf38dd Add claim in collections selector 2021-07-30 11:01:18 -04:00
infiinte-persistence
aabae5ce59
Add custom comments-server settings
## Issue
5459: Add setting for changing your comment server. Visible on desktop (and possibly defaulting to Odysee URL), hidden on odysee.
2021-07-25 20:52:22 +08:00
zeppi
a327385cdf clean 2021-07-15 17:14:28 -04:00
zeppi
64ce7aa99c handle colons maybe 2021-07-15 17:14:28 -04:00
zeppi
34dfd384e4 logging 2021-07-15 17:14:28 -04:00
zeppi
707c60b813 parse either claimId separator 2021-07-15 17:14:28 -04:00
zeppi
8f66a2fe7c fix pending ids selector 2021-07-05 15:57:47 -04:00
zeppi
729a4831ad channel modlist on confirm 2021-07-05 09:42:33 -04:00
zeppi
88370997b4 cleanup 2021-07-05 09:42:33 -04:00
zeppi
b93598b0ff fix bug canceling pending check early 2021-07-05 09:42:33 -04:00
zeppi
4cbb9a35c3 prefer pending and edited collections in selector 2021-07-05 09:42:33 -04:00
zeppi
04ce1df03d collection check pending 2021-07-05 09:42:33 -04:00
zeppi
347fe25e85 cleanup 2021-07-05 09:42:33 -04:00
zeppi
e66698eadc store pendingById 2021-07-05 09:42:33 -04:00
zeppi
0b505fb0f4 selectClaimIdIsPending 2021-06-23 10:47:24 -04:00
Thomas Zarebczan
508e8d36fd
Merge pull request #416 from saltrafael/master
Fix breaking when no languages set
2021-06-14 11:13:06 -04:00
saltrafael
0758827e6d fix breaking when no languages set 2021-06-13 08:51:49 -03:00
Thomas Zarebczan
166c3b2832
Merge pull request #414 from saltrafael/master
Don't reset content language on edit
2021-06-11 11:45:35 -04:00
Thomas Zarebczan
d298c00f24
Merge pull request #415 from lbryio/fix-autoplay
fix autoplay after playlist click and select next playable
2021-06-10 17:20:08 -04:00
zeppi
06b09f5a81 add list reducer key consts 2021-06-10 16:07:41 -04:00
zeppi
4dfc4689c6 fix autoplay after playlist click and select next playable 2021-06-10 14:03:11 -04:00
saltrafael
85ad697e0a Don't reset content language on edit 2021-06-09 11:32:22 -03:00
zeppi
609f13991f bugfix 2021-06-08 11:51:49 -04:00
zeppi
503e18be1b refactor select collection index / next 2021-06-08 11:51:49 -04:00
zeppi
32a85a9ff3 cleanup 2021-06-08 11:51:49 -04:00
zeppi
40fc75320d fix crash on abandoned collection claim 2021-06-08 11:51:49 -04:00
zeppi
e20baa0683 fix sync bringing back unpublished 2021-06-08 11:51:49 -04:00
zeppi
a1cb16400d bugfix 2021-06-08 11:51:49 -04:00
zeppi
9461cf1bee cleanup
cleanup

cleanup
2021-06-08 11:51:49 -04:00
zeppi
7e049487c3 fix sync 2021-06-08 11:51:49 -04:00
zeppi
f7775fd837 finalize collections sync keys 2021-06-08 11:51:49 -04:00
zeppi
06531c6b48 fix tags bug, flow 2021-06-08 11:51:49 -04:00
zeppi
b280d66f5d return new collection on publish 2021-06-08 11:51:49 -04:00
zeppi
bfb50ebeb5 handle collection claim delete 2021-06-08 11:51:49 -04:00
zeppi
06ce8c623c refactor fetch to fix pending 2021-06-08 11:51:49 -04:00
zeppi
f8ff4cfc8f thumb param 2021-06-08 11:51:49 -04:00
zeppi
e34f451025 collections length 2021-06-08 11:51:49 -04:00
zeppi
d3c045b037 prefer title for collection name on resolve 2021-06-08 11:51:49 -04:00
zeppi
63946a0a6d pending, edit fixes, support collectionCount 2021-06-08 11:51:49 -04:00
zeppi
dd697ed70e make edits work 2021-06-08 11:51:49 -04:00
zeppi
fd2551e764 fix pending, support new collection add ui 2021-06-08 11:51:49 -04:00
zeppi
8d0f9c18fd wip
wip

clean

clean

review

wip

wallet sync

wip

collection publishing

build

refactor, publishing, pending, editing

wip

wip

fetch collections on resolve

select collections or playlists

build

return edit success

fix collection claimId selector

small rename

flow type fixes

collection edit params type param and flowtypes
2021-06-08 11:51:49 -04:00
Thomas Zarebczan
757e8c24ec
Merge pull request #413 from lbryio/ip/thumbnail-error-part-2
Clear 'thumbnailError' when uploading new one
2021-05-17 15:46:25 -04:00
infiinte-persistence
ecfcc95beb
Clear 'thumbnailError' when uploading new one
## Issue
"thumbnail is invalid" not reset with new thumbnail upload #6044
https://github.com/lbryio/lbry-desktop/issues/6044

## Change
The previous PR only covered the scenario of changing between NEW and EDIT. This one covers "uploading new".
2021-05-18 01:23:37 +08:00
Thomas Zarebczan
b2ad71fb74
Merge pull request #412 from lbryio/ip/release-time
Change how release_time is edited.
2021-05-14 17:30:25 -04:00
infiinte-persistence
35dd7650fb
Change how release_time is edited.
- `releaseTime` is now a number instead of a string, matching `release_time`. It was getting confusing what the variable units were.

- `releaseTime` will always match `release_time` for an edit. It will be used in the GUI to reset just the date to the original, instead of having to reset the entire form.

- `releaseTimeEdited` will be used by `updatePublishForm` in the GUI to represent the desired new release time. Set to `undefined` if we don't want to change the date.
2021-05-13 07:57:01 +08:00
infiinte-persistence
babfec7d43
Complete rename of 'release_time'
I believe this was missed out in c31161c4
2021-05-13 07:57:00 +08:00
Thomas Zarebczan
6fc11454eb
Merge pull request #411 from lbryio/ip/thumbnail-error
Define default value for 'thumbnailError'
2021-05-12 10:20:33 -04:00
infiinte-persistence
3b853b6ddd
Define default value for 'thumbnailError'
## Issue
"thumbnail is invalid" not reset with new thumbnail upload #6044
https://github.com/lbryio/lbry-desktop/issues/6044

## Change
Defining a default value will cover both CLEAR_PUBLISH and DO_PREPARE_EDIT
2021-05-11 11:02:48 +08:00
Thomas Zarebczan
41ef1117e5
Merge pull request #408 from lbryio/ip/bump-transaction-page-size
doFetchTransactions: bump pageSize to 999999; remove doFetchSupport
2021-04-26 15:11:24 -04:00
zeppi
66c77fc39b delay preference set two seconds 2021-04-26 15:06:00 -04:00
infiinte-persistence
e5c0b5f0a6
doFetchTransactions: bump pageSize to 999999; remove doFetchSupport
## Issue
5899 Re-add ability to export transactions
2021-04-26 12:26:36 +08:00
Sean Yesmunt
7e17344683 remove unused comment types 2021-04-23 14:52:16 -04:00
Sean Yesmunt
b511282c35 superchat support 2021-04-23 14:52:16 -04:00
Thomas Zarebczan
eb37009a98
Merge pull request #405 from lbryio/feat-supportAsyncForDesktop
support claim search async dispatch
2021-04-23 11:12:44 -04:00
zeppi
c2e03fa71d support claim search async dispatch 2021-04-22 22:31:08 -04:00
Thomas Zarebczan
4e37ab6580
Merge pull request #404 from lbryio/ip/export-transactions
Transaction export: move file-creation to background.
2021-04-19 16:44:04 -04:00
infiinte-persistence
a0bfbee958
Transaction export: move file-creation to background. 2021-04-17 22:09:02 +08:00
infiinte-persistence
3ca0c8d204 CoinSwap: handle "receiving/received LBC" 2021-04-12 16:18:51 -04:00
infiinte-persistence
5f3a40a420 CoinSwap: websocket + multi-coin
- For the active swap, switch from polling to websocket. The returned data is now the Charge data from the commerce, so some parsing will be required.

- Only save the 'chargeCode' to the wallet. The other data can be repopulated from this.
2021-04-07 14:35:15 -04:00
infiinte-persistence
8335c9d2de Save CoinSwapInfo instead of just the swap address.
User should be able to retrieve the expected send/receive amount, otherwise they might be sending insufficient amounts.

This change also includes the coin type, as we might be supporting other coins beyond BTC.
2021-04-07 14:35:15 -04:00
infiinte-persistence
9f7902aa0b Persist BTC swap address across devices
## Issue
Used by [Support for swapping into LBC](https://github.com/lbryio/lbry-desktop/pull/5654)
2021-04-07 14:35:15 -04:00
Thomas Zarebczan
9a17013728
Merge pull request #401 from lbryio/feat-remotePublishUrl
add remote publish url
2021-03-29 19:18:00 -04:00
zeppi
35088a6d10 add remote publish url 2021-03-29 17:48:35 -04:00
Thomas Zarebczan
8e74e3137a
bundle me up 2021-03-28 12:36:14 -04:00
Thomas Zarebczan
2cf645ab14
Update tags.js 2021-03-28 12:04:35 -04:00
zeppi
c494c92505 provide selector for placeholder stream 2021-03-25 18:56:48 -04:00
jessopb
e9712dc954 Revert "fix claimHasSource selector"
This reverts commit 5416b6bc42.
2021-03-25 18:37:39 -04:00
zeppi
5416b6bc42 fix claimHasSource selector 2021-03-25 17:50:36 -04:00
Thomas Zarebczan
86c7741d4c
Merge pull request #396 from lbryio/ip-txo-fetch-id
Drop old txo-fetch results
2021-03-23 21:35:10 -04:00
infiinte-persistence
a5a326e73a Drop old txo-fetch results
## Issue
Closes lbry-desktop 4317: `Transaction list shows previously requested data / pages`

## Approach
A naive approach of creating a random transaction ID for each fetch. The latest ID, stored in `state`, will be the expected one -- any other transaction results will be dropped.

The loading spinning will continue to spin until the latest ID's results are fetched.
2021-03-23 21:01:47 -04:00
zeppi
4e2a6c8201 create selectPendingClaims 2021-03-23 20:46:04 -04:00
Sean Yesmunt
629c3273f5 create makeSelectClaimHasSource 2021-03-18 11:33:54 -04:00
Sean Yesmunt
638a78695a add has_source and has_no_source to doClaimSearch options 2021-03-18 11:33:54 -04:00
Sean Yesmunt
d75e7725fe Revert "Revert "connection_status is dead, long live connected""
This reverts commit f449d7916c.
2021-03-15 15:41:14 -04:00
Sean Yesmunt
d91ec1773c update build 2021-03-15 14:20:50 -04:00
infiinte-persistence
e5b79a8400 doFetchClaimListMine: add 4th param to filter out claim_type 2021-03-15 14:20:50 -04:00
Sean Yesmunt
f449d7916c Revert "connection_status is dead, long live connected"
This reverts commit 74ab5bbf84.
2021-03-15 13:56:13 -04:00
infiinte-persistence
87e67aa714 makeSelectClaimForUri: Use either canonical or permanent url for repost data
## Issue
Closes 5673 (lbry-desktop): Reposts are all listed under "Annoymous"
2021-03-15 13:39:41 -04:00
Victor Shyba
74ab5bbf84 connection_status is dead, long live connected 2021-03-15 13:39:17 -04:00
Sean Yesmunt
bf728f8716 update staked amount constants 2021-03-11 12:57:53 -05:00
infiinte-persistence
bf3645df44 Move getChannelLevel to a selector.
## Issue
5636: Disable video previews in comments/posts made by channels below a certain channel staked level
2021-03-11 12:57:53 -05:00
Franco Montenegro
0d2d64aca7 Clear uri when clearing publish 2021-03-09 16:18:34 -05:00
Sean Yesmunt
c31161c41a rename release_time to releaseTime 2021-03-05 15:38:55 -05:00
Franco Montenegro
251c646851 Run yarn build for production 2021-03-05 15:38:55 -05:00
Franco Montenegro
5ea369ee76 Add release time to publish 2021-03-05 15:38:55 -05:00
zeppi
37f17fae0c populate channel claims for txo signing_channels 2021-03-05 15:29:04 -05:00
infiinte-persistence
f8a4264307 Bump copyright year to 2021 2021-02-24 11:58:13 -05:00
Sean Yesmunt
f37fd9bf92 store channel when claim is resolved 2021-02-18 23:30:20 -05:00
Sean Yesmunt
bcaedbcd9c add channel_sign 2021-02-11 10:07:18 -05:00
zeppi
f0849b4ce1 review changes 2021-02-05 16:04:48 -05:00
zeppi
a2f8646f95 better track txids for large wallet operations 2021-02-05 16:04:48 -05:00
zeppi
92acb9a6c9 support mass claiming tips 2021-02-02 21:35:19 -05:00
Sean Yesmunt
5d41b0656c add extra check for claim to prevent crash 2021-02-02 16:18:48 -05:00
Sean Yesmunt
7c926ad8de livestream changes 2021-01-26 16:39:29 -05:00
dependabot[bot]
9e2d80909f Bump node-notifier from 8.0.0 to 8.0.1
Bumps [node-notifier](https://github.com/mikaelbr/node-notifier) from 8.0.0 to 8.0.1.
- [Release notes](https://github.com/mikaelbr/node-notifier/releases)
- [Changelog](https://github.com/mikaelbr/node-notifier/blob/v8.0.1/CHANGELOG.md)
- [Commits](https://github.com/mikaelbr/node-notifier/compare/v8.0.0...v8.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-26 16:38:47 -05:00
infiinte-persistence
7bd6ae1824 Make 'playback rate' persistent
This is the accompanying commit for https://github.com/lbryio/lbry-desktop/pull/5310
2021-01-19 10:16:05 -05:00
jessopb
746acef66a
Merge pull request #377 from lbryio/utxoCountPageLimit
remove support utxo count for now, add page limit
2021-01-18 23:33:49 -05:00
zeppi
0274f7e13e remove support utxo count for now, add page limit 2021-01-18 23:25:55 -05:00
jessopb
acc54f157f
Merge pull request #374 from lbryio/utxoCounts
Utxo counts
2021-01-05 12:16:05 -05:00
zeppi
3b523980de cleanup 2021-01-04 17:20:27 -05:00
zeppi
b5cc1f8818 bugfix 2021-01-04 10:54:17 -05:00
zeppi
f80c71a2ab support utxo consolidation 2021-01-02 12:47:17 -05:00
zeppi
ce9f720bbd support repost amount
bugfix

resolve reposted claims inline

refactor resolve reposts

further refactor

bugfix
2020-12-28 13:34:14 -05:00
zeppi
e3c05268e5 bugfix 2020-12-18 10:26:54 -05:00
zeppi
a37f195d73 support repost amount 2020-12-18 10:26:54 -05:00
jessopb
8adf3dada3
Merge pull request #369 from lbryio/selectPendingUrl
Select pending url
2020-12-16 10:19:50 -05:00
zeppi
664df6237d effective amount number 2020-12-16 09:48:49 -05:00
zeppi
4f57992762 pending 2020-12-16 09:48:49 -05:00
zeppi
f683af3b99 add selectEffectiveAmountForUri 2020-12-16 09:48:49 -05:00
zeppi
612acc6e7f allow undefined channel_id in repost flowtype 2020-12-16 09:48:49 -05:00
zeppi
034838a23c support search finding pending claims 2020-12-16 09:48:49 -05:00
Sean Yesmunt
92a4263c90 update Txo type 2020-12-15 15:46:39 -05:00
zeppi
eb40d2c058 persist publish language
fix publish language default
2020-11-19 14:08:54 -05:00
Sean Yesmunt
70c52e42e8 better handling of bad password errors 2020-11-13 14:41:26 -05:00
Sean Yesmunt
88dbef2cd0 pass error to fatal action handler 2020-11-13 14:41:26 -05:00
Sean Yesmunt
8344379bfd add fatal error sync action 2020-11-13 14:41:26 -05:00
infiinte-persistence
8093d69807 claim_search: Don't clear previous page results if subsequent pages timeout.
## Issue
https://github.com/lbryio/lbry-desktop/issues/4609

## Change
- Don't clear existing results on timeout.
- Treat this scenario as "last page reached" by marking `claimSearchByQueryLastPageReached`.
2020-11-13 13:17:46 -05:00
jessop
c86810038c pass languages in channel create 2020-11-09 10:41:15 -05:00
Sean Yesmunt
dc70da1be2 increase wallet balance interval to 10 seconds 2020-11-07 19:40:06 -05:00
Sean Yesmunt
4d11f31914 sync 'following' 2020-10-30 13:03:06 -04:00
jessop
04789190b0 remove tags from redux (to desktop) 2020-10-29 11:41:53 -04:00
jessop
1fc5afa0c4 add makeSelectTagInClaimOrChannelForUri 2020-10-28 10:35:34 -04:00
jessop
3cb3859baf add homepage setting 2020-10-23 14:38:17 -04:00
Sean Yesmunt
823197af37 throw error instead of logging 2020-10-20 11:59:33 -04:00
Sean Yesmunt
fb4bcdb4f5 update uuid import for new version 2020-10-20 00:18:21 -04:00
Sean Yesmunt
15737f9b09 check if 'claims' exists before looping over it 2020-10-19 23:01:34 -04:00
jessopb
77b27fea99 Revert "remove tags from redux (to desktop)"
This reverts commit 22a26be26f.
2020-10-19 17:16:25 -04:00
jessop
16c6ba1a24 support channel language update 2020-10-16 13:21:06 -04:00
jessop
3f14b93cbc add searchInLangage setting 2020-10-16 13:20:16 -04:00
jessop
22a26be26f remove tags from redux (to desktop) 2020-10-16 13:19:59 -04:00
jessop
a13ddadba4 support comment pinning 2020-10-14 16:54:19 -04:00
dependabot[bot]
7fbb87d38f Bump node-fetch from 2.2.0 to 2.6.1
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.2.0 to 2.6.1.
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.2.0...v2.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-12 23:26:05 -04:00
Jeremy Kauffman
fee1834bbc
add LBRYFoundationBoardCandidacy tag 2020-10-06 14:33:49 -04:00
jessop
ba5d6b84be flow support for comment reactions 2020-09-29 11:30:14 -04:00
jessop
90012bf47c updates supporting sync fixes
change alt preference key to local

remove enable_sync from sync list

restore to enable_sync in client settings
2020-09-21 12:56:39 -04:00
Sean Yesmunt
3bfdde4629 change default bid to 0.01 lbc 2020-09-18 10:05:56 -04:00
infiinte-persistence
437c54f164 Fix untranslatable string. 2020-09-10 13:59:07 -04:00
Sean Yesmunt
3eee65146b new settings 2020-09-08 14:41:50 -04:00
Sean Yesmunt
01df9522d5 remove comments flow type 2020-09-08 13:52:40 -04:00
Sean Yesmunt
1c02ca2b6b LBC => LBRY Credits 2020-09-02 14:33:12 -04:00
jessop
3df916548f return preference get promise 2020-08-31 14:29:51 -04:00
jessop
316dfcf06a include enable_sync in sync 2020-08-31 14:29:51 -04:00
ioancole
5c1a00b103 Add parseURI tests 2020-08-31 11:24:20 -04:00
Sean Yesmunt
7d90cba8a0 add first run flag 2020-08-27 12:29:44 -04:00
infiinte-persistence
aca15fe3e9 Add SETTINGS.ENABLE_PUBLISH_PREVIEW
This option allows users to bypass the "publish preview" modal.
2020-08-25 10:32:59 -04:00
infiinte-persistence
12ba291c3b doPublish: Add optional parameter for "preview-only" request. 2020-08-25 10:32:59 -04:00
Sean Yesmunt
903d425188 another one 2020-08-23 22:43:00 -04:00
Sean Yesmunt
0dbe6efc75 add new settings constants 2020-08-23 22:38:45 -04:00
Franco Montenegro
210bb80f2c Add to tray when closed setting 2020-08-19 16:00:08 -04:00
infiinte-persistence
05b949d470 Publish: Make 'channel' persistent by not clearing it in CLEAR_PUBLISH
Users are annoyed by the constant reset to 'Anonymous'.
2020-08-14 13:26:34 -04:00
Sean Yesmunt
7df96d4767 update build 2020-08-10 17:31:54 -04:00
Sean Yesmunt
27da80083e better handle thumbnail server being down 2020-08-10 17:31:29 -04:00
Sean Yesmunt
04e3ca8250 remove search code 2020-07-27 16:36:29 -04:00
jessop
a1d5ce7e7e provide remove abandon by uri selector 2020-07-27 10:31:01 -04:00
jessop
8c29c7e912 adds reconnect timeout and stores non-signed in prefs in different key 2020-07-24 16:47:45 -04:00
jessop
3a140c2318 provide settings constant arrays for sync 2020-07-24 16:47:45 -04:00
Sean Yesmunt
5547f53f48 add claim_id to myClaims if resolved claim is mine 2020-07-22 15:32:07 -04:00
Sean Yesmunt
3ce73c6646 remove utxo_release call before txo_list is called 2020-07-22 12:46:40 -04:00
Sean Yesmunt
11a43bae79 update types 2020-07-21 14:55:56 -04:00
Sean Yesmunt
7847224c89 update build 2020-07-14 16:05:59 -04:00
Sean Yesmunt
3dfbb7de0f remove blocked code and add additional check for middleware 2020-07-14 16:05:59 -04:00
Sean Yesmunt
6306639c34 remove comment code 2020-07-14 16:05:59 -04:00
Sean Yesmunt
7a7a1aad32 Revert "Some new translation stringfs"
This reverts commit f21f797ae3.
2020-07-14 11:07:10 -04:00
Sean Yesmunt
c57d5ea664 Revert "Removed console error strings"
This reverts commit 9c68623047.
2020-07-14 11:07:10 -04:00
Sean Yesmunt
83e20d3e6e Revert "Removed console error message from translation"
This reverts commit 10a508aaa5.
2020-07-14 11:07:10 -04:00
TigerxWood
10a508aaa5 Removed console error message from translation 2020-07-13 14:22:02 -04:00
TigerxWood
9c68623047 Removed console error strings 2020-07-13 14:22:02 -04:00
TigerxWood
f21f797ae3 Some new translation stringfs 2020-07-13 14:22:02 -04:00
Sean Yesmunt
a1673ebfa9 add permanent_url to lbryFirst publish payload 2020-07-10 13:39:55 -04:00
Sean Yesmunt
c29123e815 pass authToken to youtube.HasAuth 2020-07-10 12:36:30 -04:00
Sean Yesmunt
0be1b75343
Merge pull request #336 from infinite-persistence/ip4385--publish-status
Publish: Handle reflecting-state not updated correctly.
2020-07-10 10:04:23 -04:00
infiinte-persistence
85077f6f00
Publish: Handle reflecting-state not updated correctly.
Fixes 4385 (lbry-desktop) `Don't show upload progress if no file exists (shows 0% progress)`

An undefined result exception was causing the reflecting-state update code to be missed, so Desktop still thinks the item is still reflecting and continues to show the "Uploading(0%)" status.
2020-07-10 18:27:49 +08:00
Sean Yesmunt
e6d89b0690 send lbryfirst error to desktop for handling 2020-07-09 10:51:50 -04:00
Sean Yesmunt
ff1f95c101
Merge pull request #335 from lbryio/lbry-first 2020-07-08 11:45:02 -04:00
Sean Yesmunt
31647e4ee7 add back 'remove' function 2020-07-08 01:40:43 -04:00
Sean Yesmunt
1523bbd33f add 'lbry-first' tag to lbry-first publishes 2020-07-07 18:06:59 -04:00
Mark Beamer Jr
0f80568acd Initial LBRY First To Allow publishing to youtube channels. 2020-07-07 17:19:50 -04:00
Sean Yesmunt
8eb071d1e4
Merge pull request #334 from lbryio/fix-channelErrors 2020-07-03 11:07:28 -04:00
jessop
906199d866 provide for clearing channel errors 2020-07-02 18:03:55 -04:00
jessopb
0b867cbbdc
Merge pull request #333 from lbryio/fix-noDeletePending
prevent delete from byUri when pending
2020-07-02 16:35:11 -04:00
jessop
c0bfa4d320 prevent delete from byUri when pending 2020-07-02 12:50:54 -04:00
Sean Yesmunt
85f0f574bd update build 2020-06-29 17:43:49 -04:00
Sean Yesmunt
bcf4146e34
Merge pull request #330 from btzr-io/patch-1
Add file text to publish state
2020-06-29 17:43:13 -04:00
Sean Yesmunt
eb47b7e5b6
Merge pull request #331 from lbryio/revert 2020-06-29 17:07:36 -04:00
Sean Yesmunt
cf0b135a15 update build 2020-06-29 17:02:39 -04:00
Sean Yesmunt
cca78e9341 Revert "remove comment/blocked code"
This reverts commit e4c05cebe9.
2020-06-29 17:02:19 -04:00
Sean Yesmunt
60e41e7c9f
Merge pull request #329 from infinite-persistence/4429-unresolved-string
Fix unresolved "You sent ${amount} LBC"
2020-06-29 14:41:53 -04:00
Baltazar Gomez
ebf0d49fb0
add file text to publish state 2020-06-26 23:29:00 -05:00
infiinte-persistence
fc217d58d7
Fix unresolved "You sent ${amount} LBC"
## Fixes:
[lbry-desktop] #-4429: Unresolved string: "You sent ${amount} LBC"

## Changes:
Corrected error in commit c9492522.
2020-06-24 13:31:09 +08:00
Sean Yesmunt
e4c05cebe9 remove comment/blocked code 2020-06-23 14:48:30 -04:00
Sean Yesmunt
5ac2065b61 fix selector to properly return null for abandoned claims 2020-06-23 11:15:17 -04:00
Sean Yesmunt
d915f965b4
Merge pull request #328 from lbryio/channel-resolve-fix
handle resolving vanity channel urls
2020-06-22 14:39:30 -04:00
Sean Yesmunt
0ecf719daa handle resolving vanity channel urls 2020-06-22 14:37:37 -04:00
jessopb
c7ca11f327
Merge pull request #327 from lbryio/pendingChannels2
support pending channels
2020-06-22 13:07:32 -04:00
jessop
e9d0363588 support pending channels
bugfix

bugfix2
2020-06-22 12:38:21 -04:00
jessopb
273090d42f
Merge pull request #326 from lbryio/revert-325-pendingChannels
Revert "support pending channels"
2020-06-19 13:11:44 -04:00
jessopb
2ec145c50f
Revert "support pending channels" 2020-06-19 13:11:31 -04:00
jessopb
d00744a8b5
Merge pull request #325 from lbryio/pendingChannels
support pending channels
2020-06-19 12:15:38 -04:00
jessop
36f36cea8e bugfix 2020-06-18 19:12:29 -04:00
jessop
b32f6d0ddc support pending channels 2020-06-17 21:27:57 -04:00
Sean Yesmunt
72f9d57134
Merge pull request #323 from TigerxWood/patch-1
Update strings for translation
2020-06-17 00:56:27 -04:00
TigerxWood
c949252243
Update string for translation 2020-06-17 01:03:29 +03:00
Sean Yesmunt
f8ac5359d9
Merge pull request #322 from lbryio/signed-supports
signed support functionality
2020-06-10 18:09:23 -04:00
Sean Yesmunt
7216d2befd signed support functionality 2020-06-10 18:06:12 -04:00
Sean Yesmunt
70c2ffc0bd
Merge pull request #321 from michaelmitnick/master 2020-06-05 11:12:07 -04:00
Sean Yesmunt
a883c4f56c separate spansing and english tags 2020-06-05 11:11:03 -04:00
Sean Yesmunt
a9f1f7b61d fix typo 2020-06-04 11:40:52 -04:00
Michael Mitnick
4e0c59ee8d Default tags to spanish 2020-06-02 22:40:42 -04:00
Michael Mitnick
c7de10be2d Default tags to spanish 2020-06-02 22:26:59 -04:00
Sean Yesmunt
b2d49c2755 add case for channel_list failing 2020-06-01 14:26:26 -04:00
Jeremy Kauffman
f379c724bb update bundle 2020-05-31 10:12:48 -04:00
Jeremy Kauffman
7f1fc91b8a
add tag for current events 2020-05-31 09:31:46 -04:00
Sean Yesmunt
09ff7b0b99 remove placeholder return value 2020-05-26 22:10:52 -04:00
jessopb
65346c5977
Merge pull request #319 from lbryio/fix-uploadingState
fix uploading state selector
2020-05-26 18:00:32 -04:00
jessop
a6dce0eccf fix uploading state selector 2020-05-26 17:11:27 -04:00
Sean Yesmunt
82b1c8c51b
Merge pull request #317 from clay53/master 2020-05-26 09:47:20 -04:00
Clayton Hickey
ed68f01ff5 add case for when amount is undefined in formatCredits instead of returning '0' 2020-05-26 08:36:38 -04:00
Sean Yesmunt
d2079111b3 add selector for commet loading state 2020-05-25 14:00:22 -04:00
Sean Yesmunt
6c494eaf80
Merge pull request #315 from lbryio/paid 2020-05-21 13:16:09 -04:00
Sean Yesmunt
aa2cfa7896 fix myPurchases selector 2020-05-21 09:38:30 -04:00
Sean Yesmunt
910b55f059 don't use outpoint from get response because it might not exist 2020-05-20 15:40:25 -04:00
Sean Yesmunt
f6e5b69e5a update types 2020-05-20 13:42:36 -04:00
Sean Yesmunt
58e59acb10 update types 2020-05-20 12:06:38 -04:00
Sean Yesmunt
02831fe359 use purchase_receipt instead of content_fee 2020-05-19 14:25:46 -04:00
Sean Yesmunt
0730becb35 add more mature tags 2020-05-19 13:52:42 -04:00
Sean Yesmunt
4a59abf30c add doClearPurchasedUriSuccess 2020-05-18 22:13:18 -04:00
Sean Yesmunt
f660f1070c paid file changes for lbry.tv 2020-05-18 16:40:33 -04:00
Sean Yesmunt
486e24621e
Merge pull request #309 from lbryio/disableHttpImages 2020-05-12 10:33:00 -04:00
Sean Yesmunt
cd9c15567f
Merge pull request #311 from lbryio/purchase_list 2020-05-11 11:50:18 -04:00
Sean Yesmunt
278e12dcbe add purchase_list 2020-05-11 11:46:23 -04:00
Sean Yesmunt
259317250a update build 2020-05-11 10:09:38 -04:00
Sean Yesmunt
3af0b55b8e
Merge pull request #313 from jeffslofish/suport-lbry-link-with-timestamp 2020-05-11 10:09:07 -04:00
Sean Yesmunt
9b4bf30755
Merge pull request #312 from btzr-io/patch-1 2020-05-11 10:07:35 -04:00
jessopb
7bb6bb7ea2
Merge pull request #310 from lbryio/feat-trackReflectingFiles
track reflecting files
2020-05-08 15:07:58 -04:00
jessop
3be6fa52ac track reflecting files 2020-05-08 14:11:43 -04:00
Sean Yesmunt
1e27a854d0 update tags 2020-05-08 13:14:37 -04:00
Jeffrey Fisher
88d785a844 Support lbry:// links with timestamp 2020-05-07 14:18:40 -07:00
Baltazar Gomez
8d1ebfb9c5
enable webp images 2020-05-07 10:42:30 -05:00
Sean Yesmunt
c30e1eee2c update tags 2020-05-05 09:41:04 -04:00
jessop
efbc95f383 disable http images 2020-05-02 19:35:25 -04:00
jessopb
7d3563f856
Merge pull request #306 from lbryio/feat-paginateClaimList
fetch claim list by page
2020-05-01 14:06:24 -04:00
jessop
58ff4d8086 support paginating publishes
and removing dependencies on full claim list mine

fix pending

repost fix
2020-05-01 13:54:10 -04:00
Sean Yesmunt
f8c26fbe34 use api response for spee.ch thumbnail instead of re-creating it 2020-05-01 12:01:56 -04:00
Sean Yesmunt
17f611888c
Merge pull request #303 from lbryio/dependabot/npm_and_yarn/https-proxy-agent-2.2.4 2020-04-27 14:15:21 -04:00
Sean Yesmunt
14c8764925
Merge pull request #308 from lbryio/thumbnail-error 2020-04-27 14:15:05 -04:00
Sean Yesmunt
ce642bbae6 better handle thumbnail upload errors 2020-04-27 14:13:36 -04:00
jessopb
562a9bd40c
Merge pull request #307 from lbryio/showToHideReposts
showReposts flip it reverse it to hideReposts
2020-04-27 09:51:41 -04:00
jessop
b3b15ab0a3 showReposts flip it reverse it to hideReposts 2020-04-27 09:44:59 -04:00
Sean Yesmunt
4b4c7f9710 update tag 2020-04-22 12:14:44 -04:00
Sean Yesmunt
a64d5039b9 update list of known tags 2020-04-22 11:33:17 -04:00
Sean Yesmunt
a65d09a919
Merge pull request #305 from lbryio/utxoReleaseTxo
release utxo before txo list
2020-04-20 15:07:13 -04:00
jessop
9b63b1c7e3 release utxo before txo list 2020-04-20 14:22:15 -04:00
jessopb
3b30cd2e2d
Merge pull request #304 from lbryio/txoListConstant
add is_my_input_or_output constant
2020-04-17 08:22:00 -04:00
jessop
ee29e9a024 add is_my_input_or_output constant 2020-04-16 21:08:11 -04:00
dependabot[bot]
28c9c3e338
Bump https-proxy-agent from 2.2.1 to 2.2.4
Bumps [https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) from 2.2.1 to 2.2.4.
- [Release notes](https://github.com/TooTallNate/node-https-proxy-agent/releases)
- [Commits](https://github.com/TooTallNate/node-https-proxy-agent/compare/2.2.1...2.2.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-04-16 11:43:38 +00:00
jessopb
22c9e3563e
Merge pull request #302 from lbryio/fix-actuallyFixEmptyChannels
fix empty channels betterer
2020-04-15 17:57:34 -04:00
jessop
df043f3ef6 fix empty channels betterer 2020-04-15 17:36:48 -04:00
jessopb
4d374432cd
Merge pull request #301 from lbryio/fix-transactionPageTweaks
add txo exclude internal transfers constant
2020-04-15 13:43:27 -04:00
jessop
5994f9fb9e add txo exclude internal transfers constant 2020-04-15 13:36:29 -04:00
83 changed files with 11237 additions and 4588 deletions

View file

@ -1,5 +1,6 @@
[ignore]
[include]
[libs]
@ -12,4 +13,5 @@ module.name_mapper='^redux\(.*\)$' -> '<PROJECT_ROOT>/src/redux\1'
module.name_mapper='^util\(.*\)$' -> '<PROJECT_ROOT>/src/util\1'
module.name_mapper='^constants\(.*\)$' -> '<PROJECT_ROOT>/src/constants\1'
module.name_mapper='^lbry\(.*\)$' -> '<PROJECT_ROOT>/src/lbry\1'
module.name_mapper='^lbry-first\(.*\)$' -> '<PROJECT_ROOT>/src/lbry-first\1'
module.name_mapper='^lbryURI\(.*\)$' -> '<PROJECT_ROOT>/src/lbryURI\1'

View file

@ -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,

View file

@ -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.

5039
dist/bundle.es.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,14 +1,16 @@
// @flow
declare type Claim = StreamClaim | ChannelClaim;
declare type Claim = StreamClaim | ChannelClaim | CollectionClaim;
declare type ChannelClaim = GenericClaim & {
is_channel_signature_valid?: boolean, // we may have signed channels in the future
value: ChannelMetadata,
};
declare type CollectionClaim = GenericClaim & {
value: CollectionMetadata,
};
declare type StreamClaim = GenericClaim & {
is_channel_signature_valid?: boolean,
value: StreamMetadata,
};
@ -23,7 +25,8 @@ declare type GenericClaim = {
decoded_claim: boolean, // Not available currently https://github.com/lbryio/lbry/issues/2044
timestamp?: number, // date of last transaction
height: number, // block height the tx was confirmed
is_mine: boolean,
is_channel_signature_valid?: boolean,
is_my_output: boolean,
name: string,
normalized_name: string, // `name` normalized via unicode NFD spec,
nout: number, // index number for an output of a tx
@ -31,9 +34,13 @@ 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,
claims_in_channel?: 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
@ -121,3 +132,83 @@ declare type Fee = {
currency: string,
address: string,
};
declare type PurchaseReceipt = {
address: string,
amount: string,
claim_id: string,
confirmations: number,
height: number,
nout: number,
timestamp: number,
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
View 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
View 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,
}

View file

@ -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>,
};

View file

@ -11,6 +11,8 @@ declare type FileListItem = {
claim_id: string,
claim_name: string,
completed: false,
content_fee?: { txid: string },
purchase_receipt?: { txid: string, amount: string },
download_directory: string,
download_path: string,
file_name: string,
@ -20,6 +22,7 @@ declare type FileListItem = {
outpoint: string,
points_paid: number,
protobuf: string,
reflector_progress: number,
sd_hash: string,
status: string,
stopped: false,
@ -29,10 +32,12 @@ declare type FileListItem = {
suggested_file_name: string,
total_bytes: number,
total_bytes_lower_bound: number,
is_fully_reflected: boolean,
// TODO: sdk plans to change `tx`
// It isn't currently used by the apps
tx: {},
txid: string,
uploading_to_reflector: boolean,
written_bytes: number,
};
@ -66,7 +71,7 @@ declare type PurchaseUriStarted = {
};
declare type DeletePurchasedUri = {
type: ACTIONS.DELETE_PURCHASED_URI,
type: ACTIONS.CLEAR_PURCHASED_URI_SUCCESS,
data: {
uri: string,
},

View file

@ -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,11 +257,27 @@ declare type StreamRepostOptions = {
name: string,
bid: string,
claim_id: string,
channel_id: string,
channel_id?: string,
};
declare type StreamRepostResponse = GenericTxResponse;
declare type PurchaseListResponse = {
items: Array<PurchaseReceipt & { claim: StreamClaim }>,
page: number,
page_size: number,
total_items: number,
total_pages: number,
};
declare type PurchaseListOptions = {
page: number,
page_size: number,
resolve: boolean,
claim_id?: string,
channel_id?: string,
};
//
// Types used in the generic Lbry object that is exported
//
@ -246,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>,
@ -253,6 +318,11 @@ declare type LbryTypes = {
support_list: (params: {}) => Promise<SupportListResponse>,
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>,
@ -265,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>,
@ -283,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>,

99
dist/flow-typed/LbryFirst.js vendored Normal file
View file

@ -0,0 +1,99 @@
// @flow
declare type LbryFirstStatusResponse = {
Version: string,
Message: string,
Running: boolean,
Commit: string,
};
declare type LbryFirstVersionResponse = {
build: string,
lbrynet_version: string,
os_release: string,
os_system: string,
platform: string,
processor: string,
python_version: string,
};
/* SAMPLE UPLOAD RESPONSE (FULL)
"Video": {
"etag": "\"Dn5xIderbhAnUk5TAW0qkFFir0M/xlGLrlTox7VFTRcR8F77RbKtaU4\"",
"id": "8InjtdvVmwE",
"kind": "youtube#video",
"snippet": {
"categoryId": "22",
"channelId": "UCXiVsGTU88fJjheB2rqF0rA",
"channelTitle": "Mark Beamer",
"liveBroadcastContent": "none",
"localized": {
"title": "my title"
},
"publishedAt": "2020-05-05T04:17:53.000Z",
"thumbnails": {
"default": {
"height": 90,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/default.jpg?sqp=CMTQw_UF&rs=AOn4CLB6dlhZMSMrazDlWRsitPgCsn8fVw",
"width": 120
},
"high": {
"height": 360,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/hqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLB-Je_7l6qvASRAR_bSGWZHaXaJWQ",
"width": 480
},
"medium": {
"height": 180,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/mqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLCvSnDLqVznRNMKuvJ_0misY_chPQ",
"width": 320
}
},
"title": "my title"
},
"status": {
"embeddable": true,
"license": "youtube",
"privacyStatus": "private",
"publicStatsViewable": true,
"uploadStatus": "uploaded"
}
}
*/
declare type UploadResponse = {
Video: {
id: string,
snippet: {
channelId: string,
},
status: {
uploadStatus: string,
},
},
};
declare type HasYTAuthResponse = {
HashAuth: boolean,
};
declare type YTSignupResponse = {};
//
// Types used in the generic LbryFirst object that is exported
//
declare type LbryFirstTypes = {
isConnected: boolean,
connectPromise: ?Promise<any>,
connect: () => void,
lbryFirstConnectionString: string,
apiRequestHeaders: { [key: string]: string },
setApiHeader: (string, string) => void,
unsetApiHeader: string => void,
overrides: { [string]: ?Function },
setOverride: (string, Function) => void,
// LbryFirst Methods
stop: () => Promise<string>,
status: () => Promise<StatusResponse>,
version: () => Promise<VersionResponse>,
upload: any => Promise<?UploadResponse>,
hasYTAuth: string => Promise<HasYTAuthResponse>,
ytSignup: () => Promise<YTSignupResponse>,
};

5
dist/flow-typed/Reflector.js vendored Normal file
View file

@ -0,0 +1,5 @@
declare type ReflectingUpdate = {
fileListItem: FileListItem,
progress: number | boolean,
stalled: boolean,
};

View file

@ -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,
},
};

View file

@ -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,
}
};

View file

@ -12,6 +12,7 @@ declare type LbryUrlObj = {
secondaryClaimSequence?: number,
primaryBidPosition?: number,
secondaryBidPosition?: number,
startTime?: number,
// Below are considered deprecated and should not be used due to unreliableness with claim.canonical_url
claimName?: string,

5
dist/flow-typed/npm/from-entries.js vendored Normal file
View 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
View file

@ -0,0 +1,5 @@
// @flow
declare module 'uuid' {
declare module.exports: any;
}

View file

@ -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>;
}

101
flow-typed/Claim.js vendored
View file

@ -1,14 +1,16 @@
// @flow
declare type Claim = StreamClaim | ChannelClaim;
declare type Claim = StreamClaim | ChannelClaim | CollectionClaim;
declare type ChannelClaim = GenericClaim & {
is_channel_signature_valid?: boolean, // we may have signed channels in the future
value: ChannelMetadata,
};
declare type CollectionClaim = GenericClaim & {
value: CollectionMetadata,
};
declare type StreamClaim = GenericClaim & {
is_channel_signature_valid?: boolean,
value: StreamMetadata,
};
@ -23,7 +25,8 @@ declare type GenericClaim = {
decoded_claim: boolean, // Not available currently https://github.com/lbryio/lbry/issues/2044
timestamp?: number, // date of last transaction
height: number, // block height the tx was confirmed
is_mine: boolean,
is_channel_signature_valid?: boolean,
is_my_output: boolean,
name: string,
normalized_name: string, // `name` normalized via unicode NFD spec,
nout: number, // index number for an output of a tx
@ -31,9 +34,13 @@ 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,
claims_in_channel?: 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
@ -121,3 +132,83 @@ declare type Fee = {
currency: string,
address: string,
};
declare type PurchaseReceipt = {
address: string,
amount: string,
claim_id: string,
confirmations: number,
height: number,
nout: number,
timestamp: number,
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
View 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
View 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
View file

@ -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>,
};

7
flow-typed/File.js vendored
View file

@ -11,6 +11,8 @@ declare type FileListItem = {
claim_id: string,
claim_name: string,
completed: false,
content_fee?: { txid: string },
purchase_receipt?: { txid: string, amount: string },
download_directory: string,
download_path: string,
file_name: string,
@ -20,6 +22,7 @@ declare type FileListItem = {
outpoint: string,
points_paid: number,
protobuf: string,
reflector_progress: number,
sd_hash: string,
status: string,
stopped: false,
@ -29,10 +32,12 @@ declare type FileListItem = {
suggested_file_name: string,
total_bytes: number,
total_bytes_lower_bound: number,
is_fully_reflected: boolean,
// TODO: sdk plans to change `tx`
// It isn't currently used by the apps
tx: {},
txid: string,
uploading_to_reflector: boolean,
written_bytes: number,
};
@ -66,7 +71,7 @@ declare type PurchaseUriStarted = {
};
declare type DeletePurchasedUri = {
type: ACTIONS.DELETE_PURCHASED_URI,
type: ACTIONS.CLEAR_PURCHASED_URI_SUCCESS,
data: {
uri: string,
},

97
flow-typed/Lbry.js vendored
View file

@ -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,11 +257,27 @@ declare type StreamRepostOptions = {
name: string,
bid: string,
claim_id: string,
channel_id: string,
channel_id?: string,
};
declare type StreamRepostResponse = GenericTxResponse;
declare type PurchaseListResponse = {
items: Array<PurchaseReceipt & { claim: StreamClaim }>,
page: number,
page_size: number,
total_items: number,
total_pages: number,
};
declare type PurchaseListOptions = {
page: number,
page_size: number,
resolve: boolean,
claim_id?: string,
channel_id?: string,
};
//
// Types used in the generic Lbry object that is exported
//
@ -246,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>,
@ -253,6 +318,11 @@ declare type LbryTypes = {
support_list: (params: {}) => Promise<SupportListResponse>,
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>,
@ -265,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>,
@ -283,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>,

99
flow-typed/LbryFirst.js vendored Normal file
View file

@ -0,0 +1,99 @@
// @flow
declare type LbryFirstStatusResponse = {
Version: string,
Message: string,
Running: boolean,
Commit: string,
};
declare type LbryFirstVersionResponse = {
build: string,
lbrynet_version: string,
os_release: string,
os_system: string,
platform: string,
processor: string,
python_version: string,
};
/* SAMPLE UPLOAD RESPONSE (FULL)
"Video": {
"etag": "\"Dn5xIderbhAnUk5TAW0qkFFir0M/xlGLrlTox7VFTRcR8F77RbKtaU4\"",
"id": "8InjtdvVmwE",
"kind": "youtube#video",
"snippet": {
"categoryId": "22",
"channelId": "UCXiVsGTU88fJjheB2rqF0rA",
"channelTitle": "Mark Beamer",
"liveBroadcastContent": "none",
"localized": {
"title": "my title"
},
"publishedAt": "2020-05-05T04:17:53.000Z",
"thumbnails": {
"default": {
"height": 90,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/default.jpg?sqp=CMTQw_UF&rs=AOn4CLB6dlhZMSMrazDlWRsitPgCsn8fVw",
"width": 120
},
"high": {
"height": 360,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/hqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLB-Je_7l6qvASRAR_bSGWZHaXaJWQ",
"width": 480
},
"medium": {
"height": 180,
"url": "https://i9.ytimg.com/vi/8InjtdvVmwE/mqdefault.jpg?sqp=CMTQw_UF&rs=AOn4CLCvSnDLqVznRNMKuvJ_0misY_chPQ",
"width": 320
}
},
"title": "my title"
},
"status": {
"embeddable": true,
"license": "youtube",
"privacyStatus": "private",
"publicStatsViewable": true,
"uploadStatus": "uploaded"
}
}
*/
declare type UploadResponse = {
Video: {
id: string,
snippet: {
channelId: string,
},
status: {
uploadStatus: string,
},
},
};
declare type HasYTAuthResponse = {
HashAuth: boolean,
};
declare type YTSignupResponse = {};
//
// Types used in the generic LbryFirst object that is exported
//
declare type LbryFirstTypes = {
isConnected: boolean,
connectPromise: ?Promise<any>,
connect: () => void,
lbryFirstConnectionString: string,
apiRequestHeaders: { [key: string]: string },
setApiHeader: (string, string) => void,
unsetApiHeader: string => void,
overrides: { [string]: ?Function },
setOverride: (string, Function) => void,
// LbryFirst Methods
stop: () => Promise<string>,
status: () => Promise<StatusResponse>,
version: () => Promise<VersionResponse>,
upload: any => Promise<?UploadResponse>,
hasYTAuth: string => Promise<HasYTAuthResponse>,
ytSignup: () => Promise<YTSignupResponse>,
};

5
flow-typed/Reflector.js vendored Normal file
View file

@ -0,0 +1,5 @@
declare type ReflectingUpdate = {
fileListItem: FileListItem,
progress: number | boolean,
stalled: boolean,
};

84
flow-typed/Search.js vendored
View file

@ -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
View file

@ -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,
}
};

View file

@ -12,6 +12,7 @@ declare type LbryUrlObj = {
secondaryClaimSequence?: number,
primaryBidPosition?: number,
secondaryBidPosition?: number,
startTime?: number,
// Below are considered deprecated and should not be used due to unreliableness with claim.canonical_url
claimName?: string,

5
flow-typed/npm/from-entries.js vendored Normal file
View file

@ -0,0 +1,5 @@
// @flow
declare module '@ungap/from-entries' {
declare module.exports: any;
}

5
flow-typed/npm/uuid.js vendored Normal file
View file

@ -0,0 +1,5 @@
// @flow
declare module 'uuid' {
declare module.exports: any;
}

View file

@ -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
View 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)$"]
};

View file

@ -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",

View file

@ -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';
@ -91,6 +101,10 @@ export const ABANDON_CLAIM_STARTED = 'ABANDON_CLAIM_STARTED';
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';
@ -100,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';
@ -118,6 +133,38 @@ 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 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';
export const UPDATE_CONFIRMED_CLAIMS = 'UPDATE_CONFIRMED_CLAIMS';
export const ADD_FILES_REFLECTING = 'ADD_FILES_REFLECTING';
export const UPDATE_FILES_REFLECTING = 'UPDATE_FILES_REFLECTING';
export const TOGGLE_CHECKING_REFLECTING = 'TOGGLE_CHECKING_REFLECTING';
export const TOGGLE_CHECKING_PENDING = 'TOGGLE_CHECKING_PENDING';
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';
@ -157,20 +204,7 @@ export const SET_FILE_LIST_SORT = 'SET_FILE_LIST_SORT';
export const PURCHASE_URI_STARTED = 'PURCHASE_URI_STARTED';
export const PURCHASE_URI_COMPLETED = 'PURCHASE_URI_COMPLETED';
export const PURCHASE_URI_FAILED = 'PURCHASE_URI_FAILED';
export const DELETE_PURCHASED_URI = 'DELETE_PURCHASED_URI';
// 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';
export const CLEAR_PURCHASED_URI_SUCCESS = 'CLEAR_PURCHASED_URI_SUCCESS';
// Settings
export const DAEMON_SETTINGS_RECEIVED = 'DAEMON_SETTINGS_RECEIVED';
@ -266,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';

View file

@ -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;

View 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';

View file

@ -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',
};

View file

@ -6,10 +6,16 @@ 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 SHOW_REPOSTS = 'show_reposts';
export const HOMEPAGE = 'homepage';
export const HIDE_REPOSTS = 'hide_reposts';
export const SHOW_ANONYMOUS = 'show_anonymous';
export const SHOW_UNAVAILABLE = 'show_unavailable';
export const INSTANT_PURCHASE_ENABLED = 'instant_purchase_enabled';
@ -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';

View file

@ -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,
];

View file

@ -13,9 +13,30 @@ export const DEFAULT_FOLLOWED_TAGS = [
'technology',
];
export const MATURE_TAGS = ['porn', 'nsfw', 'mature', 'xxx'];
export const MATURE_TAGS = [
'porn',
'porno',
'nsfw',
'mature',
'xxx',
'sex',
'creampie',
'blowjob',
'handjob',
'vagina',
'boobs',
'big boobs',
'big dick',
'pussy',
'cumshot',
'anal',
'hard fucking',
'ass',
'fuck',
'hentai',
];
export const DEFAULT_KNOWN_TAGS = [
const DEFAULT_ENGLISH_KNOWN_TAGS = [
'free speech',
'censorship',
'gaming',
@ -39,6 +60,7 @@ export const DEFAULT_KNOWN_TAGS = [
'video game',
'sports',
'walkthrough',
'lbrytvpaidbeta',
'art',
'pc',
'minecraft',
@ -495,6 +517,31 @@ export const DEFAULT_KNOWN_TAGS = [
'teaser',
'lbry',
'coronavirus',
'2020protests',
'covidcuts',
'covid-19',
'LBRYFoundationBoardCandidacy',
'helplbrysavecrypto'
];
const DEFAULT_SPANISH_KNOWN_TAGS = [
'español',
'tecnología',
'criptomonedas',
'economía',
'bitcoin',
'educación',
'videojuegos',
'música',
'noticias',
'ciencia',
'deportes',
'latinoamérica',
'latam',
'conspiración',
'humor',
'política',
'tutoriales',
];
export const DEFAULT_KNOWN_TAGS = [...DEFAULT_ENGLISH_KNOWN_TAGS, ...DEFAULT_SPANISH_KNOWN_TAGS];

View file

@ -26,10 +26,11 @@ export const IS_MY_INPUT = 'is_my_input';
export const IS_MY_OUTPUT = 'is_my_output';
export const IS_NOT_MY_INPUT = 'is_not_my_input';
export const IS_NOT_MY_OUTPUT = 'is_not_my_output'; // use to further distinguish payments to self / from self.
export const IS_MY_INPUT_OR_OUTPUT = 'is_my_input_or_output';
export const EXCLUDE_INTERNAL_TRANSFERS = 'exclude_internal_transfers';
// sdk unique types
export const OTHER = 'other';
export const STREAM = 'stream';
export const PAGE_SIZE_DEFAULT = 20;

View file

@ -7,15 +7,15 @@ import * as SORT_OPTIONS from 'constants/sort_options';
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
import * as TRANSACTIONS from 'constants/transaction_types';
import * as TX_LIST from 'constants/transaction_list';
import * as TXO_ABANDON_STATES from 'constants/abandon_txo_states';
import * as ABANDON_STATES from 'constants/abandon_states';
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 { selectState as selectSearchState } from 'redux/selectors/search';
import LbryFirst from 'lbry-first';
// constants
export {
@ -23,14 +23,12 @@ export {
CLAIM_VALUES,
LICENSES,
THUMBNAIL_STATUSES,
SEARCH_TYPES,
SEARCH_OPTIONS,
SETTINGS,
DAEMON_SETTINGS,
TRANSACTIONS,
TX_LIST,
TXO_LIST,
TXO_ABANDON_STATES,
ABANDON_STATES,
SORT_OPTIONS,
PAGES,
DEFAULT_KNOWN_TAGS,
@ -38,10 +36,12 @@ export {
MATURE_TAGS,
SPEECH_URLS,
SHARED_PREFERENCES,
COLLECTIONS_CONSTS,
};
// common
export { Lbry, apiCall };
export { LbryFirst };
export {
regexInvalidURI,
regexAddress,
@ -52,6 +52,8 @@ export {
isURIClaimable,
isNameValid,
convertToShareLink,
splitBySeparator,
isURIEqual,
} from 'lbryURI';
// middlware
@ -59,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,
@ -68,20 +77,27 @@ export {
doResolveUris,
doResolveUri,
doFetchChannelListMine,
doFetchCollectionListMine,
doCreateChannel,
doUpdateChannel,
doClaimSearch,
doImportChannel,
doRepost,
doClearRepostError,
doClearChannelErrors,
doCheckPublishNameAvailability,
doPurchaseList,
doCheckPendingClaims,
doCollectionPublish,
doCollectionPublishUpdate,
} from 'redux/actions/claims';
export { doDeletePurchasedUri, doPurchaseUri, doFileGet } from 'redux/actions/file';
export { doClearPurchasedUriSuccess, doPurchaseUri, doFileGet } from 'redux/actions/file';
export {
doFetchFileInfo,
doFileList,
doFetchFileInfosAndPublishedClaims,
doFetchFileInfos,
doSetFileListSort,
} from 'redux/actions/file_info';
@ -92,19 +108,9 @@ export {
doUploadThumbnail,
doPrepareEdit,
doPublish,
doCheckPendingPublishes,
doCheckReflectingFiles,
} from 'redux/actions/publish';
export {
doSearch,
doResolvedSearch,
doUpdateSearchQuery,
doFocusSearchInput,
doBlurSearchInput,
setSearchApi,
doUpdateSearchOptions,
} from 'redux/actions/search';
export { savePosition } from 'redux/actions/content';
export {
@ -128,20 +134,11 @@ export {
doUpdateBlockHeight,
doClearSupport,
doSupportAbandonForClaim,
doFetchUtxoCounts,
doUtxoConsolidate,
doTipClaimMass,
} from 'redux/actions/wallet';
export { doToggleTagFollow, doAddTag, doDeleteTag } from 'redux/actions/tags';
export {
doCommentList,
doCommentCreate,
doCommentAbandon,
doCommentHide,
doCommentUpdate,
} from 'redux/actions/comments';
export { doToggleBlockChannel } from 'redux/actions/blocked';
export { doPopulateSharedUserState, doPreferenceGet, doPreferenceSet } from 'redux/actions/sync';
// utils
@ -152,29 +149,44 @@ export { isClaimNsfw, createNormalizedClaimSearchKey } from 'util/claim';
// reducers
export { claimsReducer } from 'redux/reducers/claims';
export { commentReducer } from 'redux/reducers/comments';
export { contentReducer } from 'redux/reducers/content';
export { fileInfoReducer } from 'redux/reducers/file_info';
export { fileReducer } from 'redux/reducers/file';
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 {
selectFailedPurchaseUris,
selectPurchasedUris,
selectPurchaseUriErrorMessage,
selectLastPurchasedUri,
makeSelectStreamingUrlForUri,
} from 'redux/selectors/file';
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,
@ -190,27 +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,
makeSelectPendingByUri,
makeSelectClaimsInChannelForCurrentPageState,
makeSelectReflectingClaimForUri,
makeSelectShortUrlForUri,
makeSelectCanonicalUrlForUri,
makeSelectPermanentUrlForUri,
makeSelectSupportsForUri,
selectPendingById,
makeSelectMyPurchasesForPage,
makeSelectClaimWasPurchased,
makeSelectAbandoningClaimById,
makeSelectIsAbandoningClaimForUri,
makeSelectClaimHasSource,
makeSelectClaimIsStreamPlaceholder,
selectPendingIds,
selectReflectingById,
makeSelectClaimForClaimId,
selectClaimsById,
selectClaimsByUri,
selectAllClaimsByChannel,
@ -219,13 +241,16 @@ export {
selectMyActiveClaims,
selectAllFetchingChannelClaims,
selectIsFetchingClaimListMine,
selectPendingClaims,
selectMyClaims,
selectPendingClaims,
selectMyClaimsWithoutChannels,
selectMyChannelUrls,
selectMyClaimUrisWithoutChannels,
selectAllMyClaimsByOutpoint,
selectMyClaimsOutpoints,
selectFetchingMyChannels,
selectFetchingMyCollections,
selectMyCollectionIds,
selectMyChannelClaims,
selectResolvingUris,
selectPlayingUri,
@ -245,10 +270,23 @@ export {
selectRepostError,
selectRepostLoading,
selectClaimIdsByUri,
selectMyClaimsPage,
selectMyClaimsPageNumber,
selectMyClaimsPageItemCount,
selectFetchingMyClaimsPageError,
selectMyPurchases,
selectIsFetchingMyPurchases,
selectFetchingMyPurchasesError,
selectMyPurchasesCount,
selectPurchaseUriSuccess,
makeSelectClaimIdForUri,
selectUpdatingCollection,
selectUpdateCollectionError,
selectCreatingCollection,
selectCreateCollectionError,
makeSelectClaimIdIsPending,
} from 'redux/selectors/claims';
export { makeSelectCommentsForUri } from 'redux/selectors/comments';
export {
makeSelectFileInfoForUri,
makeSelectDownloadingForUri,
@ -272,6 +310,7 @@ export {
makeSelectSearchDownloadUrlsForPage,
makeSelectSearchDownloadUrlsCount,
selectDownloadUrlsCount,
makeSelectStreamingUrlForUri,
} from 'redux/selectors/file_info';
export {
@ -283,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,
@ -310,6 +333,7 @@ export {
selectSupportsByOutpoint,
selectTotalSupports,
selectTransactionItems,
selectTransactionsFile,
selectRecentTransactions,
selectHasTransactions,
selectIsFetchingTransactions,
@ -347,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';

183
src/lbry-first.js Normal file
View file

@ -0,0 +1,183 @@
// @flow
import 'proxy-polyfill';
const CHECK_LBRYFIRST_STARTED_TRY_NUMBER = 200;
//
// Basic LBRYFIRST connection config
// Offers a proxy to call LBRYFIRST methods
//
const LbryFirst: LbryFirstTypes = {
isConnected: false,
connectPromise: null,
lbryFirstConnectionString: 'http://localhost:1337/rpc',
apiRequestHeaders: { 'Content-Type': 'application/json' },
// Allow overriding lbryFirst connection string (e.g. to `/api/proxy` for lbryweb)
setLbryFirstConnectionString: (value: string) => {
LbryFirst.lbryFirstConnectionString = value;
},
setApiHeader: (key: string, value: string) => {
LbryFirst.apiRequestHeaders = Object.assign(LbryFirst.apiRequestHeaders, { [key]: value });
},
unsetApiHeader: key => {
Object.keys(LbryFirst.apiRequestHeaders).includes(key) &&
delete LbryFirst.apiRequestHeaders['key'];
},
// Allow overriding Lbry methods
overrides: {},
setOverride: (methodName, newMethod) => {
LbryFirst.overrides[methodName] = newMethod;
},
getApiRequestHeaders: () => LbryFirst.apiRequestHeaders,
//
// LbryFirst Methods
//
status: (params = {}) => lbryFirstCallWithResult('status', params),
stop: () => lbryFirstCallWithResult('stop', {}),
version: () => lbryFirstCallWithResult('version', {}),
// Upload to youtube
upload: (params: { title: string, description: string, file_path: ?string } = {}) => {
// Only upload when originally publishing for now
if (!params.file_path) {
return Promise.resolve();
}
const uploadParams: {
Title: string,
Description: string,
FilePath: string,
Category: string,
Keywords: string,
} = {
Title: params.title,
Description: params.description,
FilePath: params.file_path,
Category: '',
Keywords: '',
};
return lbryFirstCallWithResult('youtube.Upload', uploadParams);
},
hasYTAuth: (token: string) => {
const hasYTAuthParams = {};
hasYTAuthParams.AuthToken = token;
return lbryFirstCallWithResult('youtube.HasAuth', hasYTAuthParams);
},
ytSignup: () => {
const emptyParams = {};
return lbryFirstCallWithResult('youtube.Signup', emptyParams);
},
remove: () => {
const emptyParams = {};
return lbryFirstCallWithResult('youtube.Remove', emptyParams);
},
// Connect to lbry-first
connect: () => {
if (LbryFirst.connectPromise === null) {
LbryFirst.connectPromise = new Promise((resolve, reject) => {
let tryNum = 0;
// Check every half second to see if the lbryFirst is accepting connections
function checkLbryFirstStarted() {
tryNum += 1;
LbryFirst.status()
.then(resolve)
.catch(() => {
if (tryNum <= CHECK_LBRYFIRST_STARTED_TRY_NUMBER) {
setTimeout(checkLbryFirstStarted, tryNum < 50 ? 400 : 1000);
} else {
reject(new Error('Unable to connect to LBRY'));
}
});
}
checkLbryFirstStarted();
});
}
// Flow thinks this could be empty, but it will always return a promise
// $FlowFixMe
return LbryFirst.connectPromise;
},
};
function checkAndParse(response) {
if (response.status >= 200 && response.status < 300) {
return response.json();
}
return response.json().then(json => {
let error;
if (json.error) {
const errorMessage = typeof json.error === 'object' ? json.error.message : json.error;
error = new Error(errorMessage);
} else {
error = new Error('Protocol error with unknown response signature');
}
return Promise.reject(error);
});
}
export function apiCall(method: string, params: ?{}, resolve: Function, reject: Function) {
const counter = new Date().getTime();
const paramsArray = [params];
const options = {
method: 'POST',
headers: LbryFirst.apiRequestHeaders,
body: JSON.stringify({
jsonrpc: '2.0',
method,
params: paramsArray,
id: counter,
}),
};
return fetch(LbryFirst.lbryFirstConnectionString, options)
.then(checkAndParse)
.then(response => {
const error = response.error || (response.result && response.result.error);
if (error) {
return reject(error);
}
return resolve(response.result);
})
.catch(reject);
}
function lbryFirstCallWithResult(name: string, params: ?{} = {}) {
return new Promise((resolve, reject) => {
apiCall(
name,
params,
result => {
resolve(result);
},
reject
);
});
}
// This is only for a fallback
// If there is a LbryFirst method that is being called by an app, it should be added to /flow-typed/LbryFirst.js
const lbryFirstProxy = new Proxy(LbryFirst, {
get(target: LbryFirstTypes, name: string) {
if (name in target) {
return target[name];
}
return (params = {}) =>
new Promise((resolve, reject) => {
apiCall(name, params, resolve, reject);
});
},
});
export default lbryFirstProxy;

View file

@ -40,7 +40,7 @@ const Lbry: LbryTypes = {
const formats = [
[/\.(mp4|m4v|webm|flv|f4v|ogv)$/i, 'video'],
[/\.(mp3|m4a|aac|wav|flac|ogg|opus)$/i, 'audio'],
[/\.(jpeg|jpg|png|gif|svg)$/i, 'image'],
[/\.(jpeg|jpg|png|gif|svg|webp)$/i, 'image'],
[/\.(h|go|ja|java|js|jsx|c|cpp|cs|css|rb|scss|sh|php|py)$/i, 'script'],
[/\.(html|json|csv|txt|log|md|markdown|docx|pdf|xml|yml|yaml)$/i, 'document'],
[/\.(pdf|odf|doc|docx|epub|org|rtf)$/i, 'e-book'],
@ -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),
@ -111,6 +116,8 @@ const Lbry: LbryTypes = {
transaction_list: (params = {}) => daemonCallWithResult('transaction_list', params),
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),

View file

@ -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('');
@ -60,6 +65,8 @@ export function parseURI(URL: string, requireProto: boolean = false): LbryUrlObj
secondaryModSeparator,
secondaryModValue,
] = rest;
const searchParams = new URLSearchParams(qs || '');
const startTime = searchParams.get('t');
// Validate protocol
if (requireProto && !proto) {
@ -73,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'));
}
});
@ -121,6 +128,7 @@ export function parseURI(URL: string, requireProto: boolean = false): LbryUrlObj
: {}),
...(primaryBidPosition ? { primaryBidPosition: parseInt(primaryBidPosition, 10) } : {}),
...(secondaryBidPosition ? { secondaryBidPosition: parseInt(secondaryBidPosition, 10) } : {}),
...(startTime ? { startTime: parseInt(startTime, 10) } : {}),
// The values below should not be used for new uses of parseURI
// They will not work properly with canonical_urls
@ -141,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;
}
}
@ -184,6 +192,7 @@ export function buildURI(
primaryBidPosition,
secondaryClaimSequence,
secondaryBidPosition,
startTime,
...deprecatedParts
} = UrlObj;
const { claimId, claimName, contentName } = deprecatedParts;
@ -233,7 +242,8 @@ export function buildURI(
(secondaryClaimName ? `/${secondaryClaimName}` : '') +
(secondaryClaimId ? `#${secondaryClaimId}` : '') +
(secondaryClaimSequence ? `:${secondaryClaimSequence}` : '') +
(secondaryBidPosition ? `${secondaryBidPosition}` : '')
(secondaryBidPosition ? `${secondaryBidPosition}` : '') +
(startTime ? `?t=${startTime}` : '')
);
}
@ -248,6 +258,7 @@ export function normalizeURI(URL: string) {
primaryBidPosition,
secondaryClaimSequence,
secondaryBidPosition,
startTime,
} = parseURI(URL);
return buildURI({
@ -259,6 +270,7 @@ export function normalizeURI(URL: string) {
primaryBidPosition,
secondaryClaimSequence,
secondaryBidPosition,
startTime,
});
}
@ -313,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;
}
}

View file

@ -1,6 +1,6 @@
// @flow
import * as ACTIONS from 'constants/action_types';
import * as TXO_STATES from 'constants/abandon_txo_states';
import * as ABANDON_STATES from 'constants/abandon_states';
import Lbry from 'lbry';
import { normalizeURI } from 'lbryURI';
import { doToast } from 'redux/actions/notifications';
@ -9,14 +9,34 @@ import {
selectResolvingUris,
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();
@ -35,6 +55,13 @@ export function doResolveUris(uris: Array<string>, returnCachedClaims: boolean =
return;
}
const options: { include_is_my_output?: boolean, include_purchase_receipt: boolean } = {
include_purchase_receipt: true,
};
if (urisToResolve.length === 1) {
options.include_is_my_output = true;
}
dispatch({
type: ACTIONS.RESOLVE_URIS_STARTED,
data: { uris: normalizedUris },
@ -45,28 +72,49 @@ export function doResolveUris(uris: Array<string>, returnCachedClaims: boolean =
stream: ?StreamClaim,
channel: ?ChannelClaim,
claimsInChannel: ?number,
collection: ?CollectionClaim,
},
} = {};
Lbry.resolve({ urls: urisToResolve }).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) {
@ -82,12 +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;
}
);
};
}
@ -98,32 +164,40 @@ 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,
});
// $FlowFixMe
Lbry.claim_list({ page, page_size: pageSize, claim_type: ['stream', 'repost'], resolve }).then(
(result: StreamListResponse) => {
const claims = result.items;
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: claimTypes,
resolve,
}).then((result: StreamListResponse) => {
dispatch({
type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
data: {
claims,
result,
resolve,
},
});
}
);
});
};
}
export function doAbandonTxo(txo: Txo, cb: string => void) {
return (dispatch: Dispatch) => {
if (cb) cb(TXO_STATES.PENDING);
if (cb) cb(ABANDON_STATES.PENDING);
const isClaim = txo.type === 'claim';
const isSupport = txo.type === 'support' && txo.is_my_input === true;
const isTip = txo.type === 'support' && txo.is_my_input === false;
@ -143,7 +217,7 @@ export function doAbandonTxo(txo: Txo, cb: string => void) {
});
const errorCallback = () => {
if (cb) cb(TXO_STATES.ERROR);
if (cb) cb(ABANDON_STATES.ERROR);
dispatch(
doToast({
message: isClaim ? 'Error abandoning your claim/support' : 'Error unlocking your tip',
@ -166,7 +240,7 @@ export function doAbandonTxo(txo: Txo, cb: string => void) {
} else {
abandonMessage = __('Successfully unlocked your tip!');
}
if (cb) cb(TXO_STATES.DONE);
if (cb) cb(ABANDON_STATES.DONE);
dispatch(
doToast({
@ -206,7 +280,7 @@ export function doAbandonTxo(txo: Txo, cb: string => void) {
};
}
export function doAbandonClaim(txid: string, nout: number) {
export function doAbandonClaim(txid: string, nout: number, cb: string => void) {
const outpoint = `${txid}:${nout}`;
return (dispatch: Dispatch, getState: GetState) => {
@ -247,6 +321,7 @@ export function doAbandonClaim(txid: string, nout: number) {
isError: true,
})
);
if (cb) cb(ABANDON_STATES.ERROR);
};
const successCallback = () => {
@ -254,6 +329,7 @@ export function doAbandonClaim(txid: string, nout: number) {
type: completedActionType,
data,
});
if (cb) cb(ABANDON_STATES.DONE);
let abandonMessage;
if (isClaim) {
@ -307,6 +383,8 @@ export function doFetchClaimsByChannel(uri: string, page: number = 1) {
valid_channel_signature: true,
page: page || 1,
order_by: ['release_time'],
include_is_my_output: true,
include_purchase_receipt: true,
}).then((result: ClaimSearchResponse) => {
const { items: claims, total_items: claimsInChannel, page: returnedPage } = result;
@ -323,7 +401,13 @@ export function doFetchClaimsByChannel(uri: string, page: number = 1) {
};
}
export function doCreateChannel(name: string, amount: number, optionalParams: any) {
export function doClearChannelErrors() {
return {
type: ACTIONS.CLEAR_CHANNEL_ERRORS,
};
}
export function doCreateChannel(name: string, amount: number, optionalParams: any, onConfirm: any) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.CREATE_CHANNEL_STARTED,
@ -339,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),
@ -368,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 (
@ -380,6 +468,13 @@ export function doCreateChannel(name: string, amount: number, optionalParams: an
type: ACTIONS.CREATE_CHANNEL_COMPLETED,
data: { channelClaim },
});
dispatch({
type: ACTIONS.UPDATE_PENDING_CLAIMS,
data: {
claims: [channelClaim],
},
});
dispatch(doCheckPendingClaims(onConfirm));
return channelClaim;
})
.catch(error => {
@ -387,13 +482,12 @@ export function doCreateChannel(name: string, amount: number, optionalParams: an
type: ACTIONS.CREATE_CHANNEL_FAILED,
data: error.message,
});
return error;
})
);
};
}
export function doUpdateChannel(params: any) {
export function doUpdateChannel(params: any, cb: any) {
return (dispatch: Dispatch, getState: GetState) => {
dispatch({
type: ACTIONS.UPDATE_CHANNEL_STARTED,
@ -413,7 +507,7 @@ export function doUpdateChannel(params: any) {
email: params.email,
tags: [],
replace: true,
languages: [],
languages: params.languages || [],
locations: [],
blocking: true,
};
@ -423,15 +517,10 @@ export function doUpdateChannel(params: 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];
@ -439,7 +528,16 @@ export function doUpdateChannel(params: any) {
type: ACTIONS.UPDATE_CHANNEL_COMPLETED,
data: { channelClaim },
});
dispatch({
type: ACTIONS.UPDATE_PENDING_CLAIMS,
data: {
claims: [channelClaim],
},
});
dispatch(doCheckPendingClaims(cb));
return Boolean(result.outputs[0]);
})
.then()
.catch(error => {
dispatch({
type: ACTIONS.UPDATE_CHANNEL_FAILED,
@ -456,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,
});
@ -487,7 +585,48 @@ export function doFetchChannelListMine(
});
};
Lbry.channel_list({ page, page_size: pageSize, resolve }).then(callback);
const failure = error => {
dispatch({
type: ACTIONS.FETCH_CHANNEL_LIST_FAILED,
data: error,
});
};
Lbry.channel_list({ page, page_size: pageSize, resolve }).then(callback, failure);
};
}
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
);
};
}
@ -495,13 +634,16 @@ 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,
@ -509,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 },
@ -533,6 +675,7 @@ export function doClaimSearch(
pageSize: options.page_size,
},
});
return resolveInfo;
};
const failure = err => {
@ -541,15 +684,18 @@ export function doClaimSearch(
data: { query },
error: err,
});
return false;
};
Lbry.claim_search(options).then(success, failure);
return await Lbry.claim_search({
...options,
include_purchase_receipt: true,
}).then(success, failure);
};
}
export function doRepost(options: StreamRepostOptions) {
return (dispatch: Dispatch) => {
// $FlowFixMe
return (dispatch: Dispatch): Promise<any> => {
return new Promise(resolve => {
dispatch({
type: ACTIONS.CLAIM_REPOST_STARTED,
@ -564,6 +710,12 @@ export function doRepost(options: StreamRepostOptions) {
repostClaim,
},
});
dispatch({
type: ACTIONS.UPDATE_PENDING_CLAIMS,
data: {
claims: [repostClaim],
},
});
dispatch(doFetchClaimListMine(1, 10));
resolve(repostClaim);
@ -583,8 +735,336 @@ 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({
type: ACTIONS.CHECK_PUBLISH_NAME_STARTED,
});
return Lbry.claim_list({ name: name }).then(result => {
dispatch({
type: ACTIONS.CHECK_PUBLISH_NAME_COMPLETED,
});
if (result.items.length) {
dispatch({
type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
data: {
result,
resolve: false,
},
});
}
return !(result && result.items && result.items.length);
});
};
}
export function doClearRepostError() {
return {
type: ACTIONS.CLEAR_REPOST_ERROR,
};
}
export function doPurchaseList(page: number = 1, pageSize: number = PAGE_SIZE) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.PURCHASE_LIST_STARTED,
});
const success = (result: PurchaseListResponse) => {
return dispatch({
type: ACTIONS.PURCHASE_LIST_COMPLETED,
data: {
result,
},
});
};
const failure = error => {
dispatch({
type: ACTIONS.PURCHASE_LIST_FAILED,
data: {
error: error.message,
},
});
};
Lbry.purchase_list({
page: page,
page_size: pageSize,
resolve: true,
}).then(success, failure);
};
}
export const doCheckPendingClaims = (onChannelConfirmed: Function) => (
dispatch: Dispatch,
getState: GetState
) => {
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];
}
});
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: claims,
pending: pendingById,
},
});
if (collectionIds.length) {
dispatch(
doFetchItemsInCollections({
collectionIds,
})
);
}
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);
}
};
// do something with onConfirmed (typically get blocklist for channel)
checkPendingInterval = setInterval(() => {
checkTxoList();
}, 30000);
};

View 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;
};

View file

@ -1,225 +0,0 @@
// @flow
import * as ACTIONS from 'constants/action_types';
import Lbry from 'lbry';
import { selectClaimsByUri, selectMyChannelClaims } from 'redux/selectors/claims';
import { doToast } from 'redux/actions/notifications';
export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const claim = selectClaimsByUri(state)[uri];
const claimId = claim ? claim.claim_id : null;
dispatch({
type: ACTIONS.COMMENT_LIST_STARTED,
});
Lbry.comment_list({
claim_id: claimId,
page,
page_size: pageSize,
})
.then((result: CommentListResponse) => {
const { items: comments } = result;
dispatch({
type: ACTIONS.COMMENT_LIST_COMPLETED,
data: {
comments,
claimId: claimId,
uri: uri,
},
});
})
.catch(error => {
console.log(error);
dispatch({
type: ACTIONS.COMMENT_LIST_FAILED,
data: error,
});
});
};
}
export function doCommentCreate(
comment: string = '',
claim_id: string = '',
channel: string,
parent_id?: string
) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
dispatch({
type: ACTIONS.COMMENT_CREATE_STARTED,
});
const myChannels = selectMyChannelClaims(state);
const namedChannelClaim =
myChannels && myChannels.find(myChannel => myChannel.name === channel);
const channel_id = namedChannelClaim.claim_id;
if (channel_id == null) {
dispatch({
type: ACTIONS.COMMENT_CREATE_FAILED,
data: {},
});
dispatch(
doToast({
message: 'Channel cannot be anonymous, please select a channel and try again.',
isError: true,
})
);
return;
}
return Lbry.comment_create({
comment: comment,
claim_id: claim_id,
channel_id: channel_id,
parent_id: parent_id,
})
.then((result: CommentCreateResponse) => {
dispatch({
type: ACTIONS.COMMENT_CREATE_COMPLETED,
data: {
comment: result,
claimId: claim_id,
},
});
})
.catch(error => {
dispatch({
type: ACTIONS.COMMENT_CREATE_FAILED,
data: error,
});
dispatch(
doToast({
message: 'Unable to create comment, please try again later.',
isError: true,
})
);
});
};
}
export function doCommentHide(comment_id: string) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.COMMENT_HIDE_STARTED,
});
return Lbry.comment_hide({
comment_ids: [comment_id],
})
.then((result: CommentHideResponse) => {
dispatch({
type: ACTIONS.COMMENT_HIDE_COMPLETED,
data: result,
});
})
.catch(error => {
dispatch({
type: ACTIONS.COMMENT_HIDE_FAILED,
data: error,
});
dispatch(
doToast({
message: 'Unable to hide this comment, please try again later.',
isError: true,
})
);
});
};
}
export function doCommentAbandon(comment_id: string) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.COMMENT_ABANDON_STARTED,
});
return Lbry.comment_abandon({
comment_id: comment_id,
})
.then((result: CommentAbandonResponse) => {
// Comment may not be deleted if the signing channel can't be signed.
// This will happen if the channel was recently created or abandoned.
if (result.abandoned) {
dispatch({
type: ACTIONS.COMMENT_ABANDON_COMPLETED,
data: {
comment_id: comment_id,
},
});
} else {
dispatch({
type: ACTIONS.COMMENT_ABANDON_FAILED,
});
dispatch(
doToast({
message: 'Your channel is still being setup, try again in a few moments.',
isError: true,
})
);
}
})
.catch(error => {
dispatch({
type: ACTIONS.COMMENT_ABANDON_FAILED,
data: error,
});
dispatch(
doToast({
message: 'Unable to delete this comment, please try again later.',
isError: true,
})
);
});
};
}
export function doCommentUpdate(comment_id: string, comment: string) {
// if they provided an empty string, they must have wanted to abandon
if (comment === '') {
return doCommentAbandon(comment_id);
} else {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.COMMENT_UPDATE_STARTED,
});
return Lbry.comment_update({
comment_id: comment_id,
comment: comment,
})
.then((result: CommentUpdateResponse) => {
if (result != null) {
dispatch({
type: ACTIONS.COMMENT_UPDATE_COMPLETED,
data: {
comment: result,
},
});
} else {
// the result will return null
dispatch({
type: ACTIONS.COMMENT_UPDATE_FAILED,
});
dispatch(
doToast({
message: 'Your channel is still being setup, try again in a few moments.',
isError: true,
})
);
}
})
.catch(error => {
dispatch({
type: ACTIONS.COMMENT_UPDATE_FAILED,
data: error,
});
dispatch(
doToast({
message: 'Unable to edit this comment, please try again later.',
isError: true,
})
);
});
};
}
}

View file

@ -3,8 +3,11 @@ import * as ACTIONS from 'constants/action_types';
import Lbry from 'lbry';
import { doToast } from 'redux/actions/notifications';
import { selectBalance } from 'redux/selectors/wallet';
import { makeSelectFileInfoForUri, selectDownloadingByOutpoint } from 'redux/selectors/file_info';
import { makeSelectStreamingUrlForUri } from 'redux/selectors/file';
import {
makeSelectFileInfoForUri,
selectDownloadingByOutpoint,
makeSelectStreamingUrlForUri,
} from 'redux/selectors/file_info';
import { makeSelectClaimForUri } from 'redux/selectors/claims';
type Dispatch = (action: any) => any;
@ -28,7 +31,6 @@ export function doFileGet(uri: string, saveFile: boolean = true, onSuccess?: Get
.then((streamInfo: GetResponse) => {
const timeout =
streamInfo === null || typeof streamInfo !== 'object' || streamInfo.error === 'Timeout';
if (timeout) {
dispatch({
type: ACTIONS.FETCH_FILE_INFO_FAILED,
@ -37,16 +39,17 @@ export function doFileGet(uri: string, saveFile: boolean = true, onSuccess?: Get
dispatch(doToast({ message: `File timeout for uri ${uri}`, isError: true }));
} else {
// purchase was completed successfully
if (streamInfo.purchase_receipt || streamInfo.content_fee) {
dispatch({
type: ACTIONS.PURCHASE_URI_COMPLETED,
data: { uri },
data: { uri, purchaseReceipt: streamInfo.purchase_receipt || streamInfo.content_fee },
});
}
dispatch({
type: ACTIONS.FETCH_FILE_INFO_COMPLETED,
data: {
fileInfo: streamInfo,
outpoint: streamInfo.outpoint,
outpoint: outpoint,
},
});
@ -55,10 +58,10 @@ export function doFileGet(uri: string, saveFile: boolean = true, onSuccess?: Get
}
}
})
.catch(() => {
.catch(error => {
dispatch({
type: ACTIONS.PURCHASE_URI_FAILED,
data: { uri },
data: { uri, error },
});
dispatch({
@ -101,7 +104,10 @@ export function doPurchaseUri(
data: { uri, error: `Already fetching uri: ${uri}` },
});
Promise.resolve();
if (onSuccess) {
onSuccess(fileInfo);
}
return;
}
@ -120,9 +126,8 @@ export function doPurchaseUri(
};
}
export function doDeletePurchasedUri(uri: string) {
export function doClearPurchasedUriSuccess() {
return {
type: ACTIONS.DELETE_PURCHASED_URI,
data: { uri },
type: ACTIONS.CLEAR_PURCHASED_URI_SUCCESS,
};
}

View file

@ -1,7 +1,6 @@
import * as ACTIONS from 'constants/action_types';
import Lbry from 'lbry';
import { doFetchClaimListMine } from 'redux/actions/claims';
import { selectClaimsByUri, selectIsFetchingClaimListMine } from 'redux/selectors/claims';
import { selectClaimsByUri } from 'redux/selectors/claims';
import { selectIsFetchingFileList, selectUrisLoading } from 'redux/selectors/file_info';
export function doFetchFileInfo(uri) {
@ -58,13 +57,10 @@ export function doFileList(page = 1, pageSize = 99999) {
};
}
export function doFetchFileInfosAndPublishedClaims() {
export function doFetchFileInfos() {
return (dispatch, getState) => {
const state = getState();
const isFetchingClaimListMine = selectIsFetchingClaimListMine(state);
const isFetchingFileInfo = selectIsFetchingFileList(state);
if (!isFetchingClaimListMine) dispatch(doFetchClaimListMine());
if (!isFetchingFileInfo) dispatch(doFileList());
};
}

View file

@ -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) {

View file

@ -4,14 +4,15 @@ import { SPEECH_STATUS, SPEECH_PUBLISH } from 'constants/speech_urls';
import * as ACTIONS from 'constants/action_types';
import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses';
import Lbry from 'lbry';
import LbryFirst from 'lbry-first';
import { batchActions } from 'util/batch-actions';
import { creditsToString } from 'util/format-credits';
import { doError } from 'redux/actions/notifications';
import { isClaimNsfw } from 'util/claim';
import {
selectMyChannelClaims,
selectPendingById,
selectMyClaimsWithoutChannels,
selectReflectingById,
} from 'redux/selectors/claims';
import { selectPublishFormValues, selectMyClaimForUri } from 'redux/selectors/publish';
@ -20,6 +21,7 @@ export const doResetThumbnailStatus = () => (dispatch: Dispatch) => {
type: ACTIONS.UPDATE_PUBLISH_FORM,
data: {
thumbnailPath: '',
thumbnailError: undefined,
},
});
@ -67,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 = () => {
@ -94,6 +98,45 @@ export const doUploadThumbnail = (
);
};
dispatch({
type: ACTIONS.UPDATE_PUBLISH_FORM,
data: {
thumbnailError: undefined,
},
});
const doUpload = data => {
return fetch(SPEECH_PUBLISH, {
method: 'POST',
body: data,
})
.then(res => res.text())
.then(text => (text.length ? JSON.parse(text) : {}))
.then(json => {
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,
},
});
})
.catch(err => {
let message = err.message;
// This sucks but ¯\_(ツ)_/¯
if (message === 'Failed to fetch') {
message = downMessage;
}
uploadError(message);
});
};
dispatch({
type: ACTIONS.UPDATE_PUBLISH_FORM,
data: { uploadThumbnailStatus: THUMBNAIL_STATUSES.IN_PROGRESS },
@ -110,24 +153,7 @@ export const doUploadThumbnail = (
data.append('name', name);
// $FlowFixMe
data.append('file', { uri: 'file://' + filePath, type: fileType, name: fileName });
return fetch(SPEECH_PUBLISH, {
method: 'POST',
body: data,
})
.then(response => response.json())
.then(json =>
json.success
? dispatch({
type: ACTIONS.UPDATE_PUBLISH_FORM,
data: {
uploadThumbnailStatus: THUMBNAIL_STATUSES.COMPLETE,
thumbnail: `${json.data.url}.${fileExt}`,
},
})
: uploadError(json.message)
)
.catch(err => uploadError(err.message));
return doUpload(data);
});
} else {
if (filePath && fs && path) {
@ -150,24 +176,7 @@ export const doUploadThumbnail = (
data.append('name', name);
// $FlowFixMe
data.append('file', file);
return fetch(SPEECH_PUBLISH, {
method: 'POST',
body: data,
})
.then(response => response.json())
.then(json =>
json.success
? dispatch({
type: ACTIONS.UPDATE_PUBLISH_FORM,
data: {
uploadThumbnailStatus: THUMBNAIL_STATUSES.COMPLETE,
thumbnail: `${json.data.url}${fileExt}`,
},
})
: uploadError(json.message)
)
.catch(err => uploadError(err.message));
return doUpload(data);
}
};
@ -186,6 +195,7 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string, fileInfo: FileLis
currency: 'LBC',
},
languages,
release_time,
license,
license_url: licenseUrl,
thumbnail,
@ -201,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,
@ -232,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);
@ -252,8 +266,10 @@ export const doPublish = (success: Function, fail: Function) => (
filePath,
description,
language,
releaseTimeEdited,
license,
licenseUrl,
useLBRYUploader,
licenseType,
otherLicenseDescription,
thumbnail,
@ -265,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;
@ -291,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,
@ -303,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,
@ -313,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;
@ -330,8 +354,14 @@ export const doPublish = (success: Function, fail: Function) => (
publishPayload.thumbnail_url = thumbnail;
}
if (useLBRYUploader) {
publishPayload.tags.push('lbry-first');
}
// 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);
@ -358,53 +388,107 @@ 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;
return Lbry.publish(publishPayload).then(success, fail);
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) {
return success(response);
}
// $FlowFixMe
publishPayload.permanent_url = response.outputs[0].permanent_url;
return LbryFirst.upload(publishPayload)
.then(() => {
// Return original publish response so app treats it like a normal publish
return success(response);
})
.catch(error => {
return success(response, error);
});
}, fail);
};
// Calls claim_list_mine until any pending publishes are confirmed
export const doCheckPendingPublishes = (onConfirmed: Function) => (
dispatch: Dispatch,
getState: GetState
) => {
// Calls file_list until any reflecting files are done
export const doCheckReflectingFiles = () => (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const pendingById = selectPendingById(state);
const { checkingReflector } = state.claims;
let reflectorCheckInterval;
if (!Object.keys(pendingById).length) {
return;
}
const checkFileList = async () => {
const state = getState();
const reflectingById = selectReflectingById(state);
const ids = Object.keys(reflectingById);
let publishCheckInterval;
const newReflectingById = {};
const promises = [];
// TODO: just use file_list({claim_id: Array<claimId>})
if (Object.keys(reflectingById).length) {
ids.forEach(claimId => {
promises.push(Lbry.file_list({ claim_id: claimId }));
});
const checkFileList = () => {
Lbry.stream_list({ page: 1, page_size: 10 }).then(result => {
const claims = result.items;
claims.forEach(claim => {
// If it's confirmed, check if it was pending previously
if (claim.confirmations > 0 && pendingById[claim.claim_id]) {
delete pendingById[claim.claim_id];
if (onConfirmed) {
onConfirmed(claim);
Promise.all(promises)
.then(results => {
results.forEach(res => {
if (res.items[0]) {
const fileListItem = res.items[0];
const fileClaimId = fileListItem.claim_id;
const {
is_fully_reflected: done,
uploading_to_reflector: uploading,
reflector_progress: progress,
} = fileListItem;
if (uploading) {
newReflectingById[fileClaimId] = {
fileListItem: fileListItem,
progress,
stalled: !done && !uploading,
};
}
}
});
})
.then(() => {
dispatch({
type: ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED,
data: {
claims,
},
type: ACTIONS.UPDATE_FILES_REFLECTING,
data: newReflectingById,
});
if (!Object.keys(pendingById).length) {
clearInterval(publishCheckInterval);
if (!Object.keys(newReflectingById).length) {
dispatch({
type: ACTIONS.TOGGLE_CHECKING_REFLECTING,
data: false,
});
clearInterval(reflectorCheckInterval);
}
});
} else {
dispatch({
type: ACTIONS.TOGGLE_CHECKING_REFLECTING,
data: false,
});
clearInterval(reflectorCheckInterval);
}
};
publishCheckInterval = setInterval(() => {
// do it once...
checkFileList();
}, 30000);
// then start the interval if it's not already started
if (!checkingReflector) {
dispatch({
type: ACTIONS.TOGGLE_CHECKING_REFLECTING,
data: true,
});
reflectorCheckInterval = setInterval(() => {
checkFileList();
}, 5000);
}
};

View file

@ -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));
}
};

View file

@ -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);
}
});
};
}

View file

@ -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,
},
});

View file

@ -1,12 +1,21 @@
import * as ACTIONS from 'constants/action_types';
import Lbry from 'lbry';
import { doToast } from 'redux/actions/notifications';
import { selectBalance, selectPendingSupportTransactions, selectTxoPageParams } from 'redux/selectors/wallet';
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 {
@ -48,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: {
@ -74,30 +80,69 @@ 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.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,
},
});
});
};
}
export function doUpdateTxoPageParams(params: TxoListParams) {
export function doUpdateTxoPageParams(params) {
return dispatch => {
dispatch({
type: ACTIONS.UPDATE_TXO_FETCH_PARAMS,
@ -125,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({
@ -164,8 +277,8 @@ export function doSendDraftTransaction(address, amount) {
if (balance - amount <= 0) {
dispatch(
doToast({
title: 'Insufficient credits',
message: 'Insufficient credits',
title: __('Insufficient credits'),
message: __('Insufficient credits'),
})
);
return;
@ -182,8 +295,8 @@ export function doSendDraftTransaction(address, amount) {
});
dispatch(
doToast({
message: `You sent ${amount} LBC`,
linkText: 'History',
message: __('You sent %amount% LBRY Credits', { amount: amount }),
linkText: __('History'),
linkTarget: '/wallet',
})
);
@ -194,7 +307,7 @@ export function doSendDraftTransaction(address, amount) {
});
dispatch(
doToast({
message: 'Transaction failed',
message: __('Transaction failed'),
isError: true,
})
);
@ -208,7 +321,7 @@ export function doSendDraftTransaction(address, amount) {
});
dispatch(
doToast({
message: 'Transaction failed',
message: __('Transaction failed'),
isError: true,
})
);
@ -235,16 +348,16 @@ export function doSetDraftTransactionAddress(address) {
};
}
export function doSendTip(amount, claimId, isSupport, successCallback, errorCallback) {
export function doSendTip(params, isSupport, successCallback, errorCallback, shouldNotify = true) {
return (dispatch, getState) => {
const state = getState();
const balance = selectBalance(state);
const myClaims = selectMyClaimsRaw(state);
const shouldSupport =
isSupport || (myClaims ? myClaims.find(claim => claim.claim_id === claimId) : false);
isSupport || (myClaims ? myClaims.find(claim => claim.claim_id === params.claim_id) : false);
if (balance - amount <= 0) {
if (balance - params.amount <= 0) {
dispatch(
doToast({
message: __('Insufficient credits'),
@ -254,23 +367,25 @@ export function doSendTip(amount, claimId, isSupport, successCallback, errorCall
return;
}
const success = () => {
const success = response => {
if (shouldNotify) {
dispatch(
doToast({
message: shouldSupport
? __('You deposited %amount% LBC as a support!', { amount })
: __('You sent %amount% LBC as a tip, Mahalo!', { 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'),
linkTarget: '/wallet',
})
);
}
dispatch({
type: ACTIONS.SUPPORT_TRANSACTION_COMPLETED,
});
if (successCallback) {
successCallback();
successCallback(response);
}
};
@ -299,10 +414,10 @@ export function doSendTip(amount, claimId, isSupport, successCallback, errorCall
});
Lbry.support_create({
claim_id: claimId,
amount: creditsToString(amount),
...params,
tip: !shouldSupport,
blocking: true,
amount: creditsToString(params.amount),
}).then(success, error);
};
}
@ -379,6 +494,7 @@ export function doWalletLock() {
};
}
// Collect all tips for a claim
export function doSupportAbandonForClaim(claimId, claimType, keep, preview) {
return dispatch => {
if (preview) {
@ -394,13 +510,12 @@ export function doSupportAbandonForClaim(claimId, claimType, keep, preview) {
const params = { claim_id: claimId };
if (preview) params['preview'] = true;
if (keep) params['keep'] = keep;
return (
Lbry.support_abandon(params)
.then((res) => {
return Lbry.support_abandon(params)
.then(res => {
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());
}
@ -411,7 +526,7 @@ export function doSupportAbandonForClaim(claimId, claimType, keep, preview) {
type: ACTIONS.ABANDON_CLAIM_SUPPORT_FAILED,
data: e.message,
});
}));
});
};
}
@ -420,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({
@ -468,7 +598,6 @@ export function doWalletStatus() {
};
}
export function doSetTransactionListFilter(filterOption) {
return {
type: ACTIONS.SET_TRANSACTION_LIST_FILTER,
@ -489,39 +618,53 @@ export function doUpdateBlockHeight() {
}
// Calls transaction_show on txes until any pending txes are confirmed
export const doCheckPendingTxs = () => (
dispatch,
getState
) => {
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 => {
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);
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,
@ -534,12 +677,31 @@ export const doCheckPendingTxs = () => (
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(() => {

View file

@ -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);
};

View file

@ -9,24 +9,35 @@
// - Sean
import * as ACTIONS from 'constants/action_types';
import { buildURI, parseURI } from 'lbryURI';
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>,
pendingById: { [string]: Claim },
reflectingById: { [string]: ReflectingUpdate },
myClaims: ?Array<string>,
myChannelClaims: ?Array<string>,
myCollectionClaims: ?Array<string>,
abandoningById: { [string]: boolean },
fetchingChannelClaims: { [string]: number },
fetchingMyChannels: boolean,
fetchingMyCollections: boolean,
fetchingClaimSearchByQuery: { [string]: boolean },
purchaseUriSuccess: boolean,
myPurchases: ?Array<string>,
myPurchasesPageNumber: ?number,
myPurchasesPageTotalResults: ?number,
fetchingMyPurchases: boolean,
fetchingMyPurchasesError: ?string,
claimSearchByQuery: { [string]: Array<string> },
claimSearchByQueryLastPageReached: { [string]: Array<boolean> },
creatingChannel: boolean,
creatingCollection: boolean,
paginatedClaimsByChannel: {
[string]: {
all: Array<string>,
@ -35,11 +46,21 @@ type State = {
[number]: Array<string>,
},
},
updateChannelError: string,
updateChannelError: ?string,
updateCollectionError: ?string,
updatingChannel: boolean,
updatingCollection: boolean,
pendingChannelImport: string | boolean,
repostLoading: boolean,
repostError: ?string,
fetchingClaimListMinePageError: ?string,
myClaimsPageResults: Array<string>,
myClaimsPageNumber: ?number,
myClaimsPageTotalResults: ?number,
isFetchingClaimListMine: boolean,
isCheckingNameForPublish: boolean,
checkingPending: boolean,
checkingReflecting: boolean,
};
const reducers = {};
@ -50,51 +71,67 @@ const defaultState = {
channelClaimCounts: {},
fetchingChannelClaims: {},
resolvingUris: [],
// This should not be a Set
// Storing sets in reducers can cause issues
myChannelClaims: undefined,
myCollectionClaims: [],
myClaims: undefined,
myPurchases: undefined,
myPurchasesPageNumber: undefined,
myPurchasesPageTotalResults: undefined,
purchaseUriSuccess: false,
fetchingMyPurchases: false,
fetchingMyPurchasesError: undefined,
fetchingMyChannels: false,
fetchingMyCollections: false,
abandoningById: {},
pendingById: {},
reflectingById: {},
claimSearchError: false,
claimSearchByQuery: {},
claimSearchByQueryLastPageReached: {},
fetchingClaimSearchByQuery: {},
updateChannelError: '',
updateCollectionError: '',
updatingChannel: false,
creatingChannel: false,
createChannelError: undefined,
updatingCollection: false,
creatingCollection: false,
createCollectionError: undefined,
pendingChannelImport: false,
repostLoading: false,
repostError: undefined,
fetchingClaimListMinePageError: undefined,
myClaimsPageResults: [],
myClaimsPageNumber: undefined,
myClaimsPageTotalResults: undefined,
isFetchingClaimListMine: false,
isFetchingMyPurchases: false,
isCheckingNameForPublish: false,
checkingPending: false,
checkingReflecting: false,
};
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 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;
if (claimsInChannel) {
channelClaimCounts[url] = claimsInChannel;
channelClaimCounts[channel.canonical_url] = claimsInChannel;
}
const { claimsInChannel, stream, channel: channelFromResolve, collection } = resolveResponse;
const channel = channelFromResolve || (stream && stream.signing_channel);
if (stream) {
if (pendingById[stream.claim_id]) {
byId[stream.claim_id] = mergeClaim(stream, byId[stream.claim_id]);
} else {
byId[stream.claim_id] = stream;
}
byUri[url] = stream.claim_id;
// If url isn't a canonical_url, make sure that is added too
@ -104,23 +141,53 @@ 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) {
if (channel && channel.claim_id) {
if (!stream) {
byUri[url] = channel.claim_id;
}
if (claimsInChannel) {
channelClaimCounts[url] = claimsInChannel;
channelClaimCounts[channel.canonical_url] = claimsInChannel;
}
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) {
if (!stream && !channel && !collection && !pendingById[byUri[url]]) {
byUri[url] = null;
}
});
@ -130,6 +197,7 @@ function handleClaimAction(state: State, action: any): State {
claimsByUri: byUri,
channelClaimCounts,
resolvingUris: Array.from(newResolvingUrls),
myClaims: Array.from(myClaimIds),
});
}
@ -162,46 +230,46 @@ reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_STARTED] = (state: State): State =>
});
reducers[ACTIONS.FETCH_CLAIM_LIST_MINE_COMPLETED] = (state: State, action: any): State => {
const { claims }: { claims: Array<Claim> } = 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 pendingById: { [string]: Claim } = Object.assign({}, state.pendingById);
const pendingById = Object.assign({}, state.pendingById);
let myClaimIds = new Set(state.myClaims);
let urlsForCurrentPage = [];
claims.forEach((claim: Claim) => {
const uri = buildURI({ streamName: claim.name, streamClaimId: claim.claim_id });
const { 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) {
pendingById[claimId] = claim;
delete byId[claimId];
delete byUri[claimId];
if (byId[claimId]) {
byId[claimId] = mergeClaim(claim, byId[claimId]);
} else {
byId[claimId] = claim;
byUri[uri] = claimId;
}
} else {
byId[claimId] = claim;
}
byUri[permanentUri] = claimId;
byUri[canonicalUri] = claimId;
myClaimIds.add(claimId);
if (pendingById[claimId] && claim.confirmations > 0) {
delete pendingById[claimId];
}
}
});
// Remove old pending publishes
Object.values(pendingById)
// $FlowFixMe
.filter(pendingClaim => byId[pendingClaim.claim_id])
.forEach(pendingClaim => {
// $FlowFixMe
delete pendingById[pendingClaim.claim_id];
});
return Object.assign({}, state, {
isFetchingClaimListMine: false,
myClaims: Array.from(myClaimIds),
byId,
claimsByUri: byUri,
pendingById,
claimsByUri: byUri,
myClaimsPageResults: urlsForCurrentPage,
myClaimsPageNumber: page,
myClaimsPageTotalResults: totalItems,
});
};
@ -210,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 pendingById = Object.assign(state.pendingById);
const pendingById = Object.assign({}, state.pendingById);
let myChannelClaims;
const byId = Object.assign({}, state.byId);
const byUri = Object.assign({}, state.claimsByUri);
@ -220,13 +287,18 @@ reducers[ACTIONS.FETCH_CHANNEL_LIST_COMPLETED] = (state: State, action: any): St
if (!claims.length) {
// $FlowFixMe
myChannelClaims = [];
myChannelClaims = null;
} else {
myChannelClaims = new Set(state.myChannelClaims);
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;
@ -235,31 +307,97 @@ reducers[ACTIONS.FETCH_CHANNEL_LIST_COMPLETED] = (state: State, action: any): St
// $FlowFixMe
myChannelClaims.add(claimId);
if (!byId[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);
if (pendingById[claimId] && claim.confirmations > 0) {
delete pendingById[claimId];
}
if (pendingById[claimId] && claim.confirmations > 0) {
delete pendingById[claimId];
}
});
}
return Object.assign({}, state, {
byId,
pendingById,
claimsByUri: byUri,
channelClaimCounts,
fetchingMyChannels: false,
myChannelClaims: Array.from(myChannelClaims),
myClaims: Array.from(myClaimIds),
myChannelClaims: myChannelClaims ? Array.from(myChannelClaims) : null,
myClaims: myClaimIds ? Array.from(myClaimIds) : null,
});
};
reducers[ACTIONS.FETCH_CHANNEL_LIST_FAILED] = (state: State, action: any): State => {
return Object.assign({}, state, {
fetchingMyChannels: false,
});
};
reducers[ACTIONS.FETCH_COLLECTION_LIST_STARTED] = (state: State): State => ({
...state,
fetchingMyCollections: true,
});
reducers[ACTIONS.FETCH_COLLECTION_LIST_COMPLETED] = (state: State, action: any): State => {
const { claims }: { claims: Array<CollectionClaim> } = action.data;
const myClaims = state.myClaims || [];
let myClaimIds = new Set(myClaims);
const 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);
@ -338,12 +476,77 @@ 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);
let myClaimIds = new Set(state.myClaims);
const myChannelClaims = new Set(state.myChannelClaims);
// $FlowFixMe
pendingClaims.forEach((claim: Claim) => {
let newClaim;
const { permanent_url: uri, claim_id: claimId, type, value_type: valueType } = claim;
pendingById[claimId] = claim; // make sure we don't need to merge?
const oldClaim = byId[claimId];
if (oldClaim && oldClaim.canonical_url) {
newClaim = mergeClaim(oldClaim, claim);
} else {
newClaim = claim;
}
if (valueType === 'channel') {
myChannelClaims.add(claimId);
}
if (type && type.match(/claim|update/)) {
byId[claimId] = newClaim;
byUri[uri] = claimId;
}
myClaimIds.add(claimId);
});
return Object.assign({}, state, {
myClaims: Array.from(myClaimIds),
byId,
pendingById,
myChannelClaims: Array.from(myChannelClaims),
claimsByUri: byUri,
});
};
reducers[ACTIONS.UPDATE_CONFIRMED_CLAIMS] = (state: State, action: any): State => {
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);
//
confirmedClaims.forEach((claim: GenericClaim) => {
const { claim_id: claimId, type } = claim;
let newClaim = claim;
const oldClaim = byId[claimId];
if (oldClaim && oldClaim.canonical_url) {
newClaim = mergeClaim(oldClaim, claim);
}
if (type && type.match(/claim|update|channel/)) {
byId[claimId] = newClaim;
}
});
return Object.assign({}, state, {
pendingById: pendingClaims,
byId,
claimsByUri: byUri,
});
};
reducers[ACTIONS.ABANDON_CLAIM_SUCCEEDED] = (state: State, action: any): State => {
const { claimId }: { claimId: string } = action.data;
const byId = Object.assign({}, state.byId);
const newMyClaims = state.myClaims ? state.myClaims.slice() : [];
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) {
@ -352,17 +555,25 @@ 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,
});
};
reducers[ACTIONS.CLEAR_CHANNEL_ERRORS] = (state: State): State => ({
...state,
createChannelError: null,
updateChannelError: null,
});
reducers[ACTIONS.CREATE_CHANNEL_STARTED] = (state: State): State => ({
...state,
creatingChannel: true,
@ -370,19 +581,7 @@ reducers[ACTIONS.CREATE_CHANNEL_STARTED] = (state: State): State => ({
});
reducers[ACTIONS.CREATE_CHANNEL_COMPLETED] = (state: State, action: any): State => {
const channelClaim: ChannelClaim = action.data.channelClaim;
const byId = Object.assign({}, state.byId);
const pendingById = Object.assign({}, state.pendingById);
const myChannelClaims = new Set(state.myChannelClaims);
byId[channelClaim.claim_id] = channelClaim;
pendingById[channelClaim.claim_id] = channelClaim;
myChannelClaims.add(channelClaim.claim_id);
return Object.assign({}, state, {
byId,
pendingById,
myChannelClaims: Array.from(myChannelClaims),
creatingChannel: false,
});
};
@ -402,13 +601,7 @@ reducers[ACTIONS.UPDATE_CHANNEL_STARTED] = (state: State, action: any): State =>
};
reducers[ACTIONS.UPDATE_CHANNEL_COMPLETED] = (state: State, action: any): State => {
const channelClaim: ChannelClaim = action.data.channelClaim;
const byId = Object.assign({}, state.byId);
byId[channelClaim.claim_id] = channelClaim;
return Object.assign({}, state, {
byId,
updateChannelError: '',
updatingChannel: false,
});
@ -421,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 });
@ -472,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,
});
};
@ -522,6 +780,133 @@ reducers[ACTIONS.CLEAR_REPOST_ERROR] = (state: State): State => {
repostError: null,
};
};
reducers[ACTIONS.ADD_FILES_REFLECTING] = (state: State, action): State => {
const pendingClaim = action.data;
const { reflectingById } = state;
const claimId = pendingClaim && pendingClaim.claim_id;
reflectingById[claimId] = { fileListItem: pendingClaim, progress: 0, stalled: false };
return Object.assign({}, state, {
...state,
reflectingById: reflectingById,
});
};
reducers[ACTIONS.UPDATE_FILES_REFLECTING] = (state: State, action): State => {
const newReflectingById = action.data;
return Object.assign({}, state, {
...state,
reflectingById: newReflectingById,
});
};
reducers[ACTIONS.TOGGLE_CHECKING_REFLECTING] = (state: State, action): State => {
const checkingReflecting = action.data;
return Object.assign({}, state, {
...state,
checkingReflecting,
});
};
reducers[ACTIONS.TOGGLE_CHECKING_PENDING] = (state: State, action): State => {
const checking = action.data;
return Object.assign({}, state, {
...state,
checkingPending: checking,
});
};
reducers[ACTIONS.PURCHASE_LIST_STARTED] = (state: State): State => {
return {
...state,
fetchingMyPurchases: true,
fetchingMyPurchasesError: null,
};
};
reducers[ACTIONS.PURCHASE_LIST_COMPLETED] = (state: State, action: any): State => {
const { result }: { result: PurchaseListResponse, resolve: boolean } = action.data;
const page = result.page;
const totalItems = result.total_items;
let byId = Object.assign({}, state.byId);
let byUri = Object.assign({}, state.claimsByUri);
let urlsForCurrentPage = [];
result.items.forEach(item => {
if (!item.claim) {
// Abandoned claim
return;
}
const { claim, ...purchaseInfo } = item;
claim.purchase_receipt = purchaseInfo;
const claimId = claim.claim_id;
const uri = claim.canonical_url;
byId[claimId] = claim;
byUri[uri] = claimId;
urlsForCurrentPage.push(uri);
});
return Object.assign({}, state, {
byId,
claimsByUri: byUri,
myPurchases: urlsForCurrentPage,
myPurchasesPageNumber: page,
myPurchasesPageTotalResults: totalItems,
fetchingMyPurchases: false,
});
};
reducers[ACTIONS.PURCHASE_LIST_FAILED] = (state: State, action: any): State => {
const { error } = action.data;
return {
...state,
fetchingMyPurchases: false,
fetchingMyPurchasesError: error,
};
};
reducers[ACTIONS.PURCHASE_URI_COMPLETED] = (state: State, action: any): State => {
const { uri, purchaseReceipt } = action.data;
let byId = Object.assign({}, state.byId);
let byUri = Object.assign({}, state.claimsByUri);
let myPurchases = state.myPurchases ? state.myPurchases.slice() : [];
let urlsForCurrentPage = [];
const claimId = byUri[uri];
if (claimId) {
let claim = byId[claimId];
claim.purchase_receipt = purchaseReceipt;
}
myPurchases.push(uri);
return {
...state,
byId,
myPurchases,
purchaseUriSuccess: true,
};
};
reducers[ACTIONS.PURCHASE_URI_FAILED] = (state: State): State => {
return {
...state,
purchaseUriSuccess: false,
};
};
reducers[ACTIONS.CLEAR_PURCHASED_URI_SUCCESS] = (state: State): State => {
return {
...state,
purchaseUriSuccess: false,
};
};
export function claimsReducer(state: State = defaultState, action: any) {
const handler = reducers[action.type];

View 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 };

View file

@ -1,153 +0,0 @@
// @flow
import * as ACTIONS from 'constants/action_types';
import { handleActions } from 'util/redux-utils';
const defaultState: CommentsState = {
commentById: {}, // commentId -> Comment
byId: {}, // ClaimID -> list of comments
commentsByUri: {}, // URI -> claimId
isLoading: false,
myComments: undefined,
};
export const commentReducer = handleActions(
{
[ACTIONS.COMMENT_CREATE_STARTED]: (state: CommentsState, action: any): CommentsState => ({
...state,
isLoading: true,
}),
[ACTIONS.COMMENT_CREATE_FAILED]: (state: CommentsState, action: any) => ({
...state,
isLoading: false,
}),
[ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
const { comment, claimId }: { comment: Comment, claimId: string } = action.data;
const commentById = Object.assign({}, state.commentById);
const byId = Object.assign({}, state.byId);
const comments = byId[claimId];
const newCommentIds = comments.slice();
// add the comment by its ID
commentById[comment.comment_id] = comment;
// push the comment_id to the top of ID list
newCommentIds.unshift(comment.comment_id);
byId[claimId] = newCommentIds;
return {
...state,
commentById,
byId,
isLoading: false,
};
},
[ACTIONS.COMMENT_LIST_STARTED]: state => ({ ...state, isLoading: true }),
[ACTIONS.COMMENT_LIST_COMPLETED]: (state: CommentsState, action: any) => {
const { comments, claimId, uri } = action.data;
const commentById = Object.assign({}, state.commentById);
const byId = Object.assign({}, state.byId);
const commentsByUri = Object.assign({}, state.commentsByUri);
if (comments) {
// we use an Array to preserve order of listing
// in reality this doesn't matter and we can just
// sort comments by their timestamp
const commentIds = Array(comments.length);
// map the comment_ids to the new comments
for (let i = 0; i < comments.length; i++) {
commentIds[i] = comments[i].comment_id;
commentById[commentIds[i]] = comments[i];
}
byId[claimId] = commentIds;
commentsByUri[uri] = claimId;
}
return {
...state,
byId,
commentById,
commentsByUri,
isLoading: false,
};
},
[ACTIONS.COMMENT_LIST_FAILED]: (state: CommentsState, action: any) => ({
...state,
isLoading: false,
}),
[ACTIONS.COMMENT_ABANDON_STARTED]: (state: CommentsState, action: any) => ({
...state,
isLoading: true,
}),
[ACTIONS.COMMENT_ABANDON_COMPLETED]: (state: CommentsState, action: any) => {
const { comment_id } = action.data;
const commentById = Object.assign({}, state.commentById);
const byId = Object.assign({}, state.byId);
// to remove the comment and its references
const claimId = commentById[comment_id].claim_id;
for (let i = 0; i < byId[claimId].length; i++) {
if (byId[claimId][i] === comment_id) {
byId[claimId].splice(i, 1);
break;
}
}
delete commentById[comment_id];
return {
...state,
commentById,
byId,
isLoading: false,
};
},
// do nothing
[ACTIONS.COMMENT_ABANDON_FAILED]: (state: CommentsState, action: any) => ({
...state,
isLoading: false,
}),
// do nothing
[ACTIONS.COMMENT_UPDATE_STARTED]: (state: CommentsState, action: any) => ({
...state,
isLoading: true,
}),
// replace existing comment with comment returned here under its comment_id
[ACTIONS.COMMENT_UPDATE_COMPLETED]: (state: CommentsState, action: any) => {
const { comment } = action.data;
const commentById = Object.assign({}, state.commentById);
commentById[comment.comment_id] = comment;
return {
...state,
commentById,
isLoading: false,
};
},
// nothing can be done here
[ACTIONS.COMMENT_UPDATE_FAILED]: (state: CommentsState, action: any) => ({
...state,
isLoading: false,
}),
// nothing can really be done here
[ACTIONS.COMMENT_HIDE_STARTED]: (state: CommentsState, action: any) => ({
...state,
isLoading: true,
}),
[ACTIONS.COMMENT_HIDE_COMPLETED]: (state: CommentsState, action: any) => ({
...state, // todo: add HiddenComments state & create selectors
isLoading: false,
}),
// nothing can be done here
[ACTIONS.COMMENT_HIDE_FAILED]: (state: CommentsState, action: any) => ({
...state,
isLoading: false,
}),
},
defaultState
);

View file

@ -1,89 +0,0 @@
// @flow
import * as ACTIONS from 'constants/action_types';
const reducers = {};
const defaultState = {
failedPurchaseUris: [],
purchasedUris: [],
purchaseUriErrorMessage: '',
};
reducers[ACTIONS.PURCHASE_URI_STARTED] = (
state: FileState,
action: PurchaseUriStarted
): FileState => {
const { uri } = action.data;
const newFailedPurchaseUris = state.failedPurchaseUris.slice();
if (newFailedPurchaseUris.includes(uri)) {
newFailedPurchaseUris.splice(newFailedPurchaseUris.indexOf(uri), 1);
}
return {
...state,
failedPurchaseUris: newFailedPurchaseUris,
purchaseUriErrorMessage: '',
};
};
reducers[ACTIONS.PURCHASE_URI_COMPLETED] = (
state: FileState,
action: PurchaseUriCompleted
): FileState => {
const { uri } = action.data;
const newPurchasedUris = state.purchasedUris.slice();
const newFailedPurchaseUris = state.failedPurchaseUris.slice();
if (!newPurchasedUris.includes(uri)) {
newPurchasedUris.push(uri);
}
if (newFailedPurchaseUris.includes(uri)) {
newFailedPurchaseUris.splice(newFailedPurchaseUris.indexOf(uri), 1);
}
return {
...state,
failedPurchaseUris: newFailedPurchaseUris,
purchasedUris: newPurchasedUris,
purchaseUriErrorMessage: '',
};
};
reducers[ACTIONS.PURCHASE_URI_FAILED] = (
state: FileState,
action: PurchaseUriFailed
): FileState => {
const { uri, error } = action.data;
const newFailedPurchaseUris = state.failedPurchaseUris.slice();
if (!newFailedPurchaseUris.includes(uri)) {
newFailedPurchaseUris.push(uri);
}
return {
...state,
failedPurchaseUris: newFailedPurchaseUris,
purchaseUriErrorMessage: error,
};
};
reducers[ACTIONS.DELETE_PURCHASED_URI] = (
state: FileState,
action: DeletePurchasedUri
): FileState => {
const { uri } = action.data;
const newPurchasedUris = state.purchasedUris.slice();
if (newPurchasedUris.includes(uri)) {
newPurchasedUris.splice(newPurchasedUris.indexOf(uri), 1);
}
return {
...state,
purchasedUris: newPurchasedUris,
};
};
export function fileReducer(state: FileState = defaultState, action: any) {
const handler = reducers[action.type];
if (handler) return handler(state, action);
return state;
}

View file

@ -7,7 +7,9 @@ import { CHANNEL_ANONYMOUS } from 'constants/claim';
type PublishState = {
editingURI: ?string,
fileText: ?string,
filePath: ?string,
remoteFileUrl: ?string,
contentIsFree: boolean,
fileDur: number,
fileSize: number,
@ -20,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,
@ -32,14 +37,17 @@ type PublishState = {
licenseUrl: string,
tags: Array<string>,
optimize: boolean,
useLBRYUploader: boolean,
};
const defaultState: PublishState = {
editingURI: undefined,
fileText: '',
filePath: undefined,
fileDur: 0,
fileSize: 0,
fileVid: false,
remoteFileUrl: undefined,
contentIsFree: true,
fee: {
amount: 1,
@ -49,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',
@ -66,6 +77,7 @@ const defaultState: PublishState = {
publishSuccess: false,
publishError: undefined,
optimize: false,
useLBRYUploader: false,
};
export const publishReducer = handleActions(
@ -79,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,

View file

@ -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
);

View file

@ -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
);

View file

@ -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,7 +324,12 @@ 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 };
@ -225,7 +349,6 @@ export const walletReducer = handleActions(
},
[ACTIONS.PENDING_SUPPORTS_UPDATED]: (state: WalletState, action: any): WalletState => {
return {
...state,
pendingSupportTransactions: action.data,

View file

@ -1,21 +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, createNormalizedClaimSearchKey } from 'util/claim';
import { getSearchQueryString } from 'util/query-params';
import { PAGE_SIZE } from 'constants/claim';
import { isClaimNsfw, filterClaims } from 'util/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 || {}
@ -47,10 +56,9 @@ export const selectRepostError = createSelector(
);
export const selectClaimsByUri = createSelector(
selectState,
selectClaimIdsByUri,
selectClaimsById,
(state, byId) => {
const byUri = state.claimsByUri || {};
(byUri, byId) => {
const claims = {};
Object.keys(byUri).forEach(uri => {
@ -75,82 +83,91 @@ export const selectAllClaimsByChannel = createSelector(
state => state.paginatedClaimsByChannel || {}
);
export const selectPendingById = createSelector(
export const selectPendingIds = createSelector(
selectState,
state => state.pendingById || {}
state => Object.keys(state.pendingById) || []
);
export const selectPendingClaims = createSelector(
selectState,
state => Object.values(state.pendingById || [])
selectPendingClaimsById,
pendingById => Object.values(pendingById)
);
export const makeSelectClaimIsPending = (uri: string) =>
createSelector(
selectPendingById,
pendingById => {
let claimId;
try {
const { isChannel, channelClaimId, streamClaimId } = parseURI(uri);
claimId = isChannel ? channelClaimId : streamClaimId;
} catch (e) {}
selectClaimIdsByUri,
selectPendingClaimsById,
(idsByUri, pendingById) => {
const claimId = idsByUri[normalizeURI(uri)];
if (claimId) {
return Boolean(pendingById[claimId]);
}
return false;
}
);
export const makeSelectPendingByUri = (uri: string) =>
export const makeSelectClaimIdIsPending = (claimId: string) =>
createSelector(
selectPendingById,
selectPendingClaimsById,
pendingById => {
const { isChannel, channelClaimId, streamClaimId } = parseURI(uri);
const claimId = isChannel ? channelClaimId : streamClaimId;
return pendingById[claimId];
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(
selectClaimsByUri,
selectPendingById,
(byUri, pendingById) => {
// Check if a claim is pending first
// It won't be in claimsByUri because resolving it will return nothing
let valid;
selectClaimIdsByUri,
selectClaimsById,
(byUri, byId) => {
let validUri;
let channelClaimId;
let streamClaimId;
let isChannel;
try {
({ isChannel, channelClaimId, streamClaimId } = parseURI(uri));
valid = true;
validUri = true;
} catch (e) {}
if (valid && byUri) {
const claimId = isChannel ? channelClaimId : streamClaimId;
const pendingClaim = pendingById[claimId];
if (validUri && byUri) {
const claimId = uri && byUri[normalizeURI(uri)];
const claim = byId[claimId];
if (pendingClaim) {
return pendingClaim;
}
const claim = byUri[normalizeURI(uri)];
if (claim === undefined || claim === null) {
// Make sure to return the claim as is so apps can check if it's been resolved before (null) or still needs to be resolved (undefined)
return claim;
if (claimId === null) {
return null;
} else if (claimId === undefined) {
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;
@ -184,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,
@ -212,11 +245,74 @@ export const makeSelectClaimIsMine = (rawUri: string) => {
return false;
}
return claims && claims[uri] && claims[uri].claim_id && myClaims.has(claims[uri].claim_id);
return (
claims &&
claims[uri] &&
(claims[uri].is_my_output || (claims[uri].claim_id && myClaims.has(claims[uri].claim_id)))
);
}
);
};
export const selectMyPurchases = createSelector(
selectState,
state => state.myPurchases
);
export const selectPurchaseUriSuccess = createSelector(
selectState,
state => state.purchaseUriSuccess
);
export const selectMyPurchasesCount = createSelector(
selectState,
state => state.myPurchasesPageTotalResults
);
export const selectIsFetchingMyPurchases = createSelector(
selectState,
state => state.fetchingMyPurchases
);
export const selectFetchingMyPurchasesError = createSelector(
selectState,
state => state.fetchingMyPurchasesError
);
export const makeSelectMyPurchasesForPage = (query: ?string, page: number = 1) =>
createSelector(
selectMyPurchases,
selectClaimsByUri,
(myPurchases: Array<string>, claimsByUri: { [string]: Claim }) => {
if (!myPurchases) {
return undefined;
}
if (!query) {
// ensure no duplicates from double purchase bugs
return [...new Set(myPurchases)];
}
const fileInfos = myPurchases.map(uri => claimsByUri[uri]);
const matchingFileInfos = filterClaims(fileInfos, query);
const start = (Number(page) - 1) * Number(CLAIM.PAGE_SIZE);
const end = Number(page) * Number(CLAIM.PAGE_SIZE);
return matchingFileInfos && matchingFileInfos.length
? matchingFileInfos
.slice(start, end)
.map(fileInfo => fileInfo.canonical_url || fileInfo.permanent_url)
: [];
}
);
export const makeSelectClaimWasPurchased = (uri: string) =>
createSelector(
makeSelectClaimForUri(uri),
claim => {
return claim && claim.purchase_receipt !== undefined;
}
);
export const selectAllFetchingChannelClaims = createSelector(
selectState,
state => state.fetchingChannelClaims || {}
@ -242,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,
@ -252,6 +349,7 @@ export const makeSelectTotalClaimsInChannelSearch = (uri: string) =>
}
);
// THIS IS LEFT OVER FROM ONE_TAB CHANNEL CONTENT
export const makeSelectTotalPagesInChannelSearch = (uri: string) =>
createSelector(
selectClaimsById,
@ -262,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),
@ -328,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),
@ -342,7 +438,9 @@ export const makeSelectThumbnailForUri = (uri: string) =>
makeSelectClaimForUri(uri),
claim => {
const thumbnail = claim && claim.value && claim.value.thumbnail;
return thumbnail && thumbnail.url ? thumbnail.url.trim() : undefined;
return thumbnail && thumbnail.url
? thumbnail.url.trim().replace(/^http:\/\//i, 'https://')
: undefined;
}
);
@ -351,7 +449,7 @@ export const makeSelectCoverForUri = (uri: string) =>
makeSelectClaimForUri(uri),
claim => {
const cover = claim && claim.value && claim.value.cover;
return cover && cover.url ? cover.url.trim() : undefined;
return cover && cover.url ? cover.url.trim().replace(/^http:\/\//i, 'https://') : undefined;
}
);
@ -360,12 +458,33 @@ export const selectIsFetchingClaimListMine = createSelector(
state => state.isFetchingClaimListMine
);
export const selectMyClaimsPage = createSelector(
selectState,
state => state.myClaimsPageResults || []
);
export const selectMyClaimsPageNumber = createSelector(
selectState,
state => (state.claimListMinePage && state.claimListMinePage.items) || [],
state => (state.txoPage && state.txoPage.page) || 1
);
export const selectMyClaimsPageItemCount = createSelector(
selectState,
state => state.myClaimsPageTotalResults || 1
);
export const selectFetchingMyClaimsPageError = createSelector(
selectState,
state => state.fetchingClaimListMinePageError
);
export const selectMyClaims = createSelector(
selectMyActiveClaims,
selectClaimsById,
selectAbandoningIds,
selectPendingClaims,
(myClaimIds, byId, abandoningIds, pendingClaims) => {
(myClaimIds, byId, abandoningIds) => {
const claims = [];
myClaimIds.forEach(id => {
@ -374,14 +493,16 @@ export const selectMyClaims = createSelector(
if (claim && abandoningIds.indexOf(id) === -1) claims.push(claim);
});
return [...claims, ...pendingClaims];
return [...claims];
}
);
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(
@ -425,6 +546,11 @@ export const selectFetchingMyChannels = createSelector(
state => state.fetchingMyChannels
);
export const selectFetchingMyCollections = createSelector(
selectState,
state => state.fetchingMyCollections
);
export const selectMyChannelClaims = createSelector(
selectState,
selectClaimsById,
@ -446,6 +572,16 @@ export const selectMyChannelClaims = createSelector(
}
);
export const selectMyChannelUrls = createSelector(
selectMyChannelClaims,
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 || []
@ -472,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>) =>
@ -497,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),
@ -545,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
@ -613,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),
@ -664,7 +774,7 @@ export const makeSelectSupportsForUri = (uri: string) =>
selectSupportsByOutpoint,
makeSelectClaimForUri(uri),
(byOutpoint, claim: ?StreamClaim) => {
if (!claim || !claim.is_mine) {
if (!claim || !claim.is_my_output) {
return null;
}
@ -691,12 +801,22 @@ export const selectUpdateChannelError = createSelector(
state => state.updateChannelError
);
export const makeSelectReflectingClaimForUri = (uri: string) =>
createSelector(
selectClaimIdsByUri,
selectReflectingById,
(claimIdsByUri, reflectingById) => {
const claimId = claimIdsByUri[normalizeURI(uri)];
return reflectingById[claimId];
}
);
export const makeSelectMyStreamUrlsForPage = (page: number = 1) =>
createSelector(
selectMyClaimUrisWithoutChannels,
urls => {
const start = (Number(page) - 1) * Number(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) : [];
}
@ -707,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
);

View 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;
}
);

View file

@ -1,66 +0,0 @@
// @flow
import { createSelector } from 'reselect';
const selectState = state => state.comments || {};
export const selectCommentsById = createSelector(
selectState,
state => state.commentById || {}
);
export const selectCommentsByClaimId = createSelector(
selectState,
selectCommentsById,
(state, byId) => {
const byClaimId = state.byId || {};
const comments = {};
// replace every comment_id in the list with the actual comment object
Object.keys(byClaimId).forEach(claimId => {
const commentIds = byClaimId[claimId];
comments[claimId] = Array(commentIds === null ? 0 : commentIds.length);
for (let i = 0; i < commentIds.length; i++) {
comments[claimId][i] = byId[commentIds[i]];
}
});
return comments;
}
);
// previously this used a mapping from claimId -> Array<Comments>
/* export const selectCommentsById = createSelector(
selectState,
state => state.byId || {}
); */
export const selectCommentsByUri = createSelector(
selectState,
state => {
const byUri = state.commentsByUri || {};
const comments = {};
Object.keys(byUri).forEach(uri => {
const claimId = byUri[uri];
if (claimId === null) {
comments[uri] = null;
} else {
comments[uri] = claimId;
}
});
return comments;
}
);
export const makeSelectCommentsForUri = (uri: string) =>
createSelector(
selectCommentsByClaimId,
selectCommentsByUri,
(byClaimId, byUri) => {
const claimId = byUri[uri];
return byClaimId && byClaimId[claimId];
}
);
// todo: allow SDK to retrieve user comments through comment_list
// todo: implement selectors for selecting comments owned by user

View file

@ -1,36 +0,0 @@
// @flow
import { createSelector } from 'reselect';
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
type State = { file: FileState };
export const selectState = (state: State): FileState => state.file || {};
export const selectPurchaseUriErrorMessage: (state: State) => string = createSelector(
selectState,
state => state.purchaseUriErrorMessage
);
export const selectFailedPurchaseUris: (state: State) => Array<string> = createSelector(
selectState,
state => state.failedPurchaseUris
);
export const selectPurchasedUris: (state: State) => Array<string> = createSelector(
selectState,
state => state.purchasedUris
);
export const selectLastPurchasedUri: (state: State) => string = createSelector(
selectState,
state =>
state.purchasedUris.length > 0 ? state.purchasedUris[state.purchasedUris.length - 1] : null
);
export const makeSelectStreamingUrlForUri = (uri: string) =>
createSelector(
makeSelectFileInfoForUri(uri),
fileInfo => {
return fileInfo && fileInfo.streaming_url;
}
);

View file

@ -212,6 +212,7 @@ function filterFileInfos(fileInfos, query) {
const queryMatchRegExp = new RegExp(query, 'i');
return fileInfos.filter(fileInfo => {
const { metadata } = fileInfo;
return (
(metadata.title && metadata.title.match(queryMatchRegExp)) ||
(fileInfo.channel_name && fileInfo.channel_name.match(queryMatchRegExp)) ||
@ -250,3 +251,11 @@ export const makeSelectSearchDownloadUrlsCount = query =>
return fileInfos && fileInfos.length ? filterFileInfos(fileInfos, query).length : 0;
}
);
export const makeSelectStreamingUrlForUri = uri =>
createSelector(
makeSelectFileInfoForUri(uri),
fileInfo => {
return fileInfo && fileInfo.streaming_url;
}
);

View file

@ -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 };
}
);

View file

@ -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;
}
);

View file

@ -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());
}
);

View file

@ -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) => {
@ -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
);

View file

@ -51,3 +51,20 @@ export function concatClaims(
return claims;
}
export function filterClaims(claims: Array<Claim>, query: ?string): Array<Claim> {
if (query) {
const queryMatchRegExp = new RegExp(query, 'i');
return claims.filter(claim => {
const { value } = claim;
return (
(value.title && value.title.match(queryMatchRegExp)) ||
(claim.signing_channel && claim.signing_channel.name.match(queryMatchRegExp)) ||
(claim.name && claim.name.match(queryMatchRegExp))
);
});
}
return claims;
}

7
src/util/merge-claim.js Normal file
View file

@ -0,0 +1,7 @@
/*
new claim = { ...maybeResolvedClaim, ...pendingClaim, meta: maybeResolvedClaim['meta'] }
*/
export default function mergeClaims(maybeResolved, pending){
return { ...maybeResolved, ...pending, meta: maybeResolved.meta };
}

61
src/util/parse-data.js Normal file
View 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;

View file

@ -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('&');
};

View 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
View 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");
});
})

3331
yarn.lock

File diff suppressed because it is too large Load diff