Compare commits
300 commits
master
...
update-tre
Author | SHA1 | Date | |
---|---|---|---|
|
1030f7c1ef | ||
|
39f0a6aa97 | ||
|
82643b1f4a | ||
|
a842a58608 | ||
|
afefd5f4f5 | ||
|
8eff3dca21 | ||
|
36c10a9c78 | ||
|
428c00901b | ||
|
2ecf04a2e5 | ||
|
1eeacadbf2 | ||
|
f6b17909f2 | ||
|
c492204e26 | ||
|
787ebd9588 | ||
|
7e9e213974 | ||
|
6d3ec149b3 | ||
|
935eaa6edb | ||
|
dd96d1222d | ||
|
8bc6718a4a | ||
|
dcd93af6eb | ||
|
beeec64271 | ||
|
6fb2e02e3a | ||
|
0aff130ea4 | ||
|
fcb70c8e8b | ||
|
1e071550ae | ||
|
873ac4dc5d | ||
|
4fd4309829 | ||
|
a7b991efb1 | ||
|
bfccca9aaf | ||
|
e96807fa6d | ||
|
6d4c93968f | ||
|
34eaccdbee | ||
|
7613d07c35 | ||
|
27d8f4174c | ||
|
f5f3b08cca | ||
|
71fc850df4 | ||
|
ac11dec484 | ||
|
c15a52cb46 | ||
|
56ecdec2cb | ||
|
406d91948d | ||
|
11c8024c2a | ||
|
823fdcdd97 | ||
|
fd17ab4c8b | ||
|
2adbbc2899 | ||
|
5c643cc796 | ||
|
781f1b712e | ||
|
6bbf310348 | ||
|
7ea74cfa0d | ||
|
4267c1ccf7 | ||
|
c74dd49bc5 | ||
|
b762cac50b | ||
|
84e75fdfe8 | ||
|
f8b694d7d7 | ||
|
bc64802f6e | ||
|
f2715fa97b | ||
|
3c4ccdd2fe | ||
|
eb83a834a1 | ||
|
605a8f371d | ||
|
a3111003a2 | ||
|
e2c7337d11 | ||
|
87c3dcc057 | ||
|
13cbbc8342 | ||
|
2d3057d5cf | ||
|
b6e9c7aabf | ||
|
352ee7bfaa | ||
|
95d7582f08 | ||
|
21cb405965 | ||
|
328b60d021 | ||
|
6a33ed337b | ||
|
0941667150 | ||
|
b351617d2f | ||
|
ff20663b8d | ||
|
7515d21510 | ||
|
d48a7c7295 | ||
|
314b63705d | ||
|
3269b84385 | ||
|
4a2305dca1 | ||
|
e288833085 | ||
|
4cf9309ee1 | ||
|
e35069de1c | ||
|
47c316e0ad | ||
|
75bde149cf | ||
|
6382238834 | ||
|
b69c1ec5fe | ||
|
01f771c6ca | ||
|
6b6879ba64 | ||
|
91f1f588e6 | ||
|
5204bb366e | ||
|
e6caa8c7ff | ||
|
51546436ce | ||
|
ac93b379a9 | ||
|
abbeb45d17 | ||
|
4af72806dc | ||
|
39baf1a3c9 | ||
|
201a826381 | ||
|
bf324a1b79 | ||
|
d03b3fd50d | ||
|
8deac56e40 | ||
|
dae0e3ccae | ||
|
73f208923a | ||
|
4aea0081ea | ||
|
27dffaaf2f | ||
|
652ec4b69b | ||
|
c8ad9718bb | ||
|
cb104017ad | ||
|
0c28f3c6f1 | ||
|
e02bc6cc03 | ||
|
70d18eba59 | ||
|
38c13cf5ef | ||
|
342fcb4024 | ||
|
6546eaeb63 | ||
|
f084288ac9 | ||
|
93c28b24bb | ||
|
c242c37869 | ||
|
6d217dbc50 | ||
|
529a9cbc40 | ||
|
6f8758c819 | ||
|
53406a60cf | ||
|
b0509bc990 | ||
|
d8080a9fda | ||
|
62e7fe06a5 | ||
|
dfe30b6d78 | ||
|
861aaf4cde | ||
|
9bfa1a3577 | ||
|
d047a748b7 | ||
|
ef0329e03b | ||
|
0f68bad3eb | ||
|
827a08ac26 | ||
|
6492fe1c66 | ||
|
7ef5975ee8 | ||
|
b5f1ae1291 | ||
|
a90b6415de | ||
|
77087d2916 | ||
|
38f511c2fb | ||
|
9d25d82bed | ||
|
bef7ff4a2d | ||
|
fa48b4a99b | ||
|
5b630d6a20 | ||
|
236e2cfe8e | ||
|
263c09500f | ||
|
d649e3563f | ||
|
5c23c6d88e | ||
|
5639e4c1ff | ||
|
8fd6382bf4 | ||
|
cb6a044584 | ||
|
b508fe8679 | ||
|
d211450b5b | ||
|
81d77da17e | ||
|
7cefb0fadc | ||
|
ece2312ec5 | ||
|
97b9b733c6 | ||
|
c681d95ad7 | ||
|
baa15d0c42 | ||
|
1d8753e2ba | ||
|
60f06dac52 | ||
|
a7c7881795 | ||
|
ef1ebfc491 | ||
|
bc67379c26 | ||
|
0e2bb350c0 | ||
|
f0591b8956 | ||
|
9fc417edfa | ||
|
45ad08ec32 | ||
|
d7fc5069be | ||
|
b9a5dc3c70 | ||
|
1426dd5b83 | ||
|
d80cea1caa | ||
|
7a6a8c2fd7 | ||
|
ebf81a61c3 | ||
|
07750bfb4c | ||
|
0736723200 | ||
|
de2bec4425 | ||
|
121b0f0cd6 | ||
|
c97cab0ebb | ||
|
cfd67b1c8d | ||
|
9138e508c6 | ||
|
d7ada7904b | ||
|
0f1d4039a9 | ||
|
9c5fbe5521 | ||
|
cd0ec4dbcd | ||
|
238a64bca9 | ||
|
fc2e2d2cfc | ||
|
7cae754867 | ||
|
6cb011ff96 | ||
|
21e1af8ce5 | ||
|
17903f6c15 | ||
|
a65e68d023 | ||
|
61a2ed2583 | ||
|
4876ad8671 | ||
|
59db2860d7 | ||
|
531a87e969 | ||
|
60a0d6d31a | ||
|
11d3f88654 | ||
|
db12a4b991 | ||
|
6d8e265f50 | ||
|
842431feaf | ||
|
7b621b7417 | ||
|
3d1d448afb | ||
|
bf0aac2339 | ||
|
704452732a | ||
|
fa029e0c09 | ||
|
994a39b0df | ||
|
56b800cd33 | ||
|
b8399f10b2 | ||
|
0aa6cc7e5a | ||
|
f956f9d2fe | ||
|
08c6df434e | ||
|
11bbd58e33 | ||
|
4bfb4fb55d | ||
|
5f1f702490 | ||
|
a77e59cb53 | ||
|
4b0a06cef7 | ||
|
00d28fe26e | ||
|
5dd5826b33 | ||
|
cbedc4b933 | ||
|
6b39fc1bbb | ||
|
f8f9b86cb4 | ||
|
1a5fb5fa51 | ||
|
c24153c6ca | ||
|
762bddb158 | ||
|
2922f0f2dc | ||
|
3849683a59 | ||
|
247ee757d1 | ||
|
03f69eff86 | ||
|
08adb805e9 | ||
|
3788ef58ec | ||
|
310fc81bd9 | ||
|
9569b6c7a5 | ||
|
0febd32c71 | ||
|
a90c516c71 | ||
|
dad7264636 | ||
|
27f346d8f1 | ||
|
e2176d0566 | ||
|
17121b2066 | ||
|
1b43c54725 | ||
|
398388de10 | ||
|
b8c763f749 | ||
|
b4f62e78de | ||
|
8c4224f1ce | ||
|
b7685a151d | ||
|
e14ec9b83e | ||
|
23525b0baa | ||
|
d62f63aff8 | ||
|
dcd00c2308 | ||
|
c782f73f30 | ||
|
6ff9a51058 | ||
|
9041e5e38d | ||
|
ce1621f7ed | ||
|
da63991972 | ||
|
b6ad4ae974 | ||
|
5d8fc40051 | ||
|
4b0318cd38 | ||
|
0c2c21b67e | ||
|
9bbd72d179 | ||
|
249b73f8c6 | ||
|
aabfc41ce9 | ||
|
5bcf89394e | ||
|
35072c0400 | ||
|
296febcffa | ||
|
cfdfdce2fe | ||
|
2a7f89d6b5 | ||
|
30023422b8 | ||
|
702297e722 | ||
|
07102c4988 | ||
|
3b442531ef | ||
|
f6e60abbf5 | ||
|
dab1ca1cb7 | ||
|
bba3a17977 | ||
|
4a22814c75 | ||
|
91be939c19 | ||
|
0b0f2848da | ||
|
055d437865 | ||
|
d1493d5fb3 | ||
|
b86a56f75b | ||
|
8498554f23 | ||
|
2505d67a7d | ||
|
03ea298236 | ||
|
a3302b1be8 | ||
|
58db9576b9 | ||
|
a9b9c3ccf0 | ||
|
ea516f88dc | ||
|
5f55a3f128 | ||
|
53063931ab | ||
|
6727e2766b | ||
|
c10fc675db | ||
|
fa889112c5 | ||
|
0e96f8d468 | ||
|
d5ad63c6e9 | ||
|
cd8f90c82d | ||
|
24eb2ef8ec | ||
|
37ddc395ea | ||
|
02a8099514 | ||
|
9dbee19961 | ||
|
b49fed4cf5 | ||
|
2b5d32c313 | ||
|
7c518aa712 | ||
|
6f3c43c95f | ||
|
a168dbcc01 | ||
|
3087f7c367 | ||
|
3f969ae20d | ||
|
19797c747e | ||
|
caadd889ce |
637 changed files with 22611 additions and 11199 deletions
|
@ -13,9 +13,11 @@ LBRY_WEB_STREAMING_API=https://cdn.lbryplayer.xyz
|
|||
LBRY_WEB_BUFFER_API=https://collector-service.api.lbry.tv/api/v1/events/video
|
||||
COMMENT_SERVER_API=https://comments.odysee.com/api/v2
|
||||
COMMENT_SERVER_NAME=Odysee
|
||||
SEARCH_SERVER_API_ALT=https://recsys.odysee.com/search
|
||||
SEARCH_SERVER_API=https://lighthouse.odysee.com/search
|
||||
SOCKETY_SERVER_API=wss://sockety.odysee.com/ws
|
||||
THUMBNAIL_CDN_URL=https://thumbnails.odysee.com/optimize/
|
||||
THUMBNAIL_CARDS_CDN_URL=https://cards.odysee.com/
|
||||
THUMBNAIL_HEIGHT=220
|
||||
THUMBNAIL_WIDTH=390
|
||||
THUMBNAIL_QUALITY=85
|
||||
|
@ -25,8 +27,6 @@ WELCOME_VERSION=1.0
|
|||
# STRIPE_PUBLIC_KEY='pk_test_NoL1JWL7i1ipfhVId5KfDZgo'
|
||||
|
||||
# Analytics
|
||||
MATOMO_URL=https://analytics.lbry.com/
|
||||
MATOMO_ID=4
|
||||
|
||||
# OG
|
||||
OG_TITLE_SUFFIX=| lbry.tv
|
||||
|
@ -116,3 +116,13 @@ ENABLE_UI_NOTIFICATIONS=false
|
|||
#MODELS_ENABLED=true
|
||||
|
||||
BRANDED_SITE=odysee
|
||||
|
||||
#FIREBASE
|
||||
FIREBASE_API_KEY=AIzaSyAgc-4QORyglpYZ3qH9E5pDauEDOJXgM3A
|
||||
FIREBASE_AUTH_DOMAIN=lbry-mobile.firebaseapp.com
|
||||
FIREBASE_PROJECT_ID=lbry-mobile
|
||||
FIREBASE_STORAGE_BUCKET=lbry-mobile.appspot.com
|
||||
FIREBASE_MESSAGING_SENDER_ID=638894153788
|
||||
FIREBASE_APP_ID=1:638894153788:web:35b295b15297201bd2e339
|
||||
FIREBASE_MEASUREMENT_ID=G-2MPJGFEEXC
|
||||
FIREBASE_VAPID_KEY=BFayEBpwMTU9GQQpXgitIJkfx-SD8-ltrFb3wLTZWgA27MfBhG4948pe0eERl432NzPrMKsbkXnA7ap_vLPgLYk
|
||||
|
|
11
.flowconfig
11
.flowconfig
|
@ -7,13 +7,6 @@
|
|||
[include]
|
||||
|
||||
[libs]
|
||||
./flow-typed
|
||||
node_modules/lbry-redux/flow-typed/
|
||||
node_modules/lbryinc/flow-typed/
|
||||
|
||||
[untyped]
|
||||
.*/node_modules/lbry-redux
|
||||
.*/node_modules/lbryinc
|
||||
|
||||
[lints]
|
||||
|
||||
|
@ -31,7 +24,7 @@ module.name_mapper='^modal\(.*\)$' -> '<PROJECT_ROOT>/ui/modal\1'
|
|||
module.name_mapper='^app\(.*\)$' -> '<PROJECT_ROOT>/ui/app\1'
|
||||
module.name_mapper='^native\(.*\)$' -> '<PROJECT_ROOT>/ui/native\1'
|
||||
module.name_mapper='^analytics\(.*\)$' -> '<PROJECT_ROOT>/ui/analytics\1'
|
||||
module.name_mapper='^recsys\(.*\)$' -> '<PROJECT_ROOT>/ui/recsys\1'
|
||||
module.name_mapper='^recsys\(.*\)$' -> '<PROJECT_ROOT>/extras/recsys\1'
|
||||
module.name_mapper='^rewards\(.*\)$' -> '<PROJECT_ROOT>/ui/rewards\1'
|
||||
module.name_mapper='^i18n\(.*\)$' -> '<PROJECT_ROOT>/ui/i18n\1'
|
||||
module.name_mapper='^effects\(.*\)$' -> '<PROJECT_ROOT>/ui/effects\1'
|
||||
|
@ -42,6 +35,8 @@ module.name_mapper='^web\/effects\(.*\)$' -> '<PROJECT_ROOT>/web/effects\1'
|
|||
module.name_mapper='^web\/page\(.*\)$' -> '<PROJECT_ROOT>/web/page\1'
|
||||
module.name_mapper='^homepage\(.*\)$' -> '<PROJECT_ROOT>/ui/util/homepage\1'
|
||||
module.name_mapper='^scss\/component\(.*\)$' -> '<PROJECT_ROOT>/ui/scss/component/\1'
|
||||
module.name_mapper='^\$web\(.*\)$' -> '<PROJECT_ROOT>/web\1'
|
||||
module.name_mapper='^\$ui\(.*\)$' -> '<PROJECT_ROOT>/ui\1'
|
||||
|
||||
esproposal.optional_chaining=enable
|
||||
|
||||
|
|
10
.github/ISSUE_TEMPLATE/--say-thank-you.md
vendored
10
.github/ISSUE_TEMPLATE/--say-thank-you.md
vendored
|
@ -1,22 +1,22 @@
|
|||
---
|
||||
name: "❤️Say thank you"
|
||||
about: If you enjoy using the LBRY app, let us know!
|
||||
title: LBRY rocks!
|
||||
about: If you enjoy using Odysee's website, let us know!
|
||||
title: Odysee rocks!
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
If you are using the LBRY app - please let us know. We'd love to hear from you!
|
||||
If you are using the Odysee's website - please let us know. We'd love to hear from you!
|
||||
|
||||
If you would like to help Nock - any of the following is greatly appreciated.
|
||||
|
||||
- Give the repository a star ⭐️
|
||||
- Help out with issues
|
||||
- Blog about LBRY
|
||||
- Blog about Odysee
|
||||
- Make tutorials
|
||||
- Give talks
|
||||
- Convince other people to use LBRY
|
||||
- Convince other people to use Odysee
|
||||
- Anything your heart desires
|
||||
|
||||
Thank you! 💐
|
||||
|
|
2
.github/workflows/node.js.yml
vendored
2
.github/workflows/node.js.yml
vendored
|
@ -41,8 +41,6 @@ jobs:
|
|||
yarn compile:web
|
||||
env:
|
||||
# UI
|
||||
MATOMO_URL: https://analytics.lbry.com/
|
||||
MATOMO_ID: 4
|
||||
WELCOME_VERSION: 1.0
|
||||
DOMAIN: odysee.com
|
||||
URL: https://odysee.com
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -36,3 +36,4 @@ package-lock.json
|
|||
.env.ody
|
||||
.env.desktop
|
||||
.env.lbrytv
|
||||
analyzeResults*.html
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
{
|
||||
"linters": {
|
||||
"ui/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
|
||||
"extras/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
|
||||
"web/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
|
||||
"ui/**/*.{js,jsx}": ["eslint", "flow focus-check --color always", "git add"],
|
||||
"extras/**/*.{js,jsx}": ["eslint", "flow focus-check --color always", "git add"],
|
||||
"web/**/*.{js,jsx}": ["eslint", "git add"]
|
||||
},
|
||||
"ignore": ["node_modules", "web/dist/**/*", "dist/**/*", "package-lock.json"]
|
||||
|
|
|
@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- Stream Key is now hidden _community pr!_ ([#7127](https://github.com/lbryio/lbry-desktop/pull/7127))
|
||||
- Fix playlist preview thumbnail ([#7178](https://github.com/lbryio/lbry-desktop/pull/7178)
|
||||
- Fixed “Your Account” popup on mobile ([#7172](https://github.com/lbryio/lbry-desktop/pull/7172))
|
||||
- Fix disable-support for comments ([#7245](https://github.com/lbryio/lbry-desktop/pull/7245))
|
||||
|
||||
## [0.51.2] - [2021-08-20]
|
||||
|
||||
|
@ -734,7 +735,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
### Added
|
||||
|
||||
- Channels page above Publishes which lists all your channels ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925))
|
||||
- YouTube channel claiming and transfer ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925)). See our [YouTube FAQ](https://lbry.com/faq/youtube) for more information.
|
||||
- YouTube channel claiming and transfer ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925)). See our [YouTube FAQ](https://odysee.com/@OdyseeHelp:b/youtube-sync:b) for more information.
|
||||
- New user sign in flow now includes automatic redeeming of 1 LBC and channel creation ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925))
|
||||
- Ability to save wallet encryption password ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925))
|
||||
- Sync your balance (only for users with new wallets) and preferences (subscriptions and tags) between devices ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925)). See our [FAQ for more information](https://lbry.com/faq/account-sync)
|
||||
|
@ -889,7 +890,7 @@ This release includes a breaking change that will reset many of your settings. T
|
|||
|
||||
### Added
|
||||
|
||||
- New app design for better [content discovery](https://lbry.com/faq/trending) with infinite scroll ([#2477](https://github.com/lbryio/lbry-desktop/pull/2477))
|
||||
- New app design for better [content discovery](https://odysee.com/@OdyseeHelp:b/OdyseeBasics:c) with infinite scroll ([#2477](https://github.com/lbryio/lbry-desktop/pull/2477))
|
||||
- First implementation of comments ([#2510](https://github.com/lbryio/lbry-desktop/pull/2510))
|
||||
- Ability to edit channels with new metadata and tags ([#2584](https://github.com/lbryio/lbry-desktop/pull/2584))
|
||||
- Tagging content on publish page ([#2593](https://github.com/lbryio/lbry-desktop/pull/2593))
|
||||
|
|
76
README.md
76
README.md
|
@ -28,48 +28,20 @@ This repo contains the UI and front end code that powers Odysee.com.
|
|||
|
||||
## Table of Contents
|
||||
|
||||
1. [Install](#install)
|
||||
2. [Usage](#usage)
|
||||
3. [Running from Source](#running-from-source)
|
||||
4. [Contributing](#contributing)
|
||||
5. [License](#license)
|
||||
6. [Security](#security)
|
||||
7. [Contact](#contact)
|
||||
|
||||
## Install
|
||||
|
||||
[![Windows](https://img.shields.io/badge/Windows-Install-blue)](https://lbry.com/get/lbry.exe)
|
||||
[![Linux](https://img.shields.io/badge/Linux-Install-blue)](https://lbry.com/get/lbry.deb)
|
||||
[![MacOS](https://img.shields.io/badge/MacOS-Install-blue)](https://lbry.com/get/lbry.dmg)
|
||||
|
||||
We provide installers for Windows, macOS (v10.12.4, Sierra, or greater), and Debian-based Linux. See community maintained builds section for alternative Linux installations.
|
||||
|
||||
| | Windows | macOS | Linux |
|
||||
| --------------------- | --------------------------------------------- | --------------------------------------------- | --------------------------------------------- |
|
||||
| Latest Stable Release | [Download](https://lbry.com/get/lbry.exe) | [Download](https://lbry.com/get/lbry.dmg) | [Download](https://lbry.com/get/lbry.deb) |
|
||||
| Latest Pre-release | [Download](https://lbry.com/get/lbry.pre.exe) | [Download](https://lbry.com/get/lbry.pre.dmg) | [Download](https://lbry.com/get/lbry.pre.deb) |
|
||||
|
||||
Our [releases page](https://github.com/lbryio/lbry-desktop/releases) also contains the latest
|
||||
release, pre-releases, and past builds.
|
||||
_Note: If the deb fails to install using the Ubuntu Software Center, install manually via `sudo dpkg -i <path to deb>`. You'll need to run `sudo apt-get install -f` if this is the first time installing it to install dependencies_
|
||||
|
||||
To install from source or make changes to the application, continue to the next section below.
|
||||
|
||||
**Community maintained** builds for Arch Linux and Flatpak are available, see below. These installs will need to be updated manually as the in-app update process only supports Debian installs at this time.
|
||||
_Note: If coming from a deb install, the directory structure is different and you'll need to [migrate data](https://lbry.com/faq/backup-data)._
|
||||
|
||||
| | Flatpak | Arch | Nixpkgs | ARM/ARM64 |
|
||||
| -------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------- |
|
||||
| Latest Release | [FlatHub Page](https://flathub.org/apps/details/io.lbry.lbry-app) | [AUR Package](https://aur.archlinux.org/packages/lbry-app-bin/) | [Nixpkgs](https://search.nixos.org/packages?channel=unstable&show=lbry&query=lbry) | [Build Guide](https://lbry.tv/@LBRYarm:5) |
|
||||
| Maintainers | [@kcSeb](https://keybase.io/kcseb) | [@kcSeb](https://keybase.io/kcseb) | [@Enderger](https://github.com/enderger) | [@Madiator2011](https://github.com/kodxana) |
|
||||
1. [Usage](#usage)
|
||||
2. [Running from Source](#running-from-source)
|
||||
3. [Contributing](#contributing)
|
||||
4. [License](#license)
|
||||
5. [Security](#security)
|
||||
6. [Contact](#contact)
|
||||
|
||||
## Usage
|
||||
|
||||
Double click the installed application to interact with the LBRY network.
|
||||
Go to the website to interact on this frontend.
|
||||
|
||||
## Running from Source
|
||||
|
||||
You can run the web version (lbry.tv), the electron app, or both at the same time.
|
||||
You can run the web version (odysee.com), via running onto your host machine, or go to the website itself.
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
|
@ -77,21 +49,15 @@ You can run the web version (lbry.tv), the electron app, or both at the same tim
|
|||
- [Node.js](https://nodejs.org/en/download/) (v14 required)
|
||||
- [Yarn](https://yarnpkg.com/en/docs/install)
|
||||
|
||||
1. Clone (or [fork](https://help.github.com/articles/fork-a-repo/)) this repository: `git clone https://github.com/lbryio/lbry-desktop`
|
||||
2. Change directory into the cloned repository: `cd lbry-desktop`
|
||||
1. Clone (or [fork](https://help.github.com/articles/fork-a-repo/)) this repository: `git clone https://github.com/OdyseeTeam/odysee-frontend`
|
||||
2. Change directory into the cloned repository: `cd odysee-frontend`
|
||||
3. Install the dependencies: `yarn`
|
||||
|
||||
#### Run the electron app
|
||||
|
||||
`yarn dev`
|
||||
|
||||
- If you want to build and launch the production app you can run `yarn build`. This will give you an executable inside the `/dist` folder. We use [electron-builder](https://github.com/electron-userland/electron-builder) to create distributable packages.
|
||||
|
||||
#### Run the web app for development
|
||||
|
||||
`yarn dev:web`
|
||||
|
||||
- This uses webpack-dev-server and includes hot-reloading. If you want to debug the [web server we use in production](https://github.com/lbryio/lbry-desktop/blob/master/web/index.js) you can run `yarn dev:web-server`. This starts a server at `localhost:1337` and does not include hot reloading.
|
||||
- This uses webpack-dev-server and includes hot-reloading. If you want to debug the [web server we use in production](https://github.com/OdyseeTeam/odysee-frontend/blob/master/web/index.js) you can run `yarn dev:web-server`. This starts a server at `localhost:1337` and does not include hot reloading.
|
||||
|
||||
#### Customize the web app
|
||||
|
||||
|
@ -113,7 +79,7 @@ nano .env
|
|||
1. add `CUSTOM_HOMEPAGE=true` to the '.env' file
|
||||
2. copy `/custom/homepage.example.js` to `/custom/homepage.js` and make desired changes to `homepage.js`
|
||||
|
||||
- If you want up to two custom sidebar links
|
||||
- If you want up to two custom sidebar links:
|
||||
|
||||
```
|
||||
PINNED_URI_1=@someurl#2/someclaim#4
|
||||
|
@ -136,17 +102,6 @@ PINNED_LABEL_2=OtherLinkText
|
|||
6. Run `NODE_ENV=production yarn compile:web` to build
|
||||
7. Set up pm2 to start ./web/index.js
|
||||
|
||||
#### Run both at the same time
|
||||
|
||||
Run the two commands above in separate terminal windows
|
||||
|
||||
```
|
||||
yarn dev
|
||||
|
||||
// in another terminal window
|
||||
yarn dev:web
|
||||
```
|
||||
|
||||
#### Resetting your Packages
|
||||
|
||||
If the app isn't building, or `yarn xxx` commands aren't working you may need to just reset your `node_modules`. To do so you can run: `rm -r node_modules && yarn` or `del /s /q node_modules && yarn` on Windows.
|
||||
|
@ -155,9 +110,9 @@ If you _really_ think something might have gone wrong, you can force your repo t
|
|||
|
||||
## Contributing
|
||||
|
||||
We :heart: contributions from everyone and contributions to this project are encouraged, and compensated. We welcome [bug reports](https://github.com/lbryio/lbry-desktop/issues/), [bug fixes](https://github.com/lbryio/lbry-desktop/pulls) and feedback is always appreciated. For more details, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
We :heart: contributions from everyone and contributions to this project are encouraged, and compensated. We welcome [bug reports](https://github.com/OdyseeTeam/odysee-frontend/issues/), [bug fixes](https://github.com/OdyseeTeam/odysee-frontend/pulls) and feedback is always appreciated. For more details, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/lbryio/lbry-desktop/issues) [![GitHub contributors](https://img.shields.io/github/contributors/lbryio/lbry-desktop.svg)](https://GitHub.com/lbryio/lbry-desktop/graphs/contributors/)
|
||||
## [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/OdyseeTeam/odysee-frontend/issues) [![GitHub contributors](https://img.shields.io/github/contributors/lbryio/lbry-desktop.svg)](https://GitHub.com/OdyseeTeam/odysee-frontend/graphs/contributors/)
|
||||
|
||||
## License
|
||||
|
||||
|
@ -165,6 +120,5 @@ This project is MIT licensed. For the full license, see [LICENSE](LICENSE).
|
|||
|
||||
## Security
|
||||
|
||||
We take security seriously. Please contact security@odysee.com regarding any security issues. Our PGP key is [here](https://lbry.com/faq/pgp-key) if you need it. Previous versions up to v0.50.2 were signed by [Sean Yesmunt](https://keybase.io/seanyesmunt/key.asc).
|
||||
New Releases are signed by [Jessop Breth](https://keybase.io/jessopb/key.asc).
|
||||
For security issues, please reach out to security@odysee.com
|
||||
|
||||
|
|
27
config.js
27
config.js
|
@ -3,17 +3,17 @@
|
|||
require('dotenv-defaults').config({ silent: false });
|
||||
|
||||
const config = {
|
||||
MATOMO_URL: process.env.MATOMO_URL,
|
||||
MATOMO_ID: process.env.MATOMO_ID,
|
||||
WEBPACK_WEB_PORT: process.env.WEBPACK_WEB_PORT,
|
||||
WEBPACK_ELECTRON_PORT: process.env.WEBPACK_ELECTRON_PORT,
|
||||
WEB_SERVER_PORT: process.env.WEB_SERVER_PORT,
|
||||
LBRY_WEB_API: process.env.LBRY_WEB_API, //api.na-backend.odysee.com',
|
||||
LBRY_WEB_API: process.env.LBRY_WEB_API, // api.na-backend.odysee.com',
|
||||
LBRY_WEB_PUBLISH_API: process.env.LBRY_WEB_PUBLISH_API,
|
||||
LBRY_API_URL: process.env.LBRY_API_URL, //api.lbry.com',
|
||||
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //cdn.lbryplayer.xyz',
|
||||
LBRY_WEB_PUBLISH_API_V2: process.env.LBRY_WEB_PUBLISH_API_V2,
|
||||
LBRY_API_URL: process.env.LBRY_API_URL, // api.lbry.com',
|
||||
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, // cdn.lbryplayer.xyz',
|
||||
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
|
||||
SEARCH_SERVER_API: process.env.SEARCH_SERVER_API,
|
||||
SEARCH_SERVER_API_ALT: process.env.SEARCH_SERVER_API_ALT,
|
||||
COMMENT_SERVER_API: process.env.COMMENT_SERVER_API,
|
||||
COMMENT_SERVER_NAME: process.env.COMMENT_SERVER_NAME,
|
||||
SOCKETY_SERVER_API: process.env.SOCKETY_SERVER_API,
|
||||
|
@ -22,6 +22,7 @@ const config = {
|
|||
SHARE_DOMAIN_URL: process.env.SHARE_DOMAIN_URL,
|
||||
URL: process.env.URL,
|
||||
THUMBNAIL_CDN_URL: process.env.THUMBNAIL_CDN_URL,
|
||||
THUMBNAIL_CARDS_CDN_URL: process.env.THUMBNAIL_CARDS_CDN_URL,
|
||||
THUMBNAIL_HEIGHT: process.env.THUMBNAIL_HEIGHT,
|
||||
THUMBNAIL_WIDTH: process.env.THUMBNAIL_WIDTH,
|
||||
THUMBNAIL_QUALITY: process.env.THUMBNAIL_QUALITY,
|
||||
|
@ -33,7 +34,6 @@ const config = {
|
|||
TWITTER_ACCOUNT: process.env.TWITTER_ACCOUNT,
|
||||
// LOGO
|
||||
LOGO_TITLE: process.env.LOGO_TITLE,
|
||||
FAVICON: process.env.FAVICON,
|
||||
LOGO: process.env.LOGO,
|
||||
LOGO_TEXT_LIGHT: process.env.LOGO_TEXT_LIGHT,
|
||||
LOGO_TEXT_DARK: process.env.LOGO_TEXT_DARK,
|
||||
|
@ -76,9 +76,24 @@ const config = {
|
|||
SHOW_TAGS_INTRO: process.env.SHOW_TAGS_INTRO === 'true',
|
||||
LIGHTHOUSE_DEFAULT_TYPES: process.env.LIGHTHOUSE_DEFAULT_TYPES,
|
||||
BRANDED_SITE: process.env.BRANDED_SITE,
|
||||
|
||||
// FIREBASE SDK
|
||||
FIREBASE_API_KEY: process.env.FIREBASE_API_KEY,
|
||||
FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN,
|
||||
FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID,
|
||||
FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET,
|
||||
FIREBASE_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID,
|
||||
FIREBASE_APP_ID: process.env.FIREBASE_APP_ID,
|
||||
FIREBASE_MEASUREMENT_ID: process.env.FIREBASE_MEASUREMENT_ID,
|
||||
FIREBASE_VAPID_KEY: process.env.FIREBASE_VAPID_KEY,
|
||||
|
||||
};
|
||||
|
||||
config.SDK_API_PATH = `${config.LBRY_WEB_API}/api/v1`;
|
||||
config.PROXY_URL = `${config.SDK_API_PATH}/proxy`;
|
||||
|
||||
config.URL_DEV = `http://localhost:${config.WEBPACK_WEB_PORT}`;
|
||||
config.URL_LOCAL = `http://localhost:${config.WEB_SERVER_PORT}`;
|
||||
config.FAVICON = `/public/favicon-spaceman.png`;
|
||||
|
||||
module.exports = config;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { spawn, execSync } from 'child_process';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import Lbry from 'lbry';
|
||||
|
||||
export default class Daemon {
|
||||
static lbrynetPath =
|
||||
|
|
|
@ -6,7 +6,7 @@ import SemVer from 'semver';
|
|||
import https from 'https';
|
||||
import { app, dialog, ipcMain, session, shell } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import Lbry from 'lbry';
|
||||
import LbryFirstInstance from './LbryFirstInstance';
|
||||
import Daemon from './Daemon';
|
||||
import isDev from 'electron-is-dev';
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { app, Menu, shell } from 'electron';
|
||||
import { ZOOM } from 'util/zoomWindow';
|
||||
|
||||
export default () => {
|
||||
const template = [
|
||||
|
@ -23,38 +22,6 @@ export default () => {
|
|||
label: 'View',
|
||||
submenu: [
|
||||
{ role: 'reload' },
|
||||
{
|
||||
label: 'Zoom',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Zoom In',
|
||||
accelerator: 'CmdOrCtrl+=',
|
||||
click: (menuItem, browserWindow) => {
|
||||
if (browserWindow) {
|
||||
browserWindow.webContents.send('zoom-window', ZOOM.INCREMENT);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Zoom Out',
|
||||
accelerator: 'CmdOrCtrl+-',
|
||||
click: (menuItem, browserWindow) => {
|
||||
if (browserWindow) {
|
||||
browserWindow.webContents.send('zoom-window', ZOOM.DECREMENT);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Reset Zoom',
|
||||
accelerator: 'CmdOrCtrl+0',
|
||||
click: (menuItem, browserWindow) => {
|
||||
if (browserWindow) {
|
||||
browserWindow.webContents.send('zoom-window', ZOOM.RESET);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Developer',
|
||||
submenu: [{ role: 'forcereload' }, { role: 'toggledevtools' }],
|
||||
|
|
|
@ -8,7 +8,7 @@ if (typeof global.fetch === 'object') {
|
|||
global.fetch = global.fetch.default;
|
||||
}
|
||||
|
||||
const { Lbry } = require('lbry-redux');
|
||||
const Lbry = require('lbry');
|
||||
|
||||
delete global.window;
|
||||
|
||||
|
|
184
extras/lbry-first/lbry-first.js
Normal file
184
extras/lbry-first/lbry-first.js
Normal file
|
@ -0,0 +1,184 @@
|
|||
// @flow
|
||||
/*
|
||||
LBRY FIRST does not work due to api changes
|
||||
*/
|
||||
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;
|
89
extras/lbryinc/constants/action_types.js
Normal file
89
extras/lbryinc/constants/action_types.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
// Claims
|
||||
export const FETCH_FEATURED_CONTENT_STARTED = 'FETCH_FEATURED_CONTENT_STARTED';
|
||||
export const FETCH_FEATURED_CONTENT_COMPLETED = 'FETCH_FEATURED_CONTENT_COMPLETED';
|
||||
export const FETCH_TRENDING_CONTENT_STARTED = 'FETCH_TRENDING_CONTENT_STARTED';
|
||||
export const FETCH_TRENDING_CONTENT_COMPLETED = 'FETCH_TRENDING_CONTENT_COMPLETED';
|
||||
export const RESOLVE_URIS_STARTED = 'RESOLVE_URIS_STARTED';
|
||||
export const RESOLVE_URIS_COMPLETED = 'RESOLVE_URIS_COMPLETED';
|
||||
export const FETCH_CHANNEL_CLAIMS_STARTED = 'FETCH_CHANNEL_CLAIMS_STARTED';
|
||||
export const FETCH_CHANNEL_CLAIMS_COMPLETED = 'FETCH_CHANNEL_CLAIMS_COMPLETED';
|
||||
export const FETCH_CHANNEL_CLAIM_COUNT_STARTED = 'FETCH_CHANNEL_CLAIM_COUNT_STARTED';
|
||||
export const FETCH_CHANNEL_CLAIM_COUNT_COMPLETED = 'FETCH_CHANNEL_CLAIM_COUNT_COMPLETED';
|
||||
export const FETCH_CLAIM_LIST_MINE_STARTED = 'FETCH_CLAIM_LIST_MINE_STARTED';
|
||||
export const FETCH_CLAIM_LIST_MINE_COMPLETED = 'FETCH_CLAIM_LIST_MINE_COMPLETED';
|
||||
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 CREATE_CHANNEL_STARTED = 'CREATE_CHANNEL_STARTED';
|
||||
export const CREATE_CHANNEL_COMPLETED = 'CREATE_CHANNEL_COMPLETED';
|
||||
export const SET_PLAYING_URI = 'SET_PLAYING_URI';
|
||||
export const SET_CONTENT_POSITION = 'SET_CONTENT_POSITION';
|
||||
export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED';
|
||||
export const CLEAR_CONTENT_HISTORY_URI = 'CLEAR_CONTENT_HISTORY_URI';
|
||||
export const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL';
|
||||
|
||||
// Subscriptions
|
||||
export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
|
||||
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
|
||||
export const CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS = 'CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS';
|
||||
export const CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS = 'CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS';
|
||||
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
|
||||
export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST';
|
||||
export const UPDATE_SUBSCRIPTION_UNREADS = 'UPDATE_SUBSCRIPTION_UNREADS';
|
||||
export const REMOVE_SUBSCRIPTION_UNREADS = 'REMOVE_SUBSCRIPTION_UNREADS';
|
||||
export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
|
||||
export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
|
||||
export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
|
||||
export const FETCH_SUBSCRIPTIONS_START = 'FETCH_SUBSCRIPTIONS_START';
|
||||
export const FETCH_SUBSCRIPTIONS_FAIL = 'FETCH_SUBSCRIPTIONS_FAIL';
|
||||
export const FETCH_SUBSCRIPTIONS_SUCCESS = 'FETCH_SUBSCRIPTIONS_SUCCESS';
|
||||
export const SET_VIEW_MODE = 'SET_VIEW_MODE';
|
||||
export const GET_SUGGESTED_SUBSCRIPTIONS_START = 'GET_SUGGESTED_SUBSCRIPTIONS_START';
|
||||
export const GET_SUGGESTED_SUBSCRIPTIONS_SUCCESS = 'GET_SUGGESTED_SUBSCRIPTIONS_SUCCESS';
|
||||
export const GET_SUGGESTED_SUBSCRIPTIONS_FAIL = 'GET_SUGGESTED_SUBSCRIPTIONS_FAIL';
|
||||
export const SUBSCRIPTION_FIRST_RUN_COMPLETED = 'SUBSCRIPTION_FIRST_RUN_COMPLETED';
|
||||
export const VIEW_SUGGESTED_SUBSCRIPTIONS = 'VIEW_SUGGESTED_SUBSCRIPTIONS';
|
||||
|
||||
// Blacklist
|
||||
export const FETCH_BLACK_LISTED_CONTENT_STARTED = 'FETCH_BLACK_LISTED_CONTENT_STARTED';
|
||||
export const FETCH_BLACK_LISTED_CONTENT_COMPLETED = 'FETCH_BLACK_LISTED_CONTENT_COMPLETED';
|
||||
export const FETCH_BLACK_LISTED_CONTENT_FAILED = 'FETCH_BLACK_LISTED_CONTENT_FAILED';
|
||||
export const BLACK_LISTED_CONTENT_SUBSCRIBE = 'BLACK_LISTED_CONTENT_SUBSCRIBE';
|
||||
|
||||
// Filtered list
|
||||
export const FETCH_FILTERED_CONTENT_STARTED = 'FETCH_FILTERED_CONTENT_STARTED';
|
||||
export const FETCH_FILTERED_CONTENT_COMPLETED = 'FETCH_FILTERED_CONTENT_COMPLETED';
|
||||
export const FETCH_FILTERED_CONTENT_FAILED = 'FETCH_FILTERED_CONTENT_FAILED';
|
||||
export const FILTERED_CONTENT_SUBSCRIBE = 'FILTERED_CONTENT_SUBSCRIBE';
|
||||
|
||||
// Cost Info
|
||||
export const FETCH_COST_INFO_STARTED = 'FETCH_COST_INFO_STARTED';
|
||||
export const FETCH_COST_INFO_COMPLETED = 'FETCH_COST_INFO_COMPLETED';
|
||||
|
||||
// Stats
|
||||
export const FETCH_VIEW_COUNT_STARTED = 'FETCH_VIEW_COUNT_STARTED';
|
||||
export const FETCH_VIEW_COUNT_FAILED = 'FETCH_VIEW_COUNT_FAILED';
|
||||
export const FETCH_VIEW_COUNT_COMPLETED = 'FETCH_VIEW_COUNT_COMPLETED';
|
||||
export const FETCH_SUB_COUNT_STARTED = 'FETCH_SUB_COUNT_STARTED';
|
||||
export const FETCH_SUB_COUNT_FAILED = 'FETCH_SUB_COUNT_FAILED';
|
||||
export const FETCH_SUB_COUNT_COMPLETED = 'FETCH_SUB_COUNT_COMPLETED';
|
||||
|
||||
// Cross-device Sync
|
||||
export const GET_SYNC_STARTED = 'GET_SYNC_STARTED';
|
||||
export const GET_SYNC_COMPLETED = 'GET_SYNC_COMPLETED';
|
||||
export const GET_SYNC_FAILED = 'GET_SYNC_FAILED';
|
||||
export const SET_SYNC_STARTED = 'SET_SYNC_STARTED';
|
||||
export const SET_SYNC_FAILED = 'SET_SYNC_FAILED';
|
||||
export const SET_SYNC_COMPLETED = 'SET_SYNC_COMPLETED';
|
||||
export const SET_DEFAULT_ACCOUNT = 'SET_DEFAULT_ACCOUNT';
|
||||
export const SYNC_APPLY_STARTED = 'SYNC_APPLY_STARTED';
|
||||
export const SYNC_APPLY_COMPLETED = 'SYNC_APPLY_COMPLETED';
|
||||
export const SYNC_APPLY_FAILED = 'SYNC_APPLY_FAILED';
|
||||
export const SYNC_APPLY_BAD_PASSWORD = 'SYNC_APPLY_BAD_PASSWORD';
|
||||
export const SYNC_RESET = 'SYNC_RESET';
|
||||
|
||||
// User
|
||||
export const GENERATE_AUTH_TOKEN_FAILURE = 'GENERATE_AUTH_TOKEN_FAILURE';
|
||||
export const GENERATE_AUTH_TOKEN_STARTED = 'GENERATE_AUTH_TOKEN_STARTED';
|
||||
export const GENERATE_AUTH_TOKEN_SUCCESS = 'GENERATE_AUTH_TOKEN_SUCCESS';
|
5
extras/lbryinc/constants/claim.js
Normal file
5
extras/lbryinc/constants/claim.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
export const MINIMUM_PUBLISH_BID = 0.00000001;
|
||||
|
||||
export const CHANNEL_ANONYMOUS = 'anonymous';
|
||||
export const CHANNEL_NEW = 'new';
|
||||
export const PAGE_SIZE = 20;
|
4
extras/lbryinc/constants/errors.js
Normal file
4
extras/lbryinc/constants/errors.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const ALREADY_CLAIMED =
|
||||
'once the invite reward has been claimed the referrer cannot be changed';
|
||||
export const REFERRER_NOT_FOUND =
|
||||
'A lbry.tv account could not be found for the referrer you provided.';
|
11
extras/lbryinc/constants/youtube.js
Normal file
11
extras/lbryinc/constants/youtube.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
export const YOUTUBE_SYNC_NOT_TRANSFERRED = 'not_transferred';
|
||||
export const YOUTUBE_SYNC_PENDING = 'pending';
|
||||
export const YOUTUBE_SYNC_PENDING_EMAIL = 'pendingemail';
|
||||
export const YOUTUBE_SYNC_PENDING_TRANSFER = 'pending_transfer';
|
||||
export const YOUTUBE_SYNC_COMPLETED_TRANSFER = 'completed_transfer';
|
||||
export const YOUTUBE_SYNC_QUEUED = 'queued';
|
||||
export const YOUTUBE_SYNC_SYNCING = 'syncing';
|
||||
export const YOUTUBE_SYNC_SYNCED = 'synced';
|
||||
export const YOUTUBE_SYNC_FAILED = 'failed';
|
||||
export const YOUTUBE_SYNC_PENDINGUPGRADE = 'pendingupgrade';
|
||||
export const YOUTUBE_SYNC_ABANDONDED = 'abandoned';
|
70
extras/lbryinc/index.js
Normal file
70
extras/lbryinc/index.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import * as LBRYINC_ACTIONS from 'constants/action_types';
|
||||
import * as YOUTUBE_STATUSES from 'constants/youtube';
|
||||
import * as ERRORS from 'constants/errors';
|
||||
import Lbryio from './lbryio';
|
||||
|
||||
export { Lbryio };
|
||||
|
||||
// constants
|
||||
export { LBRYINC_ACTIONS, YOUTUBE_STATUSES, ERRORS };
|
||||
|
||||
// utils
|
||||
export { doTransifexUpload } from 'util/transifex-upload';
|
||||
|
||||
// actions
|
||||
export { doGenerateAuthToken } from './redux/actions/auth';
|
||||
export { doFetchCostInfoForUri } from './redux/actions/cost_info';
|
||||
export { doBlackListedOutpointsSubscribe } from './redux/actions/blacklist';
|
||||
export { doFilteredOutpointsSubscribe } from './redux/actions/filtered';
|
||||
// export { doFetchFeaturedUris, doFetchTrendingUris } from './redux/actions/homepage';
|
||||
export { doFetchViewCount, doFetchSubCount } from './redux/actions/stats';
|
||||
export {
|
||||
doCheckSync,
|
||||
doGetSync,
|
||||
doSetSync,
|
||||
doSetDefaultAccount,
|
||||
doSyncApply,
|
||||
doResetSync,
|
||||
doSyncEncryptAndDecrypt,
|
||||
} from 'redux/actions/sync';
|
||||
|
||||
// reducers
|
||||
export { authReducer } from './redux/reducers/auth';
|
||||
export { costInfoReducer } from './redux/reducers/cost_info';
|
||||
export { blacklistReducer } from './redux/reducers/blacklist';
|
||||
export { filteredReducer } from './redux/reducers/filtered';
|
||||
// export { homepageReducer } from './redux/reducers/homepage';
|
||||
export { statsReducer } from './redux/reducers/stats';
|
||||
export { syncReducer } from './redux/reducers/sync';
|
||||
|
||||
// selectors
|
||||
export { selectAuthToken, selectIsAuthenticating } from './redux/selectors/auth';
|
||||
export {
|
||||
selectFetchingCostInfoForUri,
|
||||
selectCostInfoForUri,
|
||||
selectAllCostInfoByUri,
|
||||
selectFetchingCostInfo,
|
||||
} from './redux/selectors/cost_info';
|
||||
export { selectBlackListedOutpoints, selectBlacklistedOutpointMap } from './redux/selectors/blacklist';
|
||||
export { selectFilteredOutpoints, selectFilteredOutpointMap } from './redux/selectors/filtered';
|
||||
// export {
|
||||
// selectFeaturedUris,
|
||||
// selectFetchingFeaturedUris,
|
||||
// selectTrendingUris,
|
||||
// selectFetchingTrendingUris,
|
||||
// } from './redux/selectors/homepage';
|
||||
export { selectViewCount, selectViewCountForUri, selectSubCountForUri } from './redux/selectors/stats';
|
||||
export { selectBanStateForUri } from './redux/selectors/ban';
|
||||
export {
|
||||
selectHasSyncedWallet,
|
||||
selectSyncData,
|
||||
selectSyncHash,
|
||||
selectSetSyncErrorMessage,
|
||||
selectGetSyncErrorMessage,
|
||||
selectGetSyncIsPending,
|
||||
selectSetSyncIsPending,
|
||||
selectSyncApplyIsPending,
|
||||
selectHashChanged,
|
||||
selectSyncApplyErrorMessage,
|
||||
selectSyncApplyPasswordError,
|
||||
} from './redux/selectors/sync';
|
259
extras/lbryinc/lbryio.js
Normal file
259
extras/lbryinc/lbryio.js
Normal file
|
@ -0,0 +1,259 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import Lbry from 'lbry';
|
||||
import querystring from 'querystring';
|
||||
import analytics from 'analytics';
|
||||
|
||||
const Lbryio = {
|
||||
enabled: true,
|
||||
authenticationPromise: null,
|
||||
exchangePromise: null,
|
||||
exchangeLastFetched: null,
|
||||
CONNECTION_STRING: 'https://api.lbry.com/',
|
||||
};
|
||||
|
||||
const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
|
||||
const INTERNAL_APIS_DOWN = 'internal_apis_down';
|
||||
|
||||
// We can't use env's because they aren't passed into node_modules
|
||||
Lbryio.setLocalApi = (endpoint) => {
|
||||
Lbryio.CONNECTION_STRING = endpoint.replace(/\/*$/, '/'); // exactly one slash at the end;
|
||||
};
|
||||
|
||||
Lbryio.call = (resource, action, params = {}, method = 'get') => {
|
||||
if (!Lbryio.enabled) {
|
||||
return Promise.reject(new Error(__('LBRY internal API is disabled')));
|
||||
}
|
||||
|
||||
if (!(method === 'get' || method === 'post')) {
|
||||
return Promise.reject(new Error(__('Invalid method')));
|
||||
}
|
||||
|
||||
function checkAndParse(response) {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (response.status === 500) {
|
||||
return Promise.reject(INTERNAL_APIS_DOWN);
|
||||
}
|
||||
|
||||
if (response) {
|
||||
return response.json().then((json) => {
|
||||
let error;
|
||||
if (json.error) {
|
||||
error = new Error(json.error);
|
||||
} else {
|
||||
error = new Error('Unknown API error signature');
|
||||
}
|
||||
error.response = response; // This is primarily a hack used in actions/user.js
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function makeRequest(url, options) {
|
||||
return fetch(url, options).then(checkAndParse);
|
||||
}
|
||||
|
||||
return Lbryio.getAuthToken().then((token) => {
|
||||
const fullParams = { auth_token: token, ...params };
|
||||
Object.keys(fullParams).forEach((key) => {
|
||||
const value = fullParams[key];
|
||||
if (typeof value === 'object') {
|
||||
fullParams[key] = JSON.stringify(value);
|
||||
}
|
||||
});
|
||||
|
||||
const qs = querystring.stringify(fullParams);
|
||||
let url = `${Lbryio.CONNECTION_STRING}${resource}/${action}?${qs}`;
|
||||
|
||||
let options = {
|
||||
method: 'GET',
|
||||
};
|
||||
|
||||
if (method === 'post') {
|
||||
options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: qs,
|
||||
};
|
||||
url = `${Lbryio.CONNECTION_STRING}${resource}/${action}`;
|
||||
}
|
||||
|
||||
return makeRequest(url, options).then((response) => {
|
||||
sendCallAnalytics(resource, action, params);
|
||||
return response.data;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Lbryio.authToken = null;
|
||||
|
||||
Lbryio.getAuthToken = () =>
|
||||
new Promise((resolve) => {
|
||||
if (Lbryio.authToken) {
|
||||
resolve(Lbryio.authToken);
|
||||
} else if (Lbryio.overrides.getAuthToken) {
|
||||
Lbryio.overrides.getAuthToken().then((token) => {
|
||||
resolve(token);
|
||||
});
|
||||
} else if (typeof window !== 'undefined') {
|
||||
const { store } = window;
|
||||
if (store) {
|
||||
const state = store.getState();
|
||||
const token = state.auth ? state.auth.authToken : null;
|
||||
Lbryio.authToken = token;
|
||||
resolve(token);
|
||||
}
|
||||
|
||||
resolve(null);
|
||||
} else {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
|
||||
Lbryio.getCurrentUser = () => Lbryio.call('user', 'me');
|
||||
|
||||
Lbryio.authenticate = (domain, language) => {
|
||||
if (!Lbryio.enabled) {
|
||||
const params = {
|
||||
id: 1,
|
||||
primary_email: 'disabled@lbry.io',
|
||||
has_verified_email: true,
|
||||
is_identity_verified: true,
|
||||
is_reward_approved: false,
|
||||
language: language || 'en',
|
||||
};
|
||||
|
||||
return new Promise((resolve) => {
|
||||
resolve(params);
|
||||
});
|
||||
}
|
||||
|
||||
if (Lbryio.authenticationPromise === null) {
|
||||
Lbryio.authenticationPromise = new Promise((resolve, reject) => {
|
||||
Lbryio.getAuthToken()
|
||||
.then((token) => {
|
||||
if (!token || token.length > 60) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check that token works
|
||||
return Lbryio.getCurrentUser()
|
||||
.then((user) => user)
|
||||
.catch((error) => {
|
||||
if (error === INTERNAL_APIS_DOWN) {
|
||||
throw new Error('Internal APIS down');
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
})
|
||||
.then((user) => {
|
||||
if (user) {
|
||||
return user;
|
||||
}
|
||||
|
||||
return Lbry.status()
|
||||
.then(
|
||||
(status) =>
|
||||
new Promise((res, rej) => {
|
||||
const appId =
|
||||
domain && domain !== 'lbry.tv'
|
||||
? (domain.replace(/[.]/gi, '') + status.installation_id).slice(0, 66)
|
||||
: status.installation_id;
|
||||
Lbryio.call(
|
||||
'user',
|
||||
'new',
|
||||
{
|
||||
auth_token: '',
|
||||
language: language || 'en',
|
||||
app_id: appId,
|
||||
},
|
||||
'post'
|
||||
)
|
||||
.then((response) => {
|
||||
if (!response.auth_token) {
|
||||
throw new Error('auth_token was not set in the response');
|
||||
}
|
||||
|
||||
const { store } = window;
|
||||
if (Lbryio.overrides.setAuthToken) {
|
||||
Lbryio.overrides.setAuthToken(response.auth_token);
|
||||
}
|
||||
|
||||
if (store) {
|
||||
store.dispatch({
|
||||
type: ACTIONS.GENERATE_AUTH_TOKEN_SUCCESS,
|
||||
data: { authToken: response.auth_token },
|
||||
});
|
||||
}
|
||||
Lbryio.authToken = response.auth_token;
|
||||
return res(response);
|
||||
})
|
||||
.catch((error) => rej(error));
|
||||
})
|
||||
)
|
||||
.then((newUser) => {
|
||||
if (!newUser) {
|
||||
return Lbryio.getCurrentUser();
|
||||
}
|
||||
return newUser;
|
||||
});
|
||||
})
|
||||
.then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
return Lbryio.authenticationPromise;
|
||||
};
|
||||
|
||||
Lbryio.getStripeToken = () =>
|
||||
Lbryio.CONNECTION_STRING.startsWith('http://localhost:')
|
||||
? 'pk_test_NoL1JWL7i1ipfhVId5KfDZgo'
|
||||
: 'pk_live_e8M4dRNnCCbmpZzduEUZBgJO';
|
||||
|
||||
Lbryio.getExchangeRates = () => {
|
||||
if (!Lbryio.exchangeLastFetched || Date.now() - Lbryio.exchangeLastFetched > EXCHANGE_RATE_TIMEOUT) {
|
||||
Lbryio.exchangePromise = new Promise((resolve, reject) => {
|
||||
Lbryio.call('lbc', 'exchange_rate', {}, 'get', true)
|
||||
.then(({ lbc_usd: LBC_USD, lbc_btc: LBC_BTC, btc_usd: BTC_USD }) => {
|
||||
const rates = { LBC_USD, LBC_BTC, BTC_USD };
|
||||
resolve(rates);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
Lbryio.exchangeLastFetched = Date.now();
|
||||
}
|
||||
return Lbryio.exchangePromise;
|
||||
};
|
||||
|
||||
// Allow overriding lbryio methods
|
||||
// The desktop app will need to use it for getAuthToken because we use electron's ipcRenderer
|
||||
Lbryio.overrides = {};
|
||||
Lbryio.setOverride = (methodName, newMethod) => {
|
||||
Lbryio.overrides[methodName] = newMethod;
|
||||
};
|
||||
|
||||
function sendCallAnalytics(resource, action, params) {
|
||||
switch (resource) {
|
||||
case 'customer':
|
||||
if (action === 'tip') {
|
||||
analytics.reportEvent('spend_virtual_currency', {
|
||||
// https://developers.google.com/analytics/devguides/collection/ga4/reference/events#spend_virtual_currency
|
||||
value: params.amount,
|
||||
virtual_currency_name: params.currency.toLowerCase(),
|
||||
item_name: 'tip',
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export default Lbryio;
|
38
extras/lbryinc/redux/actions/auth.js
Normal file
38
extras/lbryinc/redux/actions/auth.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
|
||||
export function doGenerateAuthToken(installationId) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.GENERATE_AUTH_TOKEN_STARTED,
|
||||
});
|
||||
|
||||
Lbryio.call(
|
||||
'user',
|
||||
'new',
|
||||
{
|
||||
auth_token: '',
|
||||
language: 'en',
|
||||
app_id: installationId,
|
||||
},
|
||||
'post'
|
||||
)
|
||||
.then(response => {
|
||||
if (!response.auth_token) {
|
||||
dispatch({
|
||||
type: ACTIONS.GENERATE_AUTH_TOKEN_FAILURE,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: ACTIONS.GENERATE_AUTH_TOKEN_SUCCESS,
|
||||
data: { authToken: response.auth_token },
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch({
|
||||
type: ACTIONS.GENERATE_AUTH_TOKEN_FAILURE,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
52
extras/lbryinc/redux/actions/blacklist.js
Normal file
52
extras/lbryinc/redux/actions/blacklist.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { Lbryio } from 'lbryinc';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const CHECK_BLACK_LISTED_CONTENT_INTERVAL = 60 * 60 * 1000;
|
||||
|
||||
export function doFetchBlackListedOutpoints() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_STARTED,
|
||||
});
|
||||
|
||||
const success = ({ outpoints }) => {
|
||||
const splitOutpoints = [];
|
||||
if (outpoints) {
|
||||
outpoints.forEach((outpoint, index) => {
|
||||
const [txid, nout] = outpoint.split(':');
|
||||
|
||||
splitOutpoints[index] = { txid, nout: Number.parseInt(nout, 10) };
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
outpoints: splitOutpoints,
|
||||
success: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const failure = ({ message: error }) => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_BLACK_LISTED_CONTENT_FAILED,
|
||||
data: {
|
||||
error,
|
||||
success: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Lbryio.call('file', 'list_blocked', {
|
||||
auth_token: '',
|
||||
}).then(success, failure);
|
||||
};
|
||||
}
|
||||
|
||||
export function doBlackListedOutpointsSubscribe() {
|
||||
return dispatch => {
|
||||
dispatch(doFetchBlackListedOutpoints());
|
||||
setInterval(() => dispatch(doFetchBlackListedOutpoints()), CHECK_BLACK_LISTED_CONTENT_INTERVAL);
|
||||
};
|
||||
}
|
36
extras/lbryinc/redux/actions/cost_info.js
Normal file
36
extras/lbryinc/redux/actions/cost_info.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import { selectClaimForUri } from 'redux/selectors/claims';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function doFetchCostInfoForUri(uri: string) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const claim = selectClaimForUri(state, uri);
|
||||
|
||||
if (!claim) return;
|
||||
|
||||
function resolve(costInfo) {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_COST_INFO_COMPLETED,
|
||||
data: {
|
||||
uri,
|
||||
costInfo,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const fee = claim.value ? claim.value.fee : undefined;
|
||||
|
||||
if (fee === undefined) {
|
||||
resolve({ cost: 0, includesData: true });
|
||||
} else if (fee.currency === 'LBC') {
|
||||
resolve({ cost: fee.amount, includesData: true });
|
||||
} else {
|
||||
Lbryio.getExchangeRates().then(({ LBC_USD }) => {
|
||||
resolve({ cost: fee.amount / LBC_USD, includesData: true });
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
47
extras/lbryinc/redux/actions/filtered.js
Normal file
47
extras/lbryinc/redux/actions/filtered.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { Lbryio } from 'lbryinc';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const CHECK_FILTERED_CONTENT_INTERVAL = 60 * 60 * 1000;
|
||||
|
||||
export function doFetchFilteredOutpoints() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_FILTERED_CONTENT_STARTED,
|
||||
});
|
||||
|
||||
const success = ({ outpoints }) => {
|
||||
let formattedOutpoints = [];
|
||||
if (outpoints) {
|
||||
formattedOutpoints = outpoints.map(outpoint => {
|
||||
const [txid, nout] = outpoint.split(':');
|
||||
return { txid, nout: Number.parseInt(nout, 10) };
|
||||
});
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_FILTERED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
outpoints: formattedOutpoints,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const failure = ({ error }) => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_FILTERED_CONTENT_FAILED,
|
||||
data: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Lbryio.call('file', 'list_filtered', { auth_token: '' }).then(success, failure);
|
||||
};
|
||||
}
|
||||
|
||||
export function doFilteredOutpointsSubscribe() {
|
||||
return dispatch => {
|
||||
dispatch(doFetchFilteredOutpoints());
|
||||
setInterval(() => dispatch(doFetchFilteredOutpoints()), CHECK_FILTERED_CONTENT_INTERVAL);
|
||||
};
|
||||
}
|
79
extras/lbryinc/redux/actions/homepage.js
Normal file
79
extras/lbryinc/redux/actions/homepage.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { Lbryio } from 'lbryinc';
|
||||
import { batchActions } from 'util/batch-actions';
|
||||
import { doResolveUris } from 'util/lbryURI';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
export function doFetchFeaturedUris(offloadResolve = false) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_FEATURED_CONTENT_STARTED,
|
||||
});
|
||||
|
||||
const success = ({ Uris }) => {
|
||||
let urisToResolve = [];
|
||||
Object.keys(Uris).forEach(category => {
|
||||
urisToResolve = [...urisToResolve, ...Uris[category]];
|
||||
});
|
||||
|
||||
const actions = [
|
||||
{
|
||||
type: ACTIONS.FETCH_FEATURED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
uris: Uris,
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
if (urisToResolve.length && !offloadResolve) {
|
||||
actions.push(doResolveUris(urisToResolve));
|
||||
}
|
||||
|
||||
dispatch(batchActions(...actions));
|
||||
};
|
||||
|
||||
const failure = () => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_FEATURED_CONTENT_COMPLETED,
|
||||
data: {
|
||||
uris: {},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Lbryio.call('file', 'list_homepage').then(success, failure);
|
||||
};
|
||||
}
|
||||
|
||||
export function doFetchTrendingUris() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_TRENDING_CONTENT_STARTED,
|
||||
});
|
||||
|
||||
const success = data => {
|
||||
const urisToResolve = data.map(uri => uri.url);
|
||||
const actions = [
|
||||
doResolveUris(urisToResolve),
|
||||
{
|
||||
type: ACTIONS.FETCH_TRENDING_CONTENT_COMPLETED,
|
||||
data: {
|
||||
uris: data,
|
||||
success: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
dispatch(batchActions(...actions));
|
||||
};
|
||||
|
||||
const failure = () => {
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_TRENDING_CONTENT_COMPLETED,
|
||||
data: {
|
||||
uris: [],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Lbryio.call('file', 'list_trending').then(success, failure);
|
||||
};
|
||||
}
|
68
extras/lbryinc/redux/actions/stats.js
Normal file
68
extras/lbryinc/redux/actions/stats.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
// @flow
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const FETCH_SUB_COUNT_MIN_INTERVAL_MS = 5 * 60 * 1000;
|
||||
const FETCH_SUB_COUNT_IDLE_FIRE_MS = 100;
|
||||
|
||||
export const doFetchViewCount = (claimIdCsv: string) => (dispatch: Dispatch) => {
|
||||
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_STARTED });
|
||||
|
||||
return Lbryio.call('file', 'view_count', { claim_id: claimIdCsv })
|
||||
.then((result: Array<number>) => {
|
||||
const viewCounts = result;
|
||||
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_COMPLETED, data: { claimIdCsv, viewCounts } });
|
||||
})
|
||||
.catch((error) => {
|
||||
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_FAILED, data: error });
|
||||
});
|
||||
};
|
||||
|
||||
const executeFetchSubCount = (claimIdCsv: string) => (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const subCountLastFetchedById = state.stats.subCountLastFetchedById;
|
||||
const now = Date.now();
|
||||
|
||||
const claimIds = claimIdCsv.split(',').filter((id) => {
|
||||
const prev = subCountLastFetchedById[id];
|
||||
return !prev || now - prev > FETCH_SUB_COUNT_MIN_INTERVAL_MS;
|
||||
});
|
||||
|
||||
if (claimIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ type: ACTIONS.FETCH_SUB_COUNT_STARTED });
|
||||
|
||||
return Lbryio.call('subscription', 'sub_count', { claim_id: claimIds.join(',') })
|
||||
.then((result: Array<number>) => {
|
||||
const subCounts = result;
|
||||
dispatch({
|
||||
type: ACTIONS.FETCH_SUB_COUNT_COMPLETED,
|
||||
data: { claimIdCsv, subCounts, fetchDate: now },
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
dispatch({ type: ACTIONS.FETCH_SUB_COUNT_FAILED, data: error });
|
||||
});
|
||||
};
|
||||
|
||||
let fetchSubCountTimer;
|
||||
let fetchSubCountQueue = '';
|
||||
|
||||
export const doFetchSubCount = (claimIdCsv: string) => (dispatch: Dispatch) => {
|
||||
if (fetchSubCountTimer) {
|
||||
clearTimeout(fetchSubCountTimer);
|
||||
}
|
||||
|
||||
if (fetchSubCountQueue && !fetchSubCountQueue.endsWith(',')) {
|
||||
fetchSubCountQueue += ',';
|
||||
}
|
||||
|
||||
fetchSubCountQueue += claimIdCsv;
|
||||
|
||||
fetchSubCountTimer = setTimeout(() => {
|
||||
dispatch(executeFetchSubCount(fetchSubCountQueue));
|
||||
fetchSubCountQueue = '';
|
||||
}, FETCH_SUB_COUNT_IDLE_FIRE_MS);
|
||||
};
|
289
extras/lbryinc/redux/actions/sync.js
Normal file
289
extras/lbryinc/redux/actions/sync.js
Normal file
|
@ -0,0 +1,289 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import Lbry from 'lbry';
|
||||
import { doWalletEncrypt, doWalletDecrypt } from 'redux/actions/wallet';
|
||||
|
||||
const NO_WALLET_ERROR = 'no wallet found for this user';
|
||||
|
||||
export function doSetDefaultAccount(success, failure) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_DEFAULT_ACCOUNT,
|
||||
});
|
||||
|
||||
Lbry.account_list()
|
||||
.then(accountList => {
|
||||
const { lbc_mainnet: accounts } = accountList;
|
||||
let defaultId;
|
||||
for (let i = 0; i < accounts.length; ++i) {
|
||||
if (accounts[i].satoshis > 0) {
|
||||
defaultId = accounts[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// In a case where there's no balance on either account
|
||||
// assume the second (which is created after sync) as default
|
||||
if (!defaultId && accounts.length > 1) {
|
||||
defaultId = accounts[1].id;
|
||||
}
|
||||
|
||||
// Set the default account
|
||||
if (defaultId) {
|
||||
Lbry.account_set({ account_id: defaultId, default: true })
|
||||
.then(() => {
|
||||
if (success) {
|
||||
success();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
if (failure) {
|
||||
failure(err);
|
||||
}
|
||||
});
|
||||
} else if (failure) {
|
||||
// no default account to set
|
||||
failure('Could not set a default account'); // fail
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
if (failure) {
|
||||
failure(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doSetSync(oldHash, newHash, data) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_SYNC_STARTED,
|
||||
});
|
||||
|
||||
return Lbryio.call('sync', 'set', { old_hash: oldHash, new_hash: newHash, data }, 'post')
|
||||
.then(response => {
|
||||
if (!response.hash) {
|
||||
throw Error('No hash returned for sync/set.');
|
||||
}
|
||||
|
||||
return dispatch({
|
||||
type: ACTIONS.SET_SYNC_COMPLETED,
|
||||
data: { syncHash: response.hash },
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({
|
||||
type: ACTIONS.SET_SYNC_FAILED,
|
||||
data: { error },
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doGetSync(passedPassword, callback) {
|
||||
const password = passedPassword === null || passedPassword === undefined ? '' : passedPassword;
|
||||
|
||||
function handleCallback(error, hasNewData) {
|
||||
if (callback) {
|
||||
if (typeof callback !== 'function') {
|
||||
throw new Error('Second argument passed to "doGetSync" must be a function');
|
||||
}
|
||||
|
||||
callback(error, hasNewData);
|
||||
}
|
||||
}
|
||||
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_STARTED,
|
||||
});
|
||||
|
||||
const data = {};
|
||||
|
||||
Lbry.wallet_status()
|
||||
.then(status => {
|
||||
if (status.is_locked) {
|
||||
return Lbry.wallet_unlock({ password });
|
||||
}
|
||||
|
||||
// Wallet is already unlocked
|
||||
return true;
|
||||
})
|
||||
.then(isUnlocked => {
|
||||
if (isUnlocked) {
|
||||
return Lbry.sync_hash();
|
||||
}
|
||||
data.unlockFailed = true;
|
||||
throw new Error();
|
||||
})
|
||||
.then(hash => Lbryio.call('sync', 'get', { hash }, 'post'))
|
||||
.then(response => {
|
||||
const syncHash = response.hash;
|
||||
data.syncHash = syncHash;
|
||||
data.syncData = response.data;
|
||||
data.changed = response.changed;
|
||||
data.hasSyncedWallet = true;
|
||||
|
||||
if (response.changed) {
|
||||
return Lbry.sync_apply({ password, data: response.data, blocking: true });
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
if (!response) {
|
||||
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
|
||||
handleCallback(null, data.changed);
|
||||
return;
|
||||
}
|
||||
|
||||
const { hash: walletHash, data: walletData } = response;
|
||||
|
||||
if (walletHash !== data.syncHash) {
|
||||
// different local hash, need to synchronise
|
||||
dispatch(doSetSync(data.syncHash, walletHash, walletData));
|
||||
}
|
||||
|
||||
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
|
||||
handleCallback(null, data.changed);
|
||||
})
|
||||
.catch(syncAttemptError => {
|
||||
if (data.unlockFailed) {
|
||||
dispatch({ type: ACTIONS.GET_SYNC_FAILED, data: { error: syncAttemptError } });
|
||||
|
||||
if (password !== '') {
|
||||
dispatch({ type: ACTIONS.SYNC_APPLY_BAD_PASSWORD });
|
||||
}
|
||||
|
||||
handleCallback(syncAttemptError);
|
||||
} else if (data.hasSyncedWallet) {
|
||||
const error =
|
||||
(syncAttemptError && syncAttemptError.message) || 'Error getting synced wallet';
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_FAILED,
|
||||
data: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
// Temp solution until we have a bad password error code
|
||||
// Don't fail on blank passwords so we don't show a "password error" message
|
||||
// before users have ever entered a password
|
||||
if (password !== '') {
|
||||
dispatch({ type: ACTIONS.SYNC_APPLY_BAD_PASSWORD });
|
||||
}
|
||||
|
||||
handleCallback(error);
|
||||
} else {
|
||||
// user doesn't have a synced wallet
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_COMPLETED,
|
||||
data: { hasSyncedWallet: false, syncHash: null },
|
||||
});
|
||||
|
||||
// call sync_apply to get data to sync
|
||||
// first time sync. use any string for old hash
|
||||
if (syncAttemptError.message === NO_WALLET_ERROR) {
|
||||
Lbry.sync_apply({ password })
|
||||
.then(({ hash: walletHash, data: syncApplyData }) => {
|
||||
dispatch(doSetSync('', walletHash, syncApplyData, password));
|
||||
handleCallback();
|
||||
})
|
||||
.catch(syncApplyError => {
|
||||
handleCallback(syncApplyError);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doSyncApply(syncHash, syncData, password) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.SYNC_APPLY_STARTED,
|
||||
});
|
||||
|
||||
Lbry.sync_apply({ password, data: syncData })
|
||||
.then(({ hash: walletHash, data: walletData }) => {
|
||||
dispatch({
|
||||
type: ACTIONS.SYNC_APPLY_COMPLETED,
|
||||
});
|
||||
|
||||
if (walletHash !== syncHash) {
|
||||
// different local hash, need to synchronise
|
||||
dispatch(doSetSync(syncHash, walletHash, walletData));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch({
|
||||
type: ACTIONS.SYNC_APPLY_FAILED,
|
||||
data: {
|
||||
error:
|
||||
'Invalid password specified. Please enter the password for your previously synchronised wallet.',
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doCheckSync() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_STARTED,
|
||||
});
|
||||
|
||||
Lbry.sync_hash().then(hash => {
|
||||
Lbryio.call('sync', 'get', { hash }, 'post')
|
||||
.then(response => {
|
||||
const data = {
|
||||
hasSyncedWallet: true,
|
||||
syncHash: response.hash,
|
||||
syncData: response.data,
|
||||
hashChanged: response.changed,
|
||||
};
|
||||
dispatch({ type: ACTIONS.GET_SYNC_COMPLETED, data });
|
||||
})
|
||||
.catch(() => {
|
||||
// user doesn't have a synced wallet
|
||||
dispatch({
|
||||
type: ACTIONS.GET_SYNC_COMPLETED,
|
||||
data: { hasSyncedWallet: false, syncHash: null },
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doResetSync() {
|
||||
return dispatch =>
|
||||
new Promise(resolve => {
|
||||
dispatch({ type: ACTIONS.SYNC_RESET });
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
export function doSyncEncryptAndDecrypt(oldPassword, newPassword, encrypt) {
|
||||
return dispatch => {
|
||||
const data = {};
|
||||
return Lbry.sync_hash()
|
||||
.then(hash => Lbryio.call('sync', 'get', { hash }, 'post'))
|
||||
.then(syncGetResponse => {
|
||||
data.oldHash = syncGetResponse.hash;
|
||||
|
||||
return Lbry.sync_apply({ password: oldPassword, data: syncGetResponse.data });
|
||||
})
|
||||
.then(() => {
|
||||
if (encrypt) {
|
||||
dispatch(doWalletEncrypt(newPassword));
|
||||
} else {
|
||||
dispatch(doWalletDecrypt());
|
||||
}
|
||||
})
|
||||
.then(() => Lbry.sync_apply({ password: newPassword }))
|
||||
.then(syncApplyResponse => {
|
||||
if (syncApplyResponse.hash !== data.oldHash) {
|
||||
return dispatch(doSetSync(data.oldHash, syncApplyResponse.hash, syncApplyResponse.data));
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
};
|
||||
}
|
29
extras/lbryinc/redux/reducers/auth.js
Normal file
29
extras/lbryinc/redux/reducers/auth.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const reducers = {};
|
||||
const defaultState = {
|
||||
authenticating: false,
|
||||
};
|
||||
|
||||
reducers[ACTIONS.GENERATE_AUTH_TOKEN_FAILURE] = state =>
|
||||
Object.assign({}, state, {
|
||||
authToken: null,
|
||||
authenticating: false,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.GENERATE_AUTH_TOKEN_STARTED] = state =>
|
||||
Object.assign({}, state, {
|
||||
authenticating: true,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.GENERATE_AUTH_TOKEN_SUCCESS] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
authToken: action.data.authToken,
|
||||
authenticating: false,
|
||||
});
|
||||
|
||||
export function authReducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
return state;
|
||||
}
|
37
extras/lbryinc/redux/reducers/blacklist.js
Normal file
37
extras/lbryinc/redux/reducers/blacklist.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
|
||||
const defaultState = {
|
||||
fetchingBlackListedOutpoints: false,
|
||||
fetchingBlackListedOutpointsSucceed: undefined,
|
||||
blackListedOutpoints: undefined,
|
||||
};
|
||||
|
||||
export const blacklistReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.FETCH_BLACK_LISTED_CONTENT_STARTED]: state => ({
|
||||
...state,
|
||||
fetchingBlackListedOutpoints: true,
|
||||
}),
|
||||
[ACTIONS.FETCH_BLACK_LISTED_CONTENT_COMPLETED]: (state, action) => {
|
||||
const { outpoints, success } = action.data;
|
||||
return {
|
||||
...state,
|
||||
fetchingBlackListedOutpoints: false,
|
||||
fetchingBlackListedOutpointsSucceed: success,
|
||||
blackListedOutpoints: outpoints,
|
||||
};
|
||||
},
|
||||
[ACTIONS.FETCH_BLACK_LISTED_CONTENT_FAILED]: (state, action) => {
|
||||
const { error, success } = action.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingBlackListedOutpoints: false,
|
||||
fetchingBlackListedOutpointsSucceed: success,
|
||||
fetchingBlackListedOutpointsError: error,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
38
extras/lbryinc/redux/reducers/cost_info.js
Normal file
38
extras/lbryinc/redux/reducers/cost_info.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { handleActions } from 'util/redux-utils';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const defaultState = {
|
||||
fetching: {},
|
||||
byUri: {},
|
||||
};
|
||||
|
||||
export const costInfoReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.FETCH_COST_INFO_STARTED]: (state, action) => {
|
||||
const { uri } = action.data;
|
||||
const newFetching = Object.assign({}, state.fetching);
|
||||
newFetching[uri] = true;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetching: newFetching,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.FETCH_COST_INFO_COMPLETED]: (state, action) => {
|
||||
const { uri, costInfo } = action.data;
|
||||
const newByUri = Object.assign({}, state.byUri);
|
||||
const newFetching = Object.assign({}, state.fetching);
|
||||
|
||||
newByUri[uri] = costInfo;
|
||||
delete newFetching[uri];
|
||||
|
||||
return {
|
||||
...state,
|
||||
byUri: newByUri,
|
||||
fetching: newFetching,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
34
extras/lbryinc/redux/reducers/filtered.js
Normal file
34
extras/lbryinc/redux/reducers/filtered.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
import { handleActions } from 'util/redux-utils';
|
||||
|
||||
const defaultState = {
|
||||
loading: false,
|
||||
filteredOutpoints: undefined,
|
||||
};
|
||||
|
||||
export const filteredReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.FETCH_FILTERED_CONTENT_STARTED]: state => ({
|
||||
...state,
|
||||
loading: true,
|
||||
}),
|
||||
[ACTIONS.FETCH_FILTERED_CONTENT_COMPLETED]: (state, action) => {
|
||||
const { outpoints } = action.data;
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
filteredOutpoints: outpoints,
|
||||
};
|
||||
},
|
||||
[ACTIONS.FETCH_FILTERED_CONTENT_FAILED]: (state, action) => {
|
||||
const { error } = action.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
fetchingFilteredOutpointsError: error,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
48
extras/lbryinc/redux/reducers/homepage.js
Normal file
48
extras/lbryinc/redux/reducers/homepage.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { handleActions } from 'util/redux-utils';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const defaultState = {
|
||||
fetchingFeaturedContent: false,
|
||||
fetchingFeaturedContentFailed: false,
|
||||
featuredUris: undefined,
|
||||
fetchingTrendingContent: false,
|
||||
fetchingTrendingContentFailed: false,
|
||||
trendingUris: undefined,
|
||||
};
|
||||
|
||||
export const homepageReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.FETCH_FEATURED_CONTENT_STARTED]: state => ({
|
||||
...state,
|
||||
fetchingFeaturedContent: true,
|
||||
}),
|
||||
|
||||
[ACTIONS.FETCH_FEATURED_CONTENT_COMPLETED]: (state, action) => {
|
||||
const { uris, success } = action.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingFeaturedContent: false,
|
||||
fetchingFeaturedContentFailed: !success,
|
||||
featuredUris: uris,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.FETCH_TRENDING_CONTENT_STARTED]: state => ({
|
||||
...state,
|
||||
fetchingTrendingContent: true,
|
||||
}),
|
||||
|
||||
[ACTIONS.FETCH_TRENDING_CONTENT_COMPLETED]: (state, action) => {
|
||||
const { uris, success } = action.data;
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingTrendingContent: false,
|
||||
fetchingTrendingContentFailed: !success,
|
||||
trendingUris: uris,
|
||||
};
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
81
extras/lbryinc/redux/reducers/stats.js
Normal file
81
extras/lbryinc/redux/reducers/stats.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { handleActions } from 'util/redux-utils';
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const defaultState = {
|
||||
fetchingViewCount: false,
|
||||
viewCountError: undefined,
|
||||
viewCountById: {},
|
||||
fetchingSubCount: false,
|
||||
subCountError: undefined,
|
||||
subCountById: {},
|
||||
subCountLastFetchedById: {},
|
||||
};
|
||||
|
||||
export const statsReducer = handleActions(
|
||||
{
|
||||
[ACTIONS.FETCH_VIEW_COUNT_STARTED]: (state) => ({ ...state, fetchingViewCount: true }),
|
||||
|
||||
[ACTIONS.FETCH_VIEW_COUNT_FAILED]: (state, action) => ({
|
||||
...state,
|
||||
viewCountError: action.data,
|
||||
}),
|
||||
|
||||
[ACTIONS.FETCH_VIEW_COUNT_COMPLETED]: (state, action) => {
|
||||
const { claimIdCsv, viewCounts } = action.data;
|
||||
|
||||
const viewCountById = Object.assign({}, state.viewCountById);
|
||||
const claimIds = claimIdCsv.split(',');
|
||||
|
||||
if (claimIds.length === viewCounts.length) {
|
||||
claimIds.forEach((claimId, index) => {
|
||||
viewCountById[claimId] = viewCounts[index];
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
fetchingViewCount: false,
|
||||
viewCountById,
|
||||
};
|
||||
},
|
||||
|
||||
[ACTIONS.FETCH_SUB_COUNT_STARTED]: (state) => ({ ...state, fetchingSubCount: true }),
|
||||
|
||||
[ACTIONS.FETCH_SUB_COUNT_FAILED]: (state, action) => ({
|
||||
...state,
|
||||
subCountError: action.data,
|
||||
}),
|
||||
|
||||
[ACTIONS.FETCH_SUB_COUNT_COMPLETED]: (state, action) => {
|
||||
const { claimIdCsv, subCounts, fetchDate } = action.data;
|
||||
|
||||
const subCountById = Object.assign({}, state.subCountById);
|
||||
const subCountLastFetchedById = Object.assign({}, state.subCountLastFetchedById);
|
||||
const claimIds = claimIdCsv.split(',');
|
||||
let dataChanged = false;
|
||||
|
||||
if (claimIds.length === subCounts.length) {
|
||||
claimIds.forEach((claimId, index) => {
|
||||
if (subCountById[claimId] !== subCounts[index]) {
|
||||
subCountById[claimId] = subCounts[index];
|
||||
dataChanged = true;
|
||||
}
|
||||
subCountLastFetchedById[claimId] = fetchDate;
|
||||
});
|
||||
}
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
fetchingSubCount: false,
|
||||
subCountLastFetchedById,
|
||||
};
|
||||
|
||||
if (dataChanged) {
|
||||
newState.subCountById = subCountById;
|
||||
}
|
||||
|
||||
return newState;
|
||||
},
|
||||
},
|
||||
defaultState
|
||||
);
|
89
extras/lbryinc/redux/reducers/sync.js
Normal file
89
extras/lbryinc/redux/reducers/sync.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
const reducers = {};
|
||||
const defaultState = {
|
||||
hasSyncedWallet: false,
|
||||
syncHash: null,
|
||||
syncData: null,
|
||||
setSyncErrorMessage: null,
|
||||
getSyncErrorMessage: null,
|
||||
syncApplyErrorMessage: '',
|
||||
syncApplyIsPending: false,
|
||||
syncApplyPasswordError: false,
|
||||
getSyncIsPending: false,
|
||||
setSyncIsPending: false,
|
||||
hashChanged: false,
|
||||
};
|
||||
|
||||
reducers[ACTIONS.GET_SYNC_STARTED] = state =>
|
||||
Object.assign({}, state, {
|
||||
getSyncIsPending: true,
|
||||
getSyncErrorMessage: null,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.GET_SYNC_COMPLETED] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
syncHash: action.data.syncHash,
|
||||
syncData: action.data.syncData,
|
||||
hasSyncedWallet: action.data.hasSyncedWallet,
|
||||
getSyncIsPending: false,
|
||||
hashChanged: action.data.hashChanged,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.GET_SYNC_FAILED] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
getSyncIsPending: false,
|
||||
getSyncErrorMessage: action.data.error,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SET_SYNC_STARTED] = state =>
|
||||
Object.assign({}, state, {
|
||||
setSyncIsPending: true,
|
||||
setSyncErrorMessage: null,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SET_SYNC_FAILED] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
setSyncIsPending: false,
|
||||
setSyncErrorMessage: action.data.error,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SET_SYNC_COMPLETED] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
setSyncIsPending: false,
|
||||
setSyncErrorMessage: null,
|
||||
hasSyncedWallet: true, // sync was successful, so the user has a synced wallet at this point
|
||||
syncHash: action.data.syncHash,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SYNC_APPLY_STARTED] = state =>
|
||||
Object.assign({}, state, {
|
||||
syncApplyPasswordError: false,
|
||||
syncApplyIsPending: true,
|
||||
syncApplyErrorMessage: '',
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SYNC_APPLY_COMPLETED] = state =>
|
||||
Object.assign({}, state, {
|
||||
syncApplyIsPending: false,
|
||||
syncApplyErrorMessage: '',
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SYNC_APPLY_FAILED] = (state, action) =>
|
||||
Object.assign({}, state, {
|
||||
syncApplyIsPending: false,
|
||||
syncApplyErrorMessage: action.data.error,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SYNC_APPLY_BAD_PASSWORD] = state =>
|
||||
Object.assign({}, state, {
|
||||
syncApplyPasswordError: true,
|
||||
});
|
||||
|
||||
reducers[ACTIONS.SYNC_RESET] = () => defaultState;
|
||||
|
||||
export function syncReducer(state = defaultState, action) {
|
||||
const handler = reducers[action.type];
|
||||
if (handler) return handler(state, action);
|
||||
return state;
|
||||
}
|
4
extras/lbryinc/redux/selectors/auth.js
Normal file
4
extras/lbryinc/redux/selectors/auth.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
const selectState = (state) => state.auth || {};
|
||||
|
||||
export const selectAuthToken = (state) => selectState(state).authToken;
|
||||
export const selectIsAuthenticating = (state) => selectState(state).authenticating;
|
68
extras/lbryinc/redux/selectors/ban.js
Normal file
68
extras/lbryinc/redux/selectors/ban.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
// @flow
|
||||
|
||||
// TODO: This should be in 'redux/selectors/claim.js'. Temporarily putting it
|
||||
// here to get past importing issues with 'lbryinc', which the real fix might
|
||||
// involve moving it from 'extras' to 'ui' (big change).
|
||||
|
||||
import { createCachedSelector } from 're-reselect';
|
||||
import { selectClaimForUri } from 'redux/selectors/claims';
|
||||
import { selectMutedChannels } from 'redux/selectors/blocked';
|
||||
import { selectModerationBlockList } from 'redux/selectors/comments';
|
||||
import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc';
|
||||
import { getChannelFromClaim } from 'util/claim';
|
||||
import { isURIEqual } from 'util/lbryURI';
|
||||
|
||||
export const selectBanStateForUri = createCachedSelector(
|
||||
selectClaimForUri,
|
||||
selectBlacklistedOutpointMap,
|
||||
selectFilteredOutpointMap,
|
||||
selectMutedChannels,
|
||||
selectModerationBlockList,
|
||||
(claim, blackListedOutpointMap, filteredOutpointMap, mutedChannelUris, personalBlocklist) => {
|
||||
const banState = {};
|
||||
|
||||
if (!claim) {
|
||||
return banState;
|
||||
}
|
||||
|
||||
const channelClaim = getChannelFromClaim(claim);
|
||||
|
||||
// This will be replaced once blocking is done at the wallet server level.
|
||||
if (blackListedOutpointMap) {
|
||||
if (
|
||||
(channelClaim && blackListedOutpointMap[`${channelClaim.txid}:${channelClaim.nout}`]) ||
|
||||
blackListedOutpointMap[`${claim.txid}:${claim.nout}`]
|
||||
) {
|
||||
banState['blacklisted'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// We're checking to see if the stream outpoint or signing channel outpoint
|
||||
// is in the filter list.
|
||||
if (filteredOutpointMap) {
|
||||
if (
|
||||
(channelClaim && filteredOutpointMap[`${channelClaim.txid}:${channelClaim.nout}`]) ||
|
||||
filteredOutpointMap[`${claim.txid}:${claim.nout}`]
|
||||
) {
|
||||
banState['filtered'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// block stream claims
|
||||
// block channel claims if we can't control for them in claim search
|
||||
if (mutedChannelUris.length && channelClaim) {
|
||||
if (mutedChannelUris.some((blockedUri) => isURIEqual(blockedUri, channelClaim.permanent_url))) {
|
||||
banState['muted'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Commentron blocklist
|
||||
if (personalBlocklist.length && channelClaim) {
|
||||
if (personalBlocklist.some((blockedUri) => isURIEqual(blockedUri, channelClaim.permanent_url))) {
|
||||
banState['blocked'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return banState;
|
||||
}
|
||||
)((state, uri) => String(uri));
|
15
extras/lbryinc/redux/selectors/blacklist.js
Normal file
15
extras/lbryinc/redux/selectors/blacklist.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
export const selectState = (state) => state.blacklist || {};
|
||||
|
||||
export const selectBlackListedOutpoints = (state) => selectState(state).blackListedOutpoints;
|
||||
|
||||
export const selectBlacklistedOutpointMap = createSelector(selectBlackListedOutpoints, (outpoints) =>
|
||||
outpoints
|
||||
? outpoints.reduce((acc, val) => {
|
||||
const outpoint = `${val.txid}:${val.nout}`;
|
||||
acc[outpoint] = 1;
|
||||
return acc;
|
||||
}, {})
|
||||
: {}
|
||||
);
|
16
extras/lbryinc/redux/selectors/cost_info.js
Normal file
16
extras/lbryinc/redux/selectors/cost_info.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
// @flow
|
||||
type State = { costInfo: any };
|
||||
|
||||
export const selectState = (state: State) => state.costInfo || {};
|
||||
export const selectAllCostInfoByUri = (state: State) => selectState(state).byUri;
|
||||
export const selectFetchingCostInfo = (state: State) => selectState(state).fetching;
|
||||
|
||||
export const selectCostInfoForUri = (state: State, uri: string) => {
|
||||
const costInfos = selectAllCostInfoByUri(state);
|
||||
return costInfos && costInfos[uri];
|
||||
};
|
||||
|
||||
export const selectFetchingCostInfoForUri = (state: State, uri: string) => {
|
||||
const fetchingByUri = selectFetchingCostInfo(state);
|
||||
return fetchingByUri && fetchingByUri[uri];
|
||||
};
|
15
extras/lbryinc/redux/selectors/filtered.js
Normal file
15
extras/lbryinc/redux/selectors/filtered.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { createSelector } from 'reselect';
|
||||
|
||||
export const selectState = (state) => state.filtered || {};
|
||||
|
||||
export const selectFilteredOutpoints = (state) => selectState(state).filteredOutpoints;
|
||||
|
||||
export const selectFilteredOutpointMap = createSelector(selectFilteredOutpoints, (outpoints) =>
|
||||
outpoints
|
||||
? outpoints.reduce((acc, val) => {
|
||||
const outpoint = `${val.txid}:${val.nout}`;
|
||||
acc[outpoint] = 1;
|
||||
return acc;
|
||||
}, {})
|
||||
: {}
|
||||
);
|
6
extras/lbryinc/redux/selectors/homepage.js
Normal file
6
extras/lbryinc/redux/selectors/homepage.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
const selectState = (state) => state.homepage || {};
|
||||
|
||||
export const selectFeaturedUris = (state) => selectState(state).featuredUris;
|
||||
export const selectFetchingFeaturedUris = (state) => selectState(state).fetchingFeaturedContent;
|
||||
export const selectTrendingUris = (state) => selectState(state).trendingUris;
|
||||
export const selectFetchingTrendingUris = (state) => selectState(state).fetchingTrendingContent;
|
20
extras/lbryinc/redux/selectors/stats.js
Normal file
20
extras/lbryinc/redux/selectors/stats.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
// @flow
|
||||
import { selectClaimIdForUri } from 'redux/selectors/claims';
|
||||
|
||||
type State = { claims: any, stats: any };
|
||||
|
||||
const selectState = (state: State) => state.stats || {};
|
||||
export const selectViewCount = (state: State) => selectState(state).viewCountById;
|
||||
export const selectSubCount = (state: State) => selectState(state).subCountById;
|
||||
|
||||
export const selectViewCountForUri = (state: State, uri: string) => {
|
||||
const claimId = selectClaimIdForUri(state, uri);
|
||||
const viewCountById = selectViewCount(state);
|
||||
return claimId ? viewCountById[claimId] || 0 : 0;
|
||||
};
|
||||
|
||||
export const selectSubCountForUri = (state: State, uri: string) => {
|
||||
const claimId = selectClaimIdForUri(state, uri);
|
||||
const subCountById = selectSubCount(state);
|
||||
return claimId ? subCountById[claimId] || 0 : 0;
|
||||
};
|
13
extras/lbryinc/redux/selectors/sync.js
Normal file
13
extras/lbryinc/redux/selectors/sync.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
const selectState = (state) => state.sync || {};
|
||||
|
||||
export const selectHasSyncedWallet = (state) => selectState(state).hasSyncedWallet;
|
||||
export const selectSyncHash = (state) => selectState(state).syncHash;
|
||||
export const selectSyncData = (state) => selectState(state).syncData;
|
||||
export const selectSetSyncErrorMessage = (state) => selectState(state).setSyncErrorMessage;
|
||||
export const selectGetSyncErrorMessage = (state) => selectState(state).getSyncErrorMessage;
|
||||
export const selectGetSyncIsPending = (state) => selectState(state).getSyncIsPending;
|
||||
export const selectSetSyncIsPending = (state) => selectState(state).setSyncIsPending;
|
||||
export const selectHashChanged = (state) => selectState(state).hashChanged;
|
||||
export const selectSyncApplyIsPending = (state) => selectState(state).syncApplyIsPending;
|
||||
export const selectSyncApplyErrorMessage = (state) => selectState(state).syncApplyErrorMessage;
|
||||
export const selectSyncApplyPasswordError = (state) => selectState(state).syncApplyPasswordError;
|
17
extras/lbryinc/util/redux-utils-delete-me.js
Normal file
17
extras/lbryinc/util/redux-utils-delete-me.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
// util for creating reducers
|
||||
// based off of redux-actions
|
||||
// https://redux-actions.js.org/docs/api/handleAction.html#handleactions
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const handleActions = (actionMap, defaultState) => (state = defaultState, action) => {
|
||||
const handler = actionMap[action.type];
|
||||
|
||||
if (handler) {
|
||||
const newState = handler(state, action);
|
||||
return Object.assign({}, state, newState);
|
||||
}
|
||||
|
||||
// just return the original state if no handler
|
||||
// returning a copy here breaks redux-persist
|
||||
return state;
|
||||
};
|
10
extras/lbryinc/util/swap-json.js
Normal file
10
extras/lbryinc/util/swap-json.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
export function swapKeyAndValue(dict) {
|
||||
const ret = {};
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key in dict) {
|
||||
if (dict.hasOwnProperty(key)) {
|
||||
ret[dict[key]] = key;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
78
extras/lbryinc/util/transifex-upload.js
Normal file
78
extras/lbryinc/util/transifex-upload.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
const apiBaseUrl = 'https://www.transifex.com/api/2/project';
|
||||
const resource = 'app-strings';
|
||||
|
||||
export function doTransifexUpload(contents, project, token, success, fail) {
|
||||
const url = `${apiBaseUrl}/${project}/resources/`;
|
||||
const updateUrl = `${apiBaseUrl}/${project}/resource/${resource}/content/`;
|
||||
const headers = {
|
||||
Authorization: `Basic ${Buffer.from(`api:${token}`).toString('base64')}`,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
|
||||
const req = {
|
||||
accept_translations: true,
|
||||
i18n_type: 'KEYVALUEJSON',
|
||||
name: resource,
|
||||
slug: resource,
|
||||
content: contents,
|
||||
};
|
||||
|
||||
function handleResponse(text) {
|
||||
let json;
|
||||
try {
|
||||
// transifex api returns Python dicts for some reason.
|
||||
// Any way to get the api to return valid JSON?
|
||||
json = JSON.parse(text);
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (success) {
|
||||
success(json || text);
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(err) {
|
||||
if (fail) {
|
||||
fail(err.message ? err.message : 'Could not upload strings resource to Transifex');
|
||||
}
|
||||
}
|
||||
|
||||
// check if the resource exists
|
||||
fetch(updateUrl, { headers })
|
||||
.then(response => response.json())
|
||||
.then(() => {
|
||||
// perform an update
|
||||
fetch(updateUrl, {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify({ content: contents }),
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status !== 200 && response.status !== 201) {
|
||||
throw new Error('failed to update transifex');
|
||||
}
|
||||
|
||||
return response.text();
|
||||
})
|
||||
.then(handleResponse)
|
||||
.catch(handleError);
|
||||
})
|
||||
.catch(() => {
|
||||
// resource doesn't exist, create a fresh resource
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(req),
|
||||
})
|
||||
.then(response => {
|
||||
if (response.status !== 200 && response.status !== 201) {
|
||||
throw new Error('failed to upload to transifex');
|
||||
}
|
||||
|
||||
return response.text();
|
||||
})
|
||||
.then(handleResponse)
|
||||
.catch(handleError);
|
||||
});
|
||||
}
|
3
extras/recsys/index.js
Normal file
3
extras/recsys/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Recsys from './recsys';
|
||||
|
||||
export default Recsys;
|
|
@ -1,12 +1,14 @@
|
|||
import { selectUser } from 'redux/selectors/user';
|
||||
import { makeSelectRecommendedRecsysIdForClaimId } from 'redux/selectors/search';
|
||||
import { v4 as Uuidv4 } from 'uuid';
|
||||
import { parseURI, SETTINGS, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { parseURI } from 'util/lbryURI';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { selectPlayingUri, selectPrimaryUri } from 'redux/selectors/content';
|
||||
import { makeSelectClientSetting, selectDaemonSettings } from 'redux/selectors/settings';
|
||||
import { history } from './store';
|
||||
import { selectClientSetting, selectDaemonSettings } from 'redux/selectors/settings';
|
||||
import { history } from 'ui/store';
|
||||
|
||||
const recsysEndpoint = 'https://clickstream.odysee.com/log/video/view';
|
||||
const recsysEndpoint = 'https://recsys.odysee.com/log/video/view';
|
||||
const recsysId = 'lighthouse-v0';
|
||||
|
||||
const getClaimIdsFromUris = (uris) => {
|
||||
|
@ -71,7 +73,7 @@ const recsys = {
|
|||
* Called from recommendedContent component
|
||||
*/
|
||||
onRecsLoaded: function (claimId, uris) {
|
||||
if (window.store) {
|
||||
if (window && window.store) {
|
||||
const state = window.store.getState();
|
||||
if (!recsys.entries[claimId]) {
|
||||
recsys.createRecsysEntry(claimId);
|
||||
|
@ -90,9 +92,10 @@ const recsys = {
|
|||
* @param: parentUuid: string (optional)
|
||||
*/
|
||||
createRecsysEntry: function (claimId, parentUuid) {
|
||||
if (window.store && claimId) {
|
||||
if (window && window.store && claimId) {
|
||||
const state = window.store.getState();
|
||||
const { id: userId } = selectUser(state);
|
||||
const user = selectUser(state);
|
||||
const userId = user ? user.id : null;
|
||||
if (parentUuid) {
|
||||
// Make a stub entry that will be filled out on page load
|
||||
recsys.entries[claimId] = {
|
||||
|
@ -170,7 +173,7 @@ const recsys = {
|
|||
* if so, send the Entry.
|
||||
*/
|
||||
onPlayerDispose: function (claimId, isEmbedded) {
|
||||
if (window.store) {
|
||||
if (window && window.store) {
|
||||
const state = window.store.getState();
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const primaryUri = selectPrimaryUri(state);
|
||||
|
@ -193,7 +196,7 @@ const recsys = {
|
|||
// * more events until player is disposed. Don't send unless floatingPlayer playingUri
|
||||
// */
|
||||
// onLeaveFilePage: function (primaryUri) {
|
||||
// if (window.store) {
|
||||
// if (window && window.store) {
|
||||
// const state = window.store.getState();
|
||||
// const claim = makeSelectClaimForUri(primaryUri)(state);
|
||||
// const claimId = claim ? claim.claim_id : null;
|
||||
|
@ -219,14 +222,14 @@ const recsys = {
|
|||
* Send all claimIds that aren't currently playing.
|
||||
*/
|
||||
onNavigate: function () {
|
||||
if (window.store) {
|
||||
if (window && window.store) {
|
||||
const state = window.store.getState();
|
||||
const playingUri = selectPlayingUri(state);
|
||||
const actualPlayingUri = playingUri && playingUri.uri;
|
||||
const claim = makeSelectClaimForUri(actualPlayingUri)(state);
|
||||
const playingClaimId = claim ? claim.claim_id : null;
|
||||
// const primaryUri = selectPrimaryUri(state);
|
||||
const floatingPlayer = makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state);
|
||||
const floatingPlayer = selectClientSetting(state, SETTINGS.FLOATING_PLAYER);
|
||||
// When leaving page, if floating player is enabled, play will continue.
|
||||
Object.keys(recsys.entries).forEach((claimId) => {
|
||||
const shouldSkip = recsys.entries[claimId].parentUuid && !recsys.entries[claimId].recClaimIds;
|
10
flow-typed/Blocklist.js
vendored
Normal file
10
flow-typed/Blocklist.js
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
declare type BlocklistState = {
|
||||
blockedChannels: Array<string>
|
||||
};
|
||||
|
||||
declare type BlocklistAction = {
|
||||
type: string,
|
||||
data: {
|
||||
uri: string,
|
||||
},
|
||||
};
|
214
flow-typed/Claim.js
vendored
Normal file
214
flow-typed/Claim.js
vendored
Normal file
|
@ -0,0 +1,214 @@
|
|||
// @flow
|
||||
|
||||
declare type Claim = StreamClaim | ChannelClaim | CollectionClaim;
|
||||
|
||||
declare type ChannelClaim = GenericClaim & {
|
||||
value: ChannelMetadata,
|
||||
};
|
||||
|
||||
declare type CollectionClaim = GenericClaim & {
|
||||
value: CollectionMetadata,
|
||||
};
|
||||
|
||||
declare type StreamClaim = GenericClaim & {
|
||||
value: StreamMetadata,
|
||||
};
|
||||
|
||||
declare type GenericClaim = {
|
||||
address: string, // address associated with tx
|
||||
amount: string, // bid amount at time of tx
|
||||
canonical_url: string, // URL with short id, includes channel with short id
|
||||
claim_id: string, // unique claim identifier
|
||||
claim_sequence: number, // not being used currently
|
||||
claim_op: 'create' | 'update',
|
||||
confirmations: number,
|
||||
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_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
|
||||
permanent_url: string, // name + claim_id
|
||||
short_url: string, // permanent_url with short id, no channel
|
||||
txid: string, // unique tx id
|
||||
type: 'claim' | 'update' | 'support',
|
||||
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,
|
||||
creation_height: number,
|
||||
creation_timestamp: number,
|
||||
effective_amount: string,
|
||||
expiration_height: number,
|
||||
is_controlling: boolean,
|
||||
support_amount: string,
|
||||
reposted: number,
|
||||
trending_global: number,
|
||||
trending_group: number,
|
||||
trending_local: number,
|
||||
trending_mixed: number,
|
||||
},
|
||||
};
|
||||
|
||||
declare type GenericMetadata = {
|
||||
title?: string,
|
||||
description?: string,
|
||||
thumbnail?: {
|
||||
url?: string,
|
||||
},
|
||||
languages?: Array<string>,
|
||||
tags?: Array<string>,
|
||||
locations?: Array<Location>,
|
||||
};
|
||||
|
||||
declare type ChannelMetadata = GenericMetadata & {
|
||||
public_key: string,
|
||||
public_key_id: string,
|
||||
cover_url?: string,
|
||||
email?: string,
|
||||
website_url?: string,
|
||||
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
|
||||
release_time?: number, // linux timestamp
|
||||
author?: string,
|
||||
|
||||
source: {
|
||||
sd_hash: string,
|
||||
media_type?: string,
|
||||
hash?: string,
|
||||
name?: string, // file name
|
||||
size?: number, // size of file in bytes
|
||||
},
|
||||
|
||||
// Only exists if a stream has a fee
|
||||
fee?: Fee,
|
||||
|
||||
stream_type: 'video' | 'audio' | 'image' | 'software',
|
||||
// Below correspond to `stream_type`
|
||||
video?: {
|
||||
duration: number,
|
||||
height: number,
|
||||
width: number,
|
||||
},
|
||||
audio?: {
|
||||
duration: number,
|
||||
},
|
||||
image?: {
|
||||
height: number,
|
||||
width: number,
|
||||
},
|
||||
software?: {
|
||||
os: string,
|
||||
},
|
||||
};
|
||||
|
||||
declare type Location = {
|
||||
latitude?: number,
|
||||
longitude?: number,
|
||||
country?: string,
|
||||
state?: string,
|
||||
city?: string,
|
||||
code?: string,
|
||||
};
|
||||
|
||||
declare type Fee = {
|
||||
amount: string,
|
||||
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
29
flow-typed/CoinSwap.js
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
declare type CoinSwapInfo = {
|
||||
chargeCode: string,
|
||||
coins: Array<string>,
|
||||
sendAddresses: { [string]: string},
|
||||
sendAmounts: { [string]: any },
|
||||
lbcAmount: number,
|
||||
status?: {
|
||||
status: string,
|
||||
receiptCurrency: string,
|
||||
receiptTxid: string,
|
||||
lbcTxid: string,
|
||||
},
|
||||
}
|
||||
|
||||
declare type CoinSwapState = {
|
||||
coinSwaps: Array<CoinSwapInfo>,
|
||||
};
|
||||
|
||||
declare type CoinSwapAddAction = {
|
||||
type: string,
|
||||
data: CoinSwapInfo,
|
||||
};
|
||||
|
||||
declare type CoinSwapRemoveAction = {
|
||||
type: string,
|
||||
data: {
|
||||
chargeCode: string,
|
||||
},
|
||||
};
|
34
flow-typed/Collections.js
vendored
Normal file
34
flow-typed/Collections.js
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
declare type Collection = {
|
||||
id: string,
|
||||
items: Array<?string>,
|
||||
name: string,
|
||||
type: string,
|
||||
updatedAt: number,
|
||||
totalItems?: number,
|
||||
sourceId?: string, // if copied, claimId of original collection
|
||||
};
|
||||
|
||||
declare type CollectionState = {
|
||||
unpublished: CollectionGroup,
|
||||
resolved: CollectionGroup,
|
||||
pending: CollectionGroup,
|
||||
edited: CollectionGroup,
|
||||
builtin: CollectionGroup,
|
||||
saved: Array<string>,
|
||||
isResolvingCollectionById: { [string]: boolean },
|
||||
error?: string | null,
|
||||
};
|
||||
|
||||
declare type CollectionGroup = {
|
||||
[string]: Collection,
|
||||
}
|
||||
|
||||
declare type CollectionEditParams = {
|
||||
claims?: Array<Claim>,
|
||||
remove?: boolean,
|
||||
claimIds?: Array<string>,
|
||||
replace?: boolean,
|
||||
order?: { from: number, to: number },
|
||||
type?: string,
|
||||
name?: string,
|
||||
}
|
13
flow-typed/Comment.js
vendored
13
flow-typed/Comment.js
vendored
|
@ -45,6 +45,7 @@ declare type CommentsState = {
|
|||
isLoading: boolean,
|
||||
isLoadingById: boolean,
|
||||
isLoadingByParentId: { [string]: boolean },
|
||||
isCommenting: boolean,
|
||||
myComments: ?Set<string>,
|
||||
isFetchingReacts: boolean,
|
||||
myReactsByCommentId: ?{ [string]: Array<string> }, // {"CommentId:MyChannelId": ["like", "dislike", ...]}
|
||||
|
@ -168,8 +169,10 @@ declare type CommentAbandonParams = {
|
|||
comment_id: string,
|
||||
creator_channel_id?: string,
|
||||
creator_channel_name?: string,
|
||||
channel_id?: string,
|
||||
hexdata?: string,
|
||||
signature?: string,
|
||||
signing_ts?: string,
|
||||
mod_channel_id?: string,
|
||||
mod_channel_name?: string,
|
||||
};
|
||||
|
||||
declare type CommentCreateParams = {
|
||||
|
@ -177,7 +180,7 @@ declare type CommentCreateParams = {
|
|||
claim_id: string,
|
||||
parent_id?: string,
|
||||
signature: string,
|
||||
signing_ts: number,
|
||||
signing_ts: string,
|
||||
support_tx_id?: string,
|
||||
};
|
||||
|
||||
|
@ -203,9 +206,11 @@ declare type ModerationBlockParams = {
|
|||
// Creator that Moderator is delegated from. Used for delegated moderation
|
||||
creator_channel_id?: string,
|
||||
creator_channel_name?: string,
|
||||
// ID of comment to remove as part of this block
|
||||
offending_comment_id?: string,
|
||||
// Blocks identity from comment universally, requires Admin rights on commentron instance
|
||||
block_all?: boolean,
|
||||
time_out?: number,
|
||||
time_out?: ?number,
|
||||
// If true will delete all comments of the offender, requires Admin rights on commentron for universal delete
|
||||
delete_all?: boolean,
|
||||
// The usual signature stuff
|
||||
|
|
369
flow-typed/Lbry.js
vendored
Normal file
369
flow-typed/Lbry.js
vendored
Normal file
|
@ -0,0 +1,369 @@
|
|||
// @flow
|
||||
declare type StatusResponse = {
|
||||
blob_manager: {
|
||||
finished_blobs: number,
|
||||
},
|
||||
blockchain_headers: {
|
||||
download_progress: number,
|
||||
downloading_headers: boolean,
|
||||
},
|
||||
dht: {
|
||||
node_id: string,
|
||||
peers_in_routing_table: number,
|
||||
},
|
||||
hash_announcer: {
|
||||
announce_queue_size: number,
|
||||
},
|
||||
installation_id: string,
|
||||
is_running: boolean,
|
||||
skipped_components: Array<string>,
|
||||
startup_status: {
|
||||
blob_manager: boolean,
|
||||
blockchain_headers: boolean,
|
||||
database: boolean,
|
||||
dht: boolean,
|
||||
exchange_rate_manager: boolean,
|
||||
hash_announcer: boolean,
|
||||
peer_protocol_server: boolean,
|
||||
stream_manager: boolean,
|
||||
upnp: boolean,
|
||||
wallet: boolean,
|
||||
},
|
||||
stream_manager: {
|
||||
managed_files: number,
|
||||
},
|
||||
upnp: {
|
||||
aioupnp_version: string,
|
||||
dht_redirect_set: boolean,
|
||||
external_ip: string,
|
||||
gateway: string,
|
||||
peer_redirect_set: boolean,
|
||||
redirects: {},
|
||||
},
|
||||
wallet: ?{
|
||||
connected: string,
|
||||
best_blockhash: string,
|
||||
blocks: number,
|
||||
blocks_behind: number,
|
||||
is_encrypted: boolean,
|
||||
is_locked: boolean,
|
||||
headers_synchronization_progress: number,
|
||||
available_servers: number,
|
||||
},
|
||||
};
|
||||
|
||||
declare type VersionResponse = {
|
||||
build: string,
|
||||
lbrynet_version: string,
|
||||
os_release: string,
|
||||
os_system: string,
|
||||
platform: string,
|
||||
processor: string,
|
||||
python_version: string,
|
||||
};
|
||||
|
||||
declare type BalanceResponse = {
|
||||
available: string,
|
||||
reserved: string,
|
||||
reserved_subtotals: ?{
|
||||
claims: string,
|
||||
supports: string,
|
||||
tips: string,
|
||||
},
|
||||
total: string,
|
||||
};
|
||||
|
||||
declare type ResolveResponse = {
|
||||
// Keys are the url(s) passed to resolve
|
||||
[string]: { error?: {}, stream?: StreamClaim, channel?: ChannelClaim, collection?: CollectionClaim, claimsInChannel?: number },
|
||||
};
|
||||
|
||||
declare type GetResponse = FileListItem & { error?: string };
|
||||
|
||||
declare type GenericTxResponse = {
|
||||
height: number,
|
||||
hex: string,
|
||||
inputs: Array<{}>,
|
||||
outputs: Array<{}>,
|
||||
total_fee: string,
|
||||
total_input: string,
|
||||
total_output: string,
|
||||
txid: string,
|
||||
};
|
||||
|
||||
declare type PublishResponse = GenericTxResponse & {
|
||||
// Only first value in outputs is a claim
|
||||
// That's the only value we care about
|
||||
outputs: Array<Claim>,
|
||||
};
|
||||
|
||||
declare type ClaimSearchResponse = {
|
||||
items: Array<Claim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type ClaimListResponse = {
|
||||
items: Array<ChannelClaim | Claim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type ChannelCreateResponse = GenericTxResponse & {
|
||||
outputs: Array<ChannelClaim>,
|
||||
};
|
||||
|
||||
declare type ChannelUpdateResponse = GenericTxResponse & {
|
||||
outputs: Array<ChannelClaim>,
|
||||
};
|
||||
|
||||
declare type CommentCreateResponse = Comment;
|
||||
declare type CommentUpdateResponse = Comment;
|
||||
|
||||
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 = {
|
||||
// keyed by the CommentIds entered
|
||||
[string]: { hidden: boolean },
|
||||
};
|
||||
|
||||
declare type CommentPinResponse = {
|
||||
// keyed by the CommentIds entered
|
||||
items: Comment,
|
||||
};
|
||||
|
||||
declare type CommentAbandonResponse = {
|
||||
// keyed by the CommentId given
|
||||
abandoned: boolean,
|
||||
};
|
||||
|
||||
declare type ChannelListResponse = {
|
||||
items: Array<ChannelClaim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
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,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type TxListResponse = {
|
||||
items: Array<Transaction>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type SupportListResponse = {
|
||||
items: Array<Support>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type BlobListResponse = { items: Array<string> };
|
||||
|
||||
declare type WalletListResponse = Array<{
|
||||
id: string,
|
||||
name: string,
|
||||
}>;
|
||||
|
||||
declare type WalletStatusResponse = {
|
||||
is_encrypted: boolean,
|
||||
is_locked: boolean,
|
||||
is_syncing: boolean,
|
||||
};
|
||||
|
||||
declare type SyncApplyResponse = {
|
||||
hash: string,
|
||||
data: string,
|
||||
};
|
||||
|
||||
declare type SupportAbandonResponse = GenericTxResponse;
|
||||
|
||||
declare type StreamListResponse = {
|
||||
items: Array<StreamClaim>,
|
||||
page: number,
|
||||
page_size: number,
|
||||
total_items: number,
|
||||
total_pages: number,
|
||||
};
|
||||
|
||||
declare type StreamRepostOptions = {
|
||||
name: string,
|
||||
bid: string,
|
||||
claim_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
|
||||
//
|
||||
declare type LbryTypes = {
|
||||
isConnected: boolean,
|
||||
connectPromise: any, // null |
|
||||
connect: () => any, // void | Promise<any> ?
|
||||
daemonConnectionString: string,
|
||||
alternateConnectionString: string,
|
||||
methodsUsingAlternateConnectionString: Array<string>,
|
||||
apiRequestHeaders: { [key: string]: string },
|
||||
setDaemonConnectionString: string => void,
|
||||
setApiHeader: (string, string) => void,
|
||||
unsetApiHeader: string => void,
|
||||
overrides: { [string]: ?Function },
|
||||
setOverride: (string, Function) => void,
|
||||
// getMediaType: (?string, ?string) => string,
|
||||
|
||||
// Lbry Methods
|
||||
stop: () => Promise<string>,
|
||||
status: () => Promise<StatusResponse>,
|
||||
version: () => Promise<VersionResponse>,
|
||||
resolve: (params: {}) => Promise<ResolveResponse>,
|
||||
get: (params: {}) => Promise<GetResponse>,
|
||||
publish: (params: {}) => Promise<PublishResponse>,
|
||||
|
||||
claim_search: (params: {}) => Promise<ClaimSearchResponse>,
|
||||
claim_list: (params: {}) => Promise<ClaimListResponse>,
|
||||
channel_create: (params: {}) => Promise<ChannelCreateResponse>,
|
||||
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>,
|
||||
support_create: (params: {}) => Promise<GenericTxResponse>,
|
||||
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>,
|
||||
file_delete: (params: {}) => Promise<boolean>,
|
||||
blob_delete: (params: {}) => Promise<string>,
|
||||
blob_list: (params: {}) => Promise<BlobListResponse>,
|
||||
file_set_status: (params: {}) => Promise<any>,
|
||||
file_reflect: (params: {}) => Promise<any>,
|
||||
|
||||
// Preferences
|
||||
preference_get: (params?: {}) => Promise<any>,
|
||||
preference_set: (params: {}) => Promise<any>,
|
||||
|
||||
// Commenting
|
||||
comment_update: (params: {}) => Promise<CommentUpdateResponse>,
|
||||
comment_hide: (params: {}) => Promise<CommentHideResponse>,
|
||||
comment_abandon: (params: {}) => Promise<CommentAbandonResponse>,
|
||||
comment_list: (params: {}) => Promise<any>,
|
||||
comment_create: (params: {}) => Promise<any>,
|
||||
|
||||
// Wallet utilities
|
||||
wallet_balance: (params: {}) => Promise<BalanceResponse>,
|
||||
wallet_decrypt: (prams: {}) => Promise<boolean>,
|
||||
wallet_encrypt: (params: {}) => Promise<boolean>,
|
||||
wallet_unlock: (params: {}) => Promise<boolean>,
|
||||
wallet_list: (params: {}) => Promise<WalletListResponse>,
|
||||
wallet_send: (params: {}) => Promise<GenericTxResponse>,
|
||||
wallet_status: (params?: {}) => Promise<WalletStatusResponse>,
|
||||
address_is_mine: (params: {}) => Promise<boolean>,
|
||||
address_unused: (params: {}) => Promise<string>, // New address
|
||||
address_list: (params: {}) => Promise<string>,
|
||||
transaction_list: (params: {}) => Promise<TxListResponse>,
|
||||
txo_list: (params: {}) => Promise<any>,
|
||||
account_set: (params: {}) => Promise<any>,
|
||||
account_list: (params?: {}) => Promise<any>,
|
||||
|
||||
// Sync
|
||||
sync_hash: (params?: {}) => Promise<string>,
|
||||
sync_apply: (params: {}) => Promise<SyncApplyResponse>,
|
||||
// syncGet
|
||||
|
||||
// The app shouldn't need to do this
|
||||
utxo_release: () => Promise<any>,
|
||||
};
|
99
flow-typed/LbryFirst.js
vendored
Normal file
99
flow-typed/LbryFirst.js
vendored
Normal 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
5
flow-typed/Reflector.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
declare type ReflectingUpdate = {
|
||||
fileListItem: FileListItem,
|
||||
progress: number | boolean,
|
||||
stalled: boolean,
|
||||
};
|
21
flow-typed/Tags.js
vendored
Normal file
21
flow-typed/Tags.js
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
declare type TagState = {
|
||||
followedTags: FollowedTags,
|
||||
knownTags: KnownTags,
|
||||
};
|
||||
|
||||
declare type Tag = {
|
||||
name: string,
|
||||
};
|
||||
|
||||
declare type KnownTags = {
|
||||
[string]: Tag,
|
||||
};
|
||||
|
||||
declare type FollowedTags = Array<string>;
|
||||
|
||||
declare type TagAction = {
|
||||
type: string,
|
||||
data: {
|
||||
name: string,
|
||||
},
|
||||
};
|
28
flow-typed/Transaction.js
vendored
Normal file
28
flow-typed/Transaction.js
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
// @flow
|
||||
declare type Transaction = {
|
||||
amount: number,
|
||||
claim_id: string,
|
||||
claim_name: string,
|
||||
fee: number,
|
||||
nout: number,
|
||||
txid: string,
|
||||
type: string,
|
||||
date: Date,
|
||||
};
|
||||
|
||||
declare type Support = {
|
||||
address: string,
|
||||
amount: string,
|
||||
claim_id: string,
|
||||
confirmations: number,
|
||||
height: string,
|
||||
is_change: string,
|
||||
is_mine: string,
|
||||
name: string,
|
||||
normalized_name: string,
|
||||
nout: string,
|
||||
permanent_url: string,
|
||||
timestamp: number,
|
||||
txid: string,
|
||||
type: string,
|
||||
};
|
27
flow-typed/Txo.js
vendored
Normal file
27
flow-typed/Txo.js
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
declare type Txo = {
|
||||
amount: number,
|
||||
claim_id: string,
|
||||
normalized_name: string,
|
||||
nout: number,
|
||||
txid: string,
|
||||
type: string,
|
||||
value_type: string,
|
||||
timestamp: number,
|
||||
is_my_output: boolean,
|
||||
is_my_input: boolean,
|
||||
is_spent: boolean,
|
||||
signing_channel?: {
|
||||
channel_id: string,
|
||||
},
|
||||
};
|
||||
|
||||
declare type TxoListParams = {
|
||||
page: number,
|
||||
page_size: number,
|
||||
type: string,
|
||||
is_my_input?: boolean,
|
||||
is_my_output?: boolean,
|
||||
is_not_my_input?: boolean,
|
||||
is_not_my_output?: boolean,
|
||||
is_spent?: boolean,
|
||||
};
|
2
flow-typed/i18n.js
vendored
Normal file
2
flow-typed/i18n.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
// @flow
|
||||
declare function __(a: string, b?: {}): string;
|
21
flow-typed/lbryURI.js
vendored
Normal file
21
flow-typed/lbryURI.js
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// @flow
|
||||
declare type LbryUrlObj = {
|
||||
// Path and channel will always exist when calling parseURI
|
||||
// But they may not exist when code calls buildURI
|
||||
isChannel?: boolean,
|
||||
path?: string,
|
||||
streamName?: string,
|
||||
streamClaimId?: string,
|
||||
channelName?: string,
|
||||
channelClaimId?: string,
|
||||
primaryClaimSequence?: number,
|
||||
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,
|
||||
claimId?: string,
|
||||
contentName?: string,
|
||||
};
|
93
flow-typed/notification.js
vendored
93
flow-typed/notification.js
vendored
|
@ -1,4 +1,97 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
|
||||
/*
|
||||
Toasts:
|
||||
- First-in, first-out queue
|
||||
- Simple messages that are shown in response to user interactions
|
||||
- Never saved
|
||||
- If they are the result of errors, use the isError flag when creating
|
||||
- For errors that should interrupt user behavior, use Error
|
||||
*/
|
||||
declare type ToastParams = {
|
||||
message: string,
|
||||
title?: string,
|
||||
linkText?: string,
|
||||
linkTarget?: string,
|
||||
isError?: boolean,
|
||||
};
|
||||
|
||||
declare type Toast = {
|
||||
id: string,
|
||||
params: ToastParams,
|
||||
};
|
||||
|
||||
declare type DoToast = {
|
||||
type: ACTIONS.CREATE_TOAST,
|
||||
data: Toast,
|
||||
};
|
||||
|
||||
/*
|
||||
Notifications:
|
||||
- List of notifications based on user interactions/app notifications
|
||||
- Always saved, but can be manually deleted
|
||||
- Can happen in the background, or because of user interaction (ex: publish confirmed)
|
||||
*/
|
||||
declare type Notification = {
|
||||
id: string, // Unique id
|
||||
dateCreated: number,
|
||||
isRead: boolean, // Used to display "new" notifications that a user hasn't seen yet
|
||||
source?: string, // The type/area an notification is from. Used for sorting (ex: publishes, transactions)
|
||||
// We may want to use priority/isDismissed in the future to specify how urgent a notification is
|
||||
// and if the user should see it immediately
|
||||
// isDissmied: boolean,
|
||||
// priority?: number
|
||||
};
|
||||
|
||||
declare type DoNotification = {
|
||||
type: ACTIONS.CREATE_NOTIFICATION,
|
||||
data: Notification,
|
||||
};
|
||||
|
||||
declare type DoEditNotification = {
|
||||
type: ACTIONS.EDIT_NOTIFICATION,
|
||||
data: {
|
||||
notification: Notification,
|
||||
},
|
||||
};
|
||||
|
||||
declare type DoDeleteNotification = {
|
||||
type: ACTIONS.DELETE_NOTIFICATION,
|
||||
data: {
|
||||
id: string, // The id to delete
|
||||
},
|
||||
};
|
||||
|
||||
/*
|
||||
Errors:
|
||||
- First-in, first-out queue
|
||||
- Errors that should interupt user behavior
|
||||
- For errors that can be shown without interrupting a user, use Toast with the isError flag
|
||||
*/
|
||||
declare type ErrorNotification = {
|
||||
title: string,
|
||||
text: string,
|
||||
};
|
||||
|
||||
declare type DoError = {
|
||||
type: ACTIONS.CREATE_ERROR,
|
||||
data: ErrorNotification,
|
||||
};
|
||||
|
||||
declare type DoDismissError = {
|
||||
type: ACTIONS.DISMISS_ERROR,
|
||||
};
|
||||
|
||||
/*
|
||||
NotificationState
|
||||
*/
|
||||
declare type NotificationState = {
|
||||
notifications: Array<Notification>,
|
||||
errors: Array<ErrorNotification>,
|
||||
toasts: Array<Toast>,
|
||||
};
|
||||
|
||||
declare type WebNotification = {
|
||||
active_at: string,
|
||||
created_at: string,
|
||||
|
|
25
flow-typed/publish.js
vendored
25
flow-typed/publish.js
vendored
|
@ -25,7 +25,7 @@ declare type UpdatePublishFormData = {
|
|||
licenseType?: string,
|
||||
uri?: string,
|
||||
nsfw: boolean,
|
||||
isMarkdownPost: boolean,
|
||||
isMarkdownPost?: boolean,
|
||||
};
|
||||
|
||||
declare type PublishParams = {
|
||||
|
@ -52,3 +52,26 @@ declare type PublishParams = {
|
|||
nsfw: boolean,
|
||||
tags: Array<Tag>,
|
||||
};
|
||||
|
||||
declare type TusUploader = any;
|
||||
|
||||
declare type FileUploadSdkParams = {
|
||||
file_path: string,
|
||||
name: ?string,
|
||||
preview?: boolean,
|
||||
remote_url?: string,
|
||||
thumbnail_url?: string,
|
||||
title?: string,
|
||||
// Temporary values
|
||||
uploadUrl?: string,
|
||||
};
|
||||
|
||||
declare type FileUploadItem = {
|
||||
params: FileUploadSdkParams,
|
||||
file: File,
|
||||
fileFingerprint: string,
|
||||
progress: string,
|
||||
status?: string,
|
||||
uploader?: TusUploader | XMLHttpRequest,
|
||||
resumable: boolean,
|
||||
};
|
||||
|
|
3
flow-typed/redux.js
vendored
3
flow-typed/redux.js
vendored
|
@ -1,3 +1,6 @@
|
|||
// @flow
|
||||
|
||||
/* eslint-disable no-use-before-define */
|
||||
declare type GetState = () => any;
|
||||
declare type Dispatch = any;
|
||||
/* eslint-enable */
|
||||
|
|
16
package.json
16
package.json
|
@ -20,7 +20,6 @@
|
|||
},
|
||||
"main": "./dist/electron/main.js",
|
||||
"scripts": {
|
||||
"analyze": "source-map-explorer --only-mapped dist/electron/webpack/ui*.js --html dist/sourceMap.html",
|
||||
"compile:electron": "node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.electron.config.js",
|
||||
"compile:web": "yarn copyenv && cd web && node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.config.js",
|
||||
"compile": "cross-env NODE_ENV=production yarn compile:electron && cross-env NODE_ENV=production yarn compile:web",
|
||||
|
@ -38,17 +37,18 @@
|
|||
"build:dir": "yarn build -- --dir -c.compression=store -c.mac.identity=null",
|
||||
"crossenv": "./node_modules/cross-env/dist/bin/cross-env",
|
||||
"flow": "flow",
|
||||
"lint": "eslint 'ui/**/*.{js,jsx}' && eslint 'web/**/*.{js,jsx}' && eslint 'electron/**/*.js' && flow",
|
||||
"lint-fix": "eslint --fix --quiet 'ui/**/*.{js,jsx}' && eslint --fix --quiet 'web/**/*.{js,jsx}' && eslint --fix --quiet 'electron/**/*.js'",
|
||||
"lint": "eslint 'ui/**/*.{js,jsx}' && eslint 'extras/**/*.{js,jsx}' && eslint 'web/**/*.{js,jsx}' && eslint 'electron/**/*.js' && flow",
|
||||
"lint-fix": "eslint --fix --quiet 'ui/**/*.{js,jsx}' && eslint --fix --quiet 'extras/**/*.{js,jsx}' && eslint --fix --quiet 'web/**/*.{js,jsx}' && eslint --fix --quiet 'electron/**/*.js'",
|
||||
"format": "prettier 'src/**/*.{js,jsx,scss,json}' --write",
|
||||
"flow-defs": "flow-typed install",
|
||||
"precommit": "lint-staged",
|
||||
"preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc",
|
||||
"preinstall": "",
|
||||
"postinstall": "cd web && yarn && cd .. && if-env NODE_ENV=production && yarn postinstall:warning || if-env APP_ENV=web && echo 'Done installing deps' || yarn postinstall:electron",
|
||||
"postinstall:electron": "electron-builder install-app-deps && node ./build/downloadDaemon.js && node ./build/downloadLBRYFirst.js",
|
||||
"postinstall:warning": "echo '\n\nWARNING\n\nNot all node modules were installed because NODE_ENV is set to \"production\".\nThis should only be set after installing dependencies with \"yarn\". The app will not work.\n\n'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ungap/from-entries": "^0.2.1",
|
||||
"auto-launch": "^5.0.5",
|
||||
"electron-dl": "^1.11.0",
|
||||
"electron-log": "^2.2.12",
|
||||
|
@ -59,6 +59,9 @@
|
|||
"if-env": "^1.0.4",
|
||||
"match-sorter": "^6.3.0",
|
||||
"parse-duration": "^1.0.0",
|
||||
"player.js": "^0.1.0",
|
||||
"proxy-polyfill": "0.1.6",
|
||||
"re-reselect": "^4.0.0",
|
||||
"react-datetime-picker": "^3.2.1",
|
||||
"react-plastic": "^1.1.1",
|
||||
"react-top-loading-bar": "^2.0.1",
|
||||
|
@ -66,8 +69,10 @@
|
|||
"rss": "^1.2.2",
|
||||
"source-map-explorer": "^2.5.2",
|
||||
"tempy": "^0.6.0",
|
||||
"tus-js-client": "^2.3.0",
|
||||
"videojs-contrib-ads": "^6.9.0",
|
||||
"videojs-ima": "^1.11.0",
|
||||
"videojs-ima-player": "^0.5.6",
|
||||
"videojs-logo": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -84,7 +89,6 @@
|
|||
"@babel/preset-flow": "^7.12.1",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/register": "^7.0.0",
|
||||
"@datapunt/matomo-tracker-js": "^0.1.4",
|
||||
"@exponent/electron-cookies": "^2.0.0",
|
||||
"@hot-loader/react-dom": "^16.13",
|
||||
"@reach/auto-id": "^0.13.0",
|
||||
|
@ -157,8 +161,6 @@
|
|||
"imagesloaded": "^4.1.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#0f930c4a7bfc7f164e6b3c6044050c1bc73f6ab8",
|
||||
"lbryinc": "lbryio/lbryinc#0b4e41ef90d6347819dd3453f2f9398a5c1b4f36",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
"lodash-es": "^4.17.14",
|
||||
|
|
|
@ -55,19 +55,30 @@
|
|||
"Deposit": "Deposit",
|
||||
"Language": "Language",
|
||||
"English": "English",
|
||||
"Arabic": "Arabic",
|
||||
"Chinese": "Chinese",
|
||||
"Croatian": "Croatian",
|
||||
"Czech": "Czech",
|
||||
"Dutch": "Dutch",
|
||||
"French": "French",
|
||||
"German": "German",
|
||||
"Japanese": "Japanese",
|
||||
"Russian": "Russian",
|
||||
"Spanish": "Spanish",
|
||||
"Greek": "Greek",
|
||||
"Hindi": "Hindi",
|
||||
"Indonesian": "Indonesian",
|
||||
"Italian": "Italian",
|
||||
"Dutch": "Dutch",
|
||||
"Turkish": "Turkish",
|
||||
"Polish": "Polish",
|
||||
"Malay": "Malay",
|
||||
"Japanese": "Japanese",
|
||||
"Khmer": "Khmer",
|
||||
"Korean": "Korean",
|
||||
"Malay": "Malay",
|
||||
"Norwegian": "Norwegian",
|
||||
"Persian": "Persian",
|
||||
"Polish": "Polish",
|
||||
"Romanian": "Romanian",
|
||||
"Russian": "Russian",
|
||||
"Spanish": "Spanish",
|
||||
"Thai": "Thai",
|
||||
"Turkish": "Turkish",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"By continuing, you accept the %lbry_terms_of_service%.": "By continuing, you accept the %lbry_terms_of_service%.",
|
||||
"LBRY Terms of Service": "LBRY Terms of Service",
|
||||
"Enter a thumbnail URL": "Enter a thumbnail URL",
|
||||
|
@ -97,6 +108,7 @@
|
|||
"About --[tab title in Channel Page]--": "About",
|
||||
"About --[link title in Sidebar or Footer]--": "About",
|
||||
"Community Guidelines": "Community Guidelines",
|
||||
"FAQ and Support": "FAQ and Support",
|
||||
"Share Channel": "Share Channel",
|
||||
"Go to page:": "Go to page:",
|
||||
"Enter a URL for your thumbnail.": "Enter a URL for your thumbnail.",
|
||||
|
@ -110,7 +122,7 @@
|
|||
"Balance": "Balance",
|
||||
"Full History": "Full History",
|
||||
"Refresh": "Refresh",
|
||||
"Send LBRY Credits to your friends or favorite creators.": "Send LBRY Credits to your friends or favorite creators.",
|
||||
"Send Credits to your friends or favorite creators.": "Send Credits to your friends or favorite creators.",
|
||||
"Amount": "Amount",
|
||||
"Recipient address": "Recipient address",
|
||||
"Send": "Send",
|
||||
|
@ -172,10 +184,10 @@
|
|||
"Confirm External Resource": "Confirm External Resource",
|
||||
"Continue": "Continue",
|
||||
"This file has been shared with you by other people.": "This file has been shared with you by other people.",
|
||||
"LBRY Inc is not responsible for its content, click continue to proceed at your own risk.": "LBRY Inc is not responsible for its content, click continue to proceed at your own risk.",
|
||||
"Odysee is not responsible for its content, click continue to proceed at your own risk.": "Odysee is not responsible for its content, click continue to proceed at your own risk.",
|
||||
"Yes": "Yes",
|
||||
"No": "No",
|
||||
"These search results are provided by LBRY, Inc.": "These search results are provided by LBRY, Inc.",
|
||||
"These search results are provided by Odysee.": "These search results are provided by Odysee.",
|
||||
"View file": "View file",
|
||||
"Files": "Files",
|
||||
"Channels": "Channels",
|
||||
|
@ -238,9 +250,9 @@
|
|||
"release notes": "release notes",
|
||||
"Read the FAQ": "Read the FAQ",
|
||||
"Our FAQ answers many common questions.": "Our FAQ answers many common questions.",
|
||||
"Join Our Chat": "Join Our Chat",
|
||||
"Join the Foundation Chat": "Join the Foundation Chat",
|
||||
"Report a bug or suggest something": "Report a bug or suggest something",
|
||||
"Did you find something wrong? Think LBRY could add something useful and cool?": "Did you find something wrong? Think LBRY could add something useful and cool?",
|
||||
"Did you find something wrong? Think Odysee could add something useful and cool?": "Did you find something wrong? Think Odysee could add something useful and cool?",
|
||||
"View your log": "View your log",
|
||||
"support": "support",
|
||||
"Open Log": "Open Log",
|
||||
|
@ -323,7 +335,7 @@
|
|||
"Please wait for thumbnail to finish uploading": "Please wait for thumbnail to finish uploading",
|
||||
"A thumbnail is required. Please upload or provide an image URL above.": "A thumbnail is required. Please upload or provide an image URL above.",
|
||||
"Thumbnail is invalid.": "Thumbnail is invalid.",
|
||||
"Please reselect a file after changing the LBRY URL": "Please reselect a file after changing the LBRY URL",
|
||||
"Please reselect a file after changing the URL": "Please reselect a file after changing the URL",
|
||||
"API connection string": "API connection string",
|
||||
"Method": "Method",
|
||||
"Parameters": "Parameters",
|
||||
|
@ -331,7 +343,7 @@
|
|||
"Error message": "Error message",
|
||||
"Error data": "Error data",
|
||||
"Error": "Error",
|
||||
"We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.": "We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.",
|
||||
"We're sorry that Odysee has encountered an error. Please try again or reach out to hello@odysee.com with detailed information.": "We're sorry that Odysee has encountered an error. Please try again or reach out to hello@odysee.com with detailed information.",
|
||||
"Customize": "Customize",
|
||||
"Customize Your Homepage": "Customize Your Homepage",
|
||||
"Tags You Follow": "Tags You Follow",
|
||||
|
@ -378,7 +390,6 @@
|
|||
"Read more about why we do this.": "Read more about why we do this.",
|
||||
"Submit Phone Number": "Submit Phone Number",
|
||||
"Standard messaging rates apply. Having trouble?": "Standard messaging rates apply. Having trouble?",
|
||||
"Join LBRY Chat": "Join LBRY Chat",
|
||||
"Blockchain Sync": "Blockchain Sync",
|
||||
"No rewards available": "No rewards available",
|
||||
"You have claimed all available rewards! We're regularly adding more so be sure to check back later.": "You have claimed all available rewards! We're regularly adding more so be sure to check back later.",
|
||||
|
@ -392,8 +403,8 @@
|
|||
"These LBRY Credits remain yours. It is a deposit to reserve the name and can be undone at any time.": "These LBRY Credits remain yours. It is a deposit to reserve the name and can be undone at any time.",
|
||||
"Create channel": "Create channel",
|
||||
"Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.": "Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.",
|
||||
"Read the App Basics FAQ": "Read the App Basics FAQ",
|
||||
"View all LBRY FAQs": "View all LBRY FAQs",
|
||||
"Read Odysee Basics FAQ": "Read Odysee Basics FAQ",
|
||||
"View all Odysee FAQs": "View all Odysee FAQs",
|
||||
"Find assistance": "Find assistance",
|
||||
"Email Us": "Email Us",
|
||||
"Today": "Today",
|
||||
|
@ -468,24 +479,9 @@
|
|||
"New --[clears Publish Form]--": "New",
|
||||
"Loading": "Loading",
|
||||
"This file is in your library.": "This file is in your library.",
|
||||
"'claimName', 'channelName', and 'streamName' are all empty. One must be present to build a url.": "'claimName', 'channelName', and 'streamName' are all empty. One must be present to build a url.",
|
||||
"Invalid claim ID %s.": "Invalid claim ID %s.",
|
||||
"'claimId' should no longer be used. Use 'streamClaimId' or 'channelClaimId' instead": "'claimId' should no longer be used. Use 'streamClaimId' or 'channelClaimId' instead",
|
||||
"View Tag": "View Tag",
|
||||
"'claimName' should no longer be used. Use 'streamClaimName' or 'channelClaimName' instead": "'claimName' should no longer be used. Use 'streamClaimName' or 'channelClaimName' instead",
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Thai": "Thai",
|
||||
"Arabic": "Arabic",
|
||||
"Czech": "Czech",
|
||||
"Croatian": "Croatian",
|
||||
"Korean": "Korean",
|
||||
"Norwegian": "Norwegian",
|
||||
"Romanian": "Romanian",
|
||||
"Hindi": "Hindi",
|
||||
"Greek": "Greek",
|
||||
"Hide": "Hide",
|
||||
"Persian": "Persian",
|
||||
"'contentName' should no longer be used. Use 'streamName' instead": "'contentName' should no longer be used. Use 'streamName' instead",
|
||||
"Sorry, we can't preview this file.": "Sorry, we can't preview this file.",
|
||||
"View File": "View File",
|
||||
"Close": "Close",
|
||||
|
@ -517,7 +513,7 @@
|
|||
"Not enough Credits": "Not enough Credits",
|
||||
"Decrease amount to account for transaction fee": "Decrease amount to account for transaction fee",
|
||||
"You have %credit_amount% in unclaimed rewards.": "You have %credit_amount% in unclaimed rewards.",
|
||||
"In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this channel from our applications.": "In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this channel from our applications.",
|
||||
"In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this channel from our applications. Content may also be blocked due to DMCA Red Flag rules which are obvious copyright violations we come across, are discussed in public channels, or reported to us.": "In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this channel from our applications. Content may also be blocked due to DMCA Red Flag rules which are obvious copyright violations we come across, are discussed in public channels, or reported to us.",
|
||||
"Read More": "Read More",
|
||||
"Read more": "Read more",
|
||||
"Multi-language support is brand new and incomplete. Switching your language may have unintended consequences, like glossolalia.": "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences, like glossolalia.",
|
||||
|
@ -552,7 +548,7 @@
|
|||
"Your YouTube channel": "Your YouTube channel",
|
||||
"Your YouTube channels": "Your YouTube channels",
|
||||
"Your videos are currently being transferred. There is nothing else for you to do.": "Your videos are currently being transferred. There is nothing else for you to do.",
|
||||
"Please check back later. This may take up to 1 week.": "Please check back later. This may take up to 1 week.",
|
||||
"Please check back later, this may take a few hours.": "Please check back later, this may take a few hours.",
|
||||
"%channelName% is not yet ready to be transferred. Please allow up to one week, though it is frequently faster.": "%channelName% is not yet ready to be transferred. Please allow up to one week, though it is frequently faster.",
|
||||
"here": "here",
|
||||
"%channelName% is not ready to be transferred. You can check the status %statusLink% or check back later.": "%channelName% is not ready to be transferred. You can check the status %statusLink% or check back later.",
|
||||
|
@ -650,8 +646,8 @@
|
|||
"Failed to load %language% translations.": "Failed to load %language% translations.",
|
||||
"contact support": "contact support",
|
||||
"If you continue to have issues, please %support%.": "If you continue to have issues, please %support%.",
|
||||
"lbry.tv Account": "lbry.tv Account",
|
||||
"Creating a lbry.tv account will allow you to earn rewards, receive content and security updates, and optionally backup your data.": "Creating a lbry.tv account will allow you to earn rewards, receive content and security updates, and optionally backup your data.",
|
||||
"odysee.com Account": "odysee.com Account",
|
||||
"Creating a odysee.com account will allow you to earn rewards, receive content and security updates, and optionally backup your data.": "Creating a odysee.com account will allow you to earn rewards, receive content and security updates, and optionally backup your data.",
|
||||
"Paid content cannot be embedded": "Paid content cannot be embedded",
|
||||
"This content cannot be embedded": "This content cannot be embedded",
|
||||
"Your videos are ready to be transferred.": "Your videos are ready to be transferred.",
|
||||
|
@ -709,7 +705,6 @@
|
|||
"Use custom wallet servers": "Use custom wallet servers",
|
||||
"Remove custom wallet server": "Remove custom wallet server",
|
||||
"Add": "Add",
|
||||
"In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications.": "In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this content from our applications.",
|
||||
"lbry.tv": "lbry.tv",
|
||||
"Bid position must be a number.": "Bid position must be a number.",
|
||||
"Copy": "Copy",
|
||||
|
@ -825,9 +820,9 @@
|
|||
"Can this app send information about your usage to inform publishers and improve the software?": "Can this app send information about your usage to inform publishers and improve the software?",
|
||||
"Yes, including with third-party analytics platforms": "Yes, including with third-party analytics platforms",
|
||||
"Sending information to third parties (e.g. Google Analytics or Mixpanel) allows us to use detailed analytical reports to improve all aspects of LBRY.": "Sending information to third parties (e.g. Google Analytics or Mixpanel) allows us to use detailed analytical reports to improve all aspects of LBRY.",
|
||||
"Yes, but only with LBRY, Inc.": "Yes, but only with LBRY, Inc.",
|
||||
"Sharing information with LBRY, Inc. allows us to report to publishers how their content is doing, as well as track basic usage and performance. This is the minimum required to earn rewards from LBRY, Inc.": "Sharing information with LBRY, Inc. allows us to report to publishers how their content is doing, as well as track basic usage and performance. This is the minimum required to earn rewards from LBRY, Inc.",
|
||||
"No information will be sent directly to LBRY, Inc. or third-parties about your usage. Note that as peer-to-peer software, your IP address and potentially other system information can be sent to other users, though this information is not stored permanently.": "No information will be sent directly to LBRY, Inc. or third-parties about your usage. Note that as peer-to-peer software, your IP address and potentially other system information can be sent to other users, though this information is not stored permanently.",
|
||||
"Yes, but only with Odysee, Inc.": "Yes, but only with Odysee, Inc.",
|
||||
"Sharing information with Odysee, Inc. allows us to report to publishers how their content is doing, as well as track basic usage and performance. This is the minimum required to earn rewards from Odysee, Inc.": "Sharing information with Odysee, Inc. allows us to report to publishers how their content is doing, as well as track basic usage and performance. This is the minimum required to earn rewards from Odysee, Inc.",
|
||||
"No information will be sent directly to Odysee, Inc. or third-parties about your usage. Note that as peer-to-peer software, your IP address and potentially other system information can be sent to other users, though this information is not stored permanently.": "No information will be sent directly to Odysee, Inc. or third-parties about your usage. Note that as peer-to-peer software, your IP address and potentially other system information can be sent to other users, though this information is not stored permanently.",
|
||||
"Let's go": "Let's go",
|
||||
"Do you agree to the %terms%?": "Do you agree to the %terms%?",
|
||||
"While we respect the desire for maximally private usage, please note that choosing this option hurts the ability for creators to understand how their content is performing.": "While we respect the desire for maximally private usage, please note that choosing this option hurts the ability for creators to understand how their content is performing.",
|
||||
|
@ -872,7 +867,7 @@
|
|||
"Are you sure? Type %name% to confirm that you wish to remove the channel.": "Are you sure? Type %name% to confirm that you wish to remove the channel.",
|
||||
"This will permanently remove your channel. Content published under this channel will be orphaned.": "This will permanently remove your channel. Content published under this channel will be orphaned.",
|
||||
"Are you sure you'd like to remove \"%title%\"?": "Are you sure you'd like to remove \"%title%\"?",
|
||||
"You are signed into lbry.tv which automatically shares data with LBRY inc. %signout_button%.": "You are signed into lbry.tv which automatically shares data with LBRY inc. %signout_button%.",
|
||||
"You are signed into odysee.com which automatically shares data with LBRY inc. %signout_button%.": "You are signed into odysee.com which automatically shares data with LBRY inc. %signout_button%.",
|
||||
"%SITE_NAME% works better if you find and follow a couple creators you like. You can also block channels you never want to see.": "%SITE_NAME% works better if you find and follow a couple creators you like. You can also block channels you never want to see.",
|
||||
"Nice! You are currently following %followingCount% creator": "Nice! You are currently following %followingCount% creator",
|
||||
"Nice! You are currently following %followingCount% creators": "Nice! You are currently following %followingCount% creators",
|
||||
|
@ -919,6 +914,11 @@
|
|||
"Please decrease the amount to account for transaction fees": "Please decrease the amount to account for transaction fees",
|
||||
"Amount cannot be blank": "Amount cannot be blank",
|
||||
"Amount cannot be more than available": "Amount cannot be more than available",
|
||||
"Amount is lower than price of $%price_amount%": "Amount is lower than price of $%price_amount%",
|
||||
"Amount must have no more than 2 decimal places": "Amount must have no more than 2 decimal places",
|
||||
"Insufficient amount (%input_amount% Credits = %converted_amount% USD).": "Insufficient amount (%input_amount% Credits = %converted_amount% USD).",
|
||||
"This support is priced in $USD.": "This support is priced in $USD.",
|
||||
"The current exchange rate for the submitted LBC amount is ~ $%exchange_amount%.": "The current exchange rate for the submitted LBC amount is ~ $%exchange_amount%.",
|
||||
"Amount to unlock": "Amount to unlock",
|
||||
"Unlock tips": "Unlock tips",
|
||||
"Support This Claim": "Support This Claim",
|
||||
|
@ -1011,6 +1011,7 @@
|
|||
"You deposited %amount% LBRY Credits as a support!": "You deposited %amount% LBRY Credits as a support!",
|
||||
"You sent $%amount% as a tip to %tipChannelName%, I'm sure they appreciate it!": "You sent $%amount% as a tip to %tipChannelName%, I'm sure they appreciate it!",
|
||||
"You sent %tipAmount% LBRY Credits as a tip to %tipChannelName%, I'm sure they appreciate it!": "You sent %tipAmount% LBRY Credits as a tip to %tipChannelName%, I'm sure they appreciate it!",
|
||||
"You sent %lbc% as a tip, Mahalo!": "You sent %lbc% as a tip, Mahalo!",
|
||||
"You sent %amount% LBRY Credits as a tip, Mahalo!": "You sent %amount% LBRY Credits as a tip, Mahalo!",
|
||||
"You sent %amount% LBRY Credits": "You sent %amount% LBRY Credits",
|
||||
"No stats found": "No stats found",
|
||||
|
@ -1055,15 +1056,15 @@
|
|||
"Description of your issue or feature request": "Description of your issue or feature request",
|
||||
"Submitting...": "Submitting...",
|
||||
"Submit Report": "Submit Report",
|
||||
"Developer?": "Developer?",
|
||||
"Developer? Or looking for more?": "Developer? Or looking for more?",
|
||||
"You can also:": "You can also:",
|
||||
"Submit an issue on GitHub": "Submit an issue on GitHub",
|
||||
"Most viewed recent content": "Most viewed recent content",
|
||||
"Most viewed content all time": "Most viewed content all time",
|
||||
"There are no stats for this channel yet, it will take a few views. Make sure you are signed in with the correct email and have data sharing turned on.": "There are no stats for this channel yet, it will take a few views. Make sure you are signed in with the correct email and have data sharing turned on.",
|
||||
"Join our %tech_forum%": "Join our %tech_forum%",
|
||||
"Join LBRY's %tech_forum%": "Join LBRY's %tech_forum%",
|
||||
"tech forum": "tech forum",
|
||||
"Explore our %technical_resources%": "Explore our %technical_resources%",
|
||||
"Explore LBRY's %technical_resources%": "Explore LBRY's %technical_resources%",
|
||||
"technical resources": "technical resources",
|
||||
"Check your rewards page to see if you qualify for paid content reimbursement. Only content in this section qualifies.": "Check your rewards page to see if you qualify for paid content reimbursement. Only content in this section qualifies.",
|
||||
"Discover Channels": "Discover Channels",
|
||||
|
@ -1083,11 +1084,11 @@
|
|||
"Trending For Your Tags": "Trending For Your Tags",
|
||||
"Latest From @lbry": "Latest From @lbry",
|
||||
"Latest From @lbrycast": "Latest From @lbrycast",
|
||||
"Hate these? %log_in_to_domain% or %download_the_app% for an ad free experience.": "Hate these? %log_in_to_domain% or %download_the_app% for an ad free experience.",
|
||||
"Hate these? %log_in_to_domain% for an ad free experience.": "Hate these? %log_in_to_domain% for an ad free experience.",
|
||||
"Hate these? Login to Odysee for an ad free experience": "Hate these? Login to Odysee for an ad free experience",
|
||||
"Log in to %domain%": "Log in to %domain%",
|
||||
"download the app": "download the app",
|
||||
"lbry.tv collects usage information for itself only (%more_information%).": "lbry.tv collects usage information for itself only (%more_information%).",
|
||||
"lbry.tv collects usage information for itself only (%more_information%). Want control over this and more?": "lbry.tv collects usage information for itself only (%more_information%). Want control over this and more?",
|
||||
"odysee collects usage information for itself only (%more_information%).": "odysee collects usage information for itself only (%more_information%).",
|
||||
"odysee collects usage information for itself only (%more_information%). Want control over this and more?": "odysee collects usage information for itself only (%more_information%). Want control over this and more?",
|
||||
"more --[value for \"more_information\"]--": "more",
|
||||
"%DOMAIN% performance may be degraded. You can try to use it, or wait 5 minutes and refresh. Please no crush us.": "%DOMAIN% performance may be degraded. You can try to use it, or wait 5 minutes and refresh. Please no crush us.",
|
||||
"Please %sign_in_link% to comment.": "Please %sign_in_link% to comment.",
|
||||
|
@ -1111,7 +1112,7 @@
|
|||
"minute": "minute",
|
||||
"hours": "hours",
|
||||
"hour": "hour",
|
||||
"%SITE_NAME% uploads are limited to %limit% GB. Download the app for unrestricted publishing.": "%SITE_NAME% uploads are limited to %limit% GB. Download the app for unrestricted publishing.",
|
||||
"%SITE_NAME% uploads are limited to %limit% GB.": "%SITE_NAME% uploads are limited to %limit% GB.",
|
||||
"Connected": "Connected",
|
||||
"Not connected": "Not connected",
|
||||
"this link": "this link",
|
||||
|
@ -1293,8 +1294,19 @@
|
|||
"Select a file to upload": "Select a file to upload",
|
||||
"Select file to upload": "Select file to upload",
|
||||
"Url copied.": "Url copied.",
|
||||
"Failed to initiate upload (%err%)": "Failed to initiate upload (%err%)",
|
||||
"Invalid file": "Invalid file",
|
||||
"It appears to be a different or modified file.": "It appears to be a different or modified file.",
|
||||
"Please select the same file from the initial upload.": "Please select the same file from the initial upload.",
|
||||
"Cancel upload": "Cancel upload",
|
||||
"Cancel and remove the selected upload?": "Cancel and remove the selected upload?",
|
||||
"Select the file to resume upload...": "Select the file to resume upload...",
|
||||
"Stopped.": "Stopped.",
|
||||
"Resume": "Resume",
|
||||
"Retrying...": "Retrying...",
|
||||
"Uploading...": "Uploading...",
|
||||
"Creating...": "Creating...",
|
||||
"Stopped. Duplicate session detected.": "Stopped. Duplicate session detected.",
|
||||
"Use a URL": "Use a URL",
|
||||
"Edit Cover Image": "Edit Cover Image",
|
||||
"Cover Image": "Cover Image",
|
||||
|
@ -1400,6 +1412,7 @@
|
|||
"Cheese": "Cheese",
|
||||
"Cooking": "Cooking",
|
||||
"Big Hits": "Big Hits",
|
||||
"Education": "Education",
|
||||
"Enlightenment": "Enlightenment",
|
||||
"Gaming": "Gaming",
|
||||
"Game": "Game",
|
||||
|
@ -1410,6 +1423,7 @@
|
|||
"Movies": "Movies",
|
||||
"News": "News",
|
||||
"News & Politics": "News & Politics",
|
||||
"Pop Culture": "Pop Culture",
|
||||
"Finance 2.0": "Finance 2.0",
|
||||
"The Universe": "The Universe",
|
||||
"Wild West": "Wild West",
|
||||
|
@ -1432,7 +1446,6 @@
|
|||
"Log in to %SITE_NAME%": "Log in to %SITE_NAME%",
|
||||
"Log in": "Log in",
|
||||
"Not Yet": "Not Yet",
|
||||
"Preparing...": "Preparing...",
|
||||
"Confirm Upload": "Confirm Upload",
|
||||
"Confirm Edit": "Confirm Edit",
|
||||
"Create Livestream": "Create Livestream",
|
||||
|
@ -1642,7 +1655,6 @@
|
|||
"You have no lists! Create one from any playable content.": "You have no lists! Create one from any playable content.",
|
||||
"Pick": "Pick",
|
||||
"You have unpublished lists! %pick% one and publish it!": "You have unpublished lists! %pick% one and publish it!",
|
||||
"No Reposts Found": "No Reposts Found",
|
||||
"Publish Something": "Publish Something",
|
||||
"Repost Something": "Repost Something",
|
||||
"Watch on %SITE_NAME%": "Watch on %SITE_NAME%",
|
||||
|
@ -1653,6 +1665,7 @@
|
|||
"Most Supported": "Most Supported",
|
||||
"Search Results": "Search Results",
|
||||
"View All Results": "View All Results",
|
||||
"View All Playlists": "View All Playlists",
|
||||
"View competing uploads for %name%": "View competing uploads for %name%",
|
||||
"Homepage": "Homepage",
|
||||
"Currently winning": "Currently winning",
|
||||
|
@ -1663,7 +1676,7 @@
|
|||
"POWERED BY %lbry_link%": "POWERED BY %lbry_link%",
|
||||
"Learn more about LBRY Credits on %DOMAIN%": "Learn more about LBRY Credits on %DOMAIN%",
|
||||
"Results boosted by %lbc%": "Results boosted by %lbc%",
|
||||
"This will be visible in a few minutes.": "This will be visible in a few minutes.",
|
||||
"Uploaded image will be visible in a few minutes after you submit this form.": "Uploaded image will be visible in a few minutes after you submit this form.",
|
||||
"Turn on notifications": "Turn on notifications",
|
||||
"Turn off notifications": "Turn off notifications",
|
||||
"Notifications turned off for %channel%.": "Notifications turned off for %channel%.",
|
||||
|
@ -1699,7 +1712,7 @@
|
|||
"Enter a name or %domain% URL": "Enter a name or %domain% URL",
|
||||
"Winning amount: %amount%": "Winning amount: %amount%",
|
||||
"Download or get the app": "Download or get the app",
|
||||
"This content can be downloaded from lbry.tv, but not displayed. It will display in LBRY Desktop, an app for desktop computers.": "This content can be downloaded from lbry.tv, but not displayed. It will display in LBRY Desktop, an app for desktop computers.",
|
||||
"This content can be downloaded from odysee.com, but not displayed. It will display in LBRY Desktop, an app for desktop computers.": "This content can be downloaded from odysee.com, but not displayed. It will display in LBRY Desktop, an app for desktop computers.",
|
||||
"Repost Here": "Repost Here",
|
||||
"Publish Here": "Publish Here",
|
||||
"Close sidebar - hide channels you are following.": "Close sidebar - hide channels you are following.",
|
||||
|
@ -1832,7 +1845,7 @@
|
|||
"Only visible to you": "Only visible to you",
|
||||
"People who view this link will be redirected to your livestream. Make sure to use this for sharing so your title and thumbnail are displayed properly.": "People who view this link will be redirected to your livestream. Make sure to use this for sharing so your title and thumbnail are displayed properly.",
|
||||
"View livestream": "View livestream",
|
||||
"You need to upload your livestream details before you can go live. If you already created one in this channel, it should appear soon.": "You need to upload your livestream details before you can go live. If you already created one in this channel, it should appear soon.",
|
||||
"You need to upload your livestream details before you can go live. Please note: Replays must be published manually after your stream via the Update button on the livestream.": "You need to upload your livestream details before you can go live. Please note: Replays must be published manually after your stream via the Update button on the livestream.",
|
||||
"Create A Livestream": "Create A Livestream",
|
||||
"Create a Livestream by first submitting your livestream details and waiting for approval confirmation. This can be done well in advance and will take a few minutes.": "Create a Livestream by first submitting your livestream details and waiting for approval confirmation. This can be done well in advance and will take a few minutes.",
|
||||
"The livestream will not be visible on your channel page until you are live, but you can share the URL in advance.": "The livestream will not be visible on your channel page until you are live, but you can share the URL in advance.",
|
||||
|
@ -1886,6 +1899,7 @@
|
|||
"Alternative coins": "Alternative coins",
|
||||
"Failed to initiate swap.": "Failed to initiate swap.",
|
||||
"Failed to query swap status.": "Failed to query swap status.",
|
||||
"odysee.com is currently down": "odysee.com is currently down",
|
||||
"The system is currently down. Come back later.": "The system is currently down. Come back later.",
|
||||
"Unable to obtain exchange rate. Try again later.": "Unable to obtain exchange rate. Try again later.",
|
||||
"The amount needs to be higher": "The amount needs to be higher",
|
||||
|
@ -2022,7 +2036,8 @@
|
|||
"Replay video available": "Replay video available",
|
||||
"Check for Replays": "Check for Replays",
|
||||
"You can upload your own recording or select a replay when your stream is over": "You can upload your own recording or select a replay when your stream is over",
|
||||
"This channel isn't staking enough LBRY Credits for inline image previews.": "This channel isn't staking enough LBRY Credits for inline image previews.",
|
||||
"This channel isn't staking enough Credits for inline image previews.": "This channel isn't staking enough Credits for inline image previews.",
|
||||
"This channel isn't staking enough Credits for link previews.": "This channel isn't staking enough Credits for link previews.",
|
||||
"Latest": "Latest",
|
||||
"Channel Not Found": "Channel Not Found",
|
||||
"Channel not found": "Channel not found",
|
||||
|
@ -2163,6 +2178,8 @@
|
|||
"Bank Accounts": "Bank Accounts",
|
||||
"Connect a bank account to receive tips and compensation in your local currency.": "Connect a bank account to receive tips and compensation in your local currency.",
|
||||
"Payment Methods": "Payment Methods",
|
||||
"Total Received Tips": "Total Received Tips",
|
||||
"Withdrawn": "Withdrawn",
|
||||
"Incoming": "Incoming",
|
||||
"Outgoing": "Outgoing",
|
||||
"Credits --[transactions tab]--": "Credits",
|
||||
|
@ -2178,11 +2195,52 @@
|
|||
"Card Last 4": "Card Last 4",
|
||||
"Search blocked channel name": "Search blocked channel name",
|
||||
"Discuss": "Discuss",
|
||||
"Validating...": "Validating...",
|
||||
"[Removed]": "[Removed]",
|
||||
"lbry.tv has been retired. You have been magically transported to Odysee.com. %more%": "lbry.tv has been retired. You have been magically transported to Odysee.com. %more%",
|
||||
"Show more livestreams": "Show more livestreams",
|
||||
"Creator": "Creator",
|
||||
"From comments": "From comments",
|
||||
"From search": "From search",
|
||||
"Manage tags": "Manage tags",
|
||||
"Notification Delivery": "Notification Delivery",
|
||||
"Choose how you'd like to receive your Odysee notifications.": "Choose how you'd like to receive your Odysee notifications.",
|
||||
"Desktop Notifications": "Desktop Notifications",
|
||||
"Browser Notifications": "Browser Notifications",
|
||||
"Receive push notifications in this browser, even when you're not on odysee.com": "Receive push notifications in this browser, even when you're not on odysee.com",
|
||||
"Email Notification Topics": "Email Notification Topics",
|
||||
"Choose which topics you’d like to be emailed about.": "Choose which topics you’d like to be emailed about.",
|
||||
"Email Notifications": "Email Notifications",
|
||||
"Receive notifications to the email address: %email%": "Receive notifications to the email address: %email%",
|
||||
"Realtime push notifications straight to your browser.": "Realtime push notifications straight to your browser.",
|
||||
"Don't miss another notification again.": "Don't miss another notification again.",
|
||||
"Enable Push Notifications": "Enable Push Notifications",
|
||||
"Dismiss": "Dismiss",
|
||||
"Heads up: browser notifications are currently blocked in this browser.": "Heads up: browser notifications are currently blocked in this browser.",
|
||||
"To enable push notifications please configure your browser to allow notifications on odysee.com.": "To enable push notifications please configure your browser to allow notifications on odysee.com.",
|
||||
"Browser notifications aren't supported. Here's a few tips:": "Browser notifications aren't supported. Here's a few tips:",
|
||||
"Notifications aren't available when in incognito or private mode.": "Notifications aren't available when in incognito or private mode.",
|
||||
"On Firefox, notifications won't function if cookies are set to clear on browser close. Please disable or add an exception for Odysee, then refresh.": "On Firefox, notifications won't function if cookies are set to clear on browser close. Please disable or add an exception for Odysee, then refresh.",
|
||||
"For Brave, enable google push notifications in settings.": "For Brave, enable google push notifications in settings.",
|
||||
"Check browser settings to see if notifications are disabled or otherwise restricted.": "Check browser settings to see if notifications are disabled or otherwise restricted.",
|
||||
"Claiming credits...": "Claiming credits...",
|
||||
"Sending...": "Sending...",
|
||||
"Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8": "Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8",
|
||||
"Choose %asset%": "Choose %asset%",
|
||||
"Showing %filtered% results of %total%": "Showing %filtered% results of %total%",
|
||||
"Failed to synchronize settings. Wait a while before retrying.": "Failed to synchronize settings. Wait a while before retrying.",
|
||||
"You are offline. Check your internet connection.": "You are offline. Check your internet connection.",
|
||||
"A new version of Odysee is available.": "A new version of Odysee is available.",
|
||||
"Reset stream": "Reset stream",
|
||||
"Live stream successfully reset.": "Live stream successfully reset.",
|
||||
"There was an error resetting the live stream.": "There was an error resetting the live stream.",
|
||||
"If you're having trouble starting a stream or if your stream shows that you're live but aren't, try a reset. If the problem persists, please reach out at %SITE_HELP_EMAIL%.": "If you're having trouble starting a stream or if your stream shows that you're live but aren't, try a reset. If the problem persists, please reach out at %SITE_HELP_EMAIL%.",
|
||||
"Stickers": "Stickers",
|
||||
"Different Sticker": "Different Sticker",
|
||||
"Cash": "Cash",
|
||||
"Switch to Cash": "Switch to Cash",
|
||||
"Switch to Credits": "Switch to Credits",
|
||||
"Cookies": "Cookies",
|
||||
"Did someone invite you to use Odysee? Tell us who and you both get a reward!": "Did someone invite you to use Odysee? Tell us who and you both get a reward!",
|
||||
"--end--": "--end--"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html dir="ltr">
|
||||
<head>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BB8DNPB73F"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() {
|
||||
dataLayer.push(arguments);
|
||||
}
|
||||
gtag('consent', 'default', {
|
||||
ad_storage: 'denied',
|
||||
analytics_storage: 'denied',
|
||||
});
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-BB8DNPB73F');
|
||||
</script>
|
||||
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
|
@ -13,7 +28,7 @@
|
|||
<link rel="preload" href="/public/font/v1/700.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/public/font/v1/700i.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<link rel="shortcut icon" href="/public/favicon-spaceman.png">
|
||||
<link rel="shortcut icon" href="/public/favicon-spaceman.png" />
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
|
|
274
ui/analytics.js
274
ui/analytics.js
|
@ -1,15 +1,38 @@
|
|||
// @flow
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import MatomoTracker from '@datapunt/matomo-tracker-js';
|
||||
import { history } from './store';
|
||||
import { SDK_API_PATH } from './index';
|
||||
// @if TARGET='app'
|
||||
import Native from 'native';
|
||||
import ElectronCookies from '@exponent/electron-cookies';
|
||||
import { generateInitialUrl } from 'util/url';
|
||||
// @endif
|
||||
import { MATOMO_ID, MATOMO_URL } from 'config';
|
||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||
import { SDK_API_PATH } from 'config';
|
||||
|
||||
// --- GA ---
|
||||
// - Events: 500 max (cannot be deleted).
|
||||
// - Dimensions: 25 max (cannot be deleted, but can be "archived"). Usually
|
||||
// tied to an event parameter for reporting purposes.
|
||||
//
|
||||
// Given the limitations above, we need to plan ahead before adding new Events
|
||||
// and Parameters.
|
||||
//
|
||||
// Events:
|
||||
// - Find a Recommended Event that is closest to what you need.
|
||||
// https://support.google.com/analytics/answer/9267735?hl=en
|
||||
// - If doesn't exist, use a Custom Event.
|
||||
//
|
||||
// Parameters:
|
||||
// - Custom parameters don't appear in automated reports until they are tied to
|
||||
// a Dimension.
|
||||
// - Add your entry to GA_DIMENSIONS below -- tt allows us to keep track so that
|
||||
// we don't exceed the limit. Re-use existing parameters if possible.
|
||||
// - Register the Dimension in GA Console to make it visible in reports.
|
||||
|
||||
export const GA_DIMENSIONS = {
|
||||
TYPE: 'type',
|
||||
ACTION: 'action',
|
||||
VALUE: 'value',
|
||||
START_TIME_MS: 'start_time_ms',
|
||||
DURATION_MS: 'duration_ms',
|
||||
END_TIME_MS: 'end_time_ms',
|
||||
};
|
||||
|
||||
// import getConnectionSpeed from 'util/detect-user-bandwidth';
|
||||
|
||||
// let userDownloadBandwidthInBitsPerSecond;
|
||||
|
@ -24,31 +47,23 @@ const isProduction = process.env.NODE_ENV === 'production';
|
|||
const devInternalApis = process.env.LBRY_API_URL && process.env.LBRY_API_URL.includes('dev');
|
||||
|
||||
export const SHARE_INTERNAL = 'shareInternal';
|
||||
const SHARE_THIRD_PARTY = 'shareThirdParty';
|
||||
|
||||
const WATCHMAN_BACKEND_ENDPOINT = 'https://watchman.na-backend.odysee.com/reports/playback';
|
||||
const SEND_DATA_TO_WATCHMAN_INTERVAL = 10; // in seconds
|
||||
|
||||
// @if TARGET='app'
|
||||
if (isProduction) {
|
||||
ElectronCookies.enable({
|
||||
origin: 'https://lbry.tv',
|
||||
});
|
||||
}
|
||||
// @endif
|
||||
|
||||
type Analytics = {
|
||||
appStartTime: number,
|
||||
eventStartTime: any,
|
||||
error: (string) => Promise<any>,
|
||||
sentryError: ({} | string, {}) => Promise<any>,
|
||||
pageView: (string, ?string) => void,
|
||||
setUser: (Object) => void,
|
||||
toggleInternal: (boolean, ?boolean) => void,
|
||||
apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>,
|
||||
apiLogPublish: (ChannelClaim | StreamClaim) => void,
|
||||
apiSyncTags: ({}) => void,
|
||||
tagFollowEvent: (string, boolean, ?string) => void,
|
||||
playerLoadedEvent: (?boolean) => void,
|
||||
playerStartedEvent: (?boolean) => void,
|
||||
playerLoadedEvent: (string, ?boolean) => void,
|
||||
playerVideoStartedEvent: (?boolean) => void,
|
||||
videoStartEvent: (string, number, string, number, string, any, number) => void,
|
||||
videoIsPlaying: (boolean, any) => void,
|
||||
videoBufferEvent: (
|
||||
|
@ -64,15 +79,16 @@ type Analytics = {
|
|||
}
|
||||
) => Promise<any>,
|
||||
adsFetchedEvent: () => void,
|
||||
adsReceivedEvent: (any) => void,
|
||||
adsErrorEvent: (any) => void,
|
||||
emailProvidedEvent: () => void,
|
||||
emailVerifiedEvent: () => void,
|
||||
rewardEligibleEvent: () => void,
|
||||
startupEvent: () => void,
|
||||
initAppStartTime: (startTime: number) => void,
|
||||
startupEvent: (time: number) => void,
|
||||
eventStarted: (name: string, time: number, id?: string) => void,
|
||||
eventCompleted: (name: string, time: number, id?: string) => void,
|
||||
purchaseEvent: (number) => void,
|
||||
readyEvent: (number) => void,
|
||||
openUrlEvent: (string) => void,
|
||||
reportEvent: (string, any) => void,
|
||||
};
|
||||
|
||||
type LogPublishParams = {
|
||||
|
@ -84,10 +100,8 @@ type LogPublishParams = {
|
|||
|
||||
let internalAnalyticsEnabled: boolean = IS_WEB || false;
|
||||
// let thirdPartyAnalyticsEnabled: boolean = IS_WEB || false;
|
||||
// @if TARGET='app'
|
||||
if (window.localStorage.getItem(SHARE_INTERNAL) === 'true') internalAnalyticsEnabled = true;
|
||||
// if (window.localStorage.getItem(SHARE_THIRD_PARTY) === 'true') thirdPartyAnalyticsEnabled = true;
|
||||
// @endif
|
||||
|
||||
const isGaAllowed = internalAnalyticsEnabled && isProduction;
|
||||
|
||||
/**
|
||||
* Determine the mobile device type viewing the data
|
||||
|
@ -208,15 +222,14 @@ async function sendWatchmanData(body) {
|
|||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (err) {
|
||||
console.log('ERROR FROM WATCHMAN BACKEND');
|
||||
console.log(err);
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
const analytics: Analytics = {
|
||||
appStartTime: 0,
|
||||
eventStartTime: {},
|
||||
|
||||
// receive buffer events from tracking plugin and save buffer amounts and times for backend call
|
||||
videoBufferEvent: async (claim, data) => {
|
||||
amountOfBufferEvents = amountOfBufferEvents + 1;
|
||||
|
@ -255,7 +268,7 @@ const analytics: Analytics = {
|
|||
startWatchmanIntervalIfNotRunning();
|
||||
}
|
||||
},
|
||||
videoStartEvent: (claimId, duration, poweredBy, passedUserId, canonicalUrl, passedPlayer, videoBitrate) => {
|
||||
videoStartEvent: (claimId, timeToStartVideo, poweredBy, passedUserId, canonicalUrl, passedPlayer, videoBitrate) => {
|
||||
// populate values for watchman when video starts
|
||||
userId = passedUserId;
|
||||
claimUrl = canonicalUrl;
|
||||
|
@ -264,9 +277,7 @@ const analytics: Analytics = {
|
|||
videoType = passedPlayer.currentSource().type;
|
||||
videoPlayer = passedPlayer;
|
||||
bitrateAsBitsPerSecond = videoBitrate;
|
||||
|
||||
sendPromMetric('time_to_start', duration);
|
||||
sendMatomoEvent('Media', 'TimeToStart', claimId, duration);
|
||||
sendPromMetric('time_to_start', timeToStartVideo);
|
||||
},
|
||||
error: (message) => {
|
||||
return new Promise((resolve) => {
|
||||
|
@ -292,45 +303,17 @@ const analytics: Analytics = {
|
|||
}
|
||||
});
|
||||
},
|
||||
pageView: (path, search) => {
|
||||
if (internalAnalyticsEnabled) {
|
||||
const params: { href: string, customDimensions?: Array<{ id: number, value: ?string }> } = { href: `${path}` };
|
||||
const dimensions = [];
|
||||
const searchParams = search && new URLSearchParams(search);
|
||||
|
||||
if (searchParams && searchParams.get('src')) {
|
||||
dimensions.push({ id: 1, value: searchParams.get('src') });
|
||||
}
|
||||
if (dimensions.length) {
|
||||
params['customDimensions'] = dimensions;
|
||||
}
|
||||
MatomoInstance.trackPageView(params);
|
||||
}
|
||||
},
|
||||
setUser: (userId) => {
|
||||
if (internalAnalyticsEnabled && userId) {
|
||||
window._paq.push(['setUserId', String(userId)]);
|
||||
// @if TARGET='app'
|
||||
Native.getAppVersionInfo().then(({ localVersion }) => {
|
||||
sendMatomoEvent('Version', 'Desktop-Version', localVersion);
|
||||
});
|
||||
// @endif
|
||||
if (isGaAllowed && userId && window.gtag) {
|
||||
window.gtag('set', { user_id: userId });
|
||||
}
|
||||
},
|
||||
toggleInternal: (enabled: boolean): void => {
|
||||
// Always collect analytics on lbry.tv
|
||||
// @if TARGET='app'
|
||||
internalAnalyticsEnabled = enabled;
|
||||
window.localStorage.setItem(SHARE_INTERNAL, enabled);
|
||||
// @endif
|
||||
// Always collect analytics on Odysee for now.
|
||||
},
|
||||
|
||||
toggleThirdParty: (enabled: boolean): void => {
|
||||
// Always collect analytics on lbry.tv
|
||||
// @if TARGET='app'
|
||||
// thirdPartyAnalyticsEnabled = enabled;
|
||||
window.localStorage.setItem(SHARE_THIRD_PARTY, enabled);
|
||||
// @endif
|
||||
// Always collect analytics on Odysee for now.
|
||||
},
|
||||
|
||||
apiLogView: (uri, outpoint, claimId, timeToStart) => {
|
||||
|
@ -387,56 +370,110 @@ const analytics: Analytics = {
|
|||
}
|
||||
},
|
||||
adsFetchedEvent: () => {
|
||||
sendMatomoEvent('Media', 'AdsFetched');
|
||||
sendGaEvent('ad_fetched');
|
||||
},
|
||||
adsReceivedEvent: (response) => {
|
||||
sendMatomoEvent('Media', 'AdsReceived', JSON.stringify(response));
|
||||
playerLoadedEvent: (renderMode, embedded) => {
|
||||
const RENDER_MODE_TO_EVENT = (renderMode) => {
|
||||
switch (renderMode) {
|
||||
case RENDER_MODES.VIDEO:
|
||||
return 'loaded_video';
|
||||
case RENDER_MODES.AUDIO:
|
||||
return 'loaded_audio';
|
||||
case RENDER_MODES.MARKDOWN:
|
||||
return 'loaded_markdown';
|
||||
case RENDER_MODES.IMAGE:
|
||||
return 'loaded_image';
|
||||
case 'livestream':
|
||||
return 'loaded_livestream';
|
||||
default:
|
||||
return 'loaded_misc';
|
||||
}
|
||||
};
|
||||
|
||||
sendGaEvent('player', {
|
||||
[GA_DIMENSIONS.ACTION]: RENDER_MODE_TO_EVENT(renderMode),
|
||||
[GA_DIMENSIONS.TYPE]: embedded ? 'embedded' : 'onsite',
|
||||
});
|
||||
},
|
||||
adsErrorEvent: (response) => {
|
||||
sendMatomoEvent('Media', 'AdsError', JSON.stringify(response));
|
||||
},
|
||||
playerLoadedEvent: (embedded) => {
|
||||
sendMatomoEvent('Player', 'Loaded', embedded ? 'embedded' : 'onsite');
|
||||
},
|
||||
playerStartedEvent: (embedded) => {
|
||||
sendMatomoEvent('Player', 'Started', embedded ? 'embedded' : 'onsite');
|
||||
playerVideoStartedEvent: (embedded) => {
|
||||
sendGaEvent('player', {
|
||||
[GA_DIMENSIONS.ACTION]: 'started_video',
|
||||
[GA_DIMENSIONS.TYPE]: embedded ? 'embedded' : 'onsite',
|
||||
});
|
||||
},
|
||||
tagFollowEvent: (tag, following) => {
|
||||
sendMatomoEvent('Tag', following ? 'Tag-Follow' : 'Tag-Unfollow', tag);
|
||||
},
|
||||
channelBlockEvent: (uri, blocked, location) => {
|
||||
sendMatomoEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri);
|
||||
sendGaEvent('tags', {
|
||||
[GA_DIMENSIONS.ACTION]: following ? 'follow' : 'unfollow',
|
||||
[GA_DIMENSIONS.VALUE]: tag,
|
||||
});
|
||||
},
|
||||
emailProvidedEvent: () => {
|
||||
sendMatomoEvent('Engagement', 'Email-Provided');
|
||||
sendGaEvent('engagement', {
|
||||
[GA_DIMENSIONS.TYPE]: 'email_provided',
|
||||
});
|
||||
},
|
||||
emailVerifiedEvent: () => {
|
||||
sendMatomoEvent('Engagement', 'Email-Verified');
|
||||
sendGaEvent('engagement', {
|
||||
[GA_DIMENSIONS.TYPE]: 'email_verified',
|
||||
});
|
||||
},
|
||||
rewardEligibleEvent: () => {
|
||||
sendMatomoEvent('Engagement', 'Reward-Eligible');
|
||||
sendGaEvent('engagement', {
|
||||
[GA_DIMENSIONS.TYPE]: 'reward_eligible',
|
||||
});
|
||||
},
|
||||
openUrlEvent: (url: string) => {
|
||||
sendMatomoEvent('Engagement', 'Open-Url', url);
|
||||
sendGaEvent('engagement', {
|
||||
[GA_DIMENSIONS.TYPE]: 'open_url',
|
||||
url,
|
||||
});
|
||||
},
|
||||
trendingAlgorithmEvent: (trendingAlgorithm: string) => {
|
||||
sendMatomoEvent('Engagement', 'Trending-Algorithm', trendingAlgorithm);
|
||||
sendGaEvent('engagement', {
|
||||
[GA_DIMENSIONS.TYPE]: 'trending_algorithm',
|
||||
trending_algorithm: trendingAlgorithm,
|
||||
});
|
||||
},
|
||||
startupEvent: () => {
|
||||
sendMatomoEvent('Startup', 'Startup');
|
||||
initAppStartTime: (startTime: number) => {
|
||||
analytics.appStartTime = startTime;
|
||||
},
|
||||
readyEvent: (timeToReady: number) => {
|
||||
sendMatomoEvent('Startup', 'App-Ready', 'Time', timeToReady);
|
||||
startupEvent: (time: number) => {
|
||||
if (analytics.appStartTime !== 0) {
|
||||
sendGaEvent('diag_app_ready', {
|
||||
[GA_DIMENSIONS.DURATION_MS]: time - analytics.appStartTime,
|
||||
});
|
||||
}
|
||||
},
|
||||
eventStarted: (name: string, time: number, id?: string) => {
|
||||
const key = id || name;
|
||||
analytics.eventStartTime[key] = time;
|
||||
},
|
||||
eventCompleted: (name: string, time: number, id?: string) => {
|
||||
const key = id || name;
|
||||
if (analytics.eventStartTime[key]) {
|
||||
sendGaEvent(name, {
|
||||
[GA_DIMENSIONS.START_TIME_MS]: analytics.eventStartTime[key] - analytics.appStartTime,
|
||||
[GA_DIMENSIONS.DURATION_MS]: time - analytics.eventStartTime[key],
|
||||
[GA_DIMENSIONS.END_TIME_MS]: time - analytics.appStartTime,
|
||||
});
|
||||
|
||||
delete analytics.eventStartTime[key];
|
||||
}
|
||||
},
|
||||
purchaseEvent: (purchaseInt: number) => {
|
||||
sendMatomoEvent('Purchase', 'Purchase-Complete', 'someLabel', purchaseInt);
|
||||
sendGaEvent('purchase', {
|
||||
// https://developers.google.com/analytics/devguides/collection/ga4/reference/events#purchase
|
||||
[GA_DIMENSIONS.VALUE]: purchaseInt,
|
||||
});
|
||||
},
|
||||
reportEvent: (event: string, params?: { [string]: string | number }) => {
|
||||
sendGaEvent(event, params);
|
||||
},
|
||||
};
|
||||
|
||||
function sendMatomoEvent(category, action, name, value) {
|
||||
if (internalAnalyticsEnabled) {
|
||||
const event = { category, action, name, value };
|
||||
MatomoInstance.trackEvent(event);
|
||||
function sendGaEvent(event: string, params?: { [string]: string | number }) {
|
||||
if (isGaAllowed && window.gtag) {
|
||||
window.gtag('event', event, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -445,39 +482,16 @@ function sendPromMetric(name: string, value?: number) {
|
|||
let url = new URL(SDK_API_PATH + '/metric/ui');
|
||||
const params = { name: name, value: value ? value.toString() : '' };
|
||||
url.search = new URLSearchParams(params).toString();
|
||||
return fetch(url, { method: 'post' });
|
||||
return fetch(url, { method: 'post' }).catch(function (error) {});
|
||||
}
|
||||
}
|
||||
|
||||
const MatomoInstance = new MatomoTracker({
|
||||
urlBase: MATOMO_URL,
|
||||
siteId: MATOMO_ID, // optional, default value: `1`
|
||||
// heartBeat: { // optional, enabled by default
|
||||
// active: true, // optional, default value: true
|
||||
// seconds: 10 // optional, default value: `15
|
||||
// },
|
||||
// linkTracking: false // optional, default value: true
|
||||
});
|
||||
|
||||
// Manually call the first page view
|
||||
// React Router doesn't include this on `history.listen`
|
||||
// @if TARGET='web'
|
||||
analytics.pageView(window.location.pathname + window.location.search, window.location.search);
|
||||
// @endif
|
||||
|
||||
// @if TARGET='app'
|
||||
analytics.pageView(
|
||||
window.location.pathname.split('.html')[1] + window.location.search || generateInitialUrl(window.location.hash)
|
||||
);
|
||||
// @endif;
|
||||
|
||||
// Listen for url changes and report
|
||||
// This will include search queries
|
||||
history.listen((location) => {
|
||||
const { pathname, search } = location;
|
||||
|
||||
const page = `${pathname}${search}`;
|
||||
analytics.pageView(page, search);
|
||||
});
|
||||
// Activate
|
||||
if (isGaAllowed && window.gtag) {
|
||||
window.gtag('consent', 'update', {
|
||||
ad_storage: 'granted',
|
||||
analytics_storage: 'granted',
|
||||
});
|
||||
}
|
||||
|
||||
export default analytics;
|
||||
|
|
|
@ -1,8 +1,3 @@
|
|||
import { connect } from 'react-redux';
|
||||
import IframeReact from './view';
|
||||
|
||||
const select = state => ({});
|
||||
|
||||
const perform = () => ({});
|
||||
|
||||
export default connect(select, perform)(IframeReact);
|
||||
export default IframeReact;
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { connect } from 'react-redux';
|
||||
import AbandonedChannelPreview from './view';
|
||||
|
||||
const select = (state, props) => ({});
|
||||
|
||||
export default connect(select)(AbandonedChannelPreview);
|
||||
export default AbandonedChannelPreview;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import { parseURI } from 'lbry-redux';
|
||||
import { parseURI } from 'util/lbryURI';
|
||||
import ChannelBlockButton from 'component/channelBlockButton';
|
||||
import ChannelMuteButton from 'component/channelMuteButton';
|
||||
import SubscribeButton from 'component/subscribeButton';
|
||||
|
|
|
@ -1,39 +1,25 @@
|
|||
import { hot } from 'react-hot-loader/root';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectUploadCount } from 'lbryinc';
|
||||
import { selectGetSyncErrorMessage, selectSyncFatalError } from 'redux/selectors/sync';
|
||||
import { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user';
|
||||
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
||||
import {
|
||||
doFetchChannelListMine,
|
||||
doFetchCollectionListMine,
|
||||
SETTINGS,
|
||||
selectMyChannelUrls,
|
||||
doResolveUris,
|
||||
} from 'lbry-redux';
|
||||
import { doFetchChannelListMine, doFetchCollectionListMine, doResolveUris } from 'redux/actions/claims';
|
||||
import { selectMyChannelUrls } from 'redux/selectors/claims';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import {
|
||||
makeSelectClientSetting,
|
||||
selectLanguage,
|
||||
selectLoadedLanguages,
|
||||
selectThemePath,
|
||||
} from 'redux/selectors/settings';
|
||||
import { selectClientSetting, selectLanguage, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
|
||||
import {
|
||||
selectIsUpgradeAvailable,
|
||||
selectAutoUpdateDownloaded,
|
||||
selectModal,
|
||||
selectActiveChannelClaim,
|
||||
selectIsReloadRequired,
|
||||
} from 'redux/selectors/app';
|
||||
import { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings';
|
||||
import { selectUploadCount } from 'redux/selectors/publish';
|
||||
import { doSetLanguage } from 'redux/actions/settings';
|
||||
import { doSyncLoop } from 'redux/actions/sync';
|
||||
import {
|
||||
doDownloadUpgradeRequested,
|
||||
doSignIn,
|
||||
doGetAndPopulatePreferences,
|
||||
doSetActiveChannel,
|
||||
doSetIncognito,
|
||||
} from 'redux/actions/app';
|
||||
import { doDownloadUpgradeRequested, doSignIn, doSetActiveChannel, doSetIncognito } from 'redux/actions/app';
|
||||
import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/comments';
|
||||
import App from './view';
|
||||
|
||||
|
@ -42,10 +28,11 @@ const select = (state) => ({
|
|||
accessToken: selectAccessToken(state),
|
||||
theme: selectThemePath(state),
|
||||
language: selectLanguage(state),
|
||||
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state),
|
||||
syncEnabled: selectClientSetting(state, SETTINGS.ENABLE_SYNC),
|
||||
languages: selectLoadedLanguages(state),
|
||||
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
|
||||
isUpgradeAvailable: selectIsUpgradeAvailable(state),
|
||||
isReloadRequired: selectIsReloadRequired(state),
|
||||
syncError: selectGetSyncErrorMessage(state),
|
||||
uploadCount: selectUploadCount(state),
|
||||
rewards: selectUnclaimedRewards(state),
|
||||
|
@ -64,8 +51,6 @@ const perform = (dispatch) => ({
|
|||
setLanguage: (language) => dispatch(doSetLanguage(language)),
|
||||
signIn: () => dispatch(doSignIn()),
|
||||
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),
|
||||
updatePreferences: () => dispatch(doGetAndPopulatePreferences()),
|
||||
getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()),
|
||||
syncLoop: (noInterval) => dispatch(doSyncLoop(noInterval)),
|
||||
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
|
||||
setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()),
|
||||
|
|
|
@ -4,24 +4,24 @@ import React, { useEffect, useRef, useState, useLayoutEffect } from 'react';
|
|||
import { lazyImport } from 'util/lazyImport';
|
||||
import classnames from 'classnames';
|
||||
import analytics from 'analytics';
|
||||
import { buildURI, parseURI } from 'lbry-redux';
|
||||
import { setSearchUserId } from 'redux/actions/search';
|
||||
import { buildURI, parseURI } from 'util/lbryURI';
|
||||
import { SIMPLE_SITE } from 'config';
|
||||
import Router from 'component/router/index';
|
||||
import ModalRouter from 'modal/modalRouter';
|
||||
import ReactModal from 'react-modal';
|
||||
import { openContextMenu } from 'util/context-menu';
|
||||
import useKonamiListener from 'util/enhanced-layout';
|
||||
import Yrbl from 'component/yrbl';
|
||||
import FileRenderFloating from 'component/fileRenderFloating';
|
||||
import { withRouter } from 'react-router';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
import Nag from 'component/common/nag';
|
||||
import REWARDS from 'rewards';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import Spinner from 'component/spinner';
|
||||
import LANGUAGES from 'constants/languages';
|
||||
// @if TARGET='app'
|
||||
import useZoom from 'effects/use-zoom';
|
||||
import useHistoryNav from 'effects/use-history-nav';
|
||||
// @endif
|
||||
// @if TARGET='web'
|
||||
import YoutubeWelcome from 'web/component/youtubeReferralWelcome';
|
||||
import {
|
||||
useDegradedPerformance,
|
||||
STATUS_OK,
|
||||
|
@ -29,43 +29,26 @@ import {
|
|||
STATUS_FAILING,
|
||||
STATUS_DOWN,
|
||||
} from 'web/effects/use-degraded-performance';
|
||||
// @endif
|
||||
import LANGUAGE_MIGRATIONS from 'constants/language-migrations';
|
||||
|
||||
const FileDrop = lazyImport(() => import('component/fileDrop' /* webpackChunkName: "secondary" */));
|
||||
const ModalRouter = lazyImport(() => import('modal/modalRouter' /* webpackChunkName: "secondary" */));
|
||||
const Nag = lazyImport(() => import('component/common/nag' /* webpackChunkName: "secondary" */));
|
||||
const NagContinueFirstRun = lazyImport(() =>
|
||||
import('component/nagContinueFirstRun' /* webpackChunkName: "secondary" */)
|
||||
);
|
||||
const OpenInAppLink = lazyImport(() => import('web/component/openInAppLink' /* webpackChunkName: "secondary" */));
|
||||
|
||||
// @if TARGET='web'
|
||||
const NagDataCollection = lazyImport(() =>
|
||||
import('web/component/nag-data-collection' /* webpackChunkName: "secondary" */)
|
||||
);
|
||||
const FileDrop = lazyImport(() => import('component/fileDrop' /* webpackChunkName: "fileDrop" */));
|
||||
const NagContinueFirstRun = lazyImport(() => import('component/nagContinueFirstRun' /* webpackChunkName: "nagCFR" */));
|
||||
const OpenInAppLink = lazyImport(() => import('web/component/openInAppLink' /* webpackChunkName: "openInAppLink" */));
|
||||
const NagDataCollection = lazyImport(() => import('web/component/nag-data-collection' /* webpackChunkName: "nagDC" */));
|
||||
const NagDegradedPerformance = lazyImport(() =>
|
||||
import('web/component/nag-degraded-performance' /* webpackChunkName: "secondary" */)
|
||||
import('web/component/nag-degraded-performance' /* webpackChunkName: "NagDegradedPerformance" */)
|
||||
);
|
||||
const NagNoUser = lazyImport(() => import('web/component/nag-no-user' /* webpackChunkName: "nag-no-user" */));
|
||||
const NagSunset = lazyImport(() => import('web/component/nag-sunset' /* webpackChunkName: "nag-no-user" */));
|
||||
const YoutubeWelcome = lazyImport(() =>
|
||||
import('web/component/youtubeReferralWelcome' /* webpackChunkName: "secondary" */)
|
||||
);
|
||||
|
||||
// @endif
|
||||
|
||||
const NagSunset = lazyImport(() => import('web/component/nag-sunset' /* webpackChunkName: "nag-sunset" */));
|
||||
const SyncFatalError = lazyImport(() => import('component/syncFatalError' /* webpackChunkName: "syncFatalError" */));
|
||||
const Yrbl = lazyImport(() => import('component/yrbl' /* webpackChunkName: "yrbl" */));
|
||||
|
||||
// ****************************************************************************
|
||||
|
||||
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
|
||||
export const IS_MAC = navigator.userAgent.indexOf('Mac OS X') !== -1;
|
||||
|
||||
// button numbers pulled from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
|
||||
const MOUSE_BACK_BTN = 3;
|
||||
const MOUSE_FORWARD_BTN = 4;
|
||||
const imaLibraryPath = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js';
|
||||
const securePrivacyScriptUrl = 'https://app.secureprivacy.ai/script/6194129b66262906dd4a5f43.js';
|
||||
|
||||
type Props = {
|
||||
language: string,
|
||||
|
@ -73,13 +56,7 @@ type Props = {
|
|||
theme: string,
|
||||
user: ?{ id: string, has_verified_email: boolean, is_reward_approved: boolean },
|
||||
location: { pathname: string, hash: string, search: string },
|
||||
history: {
|
||||
goBack: () => void,
|
||||
goForward: () => void,
|
||||
index: number,
|
||||
length: number,
|
||||
push: (string) => void,
|
||||
},
|
||||
history: { push: (string) => void },
|
||||
fetchAccessToken: () => void,
|
||||
fetchChannelListMine: () => void,
|
||||
fetchCollectionListMine: () => void,
|
||||
|
@ -88,9 +65,8 @@ type Props = {
|
|||
onSignedIn: () => void,
|
||||
setLanguage: (string) => void,
|
||||
isUpgradeAvailable: boolean,
|
||||
isReloadRequired: boolean,
|
||||
autoUpdateDownloaded: boolean,
|
||||
updatePreferences: () => Promise<any>,
|
||||
getWalletSyncPref: () => Promise<any>,
|
||||
uploadCount: number,
|
||||
balance: ?number,
|
||||
syncError: ?string,
|
||||
|
@ -122,6 +98,7 @@ function App(props: Props) {
|
|||
signIn,
|
||||
autoUpdateDownloaded,
|
||||
isUpgradeAvailable,
|
||||
isReloadRequired,
|
||||
requestDownloadUpgrade,
|
||||
uploadCount,
|
||||
history,
|
||||
|
@ -129,8 +106,6 @@ function App(props: Props) {
|
|||
language,
|
||||
languages,
|
||||
setLanguage,
|
||||
updatePreferences,
|
||||
getWalletSyncPref,
|
||||
rewards,
|
||||
setReferrer,
|
||||
isAuthenticated,
|
||||
|
@ -150,19 +125,18 @@ function App(props: Props) {
|
|||
const appRef = useRef();
|
||||
const isEnhancedLayout = useKonamiListener();
|
||||
const [hasSignedIn, setHasSignedIn] = useState(false);
|
||||
const [readyForSync, setReadyForSync] = useState(false);
|
||||
const [readyForPrefs, setReadyForPrefs] = useState(false);
|
||||
const hasVerifiedEmail = user && Boolean(user.has_verified_email);
|
||||
const isRewardApproved = user && user.is_reward_approved;
|
||||
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
|
||||
const previousRewardApproved = usePrevious(isRewardApproved);
|
||||
// @if TARGET='web'
|
||||
|
||||
const [showAnalyticsNag, setShowAnalyticsNag] = usePersistedState('analytics-nag', true);
|
||||
const [lbryTvApiStatus, setLbryTvApiStatus] = useState(STATUS_OK);
|
||||
// @endif
|
||||
|
||||
const { pathname, hash, search } = props.location;
|
||||
const [upgradeNagClosed, setUpgradeNagClosed] = useState(false);
|
||||
const [resolvedSubscriptions, setResolvedSubscriptions] = useState(false);
|
||||
const [retryingSync, setRetryingSync] = useState(false);
|
||||
const [sidebarOpen] = usePersistedState('sidebar', true);
|
||||
const [seenSunsestMessage, setSeenSunsetMessage] = usePersistedState('lbrytv-sunset', false);
|
||||
const showUpgradeButton =
|
||||
|
@ -182,6 +156,7 @@ function App(props: Props) {
|
|||
const hasActiveChannelClaim = activeChannelClaim !== undefined;
|
||||
const isPersonalized = !IS_WEB || hasVerifiedEmail;
|
||||
const renderFiledrop = !IS_WEB || isAuthenticated;
|
||||
const isOnline = navigator.onLine;
|
||||
|
||||
let uri;
|
||||
try {
|
||||
|
@ -189,15 +164,58 @@ function App(props: Props) {
|
|||
uri = newpath + hash;
|
||||
} catch (e) {}
|
||||
|
||||
// @if TARGET='web'
|
||||
function handleAnalyticsDismiss() {
|
||||
setShowAnalyticsNag(false);
|
||||
}
|
||||
|
||||
// @endif
|
||||
function getStatusNag() {
|
||||
// Handle "offline" first. Everything else is meaningless if it's offline.
|
||||
if (!isOnline) {
|
||||
return <Nag type="helpful" message={__('You are offline. Check your internet connection.')} />;
|
||||
}
|
||||
|
||||
// Only 1 nag is possible, so show the most important:
|
||||
|
||||
if (user === null) {
|
||||
return <NagNoUser />;
|
||||
}
|
||||
|
||||
if (lbryTvApiStatus === STATUS_DEGRADED || lbryTvApiStatus === STATUS_FAILING) {
|
||||
if (!shouldHideNag) {
|
||||
return <NagDegradedPerformance onClose={() => setLbryTvApiStatus(STATUS_OK)} />;
|
||||
}
|
||||
}
|
||||
|
||||
if (syncFatalError) {
|
||||
if (!retryingSync) {
|
||||
return (
|
||||
<Nag
|
||||
type="error"
|
||||
message={__('Failed to synchronize settings. Wait a while before retrying.')}
|
||||
actionText={__('Retry')}
|
||||
onClick={() => {
|
||||
syncLoop(true);
|
||||
setRetryingSync(true);
|
||||
setTimeout(() => setRetryingSync(false), 4000);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else if (isReloadRequired) {
|
||||
return (
|
||||
<Nag
|
||||
message={__('A new version of Odysee is available.')}
|
||||
actionText={__('Refresh')}
|
||||
onClick={() => window.location.reload()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (userId) {
|
||||
analytics.setUser(userId);
|
||||
setSearchUserId(userId);
|
||||
}
|
||||
}, [userId]);
|
||||
|
||||
|
@ -211,22 +229,6 @@ function App(props: Props) {
|
|||
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
}, [uploadCount]);
|
||||
|
||||
// allows user to navigate history using the forward and backward buttons on a mouse
|
||||
useEffect(() => {
|
||||
const handleForwardAndBackButtons = (e) => {
|
||||
switch (e.button) {
|
||||
case MOUSE_BACK_BTN:
|
||||
history.index > 0 && history.goBack();
|
||||
break;
|
||||
case MOUSE_FORWARD_BTN:
|
||||
history.index < history.length - 1 && history.goForward();
|
||||
break;
|
||||
}
|
||||
};
|
||||
window.addEventListener('mouseup', handleForwardAndBackButtons);
|
||||
return () => window.removeEventListener('mouseup', handleForwardAndBackButtons);
|
||||
});
|
||||
|
||||
// allows user to pause miniplayer using the spacebar without the page scrolling down
|
||||
useEffect(() => {
|
||||
const handleKeyPress = (e) => {
|
||||
|
@ -238,16 +240,6 @@ function App(props: Props) {
|
|||
return () => window.removeEventListener('keydown', handleKeyPress);
|
||||
}, []);
|
||||
|
||||
// Enable ctrl +/- zooming on Desktop.
|
||||
// @if TARGET='app'
|
||||
useZoom();
|
||||
// @endif
|
||||
|
||||
// Enable 'Alt + Left/Right' for history navigation on Desktop.
|
||||
// @if TARGET='app'
|
||||
useHistoryNav(history);
|
||||
// @endif
|
||||
|
||||
useEffect(() => {
|
||||
if (referredRewardAvailable && sanitizedReferrerParam && isRewardApproved) {
|
||||
setReferrer(sanitizedReferrerParam, true);
|
||||
|
@ -325,61 +317,103 @@ function App(props: Props) {
|
|||
}
|
||||
}, [previousRewardApproved, isRewardApproved]);
|
||||
|
||||
// Load IMA3 SDK for aniview: DISABLED FOR NOW
|
||||
// @if TARGET='web'
|
||||
// useEffect(() => {
|
||||
// if (ENABLE_PREROLL_ADS) {
|
||||
// const script = document.createElement('script');
|
||||
// script.src = `https://imasdk.googleapis.com/js/sdkloader/ima3.js`;
|
||||
// script.async = true;
|
||||
// // $FlowFixMe
|
||||
// document.body.appendChild(script);
|
||||
// return () => {
|
||||
// // $FlowFixMe
|
||||
// document.body.removeChild(script);
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
// @endif
|
||||
|
||||
// @if TARGET='app'
|
||||
// Load IMA3 SDK for aniview
|
||||
useEffect(() => {
|
||||
if (updatePreferences && getWalletSyncPref && readyForPrefs) {
|
||||
getWalletSyncPref()
|
||||
.then(() => updatePreferences())
|
||||
.then(() => {
|
||||
setReadyForSync(true);
|
||||
});
|
||||
const script = document.createElement('script');
|
||||
script.src = imaLibraryPath;
|
||||
script.async = true;
|
||||
// $FlowFixMe
|
||||
document.body.appendChild(script);
|
||||
return () => {
|
||||
// $FlowFixMe
|
||||
document.body.removeChild(script);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// add secure privacy script
|
||||
useEffect(() => {
|
||||
function inIframe() {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
}, [updatePreferences, getWalletSyncPref, setReadyForSync, readyForPrefs, hasVerifiedEmail]);
|
||||
// @endif
|
||||
}
|
||||
|
||||
if (inIframe()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = securePrivacyScriptUrl;
|
||||
script.async = true;
|
||||
// might use this for future checking to prevent doubleloading
|
||||
script.id = 'securePrivacy';
|
||||
|
||||
const cmpScript = document.createElement('script');
|
||||
cmpScript.src = 'https://app.secureprivacy.ai/secureprivacy-plugin/web-plugin/cmp/cmp-v2.js';
|
||||
cmpScript.async = true;
|
||||
|
||||
const getLocaleEndpoint = 'https://api.odysee.com/locale/get';
|
||||
let gdprRequired;
|
||||
try {
|
||||
gdprRequired = localStorage.getItem('gdprRequired');
|
||||
} catch (err) {
|
||||
if (err) return;
|
||||
}
|
||||
// gdpr is known to be required, add script
|
||||
if (gdprRequired === 'true') {
|
||||
// $FlowFixMe
|
||||
document.head.appendChild(script);
|
||||
// $FlowFixMe
|
||||
document.head.appendChild(cmpScript);
|
||||
}
|
||||
|
||||
// haven't done a gdpr check, do it now
|
||||
if (gdprRequired === null) {
|
||||
(async function () {
|
||||
const response = await fetch(getLocaleEndpoint);
|
||||
const json = await response.json();
|
||||
const gdprRequiredBasedOnLocation = json.data.gdpr_required;
|
||||
// note we need gdpr and load script
|
||||
if (gdprRequiredBasedOnLocation) {
|
||||
localStorage.setItem('gdprRequired', 'true');
|
||||
// $FlowFixMe
|
||||
document.head.appendChild(script);
|
||||
// $FlowFixMe
|
||||
document.head.appendChild(cmpScript);
|
||||
// note we don't need gdpr, save to session
|
||||
} else if (gdprRequiredBasedOnLocation === false) {
|
||||
localStorage.setItem('gdprRequired', 'false');
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
return () => {
|
||||
try {
|
||||
// $FlowFixMe
|
||||
document.head.removeChild(script);
|
||||
// $FlowFixMe
|
||||
document.head.removeChild(cmpScript);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// ready for sync syncs, however after signin when hasVerifiedEmail, that syncs too.
|
||||
useEffect(() => {
|
||||
// signInSyncPref is cleared after sharedState loop.
|
||||
const syncLoopWithoutInterval = () => syncLoop(true);
|
||||
if (readyForSync && hasVerifiedEmail) {
|
||||
if (hasSignedIn && hasVerifiedEmail) {
|
||||
// In case we are syncing.
|
||||
syncLoop();
|
||||
// @if TARGET='web'
|
||||
window.addEventListener('focus', syncLoopWithoutInterval);
|
||||
// @endif
|
||||
}
|
||||
// @if TARGET='web'
|
||||
return () => {
|
||||
window.removeEventListener('focus', syncLoopWithoutInterval);
|
||||
};
|
||||
// @endif
|
||||
}, [readyForSync, hasVerifiedEmail, syncLoop]);
|
||||
|
||||
// We know someone is logging in or not when we get their user object
|
||||
// We'll use this to determine when it's time to pull preferences
|
||||
// This will no longer work if desktop users no longer get a user object from lbryinc
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
setReadyForPrefs(true);
|
||||
}
|
||||
}, [user, setReadyForPrefs]);
|
||||
}, [hasSignedIn, hasVerifiedEmail, syncLoop]);
|
||||
|
||||
useEffect(() => {
|
||||
if (syncError && isAuthenticated && !pathname.includes(PAGES.AUTH_WALLET_PASSWORD) && !currentModal) {
|
||||
|
@ -393,7 +427,6 @@ function App(props: Props) {
|
|||
if (!hasSignedIn && hasVerifiedEmail) {
|
||||
signIn();
|
||||
setHasSignedIn(true);
|
||||
if (IS_WEB) setReadyForSync(true);
|
||||
}
|
||||
}, [hasVerifiedEmail, signIn, hasSignedIn]);
|
||||
|
||||
|
@ -407,11 +440,8 @@ function App(props: Props) {
|
|||
}
|
||||
}, [sidebarOpen, isPersonalized, resolvedSubscriptions, subscriptions, resolveUris, setResolvedSubscriptions]);
|
||||
|
||||
// @if TARGET='web'
|
||||
useDegradedPerformance(setLbryTvApiStatus, user);
|
||||
// @endif
|
||||
|
||||
// @if TARGET='web'
|
||||
// Require an internal-api user on lbry.tv
|
||||
// This also prevents the site from loading in the un-authed state while we wait for internal-apis to return for the first time
|
||||
// It's not needed on desktop since there is no un-authed state
|
||||
|
@ -422,16 +452,12 @@ function App(props: Props) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
// @endif
|
||||
|
||||
if (syncFatalError) {
|
||||
if (isOnline && lbryTvApiStatus === STATUS_DOWN) {
|
||||
// TODO: Rename `SyncFatalError` since it has nothing to do with syncing.
|
||||
return (
|
||||
<React.Suspense fallback={null}>
|
||||
<SyncFatalError
|
||||
// @if TARGET='web'
|
||||
lbryTvApiStatus={lbryTvApiStatus}
|
||||
// @endif
|
||||
/>
|
||||
<SyncFatalError lbryTvApiStatus={lbryTvApiStatus} />
|
||||
</React.Suspense>
|
||||
);
|
||||
}
|
||||
|
@ -448,20 +474,16 @@ function App(props: Props) {
|
|||
onContextMenu={IS_WEB ? undefined : (e) => openContextMenu(e)}
|
||||
>
|
||||
{IS_WEB && lbryTvApiStatus === STATUS_DOWN ? (
|
||||
<React.Suspense fallback={null}>
|
||||
<Yrbl
|
||||
className="main--empty"
|
||||
title={__('lbry.tv is currently down')}
|
||||
title={__('odysee.com is currently down')}
|
||||
subtitle={__('My wheel broke, but the good news is that someone from LBRY is working on it.')}
|
||||
/>
|
||||
</React.Suspense>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Router />
|
||||
<React.Suspense fallback={null}>
|
||||
<ModalRouter />
|
||||
{renderFiledrop && <FileDrop />}
|
||||
</React.Suspense>
|
||||
<React.Suspense fallback={null}>{renderFiledrop && <FileDrop />}</React.Suspense>
|
||||
<FileRenderFloating />
|
||||
<React.Suspense fallback={null}>
|
||||
{isEnhancedLayout && <Yrbl className="yrbl--enhanced" />}
|
||||
|
@ -477,21 +499,16 @@ function App(props: Props) {
|
|||
)}
|
||||
{/* @endif */}
|
||||
|
||||
{/* @if TARGET='web' */}
|
||||
<YoutubeWelcome />
|
||||
{!SIMPLE_SITE && !shouldHideNag && <OpenInAppLink uri={uri} />}
|
||||
{!shouldHideNag && <NagContinueFirstRun />}
|
||||
{fromLbrytvParam && !seenSunsestMessage && !shouldHideNag && (
|
||||
<NagSunset email={hasVerifiedEmail} onClose={() => setSeenSunsetMessage(true)} />
|
||||
)}
|
||||
{(lbryTvApiStatus === STATUS_DEGRADED || lbryTvApiStatus === STATUS_FAILING) && !shouldHideNag && (
|
||||
<NagDegradedPerformance onClose={() => setLbryTvApiStatus(STATUS_OK)} />
|
||||
)}
|
||||
{!SIMPLE_SITE && lbryTvApiStatus === STATUS_OK && showAnalyticsNag && !shouldHideNag && (
|
||||
<NagDataCollection onClose={handleAnalyticsDismiss} />
|
||||
)}
|
||||
{user === null && <NagNoUser />}
|
||||
{/* @endif */}
|
||||
{getStatusNag()}
|
||||
</React.Suspense>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { withRouter } from 'react-router';
|
||||
import AutoplayCountdown from './view';
|
||||
import { selectModal } from 'redux/selectors/app';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectMetadataItemForUri, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectMetadataItemForUri, makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import ChannelAbout from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
|
@ -94,7 +94,7 @@ function ChannelAbout(props: Props) {
|
|||
<div className="media__info-text media__info-text--constrained">{claim.claim_id}</div>
|
||||
</div>
|
||||
|
||||
<label>{__('Staked LBRY Credits')}</label>
|
||||
<label>{__('Staked Credits')}</label>
|
||||
<div className="media__info-text">
|
||||
<CreditAmount
|
||||
badge={false}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimIdForUri } from 'lbry-redux';
|
||||
import { selectClaimIdForUri } from 'redux/selectors/claims';
|
||||
import {
|
||||
doCommentModUnBlock,
|
||||
doCommentModBlock,
|
||||
|
@ -43,7 +43,7 @@ const select = (state, props) => {
|
|||
isBlocked,
|
||||
isToggling,
|
||||
isBlockingOrUnBlocking: makeSelectUriIsBlockingOrUnBlocking(props.uri)(state),
|
||||
creatorId: makeSelectClaimIdForUri(props.creatorUri)(state),
|
||||
creatorId: selectClaimIdForUri(state, props.creatorUri),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -11,11 +11,11 @@ type Props = {
|
|||
isBlockingOrUnBlocking: boolean,
|
||||
isToggling: boolean,
|
||||
doCommentModUnBlock: (string, boolean) => void,
|
||||
doCommentModBlock: (string, ?Number, boolean) => void,
|
||||
doCommentModBlock: (string, ?string, ?Number, boolean) => void,
|
||||
doCommentModUnBlockAsAdmin: (string, string) => void,
|
||||
doCommentModBlockAsAdmin: (string, string) => void,
|
||||
doCommentModBlockAsAdmin: (string, ?string, ?string) => void,
|
||||
doCommentModUnBlockAsModerator: (string, string, string) => void,
|
||||
doCommentModBlockAsModerator: (string, string, string) => void,
|
||||
doCommentModBlockAsModerator: (string, ?string, string, ?string) => void,
|
||||
};
|
||||
|
||||
function ChannelBlockButton(props: Props) {
|
||||
|
@ -41,7 +41,7 @@ function ChannelBlockButton(props: Props) {
|
|||
if (isBlocked) {
|
||||
doCommentModUnBlock(uri, false);
|
||||
} else {
|
||||
doCommentModBlock(uri, undefined, false);
|
||||
doCommentModBlock(uri, undefined, undefined, false);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -50,7 +50,7 @@ function ChannelBlockButton(props: Props) {
|
|||
if (isBlocked) {
|
||||
doCommentModUnBlockAsModerator(uri, creatorUri, '');
|
||||
} else {
|
||||
doCommentModBlockAsModerator(uri, creatorUri, '');
|
||||
doCommentModBlockAsModerator(uri, undefined, creatorUri, undefined);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -59,7 +59,7 @@ function ChannelBlockButton(props: Props) {
|
|||
if (isBlocked) {
|
||||
doCommentModUnBlockAsAdmin(uri, '');
|
||||
} else {
|
||||
doCommentModBlockAsAdmin(uri, '');
|
||||
doCommentModBlockAsAdmin(uri, undefined, undefined);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -3,16 +3,16 @@ import { PAGE_SIZE } from 'constants/claim';
|
|||
import {
|
||||
makeSelectClaimsInChannelForPage,
|
||||
makeSelectFetchingChannelClaims,
|
||||
makeSelectClaimIsMine,
|
||||
selectClaimIsMine,
|
||||
makeSelectTotalPagesInChannelSearch,
|
||||
makeSelectClaimForUri,
|
||||
doResolveUris,
|
||||
SETTINGS,
|
||||
} from 'lbry-redux';
|
||||
selectClaimForUri,
|
||||
} from 'redux/selectors/claims';
|
||||
import { doResolveUris } from 'redux/actions/claims';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
|
||||
import { withRouter } from 'react-router';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { makeSelectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
|
||||
import { selectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
|
||||
|
||||
import ChannelContent from './view';
|
||||
|
||||
|
@ -20,16 +20,18 @@ const select = (state, props) => {
|
|||
const { search } = props.location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const page = urlParams.get('page') || 0;
|
||||
const claim = props.uri && selectClaimForUri(state, props.uri);
|
||||
|
||||
return {
|
||||
pageOfClaimsInChannel: makeSelectClaimsInChannelForPage(props.uri, page)(state),
|
||||
fetching: makeSelectFetchingChannelClaims(props.uri)(state),
|
||||
totalPages: makeSelectTotalPagesInChannelSearch(props.uri, PAGE_SIZE)(state),
|
||||
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
channelIsMine: selectClaimIsMine(state, claim),
|
||||
channelIsBlocked: makeSelectChannelIsMuted(props.uri)(state),
|
||||
claim: props.uri && makeSelectClaimForUri(props.uri)(state),
|
||||
claim,
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
showMature: selectShowMatureContent(state),
|
||||
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
|
||||
tileLayout: selectClientSetting(state, SETTINGS.TILE_LAYOUT),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -125,11 +125,11 @@ function ChannelContent(props: Props) {
|
|||
<section className="card card--section">
|
||||
<p>
|
||||
{__(
|
||||
'In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this channel from our applications.'
|
||||
'In response to a complaint we received under the US Digital Millennium Copyright Act, we have blocked access to this channel from our applications. Content may also be blocked due to DMCA Red Flag rules which are obvious copyright violations we come across, are discussed in public channels, or reported to us.'
|
||||
)}
|
||||
</p>
|
||||
<div className="section__actions">
|
||||
<Button button="link" href="https://lbry.com/faq/dmca" label={__('Read More')} />
|
||||
<Button button="link" href="https://odysee.com/@OdyseeHelp:b/copyright:f" label={__('Read More')} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
|||
import { withRouter } from 'react-router';
|
||||
import { DISABLE_COMMENTS_TAG } from 'constants/tags';
|
||||
import ChannelDiscussion from './view';
|
||||
import { makeSelectTagInClaimOrChannelForUri } from 'lbry-redux';
|
||||
import { makeSelectTagInClaimOrChannelForUri } from 'redux/selectors/claims';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { search } = props.location;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import CommentsList from 'component/commentsList';
|
||||
import Empty from 'component/common/empty';
|
||||
import { lazyImport } from 'util/lazyImport';
|
||||
|
||||
const CommentsList = lazyImport(() => import('component/commentsList' /* webpackChunkName: "comments" */));
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
|
@ -17,7 +19,9 @@ function ChannelDiscussion(props: Props) {
|
|||
}
|
||||
return (
|
||||
<section className="section">
|
||||
<React.Suspense fallback={null}>
|
||||
<CommentsList uri={uri} linkedCommentId={linkedCommentId} commentsAreExpanded />
|
||||
</React.Suspense>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
makeSelectTitleForUri,
|
||||
makeSelectThumbnailForUri,
|
||||
selectTitleForUri,
|
||||
selectThumbnailForUri,
|
||||
makeSelectCoverForUri,
|
||||
makeSelectMetadataItemForUri,
|
||||
doUpdateChannel,
|
||||
doCreateChannel,
|
||||
makeSelectAmountForUri,
|
||||
makeSelectClaimForUri,
|
||||
selectUpdateChannelError,
|
||||
selectUpdatingChannel,
|
||||
selectCreateChannelError,
|
||||
selectCreatingChannel,
|
||||
selectBalance,
|
||||
doClearChannelErrors,
|
||||
} from 'lbry-redux';
|
||||
} from 'redux/selectors/claims';
|
||||
import { selectBalance } from 'redux/selectors/wallet';
|
||||
import { doUpdateChannel, doCreateChannel, doClearChannelErrors } from 'redux/actions/claims';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { doUpdateBlockListForPublishedChannel } from 'redux/actions/comments';
|
||||
import { doClaimInitialRewards } from 'redux/actions/rewards';
|
||||
|
@ -23,8 +21,8 @@ import ChannelForm from './view';
|
|||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
thumbnailUrl: makeSelectThumbnailForUri(props.uri)(state),
|
||||
title: selectTitleForUri(state, props.uri),
|
||||
thumbnailUrl: selectThumbnailForUri(state, props.uri),
|
||||
coverUrl: makeSelectCoverForUri(props.uri)(state),
|
||||
description: makeSelectMetadataItemForUri(props.uri, 'description')(state),
|
||||
website: makeSelectMetadataItemForUri(props.uri, 'website_url')(state),
|
||||
|
|
|
@ -9,7 +9,7 @@ import TagsSearch from 'component/tagsSearch';
|
|||
import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field';
|
||||
import ErrorText from 'component/common/error-text';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import { isNameValid, parseURI } from 'lbry-redux';
|
||||
import { isNameValid, parseURI } from 'util/lbryURI';
|
||||
import ClaimAbandonButton from 'component/claimAbandonButton';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR, ESTIMATED_FEE } from 'constants/claim';
|
||||
|
@ -115,23 +115,11 @@ function ChannelForm(props: Props) {
|
|||
isClaimingInitialRewards ||
|
||||
creatingChannel ||
|
||||
updatingChannel ||
|
||||
nameError ||
|
||||
thumbError ||
|
||||
coverError ||
|
||||
bidError ||
|
||||
(isNewChannel && !params.name)
|
||||
);
|
||||
}, [
|
||||
isClaimingInitialRewards,
|
||||
creatingChannel,
|
||||
updatingChannel,
|
||||
nameError,
|
||||
thumbError,
|
||||
coverError,
|
||||
bidError,
|
||||
isNewChannel,
|
||||
params.name,
|
||||
]);
|
||||
}, [isClaimingInitialRewards, creatingChannel, updatingChannel, nameError, bidError, isNewChannel, params.name]);
|
||||
|
||||
function getChannelParams() {
|
||||
// fill this in with sdk data
|
||||
|
@ -253,7 +241,7 @@ function ChannelForm(props: Props) {
|
|||
let nameError;
|
||||
if (!name && name !== undefined) {
|
||||
nameError = __('A name is required for your url');
|
||||
} else if (!isNameValid(name, false)) {
|
||||
} else if (!isNameValid(name)) {
|
||||
nameError = INVALID_NAME_ERROR;
|
||||
}
|
||||
|
||||
|
@ -305,7 +293,9 @@ function ChannelForm(props: Props) {
|
|||
</div>
|
||||
{params.coverUrl &&
|
||||
(coverError && isUpload.cover ? (
|
||||
<div className="channel-cover__custom--waiting">{__('This will be visible in a few minutes.')}</div>
|
||||
<div className="channel-cover__custom--waiting">
|
||||
<p>{__('Uploaded image will be visible in a few minutes after you submit this form.')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<img className="channel-cover__custom" src={coverSrc} onError={() => setCoverError(true)} />
|
||||
))}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, makeSelectIsUriResolving } from 'lbry-redux';
|
||||
import { selectClaimForUri, selectIsUriResolving } from 'redux/selectors/claims';
|
||||
import ChannelMentionSuggestion from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
|
||||
claim: selectClaimForUri(state, props.uri),
|
||||
isResolvingUri: selectIsUriResolving(state, props.uri),
|
||||
});
|
||||
|
||||
export default connect(select)(ChannelMentionSuggestion);
|
||||
|
|
|
@ -1,34 +1,25 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { MAX_LIVESTREAM_COMMENTS } from 'constants/livestream';
|
||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import { selectSubscriptionUris } from 'redux/selectors/subscriptions';
|
||||
import { withRouter } from 'react-router';
|
||||
import { doResolveUris, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectTopLevelCommentsForUri } from 'redux/selectors/comments';
|
||||
import { selectCanonicalUrlForUri } from 'redux/selectors/claims';
|
||||
import { doResolveUris } from 'redux/actions/claims';
|
||||
import { selectChannelMentionData } from 'redux/selectors/livestream';
|
||||
import ChannelMentionSuggestions from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
const subscriptionUris = selectSubscriptions(state).map(({ uri }) => uri);
|
||||
const topLevelComments = makeSelectTopLevelCommentsForUri(props.uri)(state);
|
||||
|
||||
const commentorUris = [];
|
||||
// Avoid repeated commentors
|
||||
topLevelComments.map(({ channel_url }) => !commentorUris.includes(channel_url) && commentorUris.push(channel_url));
|
||||
|
||||
const getUnresolved = (uris) =>
|
||||
uris.map((uri) => !makeSelectClaimForUri(uri)(state) && uri).filter((uri) => uri !== false);
|
||||
const getCanonical = (uris) =>
|
||||
uris
|
||||
.map((uri) => makeSelectClaimForUri(uri)(state) && makeSelectClaimForUri(uri)(state).canonical_url)
|
||||
.filter((uri) => Boolean(uri));
|
||||
const maxComments = props.isLivestream ? MAX_LIVESTREAM_COMMENTS : -1;
|
||||
const data = selectChannelMentionData(state, props.uri, maxComments);
|
||||
|
||||
return {
|
||||
commentorUris,
|
||||
subscriptionUris,
|
||||
unresolvedCommentors: getUnresolved(commentorUris),
|
||||
unresolvedSubscriptions: getUnresolved(subscriptionUris),
|
||||
canonicalCreator: getCanonical([props.creatorUri])[0],
|
||||
canonicalCommentors: getCanonical(commentorUris),
|
||||
canonicalSubscriptions: getCanonical(subscriptionUris),
|
||||
commentorUris: data.commentorUris,
|
||||
subscriptionUris: selectSubscriptionUris(state),
|
||||
unresolvedCommentors: data.unresolvedCommentors,
|
||||
unresolvedSubscriptions: data.unresolvedSubscriptions,
|
||||
canonicalCreator: selectCanonicalUrlForUri(state, props.creatorUri),
|
||||
canonicalCommentors: data.canonicalCommentors,
|
||||
canonicalSubscriptions: data.canonicalSubscriptions,
|
||||
showMature: selectShowMatureContent(state),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList } from '@reach/combobox';
|
||||
import { Form } from 'component/common/form';
|
||||
import { parseURI, regexInvalidURI } from 'lbry-redux';
|
||||
import { parseURI, regexInvalidURI } from 'util/lbryURI';
|
||||
import { SEARCH_OPTIONS } from 'constants/search';
|
||||
import * as KEYCODES from 'constants/keycodes';
|
||||
import ChannelMentionSuggestion from 'component/channelMentionSuggestion';
|
||||
|
@ -69,8 +69,9 @@ export default function ChannelMentionSuggestions(props: Props) {
|
|||
const allShownCanonical = [canonicalCreator, ...canonicalSubscriptions, ...canonicalCommentors];
|
||||
const possibleMatches = allShownUris.filter((uri) => {
|
||||
try {
|
||||
// yuck a try catch in a filter?
|
||||
const { channelName } = parseURI(uri);
|
||||
return channelName.toLowerCase().includes(termToMatch);
|
||||
return channelName && channelName.toLowerCase().includes(termToMatch);
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectIsUriResolving, doResolveUri } from 'lbry-redux';
|
||||
import { selectIsUriResolving } from 'redux/selectors/claims';
|
||||
import { doResolveUri } from 'redux/actions/claims';
|
||||
import { makeSelectWinningUriForQuery } from 'redux/selectors/search';
|
||||
import ChannelMentionTopSuggestion from './view';
|
||||
|
||||
|
@ -7,7 +8,7 @@ const select = (state, props) => {
|
|||
const uriFromQuery = `lbry://${props.query}`;
|
||||
return {
|
||||
uriFromQuery,
|
||||
isResolvingUri: makeSelectIsUriResolving(uriFromQuery)(state),
|
||||
isResolvingUri: selectIsUriResolving(state, uriFromQuery),
|
||||
winningUri: makeSelectWinningUriForQuery(props.query)(state),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectMyChannelClaims } from 'lbry-redux';
|
||||
import { selectMyChannelClaims } from 'redux/selectors/claims';
|
||||
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
|
||||
import { doSetActiveChannel, doSetIncognito } from 'redux/actions/app';
|
||||
import ChannelSelector from './view';
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
makeSelectClaimForUri,
|
||||
makeSelectStakedLevelForChannelUri,
|
||||
makeSelectTotalStakedAmountForChannelUri,
|
||||
} from 'lbry-redux';
|
||||
selectClaimForUri,
|
||||
selectStakedLevelForChannelUri,
|
||||
selectTotalStakedAmountForChannelUri,
|
||||
} from 'redux/selectors/claims';
|
||||
import ChannelStakedIndicator from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
channelClaim: makeSelectClaimForUri(props.uri)(state),
|
||||
amount: makeSelectTotalStakedAmountForChannelUri(props.uri)(state),
|
||||
level: makeSelectStakedLevelForChannelUri(props.uri)(state),
|
||||
channelClaim: selectClaimForUri(state, props.uri),
|
||||
amount: selectTotalStakedAmountForChannelUri(state, props.uri),
|
||||
level: selectStakedLevelForChannelUri(state, props.uri),
|
||||
});
|
||||
|
||||
export default connect(select)(ChannelStakedIndicator);
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectThumbnailForUri, doResolveUri, makeSelectClaimForUri, makeSelectIsUriResolving } from 'lbry-redux';
|
||||
import { selectThumbnailForUri, selectClaimForUri, selectIsUriResolving } from 'redux/selectors/claims';
|
||||
import { doResolveUri } from 'redux/actions/claims';
|
||||
import ChannelThumbnail from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
isResolving: makeSelectIsUriResolving(props.uri)(state),
|
||||
thumbnail: selectThumbnailForUri(state, props.uri),
|
||||
claim: selectClaimForUri(state, props.uri),
|
||||
isResolving: selectIsUriResolving(state, props.uri),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { parseURI } from 'lbry-redux';
|
||||
import { parseURI } from 'util/lbryURI';
|
||||
import classnames from 'classnames';
|
||||
import Gerbil from './gerbil.png';
|
||||
import FreezeframeWrapper from 'component/fileThumbnail/FreezeframeWrapper';
|
||||
|
@ -10,7 +10,7 @@ import { AVATAR_DEFAULT } from 'config';
|
|||
|
||||
type Props = {
|
||||
thumbnail: ?string,
|
||||
uri: ?string,
|
||||
uri: string,
|
||||
className?: string,
|
||||
thumbnailPreview: ?string,
|
||||
obscure?: boolean,
|
||||
|
@ -49,7 +49,7 @@ function ChannelThumbnail(props: Props) {
|
|||
ThumbUploadError,
|
||||
} = props;
|
||||
const [thumbLoadError, setThumbLoadError] = React.useState(ThumbUploadError);
|
||||
const shouldResolve = claim === undefined;
|
||||
const shouldResolve = !isResolving && claim === undefined;
|
||||
const thumbnail = rawThumbnail && rawThumbnail.trim().replace(/^http:\/\//i, 'https://');
|
||||
const thumbnailPreview = rawThumbnailPreview && rawThumbnailPreview.trim().replace(/^http:\/\//i, 'https://');
|
||||
const defaultAvatar = AVATAR_DEFAULT || Gerbil;
|
||||
|
@ -92,7 +92,9 @@ function ChannelThumbnail(props: Props) {
|
|||
})}
|
||||
>
|
||||
{showDelayedMessage ? (
|
||||
<div className="channel-thumbnail--waiting">{__('This will be visible in a few minutes.')}</div>
|
||||
<div className="channel-thumbnail--waiting">
|
||||
{__('This will be visible in a few minutes after you submit this form.')}
|
||||
</div>
|
||||
) : (
|
||||
<OptimizedImage
|
||||
alt={__('Channel profile picture')}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, makeSelectTitleForUri } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri, selectTitleForUri } from 'redux/selectors/claims';
|
||||
import ChannelTitle from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
title: selectTitleForUri(state, props.uri),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import { connect } from 'react-redux';
|
|||
import { doOpenModal } from 'redux/actions/app';
|
||||
import ClaimAbandonButton from './view';
|
||||
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: props.uri && makeSelectClaimForUri(props.uri)(state),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectChannelForClaimUri } from 'lbry-redux';
|
||||
import { makeSelectChannelForClaimUri } from 'redux/selectors/claims';
|
||||
import ClaimAuthor from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
|
@ -2,12 +2,12 @@ import { connect } from 'react-redux';
|
|||
import ClaimCollectionAdd from './view';
|
||||
import { withRouter } from 'react-router';
|
||||
import {
|
||||
makeSelectClaimForUri,
|
||||
doLocalCollectionCreate,
|
||||
selectBuiltinCollections,
|
||||
selectMyPublishedCollections,
|
||||
selectMyUnpublishedCollections,
|
||||
} from 'lbry-redux';
|
||||
} from 'redux/selectors/collections';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { doLocalCollectionCreate } from 'redux/actions/collections';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import CollectionAddButton from './view';
|
||||
import { makeSelectClaimForUri, makeSelectClaimUrlInCollection } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import { makeSelectClaimUrlInCollection } from 'redux/selectors/collections';
|
||||
|
||||
const select = (state, props) => {
|
||||
const claim = makeSelectClaimForUri(props.uri)(state);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri } from 'redux/selectors/claims';
|
||||
import ClaimEffectiveAmount from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectInsufficientCreditsForUri } from 'redux/selectors/content';
|
||||
import { makeSelectClaimWasPurchased } from 'lbry-redux';
|
||||
import { selectInsufficientCreditsForUri } from 'redux/selectors/content';
|
||||
import { makeSelectClaimWasPurchased } from 'redux/selectors/claims';
|
||||
import ClaimInsufficientCredits from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
isInsufficientCredits: makeSelectInsufficientCreditsForUri(props.uri)(state),
|
||||
isInsufficientCredits: selectInsufficientCreditsForUri(state, props.uri),
|
||||
claimWasPurchased: makeSelectClaimWasPurchased(props.uri)(state),
|
||||
});
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doResolveUri, makeSelectClaimForUri, makeSelectIsUriResolving } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri, selectIsUriResolving } from 'redux/selectors/claims';
|
||||
import { doResolveUri } from 'redux/actions/claims';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import { punctuationMarks } from 'util/remark-lbry';
|
||||
import { selectBlackListedOutpoints } from 'lbryinc';
|
||||
import { selectPlayingUri } from 'redux/selectors/content';
|
||||
import ClaimLink from './view';
|
||||
|
||||
|
@ -24,8 +24,7 @@ const select = (state, props) => {
|
|||
uri,
|
||||
claim,
|
||||
fullUri: props.uri,
|
||||
isResolvingUri: makeSelectIsUriResolving(uri)(state),
|
||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||
isResolvingUri: selectIsUriResolving(state, uri),
|
||||
playingUri: selectPlayingUri(state),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -15,10 +15,6 @@ type Props = {
|
|||
description: ?string,
|
||||
isResolvingUri: boolean,
|
||||
doResolveUri: (string) => void,
|
||||
blackListedOutpoints: Array<{
|
||||
txid: string,
|
||||
nout: number,
|
||||
}>,
|
||||
playingUri: ?PlayingUri,
|
||||
parentCommentId?: string,
|
||||
isMarkdownPost?: boolean,
|
||||
|
@ -43,21 +39,6 @@ class ClaimLink extends React.Component<Props> {
|
|||
this.resolve(this.props);
|
||||
}
|
||||
|
||||
isClaimBlackListed() {
|
||||
const { claim, blackListedOutpoints } = this.props;
|
||||
const signingChannel = claim && claim.signing_channel;
|
||||
if (claim && blackListedOutpoints) {
|
||||
let blackListed = false;
|
||||
|
||||
blackListed = blackListedOutpoints.some(
|
||||
(outpoint) =>
|
||||
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
|
||||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
|
||||
);
|
||||
return blackListed;
|
||||
}
|
||||
}
|
||||
|
||||
resolve = (props: Props) => {
|
||||
const { isResolvingUri, doResolveUri, claim, uri } = props;
|
||||
|
||||
|
@ -79,14 +60,13 @@ class ClaimLink extends React.Component<Props> {
|
|||
allowPreview,
|
||||
} = this.props;
|
||||
const isUnresolved = (!isResolvingUri && !claim) || !claim;
|
||||
const isBlacklisted = this.isClaimBlackListed();
|
||||
const isPlayingInline =
|
||||
playingUri &&
|
||||
playingUri.uri === uri &&
|
||||
((playingUri.source === 'comment' && parentCommentId === playingUri.commentId) ||
|
||||
playingUri.source === 'markdown');
|
||||
|
||||
if (isBlacklisted || isUnresolved) {
|
||||
if (isUnresolved) {
|
||||
return <span>{children}</span>;
|
||||
}
|
||||
|
||||
|
@ -112,7 +92,7 @@ class ClaimLink extends React.Component<Props> {
|
|||
) : (
|
||||
<Button
|
||||
button="link"
|
||||
title={SIMPLE_SITE ? __("This channel isn't staking enough LBRY Credits for link previews.") : children}
|
||||
title={SIMPLE_SITE ? __("This channel isn't staking enough Credits for link previews.") : children}
|
||||
label={children}
|
||||
className="button--external-link"
|
||||
navigate={uri}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue