Compare commits

..

300 commits

Author SHA1 Message Date
zeppi
1030f7c1ef bugfix 2021-12-02 21:17:53 -05:00
zeppi
39f0a6aa97 update trending to use trending_score 2021-12-02 15:20:34 -05:00
mayeaux
82643b1f4a
Finish cleaning out DOM (#413)
* finish cleaning out dom

* lint
2021-12-02 13:22:51 -05:00
mayeaux
a842a58608
Persist ads (#411)
* persist homepage ads

* persist all ads
2021-12-02 12:04:40 -05:00
infinite-persistence
afefd5f4f5
Skip pending-channels loop if there are no pending channels. 2021-12-02 21:36:53 +08:00
infinite-persistence
8eff3dca21
Use the lighter activeChannelId
- No need to generate the full claim since we are only using the ID.
2021-12-02 21:00:17 +08:00
infinite-persistence
36c10a9c78
Remove unused props 2021-12-02 20:41:02 +08:00
infinite-persistence
428c00901b
Fix double pause button in mobile (#408)
Restored css load-order that was changed from a recent refactor.
2021-12-01 22:06:22 -05:00
infinite-persistence
2ecf04a2e5
Sort languages (no functional change)
Just to spark a build
2021-12-02 09:32:56 +08:00
Max Kotlan
1eeacadbf2
updated the transform origin (#395) 2021-12-01 13:01:21 -05:00
infinite-persistence
f6b17909f2
File description (collapsed): show ~3 lines instead of ~1 (#403)
## Ticket
- Closes https://github.com/lbryio/lbry-desktop/issues/7222
- Also felt it's too squished for the longest time. Previously fixed comments but didn't handle this.
2021-12-01 10:52:09 -05:00
saltrafael
c492204e26
[oEmbed] some changes and fixes (#392)
* Fix query selection

* Fix xml format

* Fix link url and author_url

* Refactor repeated components

* Refactor repeated embed iframe string

* Add support for passing referrer queries to src

* Change iframe id from lbry to odysee

* Improve replace logic understanding

* Fix URL

Co-authored-by: Thomas Zarebczan <tzarebczan@users.noreply.github.com>
2021-12-01 10:36:52 -05:00
infinite-persistence
787ebd9588
Blacklist: use existing map instead of looping (#400)
We already have a pre-calculated map, but not used except for comments.

At the expense of pre-calculating it, the subsequent queries are instantaneous compared to the loop.

We are still not perfect in term of reducing re-renders, so this helps a lot.
2021-12-01 10:24:27 -05:00
infinite-persistence
7e9e213974
Add pinning in Category Pages's Trending Tab (#399)
Closes https://github.com/OdyseeTeam/odysee-homepages/issues/427
2021-12-01 10:18:57 -05:00
mayeaux
6d3ec149b3
use second card on small screens and dont load script if authenticated (#406) 2021-12-01 09:52:03 -05:00
infinite-persistence
935eaa6edb
Add persistence to live tile expansion in Wild West (#398)
* Discover: add persistence to the livestream section's fold state

The persisted value should only apply when livestream section is needed, hence the need for 2 state variables.

Also renamed the variables for clarity.

* Discover: add "show less livestreams" at upper-right

Wanted to put it as an injected tile, but requires more work to do it in a general-purpose way, as opposed to a hardcoded way like how ads are currently injected. It also needs to work on both Tile and List format.

So ... just place the button at the upper-right for now. Although a bit odd, at least it'll be a consistent place (i.e. position won't be affected by live tile count).
2021-12-01 09:36:35 -05:00
infinite-persistence
dd96d1222d
Fix inability to unblock and unsubscribe an abandoned claim (#405) 2021-12-01 21:33:03 +08:00
infinite-persistence
8bc6718a4a
Fix selectIsSubscribedForUri not handling abandoned claims
I yanked out the parseURI part in a prior commit ... the comment was misleading me to think it was redundant. But it had another hidden function, which is to handle abandoned claims which `claim` will be `null`.
2021-12-01 21:21:31 +08:00
infinite-persistence
dcd93af6eb
Fix inability to unblock an abandoned claim
The recent change to parse the channel from a Repost using a `claim` ended up breaking the case for abandoned claims, which `claim` will be `null`.

Fix by looking at `claim` first (faster), and falling back to the `parseURI` method if it remains inconclusive.
2021-12-01 21:21:09 +08:00
infinite-persistence
beeec64271 i18n ads 2021-11-30 22:53:47 -08:00
infinite-persistence
6fb2e02e3a
Change ad script injection method + fix effect dependency (#396)
## Issue
Tom seeing crashes on the line that was trying to remove the script, saying it's not a child of that node.

## Changes
- I'm guessing the found `fjs` sometimes is not in `head`, but we always remove from `head` during cleanup. Just append to the bottom of head, and remove from head. I think script order doesn't matter if we are injecting at runtime?

- Fixed effect dependency while at it (the latest PR removed the need to check for `type`).
2021-11-30 22:17:28 -05:00
infinite-persistence
0aff130ea4
Fix lint and formatting (#394) 2021-11-30 19:53:23 -05:00
John B Nelson
fcb70c8e8b
FIX recsys endpoint fix (#393) 2021-11-30 19:35:04 -05:00
mayeaux
1e071550ae
Add ad to the homepage as a card (#362)
* coming along well

* coming along well

* adding custom react element

* coming along well

* coming along well

* coming along well

* working pretty well

* almost done

* essentially working just could use a couple touchups

* cleanup and lint errors

* fix lint errors

* fix flow errors

* possible bugfix

* dynamically set width and height

* only run when rowdata is populated

* trying using ref

* better way to check for card population

* working implementation

* working implementation

* clean up flow and clean up script

* fix typo in comment and logs
2021-11-30 17:01:03 -05:00
John B Nelson
873ac4dc5d
Change clickstream endpoint for brave's shield (#391)
Brave's shield blocks the clickstream endpoint. uBlock origin
does not (it seems).
2021-11-30 15:47:48 -05:00
saltrafael
4fd4309829
add Player.js Support (#378)
* Reorder video.js imports and props

* Install player.js

* Add player.js adapter for video.js
2021-11-30 15:46:03 -05:00
jbn
a7b991efb1 Change clickstream endpoint
It seems like `clickstream` is blocked by brave's sheilds.
2021-11-30 14:28:13 -05:00
infinite-persistence
bfccca9aaf
Mobile: move 'Notifications' to the top (#389)
## Behavioral changes
- Moved Notifications to the top for mobile.
- Added separate lines between sections.

## Code changes
The array method is too restrictive (hard to move things with display logic around). It's also hard to read.

Instead of trying to populate an array, just directly populate the return tree. Added `getLink` to make things readable. It's now easier to see the sections in a glance.
2021-11-30 10:27:37 -05:00
infinite-persistence
e96807fa6d
Restore 'https' to dmca link and remove actual dup 2021-11-30 14:21:33 +08:00
Thomas Zarebczan
6d4c93968f
Update README.md 2021-11-29 21:57:23 -05:00
saltrafael
34eaccdbee
add oEmbed Support for video claims (#376)
* Refactor html.js

* Fix Favicon

* Refactor rss.js

* Create oEmbed.js
2021-11-29 21:27:56 -05:00
Thomas Zarebczan
7613d07c35
Misc updates 2021-11-29 20:32:39 -05:00
Thomas Zarebczan
27d8f4174c
fix thumbnail URLs 2021-11-29 19:31:23 -05:00
infinite-persistence
f5f3b08cca
Debounce volume and muted state update.
## Ticket
189: Overall React Lag or Extra Re-renders / Volume slider laggy since v0.48

## The problem
Every redux update results in each mounted component's prop mapping function (the `select` and `perform` stuff) to be recalculated. This is normal per redux, but we do lots of heavy stuff there.

The slider was sending tons of redux update for the Volume and Muted setting.

## Changes
The redux volume/muted setting is just used to restore vjs to the user's setting on the next video, so it doesn't need to be updated immediately/constantly -- vjs keeps it's own video settings.  Debounced that action.
2021-11-29 23:12:38 +08:00
infinite-persistence
71fc850df4
Settings Page: maximize width usage in mobile 2021-11-29 21:01:29 +08:00
infinite-persistence
ac11dec484
Trailing spaces should not be part of translation. 2021-11-29 08:45:57 +08:00
infinite-persistence
c15a52cb46
i18n update 2021-11-29 08:45:57 +08:00
infinite-persistence
56ecdec2cb
Restore "don't run SP script on iframe (368)" + lint/format (#373) 2021-11-26 09:24:51 -05:00
Thomas Zarebczan
406d91948d
Move around for Roku prod app 2021-11-25 11:51:39 -05:00
Thomas Zarebczan
11c8024c2a
Revert "dont run on iframe (#368)" (#370)
This reverts commit 823fdcdd97.
2021-11-25 11:08:21 -05:00
mayeaux
823fdcdd97
dont run on iframe (#368) 2021-11-25 10:42:26 -05:00
infinite-persistence
fd17ab4c8b
Route recommendation search to recsys + add user_id (#353)
* Route recommendation search to recsys 5% of the time + add `user_id`

## Ticket
334 send some recommended requests to recsys

## Approach
`doSearch`:
    - If the search options include `related_to`, route that to the new `searchRecommendations` which performs the 5% check + appends `user_id` at the end. This way, we don't need to alter the function signature of `doSearch`.
    - Else, run proceed as normal.

* Always go to alt provider

f

Co-authored-by: Thomas Zarebczan <thomas.zarebczan@gmail.com>
2021-11-24 15:25:22 -05:00
mayeaux
2adbbc2899
bugfix embed errors (#366) 2021-11-24 15:20:36 -05:00
infinite-persistence
5c643cc796
Re-enable reposts on homepages (#352)
* Add remove_duplicates to tile/list claim_search except for Channel Page

This removes the any duplicates from reposts.

* Re-activate the "Hide reposts" setting

* Category Rows: default to ['stream', 'repost'] unless specified otherwise.
2021-11-24 11:11:25 -05:00
infinite-persistence
781f1b712e
GA: entered livestream (#364)
## Issue
85: "user joined livestream"

## Approach
Add it into the existing "player :: action" event, so we can compare it againts `loaded_video | loaded_image | loaded_markdown | loaded_audio`.
2021-11-24 11:03:21 -05:00
infinite-persistence
6bbf310348 GA: browser notification subscription 2021-11-24 07:28:25 -08:00
infinite-persistence
7ea74cfa0d
Fix livestream tiles reloading placeholders then scrolled (#360)
## Issue
9: Wild west + scroll down tries to reload livestreams
2021-11-24 09:35:47 -05:00
infinite-persistence
4267c1ccf7
Un-authenticated resolve (#341)
* apiCall: add option to not send the auth header

## Why
Want an option to make un-authenticated `resolve` calls where appropriate, to improve caching.

## How
All `apiCall`s are authenticated by default, but when clients add NO_AUTH to the params, `apiCall` will exclude the X_LBRY_AUTH_TOKEN. It will also strip NO_AUTH from the param object before sending it out.

* Add hook for 'resolve' and 'claim_search' to check and skip auth...

... if the params does not contain anything that requires the wallet.

* doResolveUri, doClaimSearch: let clients decide when to include_my_output

- No more hardcoding 'include_purchase_receipt' and 'include_is_my_output'
- doResolveUri: include these params when opening a file page. This was the only place that was doing that prior to this PR.

* is_my_output: use the signing_channel as alternative

## Notes
`is_my_output` is more expensive to resolve, so it is not being requested all the time.

## Change
Looking at the signing channel as the additional fallback, on top of `myClaimIds`.

## Aside
I think using `myClaimIds` here is redundant, as it is usually populated from `is_my_ouput`. But leaving as is for now...
2021-11-24 09:33:34 -05:00
infinite-persistence
c74dd49bc5
Fix livestream tiles appearing in Tag Search
## Ticket
155 All live streams show on tag explore/discovery page + content type filters don't work there

`!dynamicRouteProps` wasn't good enough to determine if it's Wild West. Use direct path instead.
2021-11-24 17:32:49 +08:00
infinite-persistence
b762cac50b
i18n fixes for new category and page titles 2021-11-24 11:11:46 +08:00
mayeaux
84e75fdfe8
Workaround for SecurePrivacy issues with VPNs (#357)
* workaround for secureprivacy issues with vpn

* fix flow issues
2021-11-23 17:26:23 -05:00
saltrafael
f8b694d7d7
Add Pop Culture Icon (#355) 2021-11-23 15:31:28 -05:00
saltrafael
bc64802f6e
Add Education Icons (#354) 2021-11-23 12:35:25 -05:00
mayeaux
f2715fa97b
Adds GDPR support (#311)
* add gdpr support

* only run on production

* testing implementation

* just needs last touches then ready

* ready for merge

* add cookies to sidebar

* hide button when secureprivacy not available

* switch over to loading script as a react hook

* conditionally add secureprivacy script

* save gdpr status on session

* better design
2021-11-23 10:21:33 -05:00
infinite-persistence
3c4ccdd2fe
Kill makeSelectClientSetting
## Why
- No memo required (no transformation).
- `makeSelect*` is an incorrect pattern.

## Changes
- Replaced makeSelectClientSetting with selectClientSetting.
- Remove unused selectShowRepostedContent.
2021-11-23 12:29:53 +08:00
infinite-persistence
eb83a834a1
TUS: handle remaining locked file error messages 2021-11-23 11:28:32 +08:00
infinite-persistence
605a8f371d
TUS: Skip logging "423/409 concurrent upload" errors. 2021-11-23 09:30:55 +08:00
Thomas Zarebczan
a3111003a2
Add new stickers (#347) 2021-11-22 17:52:46 -05:00
saltrafael
e2c7337d11
[Report Page] Fix GitHub URL and improve strings (#340)
* Refactor

* Fix github URL and Improve strings
2021-11-22 09:32:33 -05:00
infinite-persistence
87c3dcc057
SyncFatalError: show nag instead of hard-crashing. (#331)
* SyncFatalError: show nag instead of hard-crashing.

## Issue
When sync fails, we crash the app.

## Ticket
Maybe closes 39 "Better handle both internal and web backend interruptions / downtime"

## Approach
I'm tackling this from the standpoint that (1) sync errors are not that fatal -- we'll just lost a few recent changes (2) network disconnection is the common cause.

## Changes
- If we are offline:
    - Inform user through a nag. All other status is meaningless if we are offline.
- If we are online:
    - If api is STATUS_DOWN, show the existing crash page.
    - If there is a sync error, show a nag saying settings are now potentially unsynchronized, and add a button to retry sync.
    - If there is a chunk error, nag to reload.

* Attempt to detect `status=DOWN`

Previous code resolves the status to either "ok" or "not", which makes the app unable to differentiate between the "degraded" (nag) and "down" (crash) states.
2021-11-22 09:30:43 -05:00
infinite-persistence
13cbbc8342
TUS: Detect and disallow concurrent uploads (#339) 2021-11-22 16:41:32 +08:00
infinite-persistence
2d3057d5cf
Detect concurrent uploads and stop it. 2021-11-22 16:12:11 +08:00
infinite-persistence
b6e9c7aabf
TUS: handle URL removal on 4xx errors
## Issue
The TUS client automatically removes the upload fingerprint whenever there is a 4xx error. When we try to resume later, we couldn't find the the fingerprint and ended up creating a new upload ID.

## Changes
Since we are also storing the uploadUrl ourselves, provided that to override the tus client's default behavior of restarting a new session on 4xx errors.
2021-11-22 16:12:10 +08:00
Thomas Zarebczan
352ee7bfaa
fix syntax 2021-11-19 11:30:28 -05:00
Thomas Zarebczan
95d7582f08
remove adsense 2021-11-19 10:57:55 -05:00
infinite-persistence
21cb405965
ClaimLink: skip blacklist check (#329)
## Issue
- It was checking the blacklist on every render.
- Finding opportunities to improve performance

## Changes
Since the final destination will be a dead end anyways, skip the blacklist check so that livestreams can render a bit faster when there lots of mentions.

The only downside is that a claim preview for a blacklisted item would now appear (vs. being a text previously)
2021-11-19 09:46:52 -05:00
infinite-persistence
328b60d021
Notifications: skip resolve during boot (#330)
## Issue
- Large resolve count (albeit batched) on bootup.

## Changes
- Skip the call on bootup. The same call will happen when you click the notification bell, so it's not too late to resolve at that time.
- Added `true` to `doResolveUris` to return cached results, otherwise it will keep resolving the same channels every time we enter Notifications Page.
2021-11-19 09:46:31 -05:00
infinite-persistence
6a33ed337b
Cost Info selector fixes (#328) 2021-11-19 16:31:45 +08:00
infinite-persistence
0941667150
Cost Info selector fixes
- no memo required since they are just directly accessing the store.
2021-11-19 16:01:25 +08:00
infinite-persistence
b351617d2f
Add flow 2021-11-19 16:00:06 +08:00
infinite-persistence
ff20663b8d
byId: update only if claim has new data
This was already being done for Content claims, and repeated for Channels, Collections, and other reducers.
2021-11-19 15:59:48 +08:00
infinite-persistence
7515d21510
claimsByUri: update only if changed 2021-11-19 15:59:47 +08:00
infinite-persistence
d48a7c7295
TUS: reduce chunk size from 100MB to 25MB.
The stalling behavior has changed a bit, probably with the removal of CF.

The stall difference between 10MB and 50MB is not too noticable, so picking 25MB as a start.
2021-11-19 14:40:03 +08:00
Dan Peterson
314b63705d
get active viewers even when not live (we want waiting count) (#322) 2021-11-18 18:41:43 -05:00
Dan Peterson
3269b84385
Remove claim search long poll + introduce pending state that blocks render + avoid polling status for non-owned claim (#320) 2021-11-18 14:43:39 -05:00
infinite-persistence
4a2305dca1
Notifications filter changes (#319)
## Issue
312 Save notification on back navigation, enable filter on mobile

## Changes
- Don't clear then filter when mounted and there are unread notifications.
   - We previously clear the filter because the user could be clicking the notification bell (which is showing some number) and we ended up with a blank page because of the filter.
- Allow the filter in mobile.
   - Previously, it was intentionally removed for mobile (see bd42418f). I believe it was just because we don't have the style set up for mobile. Here's my quick attempt.
2021-11-18 10:55:33 -05:00
infinite-persistence
e288833085
Fix blacklisted claims appearing in tiles
## Mistake
Tried to simplify the logic between checking Channel vs Content claim, and ended up always checking against Channel. This is correct for commentron blocklists, but not blacklists where the txid is per claim.

## Changes
- Restored original logic.
- While at it, restore the usage of `selectClaimForUri` (i.e. no need to split into 4 selectors anymore), since we've updated the reducer to prevent invalidation from things like 'confirmation' and 'is_my_output'.
2021-11-18 10:21:21 +08:00
jessopb
4cf9309ee1
facilitate admin temp files (#313) 2021-11-17 13:28:36 -05:00
infinite-persistence
e35069de1c
Cache the processing of ChannelMentionSuggestions (#309)
## Issue
One of the bottlenecks of livestream page.

The component probably needs a re-design:
- Don't perpetually mount -- only mount when activated by the user through "@". This would avoid the heavy processing entirely.
- Better way of resolving uris (too many arrays, too many loops).
- Tom also mentioned that we should not be resolving every commenter as we see encounter them in a livestream. This is currently the case because the component is always mounted.

## Changes
Until the re-design occurs, attempt to cache the heavy processing. Also, trimmed down the amount of loops.
2021-11-17 11:47:56 -05:00
infinite-persistence
47c316e0ad
Reduce livestream chat size to 50 2021-11-17 21:04:17 +08:00
infinite-persistence
75bde149cf
Fix url selectors
No memo required.
2021-11-17 19:57:04 +08:00
infinite-persistence
6382238834
Incremental livestream performance fixes (#307) 2021-11-17 18:16:52 +08:00
infinite-persistence
b69c1ec5fe
Reduce the chain of renders when Viewer Count is updated
3 layer of components were rendered because of the viewer-count update.  Only `fileViewCount` needs the value, so let it grab from redux directly.
2021-11-17 18:16:01 +08:00
infinite-persistence
01f771c6ca
Simplify makeSelectViewersForId 2021-11-17 18:16:00 +08:00
infinite-persistence
6b6879ba64
Cache subscription uris if we're gonna map it often. 2021-11-17 18:16:00 +08:00
infinite-persistence
91f1f588e6
Slice the comments before filtering to avoid going through everything.
For the case of livestreams, the comments are added incrementally via websocket. The selector returns everything, which grows as a user watches the livestream.

We could even make it a bit more efficient by passing in `maxCount` to `filterComments`, and do a `for` loop there, but decided to keep things readable by not changing the `filter` usage.
2021-11-17 18:15:59 +08:00
infinite-persistence
5204bb366e
Fix ChannelMention double-constructing the comment list.
As long as the input parameters are the same, the selector will return the cached value so that we don't construct the list twice, which involves blocklist filtering.
2021-11-17 18:15:58 +08:00
infinite-persistence
e6caa8c7ff
Don't use the obsolete selectCommentsByUri
- Same reason as d211450b.
- Also removed an unused selector.
2021-11-17 18:15:58 +08:00
Thomas Zarebczan
51546436ce
remove tag sync 2021-11-17 03:09:26 -05:00
infinite-persistence
ac93b379a9
Fix annoying hierarchy error with Yrbl
`<div>` cannot be a descendend of `<p>`, and `{subtitle}`s sometimes need to have `<div>`s.

Just switch from `<p>` to `<div>`, and let the client decide when the actual text paragraphs are.
2021-11-17 10:27:11 +08:00
Thomas Zarebczan
abbeb45d17
Update README.md 2021-11-16 10:01:52 -05:00
Thomas Zarebczan
4af72806dc
Update README.md 2021-11-16 10:01:42 -05:00
infinite-persistence
39baf1a3c9
ClaimPreviewTile state-map optimizations
## Issue
Lots of time is spent mapping the state to props for this component (since there are lots of tiles).

## Changes
Using this component as a starting point, go through the selectors and make the usual cleanup/fixes:
- Move away from the `makeSelect*` model, which creates a new selector on every call instead of actually re-using the cached version.
- Do proper caching for multi-param selectors using `re-reselect`.
- Don't cache simple functions or direct access to states.
2021-11-16 15:04:29 +08:00
infinite-persistence
201a826381
Simplify makeSelectIsUriResolving
- Memo not required. `resolvingUris` is very dynamic and is a short array anyways.
- Changeg from using `indexOf` to `includes`, which is more concise.
2021-11-16 14:32:58 +08:00
infinite-persistence
bf324a1b79
Simplify makeSelectTitleForUri
No need to memo given no transformation.
2021-11-16 12:23:18 +08:00
infinite-persistence
d03b3fd50d
Simplify selectShowMatureContent 2021-11-16 12:04:40 +08:00
infinite-persistence
8deac56e40
Fix memo: isLivestream & isLivestreamActive 2021-11-16 11:52:35 +08:00
infinite-persistence
dae0e3ccae
Use a lighter selector that doesn't re-create an array of claims.
Although selectClaimsByUri is memoized, it is often invalidated. Why create the array when all we need is the claim?
2021-11-16 10:47:59 +08:00
infinite-persistence
73f208923a
Optimize makeSelectClaimIsNsfw (and it's surrounding friends) 2021-11-16 10:14:01 +08:00
infinite-persistence
4aea0081ea
Remove duplicate claim utilities
These are already brought in from redux prior to the consolidation.
2021-11-16 08:59:07 +08:00
infinite-persistence
27dffaaf2f
Remove unused prop 2021-11-16 08:20:25 +08:00
infinite-persistence
652ec4b69b
Fix memo: makeSelectViewCountForUri, makeSelectSubCountForUri
- switch to a lighter selectClaimIdForUri
- also, these 2 don't need to memo because they are just simple accessors.
2021-11-16 08:15:24 +08:00
infinite-persistence
c8ad9718bb
Floating player position-listener fixes (#289)
## Issues
- 251 Dragging the floating player is super laggy

Recent changes and/or refactoring combined the effects or added dependancies into the effects, causing them to re-run excessively.

## Changes
- Restored effects to their original behavior.
- Don't perform the position check when dragging -- only do it when released.
- Do proper debouncing for the 'resize' listener -- the previous method was incorrect as a new function is created on each render.
2021-11-15 16:13:48 -05:00
Dan Peterson
cb104017ad
adjust livestream interval (#294) 2021-11-15 12:34:21 -06:00
Dan Peterson
0c28f3c6f1
reverse livestream status check (#293) 2021-11-15 11:52:00 -05:00
Dan Peterson
e02bc6cc03
Hotfix livestream status (#292)
* Fix livestream status on upcoming livestream

* update fix
2021-11-15 10:58:29 -05:00
infinite-persistence
70d18eba59 Remove "same array" check now that USER_STATE_POPULATE only runs when there is new data. 2021-11-15 07:06:39 -08:00
infinite-persistence
38c13cf5ef Skip USER_STATE_POPULATE when sync_hash is the same
## Changes
- doHandleSyncComplete: only call doGetAndPopulatePreferences when there is new data.
- But for that to work, we'll need to populate preferences at least once. We'll do that in doSignIn.
- We can also remove the "sync/prefs ready" mechanism that was mainly meant for Desktop.

Then came another problem: while trying to spark changes between 2 tabs, `sync/get` was saying "no change" despite the local and server hash being different. I think it is because the both `sync_hash + sync/get` combo is operating on server data, so the hash is the same. I'm guessing this is why we ended up just running doGetAndPopulatePreferences every time before PR, since this flag wasn't correct in this scenario.

- Updated `data.changed` to consider both API results and comparison with local hash.
2021-11-15 07:06:39 -08:00
infinite-persistence
342fcb4024 doSignIn: cleanup, no functional change
Seems unlikely that we'll need to disable notifications, so cleaned up the code a bit.
2021-11-15 07:06:39 -08:00
mayeaux
6546eaeb63
refactor ad code and dont show ads on embeds (#290) 2021-11-15 10:01:42 -05:00
infinite-persistence
f084288ac9
Fix unable to clear muted list
## Issue
When the muted list was being cleared from another app, the web version ended up restoring the previous muted list.

## Change
- As long as `blocked` is defined, return that since an empty array is a valid result.
- If undefined, something went wrong when calling the reducer, so retain the muted list. I believe this was the original intention of that line.
2021-11-15 13:36:08 +08:00
infinite-persistence
93c28b24bb Remove desktop video start time analytics 2021-11-15 09:25:58 +08:00
Dan Peterson
c242c37869
Add initialization status to push notification hook. Can be used to better control render strategy in cmpnts utilizing it. (#284) 2021-11-12 12:06:07 -05:00
infinite-persistence
6d217dbc50
Attempt to speed up sidebar menu for mobile (#283)
* Exclude default homepage data at compile time

The youtuber IDs alone is pretty huge, and is unused in the `CUSTOM_HOMEPAGE=true` configuration.

* Remove Desktop items and other cleanup

- Moved constants out of the component.
- Remove SIMPLE_SITE check.
- Remove Desktop-only items

* Sidebar: limit subscription and tag section

## Issue
Too slow for huge lists

## Change
Limit to 10 initially, and load everything on "Show more"

* Fix makeSelectThumbnailForUri

- Fix memo
- Expose function to extract directly from claim if client already have it.
2021-11-12 10:59:11 -05:00
mayeaux
529a9cbc40
Videojs component refactor (#240)
* pull out ads into its own file

* final touchup

* pull out lbry volume class

* using curried function

* coming along well

* almost done keyboard shortcuts

* pulling the guts out

* finishing keyboard shortcuts

* coming along well

* running but needs some testing

* almost done but could still use some testing

* all code working with some flow fixes needed

* fixing flow errors

* finishing flow errors
2021-11-12 09:56:46 -05:00
infinite-persistence
6f8758c819
Fix and optimize makeSelectIsSubscribed (#273)
## Issues with `makeSelectIsSubscribed`
- It will not return true if the uri provided is canonical, because the compared subscription uri is in permanent form. This was causing certain elements like the Heart to not appear in claim tiles.
- It is super slow for large subscriptions not just because of the array size + being a hot selector, but also because it is looking up the claim twice (not memo'd) and also calling `parseURI` to determine if it's a channel, which is unnecessary if you already have the claim.

## Changes
- Optimize the selector to only look up the claim once, and make operations using already-obtained info.
2021-11-12 09:47:07 -05:00
infinite-persistence
53406a60cf
Bump to rebuild with new v2 PUBLISH endpoint 2021-11-12 20:21:20 +08:00
infinite-persistence
b0509bc990
Band-aid: wait a while before sending notify
## Issue
The status = 0 is due to unresponsive backend right after the tus-upload. No root-cause found yet.

## Change
It may or may not help, but adding a delay to account for the unresponsive stage for now.
2021-11-12 18:43:57 +08:00
infinite-persistence
d8080a9fda
Notify: log retry attempts 2021-11-12 16:30:41 +08:00
infinite-persistence
62e7fe06a5
TUS: Don't retry on 4xx
## Issue/Steps
From Randy:
- started the upload then open a new tab of the same page
- one of the tab finished the upload and successfully published the file, and the other tab received 404 error on patch and head request, because the file is already removed on the server

## Changes
Use the default onRetry code that ignores all 4xx, except for LOCKED and CONFLICT. Had to duplicate some code from tus because I still need to inject the 'retry' progress for the GUI to update the string.
2021-11-12 14:32:41 +08:00
infinite-persistence
dfe30b6d78
TUS: fix parallel uploads of the same file
## Issue
If you make 2 claims from the same source file, the second upload thinks it's trying to resume from the first one. They should be unique uploads.

## Approach
Stash the upload url for comparison when looking up existing uploads to resume.

Stash that in `params` to minimize code changes. We'll just need to ensure it is cleared before we generate the SDK payload.
2021-11-12 14:32:40 +08:00
infinite-persistence
861aaf4cde Notify: Re-enable delay but only for initial connection problem
We want to avoid the double `notify`, and also to confirm whether the SDK is timing out.
2021-11-12 11:19:26 +08:00
infinite-persistence
9bfa1a3577
Notify: Disable retry + try to report status code 2021-11-12 09:01:36 +08:00
mayeaux
d047a748b7
Test ads (#277)
display ad in related sidebar for video view page

Co-authored-by: Thomas Zarebczan <thomas.zarebczan@gmail.com>
2021-11-11 17:15:50 -05:00
infinite-persistence
ef0329e03b Lazy-load comment components
## Issue
~300KB savings in `ui.js` size (production, uncompressed). Mostly coming from the emoji library.

## Notes
Most of the `Comment*` components are under `CommentsList` or `LivestreamComments`, so deferring these 2 covered most of it. The exceptions are Notification and OwnComments.
2021-11-11 15:09:28 -05:00
infinite-persistence
0f68bad3eb
Optimize selectClaimIsMine
## Why
Frequently used; top in perf profile

## Changes
Most of the time, you already have the claim object in the current context. `selectClaimIsMineForUri` will retrieve the claim again, which is wasteful, even if it is memoized (looking up the cache still takes time).

Break apart the logic and added the alternative `selectClaimIsMine` for faster lookup.
2021-11-11 16:10:06 +08:00
infinite-persistence
827a08ac26
Fix memo: stake indicator 2021-11-11 10:23:28 +08:00
infinite-persistence
6492fe1c66
Upload: remove "download app" suggestion for large files. 2021-11-11 10:16:31 +08:00
infinite-persistence
7ef5975ee8
Notify: auto-retry once after 10 seconds
Also:
- Show the resume button on notify errors.
- Changed the error message to differentiate against v1's.
2021-11-11 09:55:48 +08:00
infinite-persistence
b5f1ae1291
Tus-retry: widen delay gap + add 1 more retry 2021-11-11 09:54:25 +08:00
infinite-persistence
a90b6415de
Support resume-able upload via tus (#186) - w/ commits 2021-11-11 08:01:16 +08:00
infinite-persistence
77087d2916
Restore v1 code for livestream replay, etc.
v2 (tus) does not handle `remote_url`, so the app still needs v1 for that. Since we'll still have v1 code, use v1 for previews as well.
2021-11-11 08:00:12 +08:00
infinite-persistence
38f511c2fb
Move 'currentUploads' to 'publish' reducer
`publish` is currently rehydrated, so we can ride on that and don't need to store the `currentUploads` in `localStorage` for persistence. This would allow us to store Markdown Post data too, as `localStorage` has a 5MB limit per app.

We could have also made `webReducer` rehydrate, but in this repo, there is no need to split it to another reducer. It also makes more sense to be part of publish anyway (at least to me).

This change is mostly moving items between files, with the exception of
1. An additional REHYDRATE in the publish reducer to clean up the tusUploader.
2. Not clearing `currentUploads` in CLEAR_PUBLISH.
2021-11-11 08:00:12 +08:00
infinite-persistence
9d25d82bed
Exclude "modified date" for Firefox/Android
## Issue
It appears that the modification date of the Android file changes when selected, so that file was deemed "different" when trying to resume upload.

## Change
Exclude modification date for now. Let's assume a smart user.
2021-11-11 08:00:12 +08:00
infinite-persistence
bef7ff4a2d
Support resume-able upload via tus
## Issue
38 Handle resumable file upload

## Notes
Since we can't serialize a File object, we'll need to the user to re-select the file to resume.
2021-11-11 08:00:12 +08:00
infinite-persistence
fa48b4a99b
Add doPublishResume 2021-11-11 08:00:11 +08:00
infinite-persistence
5b630d6a20
Refactor doPublish. No functional change
This is to allow `doPublish` to accept a custom payload as an input (for resuming uploads), instead of always resolving it from the redux data.
2021-11-11 08:00:11 +08:00
infinite-persistence
236e2cfe8e
Publish button: use spinner instead of "Publishing..."
Looks better, plus the preview could take a while sometimes.
2021-11-11 08:00:11 +08:00
infinite-persistence
263c09500f
-- Revert to allow restoring commits ---
This reverts commit cb6a044584.
2021-11-11 08:00:05 +08:00
Thomas Zarebczan
d649e3563f
Adjust channel mention regex (#269) 2021-11-10 14:53:42 -05:00
Thomas Zarebczan
5c23c6d88e
Default to trending for homepages (#267) 2021-11-10 14:36:54 -05:00
Dan Peterson
5639e4c1ff
Adjust some initial states to optimize initial render (#265) 2021-11-10 14:15:40 -05:00
Thomas Zarebczan
8fd6382bf4
Update README.md 2021-11-10 13:33:44 -05:00
infinite-persistence
cb6a044584
Support resume-able upload via tus (#186)
* Publish button: use spinner instead of "Publishing..."

Looks better, plus the preview could take a while sometimes.

* Refactor `doPublish`. No functional change

This is to allow `doPublish` to accept a custom payload as an input (for resuming uploads), instead of always resolving it from the redux data.

* Add doPublishResume

* Support resume-able upload via tus

## Issue
38 Handle resumable file upload

## Notes
Since we can't serialize a File object, we'll need to the user to re-select the file to resume.

* Exclude "modified date" for Firefox/Android

## Issue
It appears that the modification date of the Android file changes when selected, so that file was deemed "different" when trying to resume upload.

## Change
Exclude modification date for now. Let's assume a smart user.

* Move 'currentUploads' to 'publish' reducer

`publish` is currently rehydrated, so we can ride on that and don't need to store the `currentUploads` in `localStorage` for persistence. This would allow us to store Markdown Post data too, as `localStorage` has a 5MB limit per app.

We could have also made `webReducer` rehydrate, but in this repo, there is no need to split it to another reducer. It also makes more sense to be part of publish anyway (at least to me).

This change is mostly moving items between files, with the exception of
1. An additional REHYDRATE in the publish reducer to clean up the tusUploader.
2. Not clearing `currentUploads` in CLEAR_PUBLISH.

* Restore v1 code for livestream replay, etc.

v2 (tus) does not handle `remote_url`, so the app still needs v1 for that. Since we'll still have v1 code, use v1 for previews as well.
2021-11-10 13:16:16 -05:00
infinite-persistence
b508fe8679 Enable BundleAnalyzerPlugin + remove 'yarn analyze'
Use the env to opt in.
2021-11-11 00:07:18 +08:00
infinite-persistence
d211450b5b
Fix selectCommentIdsForUri
- memo not required.
- start to not use the confusing and wrongly-named 'selectCommentsByUri' (per comment from Sean);  use the existing 'selectClaimIdForUri' instead.  This works because currently we do fetch any comments without first visiting a claim/uri, so we'll always have fetched the required claim, and can be queried in 'selectClaimIdForUri'.
2021-11-10 17:35:30 +08:00
infinite-persistence
81d77da17e
Fix states not updated in an immutable way.
It's technically incorrect and was causing the GUI to not update sometimes because the reference did not change, despite the array contents did. The GUI just happens to update most of the time due to other state changes.
2021-11-10 17:35:29 +08:00
infinite-persistence
7cefb0fadc
Simplify 'selectClaimIdForUri'
Memoization is not needed. But note that it is now a 2 parameter selector.
2021-11-10 16:50:26 +08:00
infinite-persistence
ece2312ec5 selectClaimIsMineForUri to replace makeSelectClaimIsMine
## Issue
`normalizeUri` | `parseURI` is expensive and has been causing sluggish operations when called repeatedly or within a loop.

## Change
Since I'm not confident enough to remove the call entirely from makeSelectClaimIsMine (although I've yet to find a scenario that the uri is not already normalized), we'll try caching the calls instead.

## Results
- in a simple test of toggling between 2 category pages, we saved 20ms from `parseURI` calls alone.

- in a test of opening all categories one time, the memory usage remained similar. This makes sense since we removed a `makeSelect*` (which creates a selector for each call + not memoizing), and replaced that with a cached selector that's actually memoizing.
2021-11-10 16:49:12 +08:00
infinite-persistence
97b9b733c6
Fix memo: selectMyActiveClaims, selectAbandoningIds
## Issue
- selectMyActiveClaims memo problem -- being recalculated on every click -- high workload for wallet with large uploads.
- Mistake in handling abandoning IDs (it was trying to extract keys from an array)

## Changes
- selectAbandoningIds: never use `state` as an input selector. Breaks memo.
- Don't use selectMyClaimsRaw and then reduce it back to IDs. Use selectMyClaimIdsRaw instead.
- selectAbandoningIds is already in array form, so don't run Object.keys.
- Fix abandoningById never clearing when succeeded.
2021-11-10 09:58:26 +08:00
infinite-persistence
c681d95ad7
Fix livestream state issues. Create unified long polling mechanism. #246 2021-11-10 08:43:26 +08:00
Dan Peterson
baa15d0c42
add long polling to reset component 2021-11-10 08:21:16 +08:00
Dan Peterson
1d8753e2ba
Revert claim preview + fix small css issue + export named function 2021-11-10 08:21:16 +08:00
Dan Peterson
60f06dac52
Fix livestream state issues. Create unified long polling mechanism. 2021-11-10 08:21:15 +08:00
saltrafael
a7c7881795
Fix string (#257) 2021-11-09 15:25:19 -05:00
infinite-persistence
ef1ebfc491
Allow admins to delete comments as well. (#250)
This is a follow-up to #235
2021-11-09 09:47:49 -05:00
infinite-persistence
bc67379c26
Block: pass comment ID for deletion when being blocked. (#255)
* Simplify dispatch map

Since none of dispatches are doing any custom transformation, just use a direct map. The number of arguments for the comment function are getting crazy.

* Block: pass comment ID for deletion when being blocked.
2021-11-09 09:43:02 -05:00
infinite-persistence
0e2bb350c0
Remove mouse-back/forward listeners
- Not needed for web since the browser does it, and should have been gated under 'app'
- This reverts lbry-desktop 3744.
2021-11-09 16:53:11 +08:00
infinite-persistence
f0591b8956
Incremental removal of Desktop code #252 2021-11-09 16:17:00 +08:00
infinite-persistence
9fc417edfa
Remove 'web' preprocessor 2021-11-09 16:08:13 +08:00
infinite-persistence
45ad08ec32
Remove use-history-nav.js
For electron.js only
2021-11-09 16:03:37 +08:00
infinite-persistence
d7fc5069be
Remove .env.ody again
Accidentally brought back in when porting over the Redux consolidation PR.
2021-11-09 15:57:13 +08:00
infinite-persistence
b9a5dc3c70
Remove use-zoom
It's only needed for electron
2021-11-09 15:57:12 +08:00
infinite-persistence
1426dd5b83
Remove skin support and lbry.tv scss
## Issue
211 - CSS load-order problem

## Notes
It is unlikely that we'll need to support different brands in the future, so simplifying the code and number of files so that we don't have to handle the various import paths. Will probably make things easier for the css-splitting work too.
2021-11-09 10:36:08 +08:00
Thomas Zarebczan
d80cea1caa
new Stickers! (#248) 2021-11-08 18:07:55 -05:00
infinite-persistence
7a6a8c2fd7
Comment: Fix missing author highlight in Community Tag (#249)
## Issue
238 Comments: author-name not highlighted when in Channel Community tab

## Changes
- Channel claims don't have a signing channel. Use `getChannelFromClaim`, which handles both content and channel claim.
2021-11-08 18:05:54 -05:00
infinite-persistence
ebf81a61c3
byId[] fixes to reduce invalidation (#239) - w/ commits
Retaining individual commits to ease tracking and partial reverts in the future.
2021-11-09 07:04:38 +08:00
infinite-persistence
07750bfb4c
Fix memo: selectMyChannelClaims, selectActiveChannelClaim
## Issue
These should never recalculate after `channel_list` has been fetched, but they do because of poor selector dependency.

## Change
With the `byId` changes from the previous commit, we are now able to memoize these selectors correctly.
2021-11-09 07:03:20 +08:00
infinite-persistence
0736723200
Don't update 'byId' if no changes + add 'selectClaimWithId'
## Ticket
116 Claim store optimization ideas (reducing unnecessary renders)

## Changes
- Ignore things like `confirmations` so that already-fetched claims aren't invalidated and causes re-rendering. The `stringify` might look expensive, but the amount of avoided re-renders outweighs it. There might be faster ways to compare, though.

- With `byId[claimId]` references more stable now, memoized selectors can now use 'selectClaimWithId' to pick a specific claim to depend on, instead of 'byId' which changes on every update.
2021-11-09 07:03:20 +08:00
infinite-persistence
de2bec4425
Don't update 'pendingById' if no changes.
'pendingById' isn't frequently updated, but using it as a proof-of-concept to fix how reducers should be written to avoid unnecessary updates.

ImmutableJS apparently does all of this for us, but there are cons to using it as well, so using own wrappers for now.
2021-11-09 07:03:19 +08:00
infinite-persistence
121b0f0cd6
~~ Revert to allow restoring to commits ~~
This reverts commit c97cab0ebb.
2021-11-09 07:03:19 +08:00
infinite-persistence
c97cab0ebb
byId[] fixes to reduce invalidation (#239)
* Don't update 'pendingById' if no changes.

'pendingById' isn't frequently updated, but using it as a proof-of-concept to fix how reducers should be written to avoid unnecessary updates.

ImmutableJS apparently does all of this for us, but there are cons to using it as well, so using own wrappers for now.

* Don't update 'byId' if no changes + add 'selectClaimWithId'

## Ticket
116 Claim store optimization ideas (reducing unnecessary renders)

## Changes
- Ignore things like `confirmations` so that already-fetched claims aren't invalidated and causes re-rendering. The `stringify` might look expensive, but the amount of avoided re-renders outweighs it. There might be faster ways to compare, though.

- With `byId[claimId]` references more stable now, memoized selectors can now use 'selectClaimWithId' to pick a specific claim to depend on, instead of 'byId' which changes on every update.

* Fix memo: selectMyChannelClaims, selectActiveChannelClaim

## Issue
These should never recalculate after `channel_list` has been fetched, but they do because of poor selector dependency.

## Change
With the `byId` changes from the previous commit, we are now able to memoize these selectors correctly.
2021-11-08 12:25:29 -05:00
infinite-persistence
cfd67b1c8d
Allow moderators to delete comment (#235)
## Ticket
223 Add ability for delegated moderators to delete comments

## Changes
- Refactored doCommentAbandon's signature so we don't end up converting between "uri" and "Claim" several times and needing to lookup redux when the client can already provide us the exact values that we need.

- Pass the new moderator fields to the API.

- Remove the need to call 'makeSelectChannelPermUrlForClaimUri' since it's a simple field query when we already have the claim.
2021-11-08 12:22:40 -05:00
saltrafael
9138e508c6
[Markdown] Fixes Quote and Fixes Images not showing (#242)
* Refactor and fix blockquote filling the full message content

* Fix images not showing on markdown
2021-11-08 09:08:22 -05:00
maxime peabody
d7ada7904b
Fixes the play/pause on drag issue with the floating player. (#221)
I tried to use event.preventDefault on the click handler but that didn't 
work. So instead I'm using css 'pointer-events: none' to disable click 
events on the player while the player is being dragged.

https://github.com/OdyseeTeam/odysee-frontend/issues/206
2021-11-08 12:51:03 +01:00
infinite-persistence
0f1d4039a9
Use 'selectHasChannel' instead of the full 'selectMyChannelClaims'
- selectMyChannelClaims depends on `byId`, which currently is always invalidated per update, so it is not memoized.

- Most of the use-cases just needs the ID or the length of the array anyways, so avoid generating a Claim array (in selectMyChannelClaims) unnecessarily -- the client need to reduce it back down to IDs again :/

- The simpler boolean also removes the need to memoize the selector, which saves a bit of memory.
2021-11-08 15:02:44 +08:00
infinite-persistence
9c5fbe5521
Remove lbry-desktop strings that were accidentally brought in. 2021-11-06 21:39:35 +08:00
Thomas Zarebczan
cd0ec4dbcd
Add script for google ads 2021-11-05 21:01:22 -04:00
jessopb
238a64bca9
improve playlists display (#232)
* improve playlists display

* fix pagination

* reset page on filter button

* pagination updates if page param changes

* carry collection active tab to playlists page
2021-11-05 21:00:27 -04:00
saltrafael
fc2e2d2cfc
Stickers/emojis fall out / improvements (#220)
* Fix error logs

* Improve LBC sticker flow/clarity

* Show inline error if custom sticker amount below min

* Sort emojis alphabetically

* Improve loading of Images

* Improve quality and display of emojis and fix CSS

* Display both USD and LBC prices

* Default to LBC tip if creator can't receive USD

* Don't clear text-field after sticker is sent

* Refactor notification component

* Handle notifications

* Don't show profile pic on sticker livestream comments

* Change Sticker icon

* Fix wording and number rounding

* Fix blurring emojis

* Disable non functional emote buttons
2021-11-05 15:31:51 -04:00
mayeaux
7cae754867
smarter tab selection functionality (#231) 2021-11-05 15:27:43 -04:00
mayeaux
6cb011ff96
bugfix persisted state issue (#228)
* bugfix persisted state issue

* bugfix and also set defaults properly
2021-11-05 11:45:19 -04:00
infinite-persistence
21e1af8ce5
Handle huge superchat list #224 2021-11-04 17:03:35 +08:00
infinite-persistence
17903f6c15
Limit to 10 superchats initially; batch-resolve when opening full list. 2021-11-04 16:30:51 +08:00
infinite-persistence
a65e68d023
Comments: use the lighter selectMyClaimIdsRaw
`selectMyActiveClaims` includes pending claims, so it gets invalidated often.

For the case of comment-filtering, we don't care about pending or abandoned own claims.
2021-11-04 16:30:50 +08:00
infinite-persistence
61a2ed2583
Simplify superchat selectors - memo not required
... since there are no transformations.
2021-11-04 16:06:06 +08:00
infinite-persistence
4876ad8671
Don't invalidate myClaimIds everytime 2021-11-04 16:06:05 +08:00
infinite-persistence
59db2860d7
Comments: use the lighter selectMyClaimIdsRaw
`selectMyActiveClaims` includes `byId`, which gets invalidated on each resolve. Having this as an input selector breaks memoization.

For the case of comment-filtering, we don't really care about pending or abandoned own claims (I think), so just grab the raw IDs.
2021-11-04 16:06:04 +08:00
infinite-persistence
531a87e969
channelThumbnail: don't resolve if already in process 2021-11-04 16:04:29 +08:00
infinite-persistence
60a0d6d31a
Use replace instead of replaceAll for browser compatibility (#222)
Also, moved the `replaced` outside of the find-loop so that we don't re-run it each time.
2021-11-04 00:56:29 -04:00
Dan Peterson
11d3f88654
WIP: live stream kill switch (#209)
* WIP: live stream kill switch

* Update hint layout / style

* update livestream API endpoint

* use the no-cors option
2021-11-03 17:52:18 -04:00
Thomas Zarebczan
db12a4b991
Odysee specific changes and other misc improvements (#219) 2021-11-03 15:47:19 -04:00
Rodion Borisov
6d8e265f50
Transform route-leading menu items to hyperlinks (#191) 2021-11-03 15:50:03 +01:00
Florence Jay Munar
842431feaf
Update README.md (#215) 2021-11-03 09:40:16 -04:00
infinite-persistence
7b621b7417
Add option to hide buildUri warnings
No point for it to keep appearing if nobody cares?

Anyway, added option to hide it via environment variable for those who are annoyed by it.
2021-11-03 13:01:13 +08:00
infinite-persistence
3d1d448afb
Fix crash with ModalError
## Issue
There was one instance of ModalError that wasn't wrapped in the Suspense.

## Fix
- Moved `getModal` outside to make the code cleaner. Due to the length of `getModal`, I didn't notice the early return statement.
- Fix ModalError's Suspense.
2021-11-03 10:09:01 +08:00
infinite-persistence
bf0aac2339
URI parsing improvements (#207)
* Prevent multiple parseURI calls

## Ticket
129

## Issue
Code was shortened to use `isURIValid` during the consolidation. `isURIValid` calls `normalizeURI`, which calls another `parseURI`.

`parseURI` is pretty expensive.

## Approach
- Add optional parameter to `isURIValid` to skip the normalization.
- Set those that were converted during the consolidation to skip the normalization. Also covered a few other instances where it is obvious to me that normalization is not required.
- For the rest, I can't tell for sure if it's safe to remove the normalization, so the default `normalize=true` will leave things as is.

The whole `parseURI` probably needs a refactoring, or a few lighter version for specific needs.

* Simplify isURIEqual

## Issue
`parseURI` is too expensive to be used in a loop, plus `normalizeURI` itself is calling `parseURI`.

## Approach
Not sure if it covers all cases, but just try convert colons to hashes before comparing.
2021-11-02 12:37:53 -04:00
Dan Peterson
704452732a
Add hints if an error occurs subscribing to notifications (#143)
* Add hints if an error occurs subscribing to notifications

* Update import (type/linting issue)

* disable optimization for debugging

* Revert "disable optimization for debugging"

This reverts commit 5b837f94e97b7488a7dc565e7f74d399e19c286f.

* improve detection of notification support + improve ux / ui surrounding that

* update translations
2021-11-01 14:51:23 -04:00
jessopb
fa029e0c09
channel parsing bugfix (#199) 2021-11-01 09:58:28 -04:00
Florence Jay Munar
994a39b0df
Readme fixes (#192)
* Update README.md

* Update README.md

* Update --say-thank-you.md
2021-11-01 09:54:35 -04:00
infinite-persistence
56b800cd33
Fix 'secondary.js' code coverage
## Issue
95% of `secondary.js` is unused code.
  - It was meant to reduce network overhead by chunking up files needed after bootup, and also to reduce the number of `vendor-*.js` files.
  - But it ended up accidentally grabbing everything, defeating the purpose of code-splitting.
2021-11-01 15:25:40 +08:00
infinite-persistence
b8399f10b2
Fix lint/auto-formatting... 2021-11-01 15:23:08 +08:00
jessopb
0aa6cc7e5a
limit collections to show to 24 (#147)
* limit collections to show to 24

A user had many collections. 
Since we have a search field, we can limit to 24.

* const
2021-10-29 10:53:56 -04:00
jessopb
f956f9d2fe
Fix cover upload ux (#184)
* do not block submit on thumb or cover error

* improve cover upload UX

* p padding
2021-10-29 10:53:46 -04:00
infinite-persistence
08c6df434e
Add "Go Live" to mobile (#183)
* Add "Go Live" button to mobile menu

* Move "Go Live" all the way to the top
2021-10-29 09:04:43 -04:00
infinite-persistence
11bbd58e33
General-purpose "Confirm" modal
Added a re-usable "yes/no" confirmation modal where the client just sets the question string and gets a callback "OK" or "Cancel" is clicked.

It doesn't make sense to create one modal for each confirmation, especially when the modal is only used in one place.

Replaced one of the existing modal as an example.
2021-10-29 13:36:27 +08:00
saltrafael
4bfb4fb55d
Fix a wrong emote (#182) 2021-10-28 18:18:23 -04:00
saltrafael
5f1f702490
[New Feature] Stickers (#131)
* Refactor filePrice

* Refactor Wallet Tip Components

* Add backend sticker support for comments

* Add stickers

* Refactor commentCreate

* Add Sticker Selector and sticker comment creation

* Add stickers display to comments and hyperchats

* Fix wrong checks for total Super Chats
2021-10-28 16:25:34 -04:00
Thomas Zarebczan
a77e59cb53
Adjust video state clearing settings
What's strange is that this only occurs when you refresh odysee in between the plays. Might be a bug there.
2021-10-28 11:48:39 -04:00
infinite-persistence
4b0a06cef7
Ensure filter is not expanded when disabled (#153)
## Issue
- Go to a channel page
- Go to Wild West
- Back
- Expand the search filter (valid here)
- Forward

## Fix
Resolve the 'expanded' setting on mount to ensure it is never true when 'hideAdvancedFilter' is set.
2021-10-28 10:51:13 -04:00
mayeaux
00d28fe26e
fix bug where scrolling on video player page changes volume (#154) 2021-10-28 10:46:24 -04:00
infinite-persistence
5dd5826b33
doFetchSubCount: batch support; fetch interval gap;
1. The API supports batching -- updated the code to use that. Retained string as the parameter (instead of changing it to array) so that existing clients won't be affected.

2. Make `doFetchSubCount` a batched command by default through an idle timer. This way, none of the clients need to collect IDs -- it's all done behind the scenes.

3. Added minimum of 5 minutes between each sub-count fetch for a claim ID.
2021-10-28 13:16:06 +08:00
infinite-persistence
cbedc4b933
ClaimPreviewSubtitle: fetch sub count only for channels 2021-10-28 13:11:25 +08:00
infinite-persistence
6b39fc1bbb
Make it easier for the next person to add View Count in specific pages. 2021-10-28 13:11:24 +08:00
infinite-persistence
f8f9b86cb4
FileViewCountInline: fix incorrect logic
4a22814c broke the intention of if-block (it essentially breaks the functionality in Search page if we enable view counts there in the future).

It also seems completely unrelated to the PR.
2021-10-28 13:11:23 +08:00
jessopb
1a5fb5fa51
improve channel mentions (#146)
something like @; was crashing the app.
this should be better.
2021-10-27 21:21:40 -04:00
saltrafael
c24153c6ca
[New Feature] Comment Emotes (#125)
* Refactor form-field

* Create new Emote Menu

* Add Emotes

* Add Emote Selector and Emote Comment creation ability

* Fix and Split CSS
2021-10-27 14:20:47 -04:00
Dan Peterson
762bddb158
Don't instantiate messaging SDK if service worker is unavailable (Firefox/Private) (#142) 2021-10-27 13:34:50 -04:00
infinite-persistence
2922f0f2dc Revert "Code-split homepages"
This reverts commit 310fc81bd9.

Was breaking the `get` api
2021-10-28 01:33:46 +08:00
mayeaux
3849683a59
Lots of player UI improvements (#134)
* various control bar fixes

* fixes for mobile

* hide advertisement div by default

* fix duration bar

* more frontend touchups

* more styles

* fix for advertisement bar showing

* dont use ima on each re-render
2021-10-27 11:08:12 -04:00
infinite-persistence
247ee757d1
ChunkLoadError: ask user to reload instead of automatically reloading (#139)
## Issue
We previously automatically reload when there is a chunk error. This works fine if it's the case of new code was pushed recently while the user was active. But if the failure was caused by other things like network problems or the file IS actually missing, we end up in an infinite loop of refreshes.

## New approach
Tell the user to reload instead of automatically doing it.
2021-10-27 11:07:06 -04:00
Dan Peterson
03f69eff86
Browser push notifications (#133)
* fix type error

fix is subscribed check

- Persist subscription data locally
- add / remove subscription during log in / out
- Use store directly in hook

Add toast error if subscription fails

Revert removal of v2

hotfix linting issue

Add custom notification handler

- fix isSupported flag
- make icon color compatible with light/dark theme
- fix icon on notifications blocked banner

wip: add push notification banner to notifications page.

- ignore failed deletions via internal API
- add ua parsing package
- add more robust meta data to token save

refactor naming + add push toggle to notification button

shift some code around

update css naming o proper BEM notation

update notifications UI

remove now unneeded util function

Update push notification system to sue firebase sdk

separate service worker webpack bundling

update service worker to use firebase sdk

Add firebase config

Add firebase and remove filemanager

Stub out the basics for browser push notifications.

* fix safari

* try smaller image for badge

* add token validation with server, refactor code

* remove param

* add special icon for web notification badge

* add translations

* add missing trans for toast error

* add pushRequest method that will not prompt users who have subscribed but since disabled notifications in the settings.
2021-10-27 10:38:10 -04:00
Thomas Zarebczan
08adb805e9
Fix syntax 2021-10-27 09:55:57 -04:00
infinite-persistence
3788ef58ec
Analyze all .js files to get the full picture. 2021-10-27 09:19:52 +08:00
infinite-persistence
310fc81bd9
Code-split homepages
## Ticket
97

## Issue
8% of the ui.js chunk consists of the 5 custom homepages
2021-10-27 07:55:24 +08:00
infinite-persistence
9569b6c7a5
Upgrade videojs to 7.15.4 that ads is using. (#130)
## Issue
93 videojs double bundle
2021-10-26 17:48:22 -04:00
Thomas Zarebczan
0febd32c71
Revert "player background color (#86)" (#132)
This reverts commit e14ec9b83e.
2021-10-26 14:47:02 -04:00
infinite-persistence
a90c516c71
Reduce impact of scanning blocklists (#121)
## Issue
- Each tile was checking against 4 blocklists (blacklisted, filtered, muted, commentron) on every render. Loading the front-page with Cheese alone caused 1400 calls.
- This is also part of the reason why pressing Back into the tile list takes forever.

## Fix
Since we still need to perform the checks at the app side for now, tried to memoize the operation through a selector.
2021-10-25 10:56:31 -04:00
infinite-persistence
dad7264636
Handle re-reselect warning on null/undefined key
Should be a harmless warning, but cleaning up nonetheless.
2021-10-25 13:26:10 +08:00
infinite-persistence
27f346d8f1
Don't memoize selectors without transformation
It was not meant to be used for these cases -- wasting resources creating and going through the cache for each simple direct access.
2021-10-24 13:05:06 +08:00
infinite-persistence
e2176d0566
Don't connect to the Redux store when not needed.
The subscription still costs something per update cycle even when the parameters are null or empty objects.
2021-10-24 13:04:01 +08:00
GG2015
17121b2066
Wallet swap disabled (#107)
* Disabled wallet swap tab.

* Cleaned up UI since Swap is disabled.
2021-10-22 10:48:34 -04:00
infinite-persistence
1b43c54725
Defer blocklists slightly to not block me
Now, with the exception of connecting to lbry.com after re-opening the browser (i.e. establishing first connection), refreshing odysee.com is almost instantaneous.
2021-10-22 17:31:39 +08:00
infinite-persistence
398388de10
Track duration of startup events
Tracking only `user/me` for now.
2021-10-22 16:02:21 +08:00
infinite-persistence
b8c763f749
ClaimList: fix render due to un-memo'd callback. 2021-10-22 12:20:29 +08:00
infinite-persistence
b4f62e78de
Additional GA events via redux/lbryio hook (#110) 2021-10-22 11:07:48 +08:00
infinite-persistence
8c4224f1ce
createAnalyticsMiddleware: Handle 'BATCH_ACTIONS' 2021-10-22 10:56:44 +08:00
infinite-persistence
b7685a151d
Additional GA events via redux/lbryio hook
## Issue
85 Add additional GA events

## Approach
Instead of placing analytic calls all over the GUI code (no separation of concerns), try to do it through a redux middleware instead.

## Changes
- Updated GA event and parameter naming after understanding how reporting works.
- Removed unused analytics.
2021-10-22 10:56:43 +08:00
GG2015
e14ec9b83e
player background color (#86)
Changes player background color

Co-authored-by: Thomas Zarebczan <thomas.zarebczan@gmail.com>
2021-10-21 16:43:08 -04:00
Thomas Zarebczan
23525b0baa
FAQ stuff (#109) 2021-10-21 16:21:51 -04:00
Dan Peterson
d62f63aff8
delete duplicate flow type files (#105)
* delete duplicate flow type files

* merge types from deleted files

* revert dispatch to type any. (linting issues)
2021-10-20 13:33:31 -05:00
jessopb
dcd00c2308
Fix top search for channels (#104) 2021-10-20 12:55:21 -04:00
mayeaux
c782f73f30
switch macro (#102)
* switch macro

* allow skip and other options
2021-10-20 11:14:33 -04:00
infinite-persistence
6ff9a51058
Upgrade codemirror + module sharing
## Issue
Our `<CodeViewer>` and `react-simplemde-editor` uses `codemirror`, and they were each bundling a different version.

## Change
Re-generate yarn.lock for `codemirror`. Since we are upgrading anyway, upgraded to the latest and greatest.

## Test
- [x] Markdown editor -- looks ok. It inherited several fixes for code-blocks.
- [x] Code viewer -- looks ok.
2021-10-20 15:03:11 +08:00
infinite-persistence
9041e5e38d
Incremental selector memoization fixes (#92) 2021-10-20 12:24:07 +08:00
infinite-persistence
ce1621f7ed
Use selectClaimForUri in livestreams
Only picking components that are involved in a livestream for now. Ideally, all usages of `makeSelectClaimForUri` should be replaced -- will do it incrementally.
2021-10-20 11:29:18 +08:00
infinite-persistence
da63991972
Comment-selectors: fix memoization 2021-10-20 11:29:18 +08:00
infinite-persistence
b6ad4ae974
Comment-store: Don't memoize selectors without transformation 2021-10-20 11:29:17 +08:00
infinite-persistence
5d8fc40051
Cache restoreScrollPos to avoid render
`CommentCreate` was getting marked for every comment that comes in because the parent was marked.
2021-10-20 11:29:17 +08:00
infinite-persistence
4b0318cd38
Optimize tags and followedTags
followedTags:
- Moved the filtering to the reducer side, so that we don't do it every time. We can't rely on `createSelector` because the store will be invalidated on each `USER_STATE_POPULATE`, unfortunately.

tags:
- Memoize via re-reselect for the "ForUri" selector.
2021-10-20 11:29:16 +08:00
infinite-persistence
0c2c21b67e
re-reselect proof of concept + fix Date selector as first example
## Issue
`makeSelectDataForUri` always returns a new reference, so `ClaimPreview` was constantly being rendered. It's pretty expensive since `ClaimPreview`'s rendering checks against a huge blocklist, which is another issue on it's own.

## Changes
- This commit tests the usage of `re-reselect` as the solution to the multi-instance memoization problem (https://github.com/toomuchdesign/re-reselect/blob/master/examples/1-join-selectors.md)
2021-10-20 11:29:15 +08:00
infinite-persistence
9bbd72d179
Fix reaction-selector reference invalidation
## Issue
When comments are refreshed, each `Comment` gets rendered 4-5 times due to reference invalidation for `othersReacts` (the data didn't actually change).

## Change
For selectors without transformation, there is no need to memoize using `createSelector` -- just access it directly. Also, don't do things like `return a[id] || {}` in a reducer, because the reference to the empty object will be different on each call.

Always return directly from the state so that the same reference is returned.

This simple change avoided the wasted resources needed for `createSelector`, and reduced to render to just 2 (initial render, and when reactions are fetched).
2021-10-19 21:15:26 +08:00
infinite-persistence
249b73f8c6
Skip muted list update if no change
## Issue
Components render unnecessarily due to reference invalidation from `selectMutedChannels` selector.

## Notes
`selectMutedChannels` run and return a new reference each time the app gains focus. `createSelector` will not help in this case, because we are indeed invalidating the data in the store in `USER_STATE_POPULATE`.

## Changes
- Don't update the state if the array is identical in content.
- Fixed `selectMutedChannels` to return the reference from the store, so `createSelector` is not needed.
    - Also, the filtering is not needed because we've already done it in the reducer.

## Comments
I've done some profiling on large blocklists. The time needed for the array comparison is still an order magnitude lower than the time needed to render all the Components that got incorrectly marked by this.

The ideal solution is for the sync code to return a hash or timestamp of the array, so that we can compare that instead of the array.
2021-10-19 21:15:26 +08:00
infinite-persistence
aabfc41ce9
Remove unused props and selector calls. 2021-10-19 21:15:25 +08:00
infinite-persistence
5bcf89394e
Port redux/inc consolidation (#81) 2021-10-19 20:53:24 +08:00
infinite-persistence
35072c0400
Remove unused actions and test function.
The past-tense version of the PUBLISH_* action is no longer used.
2021-10-19 20:43:11 +08:00
infinite-persistence
296febcffa
Lint '/extras/*' + fixes
- Add `/extras` to the precommit hooks (lint, prettier).
- Remove `preinstall` since these modules don't exist anymore.

- Fix missing brace if one single-line if-statement.
2021-10-19 20:40:08 +08:00
infinite-persistence
cfdfdce2fe
Hush repetitive debug errors + remove from i18n 2021-10-19 20:40:07 +08:00
infinite-persistence
2a7f89d6b5
Post-merge updates and fixes
- Put back SETTINGS.LANGUAGE.
- Update import for `doResolveUris`.
2021-10-19 20:40:07 +08:00
Merge
30023422b8
Desktop cherry-pick: "7240 Integrate lbry redux and lbryinc" 2021-10-19 20:40:07 +08:00
infinite-persistence
702297e722
Spew 'analyze' results to 'web/dist' so that it doesn't appear in git. 2021-10-18 22:26:52 +08:00
infinite-persistence
07102c4988
Update 'yarn analyze' to do Web instead of Desktop
Since it might not be obvious to the user that we need a production build, added `yarn compile` to the step directly.
2021-10-18 22:00:44 +08:00
infinite-persistence
3b442531ef
Remove matomo + restore GA (#63) 2021-10-18 08:25:30 +08:00
infinite-persistence
f6e60abbf5
Convert to GA4 format
- It is recommended to use "lowercase + underscore format" for events to keep things neat, since the dashboard will be mixed with Automated and Recommended events.

- GA4 event structure is no longer the same as UA's, and the recommendation is to retructure rather than trying to mimic the old pattern.

- Always check the Recommended events to see if there is an equivalent, and use the exact name. GA4 might add automated features for these events in the future, and we'll benefit from it without code changes and invalidating existing data.

- pageView: use default snippet behavior instead of manually sending
Start converting to GA4...

- Outbound click are automatically handled.
2021-10-17 20:45:40 +08:00
infinite-persistence
dab1ca1cb7
Remove references to Desktop and lbry.tv 2021-10-17 20:45:39 +08:00
infinite-persistence
bba3a17977
Remove matomo + restore GA
Reverted/restored stuff from the following repo, with minimal modifications (trying to keep the diffs clean for future reference):
- lbry-desktop@5008972
- lbry-desktop@7fe88d8
2021-10-17 20:45:39 +08:00
GG2015
4a22814c75
Adds sub count to search and other areas. (#10)
Add follower counts to search
2021-10-16 14:12:09 -04:00
infinite-persistence
91be939c19
Fix linked-comment scrolling (again)
## Issue
Now that we batch-resolve the comment authors before displaying the comments, the linked-comment scrolling logic didn't work well with nested replies.

## Change
Previously, I didn't want to put the logic at the lowest level (`Comment`) because it was hard for the child to know whether to scroll or not. For example, we don't want to scroll when user changes the comment filters or presses the Refresh Comments button.

Relented and moved the logic to `Comment`, and pass a flag via `window` (I know this is frowned upon by some) to indicate whether a scrolling is needed.

This is probably more efficient overall as we don't need to scan the DOM, and with minimal delay as we scroll immediately after the linked-comment is mounted.

## Known issues
In markdown posts with lots of images, a layout shift due to delayed inline-image fetching can cause the scrolling to be inaccurate. This should be fixed by reserving space for markdown post images.
2021-10-16 13:40:33 +08:00
infinite-persistence
0b0f2848da
i18n - refix total comments
Meant to re-use strings, but I forgot to change the variable name.
2021-10-16 11:20:34 +08:00
Thomas Zarebczan
055d437865
fix total comments 2021-10-15 13:08:31 -04:00
infinite-persistence
d1493d5fb3
i18n 2021-10-15 14:23:40 +08:00
Thomas Zarebczan
b86a56f75b
Update README.md 2021-10-14 13:21:49 -04:00
infinite-persistence
8498554f23
Improve aesthetics for deleted channel names.
## Issue
- Comments: no spacing between the strings.
- "Unused" is not intuitive.

## Changes
- Use "[Removed]" instead.
- Localization.
2021-10-14 22:29:50 +08:00
infinite-persistence
2505d67a7d
[Comments] Batch fixes (#65) 2021-10-14 21:16:33 +08:00
saltrafael
03ea298236
Fix expanding comments and scroll pagination 2021-10-14 21:05:01 +08:00
saltrafael
a3302b1be8
Fix expansion broken with layout change 2021-10-14 21:05:00 +08:00
saltrafael
58db9576b9
Fix infinite resolve 2021-10-14 21:04:59 +08:00
saltrafael
a9b9c3ccf0
Revert "Revert "[Comments] Batch resolve" (#61)"
This reverts commit 0e96f8d468.
2021-10-14 21:04:58 +08:00
infinite-persistence
ea516f88dc Fix 'setting.Get' runaway calls
## Issue
60 setting.Get calls spiked since October

It was called 24 times per livestream page load.

## Notes
The effect was intended to be a one-time effect, but the dependency was changed in 2f4dedfb
2021-10-14 20:26:11 +08:00
mayeaux
5f55a3f128
use insecure mode (#74) 2021-10-14 11:55:46 +03:00
saltrafael
53063931ab
Fix markdown preview word break (#70) 2021-10-13 16:31:12 -04:00
mayeaux
6727e2766b
fix channel value (#67) 2021-10-13 19:07:57 +03:00
mayeaux
c10fc675db
fix channel value (#66) 2021-10-13 19:00:32 +03:00
mayeaux
fa889112c5
Ads setup (#62)
* re enable preload ads

* switch macro to aniview

* point towards test server

* improving documentation

* bugfix and turn skip back on

* only run twenty percent of the time for unauthed users

* allow for embeds

* enable show internal feature

* working prototype

* seems to work well

* bugfix

* review old aniview setup

* change to production channelid

* final touchups
2021-10-13 11:04:03 -04:00
infinite-persistence
0e96f8d468
Revert "[Comments] Batch resolve" (#61)
This reverts commit caadd889ce, reversing
changes made to 8b2c7a2b21.

## Issue
- Infinite `resolve` loop when deleted channel is present in the comments.
- Since it was only displayed comments with resolved channels, it masked away those comments. While that may or may not be regarded as a defect, I think we should do it at Commentron instead of at the app if we want to filter deleted channels. I vote to show comments from deleted channels, since it might have good conversation thread.
2021-10-13 08:59:32 -04:00
infinite-persistence
d5ad63c6e9
Comments: handle 'disable-support' tag (#59) 2021-10-13 15:24:55 +08:00
Bradley Ray
cd8f90c82d
added semicolon to import statement 2021-10-13 15:16:20 +08:00
Bradley Ray
24eb2ef8ec
change to import instead of redefining const 2021-10-13 15:16:19 +08:00
Bradley Ray
37ddc395ea
fixed disable-support for comments 2021-10-13 15:16:18 +08:00
Thomas Zarebczan
02a8099514
Take 99 2021-10-12 22:30:05 -04:00
Thomas Zarebczan
9dbee19961
Add test embed domain to rule out cards 2021-10-12 20:55:33 -04:00
Thomas Zarebczan
b49fed4cf5
fix config for thumbs
whoops

Fix encoded URLs
2021-10-12 20:31:10 -04:00
Thomas Zarebczan
2b5d32c313
Use cards.odysee.com (#56) 2021-10-12 19:16:39 -04:00
saltrafael
7c518aa712
[Comment/Livestream] Markdown and style fixes (#55)
* Fix CSS for live chat embeds

* Fix Markdown Lists in Comments

* Disable copy link menu option on livestream comments

* Fix nested indents in Live Chat

* Fix mentions and timestamps not parsed in bullet lists

* Highlight livestream comment and menu button on hover

* Fix mention parsing
2021-10-12 17:06:20 -04:00
infinite-persistence
6f3c43c95f
Fix recsys submission when user is null (#54)
## Issue
44 tor browser crash related to recsys?

## Reproduce the exact error
Block the request for `me|new` in dev tools

## Fix
The code was trying to destructure a null object.

The existing code seems to indicate that null ID is expected (it uses null as fallback), so this change shouldn't impact recsys results (I didn't check the recsys docs to confirm).
2021-10-12 12:10:35 -04:00
infinite-persistence
a168dbcc01
Fix livestream player height cut-off (#53) 2021-10-12 01:27:16 -04:00
Anthony
3087f7c367
list 100 transactions for fiat received and outgoing instead of 25 2021-10-11 12:57:59 -04:00
Thomas Zarebczan
3f969ae20d
Merge pull request #48 from OdyseeTeam/ip/rescys
Handle recsys crash `new|me` fails
2021-10-11 12:57:20 -04:00
infinite-persistence
19797c747e
Handle recsys crash new|me fails
## Issue
Potentially closes 44 "tor browser crash related to recsys?"
2021-10-11 15:34:01 +08:00
infinite-persistence
caadd889ce
[Comments] Batch resolve 2021-10-11 09:46:29 +08:00
796 changed files with 50145 additions and 39744 deletions

View file

@ -13,17 +13,20 @@ LBRY_WEB_STREAMING_API=https://cdn.lbryplayer.xyz
LBRY_WEB_BUFFER_API=https://collector-service.api.lbry.tv/api/v1/events/video 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_API=https://comments.odysee.com/api/v2
COMMENT_SERVER_NAME=Odysee COMMENT_SERVER_NAME=Odysee
SEARCH_SERVER_API_ALT=https://recsys.odysee.com/search
SEARCH_SERVER_API=https://lighthouse.odysee.com/search SEARCH_SERVER_API=https://lighthouse.odysee.com/search
SOCKETY_SERVER_API=wss://sockety.odysee.com/ws SOCKETY_SERVER_API=wss://sockety.odysee.com/ws
THUMBNAIL_CDN_URL=https://image-processor.vanwanet.com/optimize/ THUMBNAIL_CDN_URL=https://thumbnails.odysee.com/optimize/
WELCOME_VERSION=1.2 THUMBNAIL_CARDS_CDN_URL=https://cards.odysee.com/
THUMBNAIL_HEIGHT=220
THUMBNAIL_WIDTH=390
THUMBNAIL_QUALITY=85
WELCOME_VERSION=1.0
# STRIPE # STRIPE
# STRIPE_PUBLIC_KEY='pk_test_NoL1JWL7i1ipfhVId5KfDZgo' # STRIPE_PUBLIC_KEY='pk_test_NoL1JWL7i1ipfhVId5KfDZgo'
# Analytics # Analytics
MATOMO_URL=https://analytics.lbry.com/
MATOMO_ID=4
# OG # OG
OG_TITLE_SUFFIX=| lbry.tv OG_TITLE_SUFFIX=| lbry.tv
@ -35,19 +38,25 @@ SITE_CANONICAL_URL=https://lbry.tv
## Custom Site info ## Custom Site info
DOMAIN=lbry.tv DOMAIN=lbry.tv
URL=https://lbry.tv URL=https://lbry.tv
SITE_TITLE=LBRY SITE_TITLE=lbry.tv
SITE_NAME=LBRY SITE_NAME=lbry.tv
SITE_DESCRIPTION=Meet LBRY, an open, free, and community-controlled content wonderland. SITE_DESCRIPTION=Meet LBRY, an open, free, and community-controlled content wonderland.
SITE_HELP_EMAIL=help@lbry.com SITE_HELP_EMAIL=help@lbry.com
LOGO_TITLE=LBRY LOGO_TITLE=lbry.tv
CLOUD_CONNECT_SITE_NAME=Odysee
## Social media ## Social media
TWITTER_ACCOUNT=LBRYcom TWITTER_ACCOUNT=LBRYcom
BRANDED_SITE=odysee BRANDED_SITE=odysee
## OLD IMAGE ASSETS ## IMAGE ASSETS
#YRBL_HAPPY_IMG_URL=https://player.odysee.com/api/v3/streams/free/yrbl-happy/7aa50a7e5adaf48691935d55e45d697547392929/839d9a YRBL_HAPPY_IMG_URL=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-happy/7aa50a7e5adaf48691935d55e45d697547392929/839d9a
#YRBL_SAD_IMG_URL=https://player.odysee.com/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd YRBL_SAD_IMG_URL=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
#LOGIN_IMG_URL=https://cdn.lbryplayer.xyz/api/v3/streams/free/login/b671946e911c66c5fa7233afb35de2badd9eceb8/0e1d81
#LOGO=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
#LOGO_TEXT_LIGHT=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
#LOGO_TEXT_DARK=https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
#AVATAR_DEFAULT=
#MISSING_THUMB_DEFAULT=
#FAVICON=
# LOCALE # LOCALE
DEFAULT_LANGUAGE=en DEFAULT_LANGUAGE=en
@ -72,8 +81,8 @@ SIMPLE_SITE=false
#BRANDED_SITE #BRANDED_SITE
ENABLE_COMMENT_REACTIONS=true ENABLE_COMMENT_REACTIONS=true
ENABLE_FILE_REACTIONS=true ENABLE_FILE_REACTIONS=false
ENABLE_CREATOR_REACTIONS=true ENABLE_CREATOR_REACTIONS=false
ENABLE_NO_SOURCE_CLAIMS=false ENABLE_NO_SOURCE_CLAIMS=false
ENABLE_PREROLL_ADS=false ENABLE_PREROLL_ADS=false
CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS=4 CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS=4
@ -82,7 +91,7 @@ WEB_PUBLISH_SIZE_LIMIT_GB=4
LOADING_BAR_COLOR=#2bbb90 LOADING_BAR_COLOR=#2bbb90
LIGHTHOUSE_DEFAULT_TYPES=audio,video,text,image,application LIGHTHOUSE_DEFAULT_TYPES=audio,video,text,image,application
SHOW_ADS=false SHOW_ADS=true
## SIMPLE_SITE REPLACEMENTS ## SIMPLE_SITE REPLACEMENTS
ENABLE_MATURE=true ENABLE_MATURE=true
@ -107,3 +116,13 @@ ENABLE_UI_NOTIFICATIONS=false
#MODELS_ENABLED=true #MODELS_ENABLED=true
BRANDED_SITE=odysee 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

View file

@ -2,9 +2,7 @@
.*\.typeface\.json .*\.typeface\.json
.*/node_modules/findup/.* .*/node_modules/findup/.*
.*/node_modules/react-plastic/.* .*/node_modules/react-plastic/.*
.*/node_modules/raf-schd/.*
.*/node_modules/react-beautiful-dnd/.*
.*/node_modules/resolve/test/.*
[include] [include]
@ -37,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='^web\/page\(.*\)$' -> '<PROJECT_ROOT>/web/page\1'
module.name_mapper='^homepage\(.*\)$' -> '<PROJECT_ROOT>/ui/util/homepage\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='^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 esproposal.optional_chaining=enable

View file

@ -1,22 +1,22 @@
--- ---
name: "❤Say thank you" name: "❤Say thank you"
about: If you enjoy using the LBRY app, let us know! about: If you enjoy using Odysee's website, let us know!
title: LBRY rocks! title: Odysee rocks!
labels: '' labels: ''
assignees: '' 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. If you would like to help Nock - any of the following is greatly appreciated.
- Give the repository a star ⭐️ - Give the repository a star ⭐️
- Help out with issues - Help out with issues
- Blog about LBRY - Blog about Odysee
- Make tutorials - Make tutorials
- Give talks - Give talks
- Convince other people to use LBRY - Convince other people to use Odysee
- Anything your heart desires - Anything your heart desires
Thank you! 💐 Thank you! 💐

View file

@ -1,127 +0,0 @@
name: Node.js CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
lint:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: corepack enable
- run: yarn
- run: yarn lint
build:
needs: ['lint']
name: 'build'
strategy:
matrix:
node-version: [16.x]
os:
- ubuntu-latest
- macos-latest
- windows-latest
runs-on: ${{ matrix.os }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: corepack enable
- uses: maxim-lobanov/setup-xcode@v1
if: startsWith(runner.os, 'mac')
with:
xcode-version: '13.1.0'
# This is gonna be hacky.
# Github made us upgrade xcode, which would force an upgrade of electron-builder to fix mac.
# But there were bugs with copyfiles / extraFiles that kept seeing duplicates erroring on ln.
# A flag USE_HARD_LINKS=false in electron-builder.json was suggested in comments, but that broke windows builds.
# So for now we'll install python2 on mac and make sure it can find it.
# Remove this after successfully upgrading electron-builder.
# HACK part 1
- uses: Homebrew/actions/setup-homebrew@master
if: startsWith(runner.os, 'mac')
# HACK part 2
- name: Install Python2
if: startsWith(runner.os, 'mac')
run: |
/bin/bash -c "$(curl -fsSL https://github.com/alfredapp/dependency-scripts/raw/main/scripts/install-python2.sh)"
echo "PYTHON_PATH=/usr/local/bin/python" >> $GITHUB_ENV
- name: Download blockchain headers
run: |
mkdir -p ./static/daemon
curl -o ./static/daemon/headers https://headers.lbry.io/blockchain_headers_latest
ls ./static/daemon
- name: Build
run: |
yarn dlx cross-env
yarn --network-timeout 600000
yarn build
env:
GH_TOKEN: ${{ secrets.GH_TOKEN_NEW }}
NOTARIZATION_USERNAME: ${{ secrets.NOTARIZATION_USERNAME }}
NOTARIZATION_PASSWORD: ${{ secrets.NOTARIZATION_PASSWORD }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
WIN_CSC_LINK: https://raw.githubusercontent.com/lbryio/lbry-desktop/master/build/cert2023.pfx
CSC_LINK: https://s3.amazonaws.com/files.lbry.io/cert/osx-csc-2021-2022.p12
# UI
MATOMO_URL: https://analytics.lbry.com/
MATOMO_ID: 4
WELCOME_VERSION: 1.0
DOMAIN: lbry.tv
URL: https://lbry.tv
SHARE_DOMAIN_URL: https://open.lbry.com
SITE_TITLE: lbry.tv
SITE_NAME: lbry.tv
SHOW_ADS: false
ENABLE_COMMENT_REACTIONS: true
ENABLE_NO_SOURCE_CLAIMS: false
DEFAULT_LANGUAGE: en
KNOWN_APP_DOMAINS: lbry.tv,lbry.lat,odysee.com
CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS: 0
- uses: actions/upload-artifact@v2.2.4
if: |
startsWith(runner.os, 'linux')
with:
name: Linux
path: ./dist/electron/*.*
- uses: actions/upload-artifact@v2.2.4
if: |
startsWith(runner.os, 'mac')
with:
name: macOS
path: ./dist/electron/*.*
- uses: actions/upload-artifact@v2.2.4
if: |
startsWith(runner.os, 'windows')
with:
name: Windows
path: ./dist/electron/*.*
- uses: jakejarvis/s3-sync-action@master
if: |
startsWith(runner.os, 'linux')
with:
args: --acl public-read --follow-symlinks --exclude '*' --include '*.deb' --include '*.AppImage' --include '*.dmg'
env:
AWS_S3_BUCKET: ${{ secrets.ARTIFACTS_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.ARTIFACTS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.ARTIFACTS_SECRET }}
AWS_REGION: 'us-east-1'
SOURCE_DIR: 'dist/electron'
DEST_DIR: 'app/release'

57
.github/workflows/node.js.yml vendored Normal file
View file

@ -0,0 +1,57 @@
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
lint:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: Borales/actions-yarn@v2.3.0
- run: yarn lint
build:
needs: ['lint']
name: 'build'
strategy:
matrix:
node-version: [14.x]
os:
- ubuntu-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2-beta
with:
node-version: ${{ matrix.node-version }}
- name: Build
run: |
yarn global add cross-env
yarn
yarn compile:web
env:
# UI
WELCOME_VERSION: 1.0
DOMAIN: odysee.com
URL: https://odysee.com
SHARE_DOMAIN_URL: https://odysee.com
SITE_TITLE: Odysee
SITE_NAME: Odysee
SHOW_ADS: false
YRBL_HAPPY_IMG_URL: https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-happy/7aa50a7e5adaf48691935d55e45d697547392929/839d9a
YRBL_SAD_IMG_URL: https://cdn.lbryplayer.xyz/api/v3/streams/free/yrbl-sad/c2d9649633d974e5ffb503925e1f17d951f1bd0f/f262dd
ENABLE_COMMENT_REACTIONS: true
ENABLE_NO_SOURCE_CLAIMS: true
DEFAULT_LANGUAGE: en
KNOWN_APP_DOMAINS: lbry.tv,lbry.lat,odysee.com
CHANNEL_STAKED_LEVEL_VIDEO_COMMENTS: 4

9
.gitignore vendored
View file

@ -33,12 +33,7 @@ package-lock.json
!/custom/robots.disallowall !/custom/robots.disallowall
!/custom/robots.allowall !/custom/robots.allowall
.env .env
!.env.ody .env.ody
.env.desktop .env.desktop
.env.lbrytv .env.lbrytv
.yarn/* analyzeResults*.html
!.yarn/patches
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
!.yarn/releases

View file

@ -1,7 +1,11 @@
{ {
"linters": { "linters": {
"ui/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"], "ui/**/*.{js,jsx,scss,json}": ["prettier --write", "git add"],
"ui/**/*.{js,jsx}": ["eslint", "flow focus-check --color always", "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", "dist/**/*", "package-lock.json"] "ignore": ["node_modules", "web/dist/**/*", "dist/**/*", "package-lock.json"]
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

View file

@ -1,7 +0,0 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
spec: "@yarnpkg/plugin-version"
yarnPath: .yarn/releases/yarn-3.2.0.cjs

View file

@ -1,239 +1,25 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [0.53.9] - [2023-2-8] ## [Unreleased for Desktop]
### Changed
- Updated lbrynet to [0.113.0](https://github.com/lbryio/lbry-sdk/releases/tag/v0.113.0)
## [0.53.8] - [2022-11-17]
### Fixed
- Selecting a large file in publish no longer crashes ([#7736](https://github.com/lbryio/lbry-desktop/pull/7736))
- Unfollowing unpublished channels ([#7737](https://github.com/lbryio/lbry-desktop/pull/7737))
### Changed
- Updated xcode to 13.1 and hacked a fix for release ([#7736](https://github.com/lbryio/lbry-desktop/pull/7736))
## [0.53.7] - [2022-11-10]
### Added ### Added
- 'Collections' to txo filter _community pr!_ ([#7711](https://github.com/lbryio/lbry-desktop/pull/7711)) - Added direct replying to notifications _community pr!_ ([#6935](https://github.com/lbryio/lbry-desktop/pull/6935))
- Swap comment servers _community pr!_ ([#7670](https://github.com/lbryio/lbry-desktop/pull/7670))
### Fixed
- Thumbnails no longer disable publish ([#7714](https://github.com/lbryio/lbry-desktop/pull/7714))
- Publishing posts were empty ([#7715](https://github.com/lbryio/lbry-desktop/pull/7715))
- Minor layout fixes _community pr!_ ([#7709](https://github.com/lbryio/lbry-desktop/pull/7709))
- Comment section buttons layout ([#7716](https://github.com/lbryio/lbry-desktop/pull/7716))
### Changed
- Removed watchman and its errors ([#7710](https://github.com/lbryio/lbry-desktop/pull/7710))
- Updated lbrynet to [0.112.0](https://github.com/lbryio/lbry-sdk/releases/tag/v0.112.0)
## [0.53.6] - [2022-10-21]
### Fixed
- Make thumbnails optional ([#7690](https://github.com/lbryio/lbry-desktop/pull/7690))
- Show downloads newest first ([#7684](https://github.com/lbryio/lbry-desktop/pull/7684))
- Only allow images in image uploader ([#7672](https://github.com/lbryio/lbry-desktop/pull/7672))
- Fixed bug with csv exports ([#7697](https://github.com/lbryio/lbry-desktop/pull/7697))
- Fixed various upload bugs including transcoding ([#7688](https://github.com/lbryio/lbry-desktop/pull/7688))
- Fallback for files with no extension ([#7704](https://github.com/lbryio/lbry-desktop/pull/7704))
### Changed
- Upgraded Electron to v17.2.0 ([#7703](https://github.com/lbryio/lbry-desktop/pull/7703))
- Upgraded Electron to v17.0.0 ([#7691](https://github.com/lbryio/lbry-desktop/pull/7691))
- Updated lbrynet to [0.111.0](https://github.com/lbryio/lbry-sdk/releases/tag/v0.111.0)
## [0.53.5] - [2022-08-26]
### Added
- Checkbox to disable background wallpaper ([#7630](https://github.com/lbryio/lbry-desktop/pull/7630))
- Handle content blocking from hub ([#7665](https://github.com/lbryio/lbry-desktop/pull/7665))
### Fixed
- Better handle decimals liquidating supports ([#7648](https://github.com/lbryio/lbry-desktop/pull/7648))
- Better handle cover uploads ([#7647](https://github.com/lbryio/lbry-desktop/pull/7647))
- Use default path when first choosing file on windows ([#7625](https://github.com/lbryio/lbry-desktop/pull/7625))
- Emoji button hover ([#7620](https://github.com/lbryio/lbry-desktop/pull/7620))
- Prevent infinite retries on thumbs ([#7618](https://github.com/lbryio/lbry-desktop/pull/7618))
- Double splash/error on app startup ([#7615](https://github.com/lbryio/lbry-desktop/pull/7615))
- App updates are now more coherent, also debs work. ([#7502](https://github.com/lbryio/lbry-desktop/pull/7502))
- Better handle many channels moderation calls at startup ([#7674](https://github.com/lbryio/lbry-desktop/pull/7674))
- Fix mobile floating viewer position ([#7677](https://github.com/lbryio/lbry-desktop/pull/7677))
### Changed
- Upgraded Electron to v15.5.5 ([#7614](https://github.com/lbryio/lbry-desktop/pull/7614))
- Upgraded to lbrynet v0.110.0 ([#7680](https://github.com/lbryio/lbry-desktop/pull/7680))
## [0.53.4] - [2022-06-10]
### Added
- Add top in language category for non-english on homepage ([#7585](https://github.com/lbryio/lbry-desktop/pull/7585))
- Auto hosting in settings and hosting first run page ([#7598](https://github.com/lbryio/lbry-desktop/pull/7598))
### Changed
- Updated lbry-sdk to [0.107.2](https://github.com/lbryio/lbry-sdk/releases/tag/v0.107.2)
### Fixed
- Better handle empty collections ([#7571](https://github.com/lbryio/lbry-desktop/pull/7571))
- Better handle thumbnails in uploads/collections ([#7574](https://github.com/lbryio/lbry-desktop/pull/7574))
- Work towards supporting collections of any claim type ([#7578](https://github.com/lbryio/lbry-desktop/pull/7578))
- Improve handling of downed custom servers on startup ([#7593](https://github.com/lbryio/lbry-desktop/pull/7593))
- Hide watch progress in related if being played ([#7606](https://github.com/lbryio/lbry-desktop/pull/7606))
- IPC disk space calls wait for daemon ready; refresh on vis. component load ([#7610](https://github.com/lbryio/lbry-desktop/pull/7610))
## [0.53.3] - [2022-04-27]
### Fixed
- Reverted lbry.tv changes that broke production login ([#7569](https://github.com/lbryio/lbry-desktop/pull/7569))
- Reverted lbry.tv changes that broke login ([#7570](https://github.com/lbryio/lbry-desktop/pull/7570))
## [0.53.2] - [2022-04-26]
### Changed
- Upgraded Yarn to Berry branch ([#7530](https://github.com/lbryio/lbry-desktop/pull/7530))
- Removed some lbrytv references ([#7560](https://github.com/lbryio/lbry-desktop/pull/7560))
- Removed some lbrytv player references ([#7552](https://github.com/lbryio/lbry-desktop/pull/7552))
### Fixed
- Repost style issues ([#7559](https://github.com/lbryio/lbry-desktop/pull/7559))
- Disappearing sidebar thumbs ([#7556](https://github.com/lbryio/lbry-desktop/pull/7556))
- Restore tags sidebar link ([#7555](https://github.com/lbryio/lbry-desktop/pull/7555))
- Playlist view link no longer crashes ([#7552](https://github.com/lbryio/lbry-desktop/pull/7552))
## [0.53.1] - [2022-04-22]
### Added
- Uploads: show placeholder when loading page _community pr!_ ([#7531](https://github.com/lbryio/lbry-desktop/pull/7531))
- Sidebar channel search _styles pr_ ([#7542](https://github.com/lbryio/lbry-desktop/pull/7542))
- Viewed content progress indicator on thumbnail part 1 ([#7541](https://github.com/lbryio/lbry-desktop/pull/7541))
- Viewed content progress indicator on thumbnail part 2 ([#7547](https://github.com/lbryio/lbry-desktop/pull/7547))
- Ability to search through publishes ([#7535](https://github.com/lbryio/lbry-desktop/pull/7535))
### Changed
- Large styles revamp following odysee _styles pr_ ([#7542](https://github.com/lbryio/lbry-desktop/pull/7542))
### Fixed
- Fix bad rerender on homepage _styles pr_ ([#7542](https://github.com/lbryio/lbry-desktop/pull/7542))
- Fix post-editor preview mode _community pr!_ ([#7532](https://github.com/lbryio/lbry-desktop/pull/7532))
- Fix send-tip default tab ([#7533](https://github.com/lbryio/lbry-desktop/pull/7533))
## [0.52.6] - [2022-04-04]
### Added
- Discover page medium duration filter ([#7506](https://github.com/lbryio/lbry-desktop/pull/7506))
- Keep last used collection for Add To ([#7491](https://github.com/lbryio/lbry-desktop/pull/7491))
- Disk space functionality on mac / windows ([#7500](https://github.com/lbryio/lbry-desktop/pull/7500))
- Enable renaming private collections ([#7519](https://github.com/lbryio/lbry-desktop/pull/7519))
### Changed
- Some upgrade modal improvements ([#7488](https://github.com/lbryio/lbry-desktop/pull/7488))
- Updated lbry-sdk to [0.107.1](https://github.com/lbryio/lbry-sdk/releases/tag/v0.107.1)
- New YRBL!; facelift for first run ([#7527](https://github.com/lbryio/lbry-desktop/pull/7527))
### Fixed
- Failed comment count increment ([#7510](https://github.com/lbryio/lbry-desktop/pull/7510))
- App crash playing media on older windows versions by updating electron ([#7509](https://github.com/lbryio/lbry-desktop/pull/7509))
- Local build failures on mac ([#7497](https://github.com/lbryio/lbry-desktop/pull/7497))
- Language change now rerenders whole app ([#7504](https://github.com/lbryio/lbry-desktop/pull/7504))
- Mac notarization ([#7518](https://github.com/lbryio/lbry-desktop/pull/7518))
- Prevent crash when deleting last comment reply ([#7526](https://github.com/lbryio/lbry-desktop/pull/7526))
## [0.52.5] - [2022-02-25]
### Fixed
- New data hosting ux ([#7493](https://github.com/lbryio/lbry-desktop/pull/7493))
- Fix markdown guide button ([#7485](https://github.com/lbryio/lbry-desktop/pull/7485))
## [0.52.4] - [2022-02-15]
### Fixed
- Fixed active channel ([#7481](https://github.com/lbryio/lbry-desktop/pull/7481))
- Remove extra search button in header ([#7482](https://github.com/lbryio/lbry-desktop/pull/7482))
## [0.52.3] - [2022-02-15]
### Fixed
- Fixed comment editing and pinning ([#7476](https://github.com/lbryio/lbry-desktop/pull/7476))
- Fixed mac header ([#7479](https://github.com/lbryio/lbry-desktop/pull/7479))
- Fixed markdown display and lbry url embedding ([#7474](https://github.com/lbryio/lbry-desktop/pull/7474))
## [0.52.2] - [2022-02-11]
### Added
- Reenabled generating thumbs from video ([#7384](https://github.com/lbryio/lbry-desktop/pull/7409))
- Brought in playlist drag and drop playlist reordering _odysee team!_ ([#7442](https://github.com/lbryio/lbry-desktop/pull/7442))
- Added duration overlays to ClaimPreview component ([#7420](https://github.com/lbryio/lbry-desktop/pull/7420))
- Some Horizontal Scroll groundwork from _odysee team!_
- Comment Emotes and Stickers and Mentions refactors from _odysee team!_ ([#7435](https://github.com/lbryio/lbry-desktop/pull/7435))
- Seek forward and back from _odysee team!_ () ([#7460](https://github.com/lbryio/lbry-desktop/pull/7460))
### Changed
- Upgraded Electron to v15 ([#7384](https://github.com/lbryio/lbry-desktop/pull/7384))
- Performance improvements in some selectors ([#7370](https://github.com/lbryio/lbry-desktop/pull/7370))
- More Header refactoring from _odysee team!_ ([#7441](https://github.com/lbryio/lbry-desktop/pull/7441))
- Header refactoring from _odysee team!_ ([#7440](https://github.com/lbryio/lbry-desktop/pull/7440))
- Data hosting ui _incomplete_ ([#7438](https://github.com/lbryio/lbry-desktop/pull/7438))
- Updated c: control tags from _odysee team!_ ([#7433](https://github.com/lbryio/lbry-desktop/pull/7433))
- Nav keycodes (alt+left) no longer navigate while textarea is focused ([#7458](https://github.com/lbryio/lbry-desktop/pull/7458))
- Improved comment-server selection ui/ux ([#7455](https://github.com/lbryio/lbry-desktop/pull/7455))
- Improved Data Hosting settings ([#7563](https://github.com/lbryio/lbry-desktop/pull/7563))
### Fixed
- Several fallout bugs from recent changes
## [0.52.1]
### Skipped patch version
## [0.52.0] - [2021-12-31]
### Compatibility
- Mac <= 10.13 (High Sierra) and Ubuntu <= 16 (Xenial) are no longer supported. If you upgrade, you will need to manually build and install your own lbrynet SDK
### Added
- Direct replying to notifications _community pr!_ ([#6935](https://github.com/lbryio/lbry-desktop/pull/6935))
- Added "Replay" option on autoplay countdown ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921)) - Added "Replay" option on autoplay countdown ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Added "Loop" option on Lists ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921)) - Added "Loop" option on Lists ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Added "Shuffle" option on Lists ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921)) - Added "Shuffle" option on Lists ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Added Play Next/Previous buttons (with shortcuts SHIFT+N/SHIFT+P) ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921)) - Added Play Next/Previous buttons (with shortcuts SHIFT+N/SHIFT+P) ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Separate control for autoplay next on video player ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921)) - Added separate control for autoplay next on video player ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Channel Mention selection ability while creating a comment ([#7151](https://github.com/lbryio/lbry-desktop/pull/7151)) - Added Channel Mention selection ability while creating a comment ([#7151](https://github.com/lbryio/lbry-desktop/pull/7151))
- Disk space setting under Data Hosting ([#7266](https://github.com/lbryio/lbry-desktop/pull/7266))
- Paginated 'All Playlists' page ([#7268](https://github.com/lbryio/lbry-desktop/pull/7268))
- Expanded playlist ordering tools ([#7305](https://github.com/lbryio/lbry-desktop/pull/7305))
- Setting to upgrade to alpha prerelease builds ([#7353](https://github.com/lbryio/lbry-desktop/pull/7353))
### Changed ### Changed
- Changing the supported language from Filipino to Tagalog _community pr!_ ([#6951](https://github.com/lbryio/lbry-desktop/pull/6951)) - Changing the supported language from Filipino to Tagalog _community pr!_ ([#6951](https://github.com/lbryio/lbry-desktop/pull/6951))
- Don't show countdown to next item in list ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921)) - Don't show countdown to next item in list ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Changed "View List" popup option to link, so can be opened on a new tab ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921)) - Changed "View List" popup option to link, so can be opened on a new tab ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- App reorganized to remove lbry-redux and lbryinc repository dependencies ([#7240](https://github.com/lbryio/lbry-desktop/pull/7240))
- Styling cleanup for file reactions ([#7251](https://github.com/lbryio/lbry-desktop/pull/7251))
- Change share url to odysee and allow custom share url in settings ([#7258](https://github.com/lbryio/lbry-desktop/pull/7258))
- Change Sign in/up to Cloud Connect for Odysee ([#7260](https://github.com/lbryio/lbry-desktop/pull/7260))
- Upgraded to lbrynet v0.106.0 ([#7315](https://github.com/lbryio/lbry-desktop/pull/7315))
- Upgraded Electron to v11.5.0 ([#7276](https://github.com/lbryio/lbry-desktop/pull/7276))
- Cleaner Discover page filters ([#7306](https://github.com/lbryio/lbry-desktop/pull/7306))
- Scroll bar styling ([#7314](https://github.com/lbryio/lbry-desktop/pull/7314))
- Remove pages for obsolete features like invites, rewards, swap ([#7330](https://github.com/lbryio/lbry-desktop/pull/7330))
- Change file repost to modal _community pr!_ ([#7341](https://github.com/lbryio/lbry-desktop/pull/7341))
### Fixed ### Fixed
- Clicking on the title of a floating player will take you back to the list ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921)) - Clicking on the title of a floating player will take you back to the list ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Fix floating player stopping on markdown or image files ([#7073](https://github.com/lbryio/lbry-desktop/pull/7073)) - Fix floating player stopping on markdown or image files ([#7073](https://github.com/lbryio/lbry-desktop/pull/7073))
- Fix list thumbnail upload ([#7074](https://github.com/lbryio/lbry-desktop/pull/7074)) - Fix list thumbnail upload ([#7074](https://github.com/lbryio/lbry-desktop/pull/7074))
@ -241,14 +27,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix playlist preview thumbnail ([#7178](https://github.com/lbryio/lbry-desktop/pull/7178) - 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)) - 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)) - Fix disable-support for comments ([#7245](https://github.com/lbryio/lbry-desktop/pull/7245))
- Fix Electron taking over .html files on linux ([#7291](https://github.com/lbryio/lbry-desktop/pull/7291))
- Fix floating player play/pause on drag _community pr!_ ([#7339](https://github.com/lbryio/lbry-desktop/pull/7339))
- Fix card dropdown menus triggering menu actions _community pr!_ ([#7335](https://github.com/lbryio/lbry-desktop/pull/7335))
## [0.51.2] - [2021-08-20] ## [0.51.2] - [2021-08-20]
### Added ### Added
- Show currently active playing item on playlist _community pr!_ ([#6453](https://github.com/lbryio/lbry-desktop/pull/6453)) - Show currently active playing item on playlist _community pr!_ ([#6453](https://github.com/lbryio/lbry-desktop/pull/6453))
- Add watch later to hover action for last used playlist on popup _community pr!_ ([#6274](https://github.com/lbryio/lbry-desktop/pull/6274)) - Add watch later to hover action for last used playlist on popup _community pr!_ ([#6274](https://github.com/lbryio/lbry-desktop/pull/6274))
- Add confirmation on comment removal _community pr!_ ([#6563](https://github.com/lbryio/lbry-desktop/pull/6563)) - Add confirmation on comment removal _community pr!_ ([#6563](https://github.com/lbryio/lbry-desktop/pull/6563))
@ -256,7 +38,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add filtering to playlists ([#6905](https://github.com/lbryio/lbry-desktop/pull/6905)) - Add filtering to playlists ([#6905](https://github.com/lbryio/lbry-desktop/pull/6905))
### Changed ### Changed
- Use Canonical Url for copy link ([#6500](https://github.com/lbryio/lbry-desktop/pull/6500)) - Use Canonical Url for copy link ([#6500](https://github.com/lbryio/lbry-desktop/pull/6500))
- Use better icon for copy link ([#6485](https://github.com/lbryio/lbry-desktop/pull/6485)) - Use better icon for copy link ([#6485](https://github.com/lbryio/lbry-desktop/pull/6485))
- Comments load paginated ([#6390](https://github.com/lbryio/lbry-desktop/pull/6390)) - Comments load paginated ([#6390](https://github.com/lbryio/lbry-desktop/pull/6390))
@ -267,7 +48,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Improved clickability of notification links _community pr!_ ([#6711](https://github.com/lbryio/lbry-desktop/pull/6711)) - Improved clickability of notification links _community pr!_ ([#6711](https://github.com/lbryio/lbry-desktop/pull/6711))
### Fixed ### Fixed
- App now supports '#' and ':' for claimId separator ([#6496](https://github.com/lbryio/lbry-desktop/pull/6496)) - App now supports '#' and ':' for claimId separator ([#6496](https://github.com/lbryio/lbry-desktop/pull/6496))
- Fix "exact match" being applied to Recommended ([#6460](https://github.com/lbryio/lbry-desktop/pull/6460)) - Fix "exact match" being applied to Recommended ([#6460](https://github.com/lbryio/lbry-desktop/pull/6460))
- Fix upload button on creator analytics _community pr!_ ([#6458](https://github.com/lbryio/lbry-desktop/pull/6458)) - Fix upload button on creator analytics _community pr!_ ([#6458](https://github.com/lbryio/lbry-desktop/pull/6458))
@ -293,13 +73,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed ### Changed
### Fixed ### Fixed
- Enable sign up on desktop ([#6071](https://github.com/lbryio/lbry-desktop/issues/6071)) - Enable sign up on desktop ([#6071](https://github.com/lbryio/lbry-desktop/issues/6071))
## [0.51.0] - [2021-06-26] ## [0.51.0] - [2021-06-26]
### Added ### Added
- Private and Publishable Playlists ([#6157](https://github.com/lbryio/lbry-desktop/pull/6157)) - Private and Publishable Playlists ([#6157](https://github.com/lbryio/lbry-desktop/pull/6157))
- Channel thumbnails in following side menu ([#6193](https://github.com/lbryio/lbry-desktop/pull/6193)) - Channel thumbnails in following side menu ([#6193](https://github.com/lbryio/lbry-desktop/pull/6193))
- Web is now PWA app ([#6120](https://github.com/lbryio/lbry-desktop/pull/6120)) - Web is now PWA app ([#6120](https://github.com/lbryio/lbry-desktop/pull/6120))
@ -957,7 +735,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added ### Added
- Channels page above Publishes which lists all your channels ([#2925](https://github.com/lbryio/lbry-desktop/pull/2925)) - 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)) - 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)) - 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) - 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)
@ -1112,7 +890,7 @@ This release includes a breaking change that will reset many of your settings. T
### Added ### 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)) - 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)) - 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)) - Tagging content on publish page ([#2593](https://github.com/lbryio/lbry-desktop/pull/2593))

122
README.md
View file

@ -1,11 +1,7 @@
<img width="40%" src="https://miro.medium.com/max/5198/1*bTVuL2THG_0mpwmE-n7Ezg.png" />
# LBRY App # Odysee Frontend - Odysee.com
This repo contains the UI code that powers the official LBRY desktop app. The LBRY app is a graphical browser for the decentralized content marketplace provided by the This repo contains the UI and front end code that powers Odysee.com.
[LBRY](https://lbry.com) protocol. It is essentially the
[lbry daemon](https://github.com/lbryio/lbry) bundled with a UI using
[Electron](https://electron.atom.io/).
<a href="https://github.com/lbryio/lbry-desktop/blob/master/LICENSE" title="MIT licensed"> <a href="https://github.com/lbryio/lbry-desktop/blob/master/LICENSE" title="MIT licensed">
<img alt="npm" src="https://img.shields.io/dub/l/vibe-d.svg?style=flat"> <img alt="npm" src="https://img.shields.io/dub/l/vibe-d.svg?style=flat">
@ -29,68 +25,82 @@ This repo contains the UI code that powers the official LBRY desktop app. The LB
</a> </a>
</h2> </h2>
![App GIF](https://spee.ch/ba/lbry-joule.gif)
## Table of Contents ## Table of Contents
1. [Install](#install) 1. [Usage](#usage)
2. [Usage](#usage) 2. [Running from Source](#running-from-source)
3. [Running from Source](#running-from-source) 3. [Contributing](#contributing)
4. [Contributing](#contributing) 4. [License](#license)
5. [License](#license) 5. [Security](#security)
6. [Security](#security) 6. [Contact](#contact)
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-desktop-bin/) | [Nixpkgs](https://search.nixos.org/packages?channel=unstable&show=lbry&query=lbry) | [Build Guide](https://lbry.tv/@LBRYarm:5) |
| Maintainers | N/A | [@RubenKelevra](https://github.com/RubenKelevra) | [@Enderger](https://github.com/enderger) | [@Madiator2011](https://github.com/kodxana) |
## Usage ## Usage
Start the installed application to interact with the LBRY network. Go to the website to interact on this frontend.
## Running from Source ## Running from Source
You can run the web version (odysee.com), via running onto your host machine, or go to the website itself.
#### Prerequisites #### Prerequisites
- [Git](https://git-scm.com/downloads) - [Git](https://git-scm.com/downloads)
- [Node.js](https://nodejs.org/en/download/) (v16 required) - [Node.js](https://nodejs.org/en/download/) (v14 required)
- [Corepack](https://nodejs.org/dist/latest-v17.x/docs/api/corepack.html) `npm i -g corepack` (Included in nodejs 14 LTS, 16 LTS and 17)
- [Yarn](https://yarnpkg.com/en/docs/install) - [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` 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 lbry-desktop` 2. Change directory into the cloned repository: `cd odysee-frontend`
3. If corepack is not enabled, run `sudo corepack enable` (the sudo is necessary for system-wide installation, if you use container, nvm etc... you might not be forced to use it) 3. Install the dependencies: `yarn`
4. Install the dependencies: `yarn`
#### Run the electron app #### Run the web app for development
`yarn dev` `yarn dev:web`
- 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. - 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
- In root directory, duplicate the .env.default file and rename it to .env then copy the code below and paste it anywhere in the .env file.
```
cp .env.defaults .env
nano .env
```
- To specify your own OG-IMAGE
You can either place a png named v2-og.png in the /custom folder or specify the OG_IMAGE_URL in .env
- To specify your own channels to be followed on first run
`AUTO_FOLLOW_URLS=lbry://@chan#123...a lbry://@chan2#456...a`
- If you want to customize the homepage content
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:
```
PINNED_URI_1=@someurl#2/someclaim#4
PINNED_LABEL_1=Linktext
PINNED_URI_2=$/discover?t=tag&[queryparams]
PINNED_LABEL_2=OtherLinkText
```
- Finally `NODE_ENV=production yarn compile:web` to rebuild
_Note: You don't need to edit the .env file in the /web folder - that is copied during compile._
#### Deploy the web app (_experimental_)
1. Create a server with a domain name and a reverse proxy https to port 1337.
2. Install pm2, node v10, yarn
3. Clone this repo
4. Make any customizations as above
5. Run `yarn` to install
6. Run `NODE_ENV=production yarn compile:web` to build
7. Set up pm2 to start ./web/index.js
#### Resetting your Packages #### Resetting your Packages
@ -100,9 +110,9 @@ If you _really_ think something might have gone wrong, you can force your repo t
## Contributing ## 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 ## License
@ -110,9 +120,5 @@ This project is MIT licensed. For the full license, see [LICENSE](LICENSE).
## Security ## Security
We take security seriously. Please contact security@lbry.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). For security issues, please reach out to security@odysee.com
New Releases are signed by [Jessop Breth](https://keybase.io/jessopb/key.asc).
## Contact
The primary contact for this project is [@jessopb](https://github.com/jessopb).

View file

@ -7,8 +7,6 @@ module.exports = api => {
'import-glob', 'import-glob',
'@babel/plugin-transform-runtime', '@babel/plugin-transform-runtime',
['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }], ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }],
['@babel/plugin-proposal-private-methods', { 'loose': false }],
['@babel/plugin-proposal-private-property-in-object', { 'loose': false }],
'@babel/plugin-transform-flow-strip-types', '@babel/plugin-transform-flow-strip-types',
'@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-class-properties',
'react-hot-loader/babel', 'react-hot-loader/babel',

Binary file not shown.

Binary file not shown.

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

View file

@ -3,18 +3,17 @@
require('dotenv-defaults').config({ silent: false }); require('dotenv-defaults').config({ silent: false });
const config = { const config = {
MATOMO_URL: process.env.MATOMO_URL,
MATOMO_ID: process.env.MATOMO_ID,
WEBPACK_WEB_PORT: process.env.WEBPACK_WEB_PORT, WEBPACK_WEB_PORT: process.env.WEBPACK_WEB_PORT,
WEBPACK_ELECTRON_PORT: process.env.WEBPACK_ELECTRON_PORT, WEBPACK_ELECTRON_PORT: process.env.WEBPACK_ELECTRON_PORT,
WEB_SERVER_PORT: process.env.WEB_SERVER_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_WEB_PUBLISH_API: process.env.LBRY_WEB_PUBLISH_API,
LBRY_WEB_PUBLISH_API_V2: process.env.LBRY_WEB_PUBLISH_API_V2,
LBRY_API_URL: process.env.LBRY_API_URL, // api.lbry.com', LBRY_API_URL: process.env.LBRY_API_URL, // api.lbry.com',
LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, //player.odysee.com LBRY_WEB_STREAMING_API: process.env.LBRY_WEB_STREAMING_API, // cdn.lbryplayer.xyz',
LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API, LBRY_WEB_BUFFER_API: process.env.LBRY_WEB_BUFFER_API,
SEARCH_SERVER_API: process.env.SEARCH_SERVER_API, SEARCH_SERVER_API: process.env.SEARCH_SERVER_API,
CLOUD_CONNECT_SITE_NAME: process.env.CLOUD_CONNECT_SITE_NAME, SEARCH_SERVER_API_ALT: process.env.SEARCH_SERVER_API_ALT,
COMMENT_SERVER_API: process.env.COMMENT_SERVER_API, COMMENT_SERVER_API: process.env.COMMENT_SERVER_API,
COMMENT_SERVER_NAME: process.env.COMMENT_SERVER_NAME, COMMENT_SERVER_NAME: process.env.COMMENT_SERVER_NAME,
SOCKETY_SERVER_API: process.env.SOCKETY_SERVER_API, SOCKETY_SERVER_API: process.env.SOCKETY_SERVER_API,
@ -23,6 +22,10 @@ const config = {
SHARE_DOMAIN_URL: process.env.SHARE_DOMAIN_URL, SHARE_DOMAIN_URL: process.env.SHARE_DOMAIN_URL,
URL: process.env.URL, URL: process.env.URL,
THUMBNAIL_CDN_URL: process.env.THUMBNAIL_CDN_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,
SITE_TITLE: process.env.SITE_TITLE, SITE_TITLE: process.env.SITE_TITLE,
SITE_NAME: process.env.SITE_NAME, SITE_NAME: process.env.SITE_NAME,
SITE_DESCRIPTION: process.env.SITE_DESCRIPTION, SITE_DESCRIPTION: process.env.SITE_DESCRIPTION,
@ -31,7 +34,6 @@ const config = {
TWITTER_ACCOUNT: process.env.TWITTER_ACCOUNT, TWITTER_ACCOUNT: process.env.TWITTER_ACCOUNT,
// LOGO // LOGO
LOGO_TITLE: process.env.LOGO_TITLE, LOGO_TITLE: process.env.LOGO_TITLE,
FAVICON: process.env.FAVICON,
LOGO: process.env.LOGO, LOGO: process.env.LOGO,
LOGO_TEXT_LIGHT: process.env.LOGO_TEXT_LIGHT, LOGO_TEXT_LIGHT: process.env.LOGO_TEXT_LIGHT,
LOGO_TEXT_DARK: process.env.LOGO_TEXT_DARK, LOGO_TEXT_DARK: process.env.LOGO_TEXT_DARK,
@ -74,9 +76,24 @@ const config = {
SHOW_TAGS_INTRO: process.env.SHOW_TAGS_INTRO === 'true', SHOW_TAGS_INTRO: process.env.SHOW_TAGS_INTRO === 'true',
LIGHTHOUSE_DEFAULT_TYPES: process.env.LIGHTHOUSE_DEFAULT_TYPES, LIGHTHOUSE_DEFAULT_TYPES: process.env.LIGHTHOUSE_DEFAULT_TYPES,
BRANDED_SITE: process.env.BRANDED_SITE, 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_DEV = `http://localhost:${config.WEBPACK_WEB_PORT}`;
config.URL_LOCAL = `http://localhost:${config.WEB_SERVER_PORT}`; config.URL_LOCAL = `http://localhost:${config.WEB_SERVER_PORT}`;
config.FAVICON = `/public/favicon-spaceman.png`;
module.exports = config; module.exports = config;

View file

@ -20,6 +20,11 @@
"to": "static/daemon/", "to": "static/daemon/",
"filter": ["**/*"] "filter": ["**/*"]
}, },
{
"from": "./static/lbry-first/",
"to": "static/lbry-first/",
"filter": ["**/*"]
},
{ {
"from": "./static/img", "from": "./static/img",
"to": "static/img", "to": "static/img",
@ -29,10 +34,6 @@
"from": "./static/font", "from": "./static/font",
"to": "static/font", "to": "static/font",
"filter": ["**/*"] "filter": ["**/*"]
},
{
"from": "./static/app-update.yml",
"to": "app-update.yml"
} }
], ],
"publish": [ "publish": [
@ -41,11 +42,7 @@
} }
], ],
"mac": { "mac": {
"category": "public.app-category.entertainment", "category": "public.app-category.entertainment"
"entitlements": "build/entitlements.mac.plist",
"entitlementsInherit": "build/entitlements.mac.plist",
"hardenedRuntime" : true,
"gatekeeperAssess": false
}, },
"dmg": { "dmg": {
"iconSize": 128, "iconSize": 128,
@ -85,7 +82,7 @@
} }
}, },
"deb": { "deb": {
"depends": ["gconf2", "gconf-service", "libnotify4", "libxtst6", "libnss3"] "depends": ["gconf2", "gconf-service", "libnotify4", "libappindicator1", "libxtst6", "libnss3"]
}, },
"nsis": { "nsis": {
"perMachine": true, "perMachine": true,

View file

@ -1,5 +1,5 @@
import { WEBPACK_ELECTRON_PORT } from 'config'; import { WEBPACK_ELECTRON_PORT } from 'config';
import { app, BrowserWindow, dialog, screen, nativeImage } from 'electron'; import { app, BrowserWindow, dialog, shell, screen, nativeImage } from 'electron';
import isDev from 'electron-is-dev'; import isDev from 'electron-is-dev';
import windowStateKeeper from 'electron-window-state'; import windowStateKeeper from 'electron-window-state';
import SUPPORTED_LANGUAGES from 'constants/supported_languages'; import SUPPORTED_LANGUAGES from 'constants/supported_languages';
@ -9,8 +9,7 @@ import { TO_TRAY_WHEN_CLOSED } from 'constants/settings';
import setupBarMenu from './menu/setupBarMenu'; import setupBarMenu from './menu/setupBarMenu';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
const remote = require('@electron/remote/main');
const shell = require('electron').shell;
function GetAppLangCode() { function GetAppLangCode() {
// https://www.electronjs.org/docs/api/locales // https://www.electronjs.org/docs/api/locales
// 1. Gets the user locale. // 1. Gets the user locale.
@ -55,8 +54,6 @@ export default appState => {
webSecurity: !isDev, webSecurity: !isDev,
plugins: true, plugins: true,
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true, // see about removing this
}, },
}; };
const lbryProto = 'lbry://'; const lbryProto = 'lbry://';
@ -64,7 +61,6 @@ export default appState => {
const rendererURL = isDev ? `http://localhost:${WEBPACK_ELECTRON_PORT}` : `file://${__dirname}/index.html`; const rendererURL = isDev ? `http://localhost:${WEBPACK_ELECTRON_PORT}` : `file://${__dirname}/index.html`;
let window = new BrowserWindow(windowConfiguration); let window = new BrowserWindow(windowConfiguration);
remote.enable(window.webContents);
// Let us register listeners on the window, so we can update the state // Let us register listeners on the window, so we can update the state
// automatically (the listeners will be removed when the window is closed) // automatically (the listeners will be removed when the window is closed)
@ -95,7 +91,7 @@ export default appState => {
// is it a lbry://? pointing to an app page // is it a lbry://? pointing to an app page
if (deepLinkingURI.includes(lbryProtoQ)) { if (deepLinkingURI.includes(lbryProtoQ)) {
let path = deepLinkingURI.slice(lbryProtoQ.length); let path = deepLinkingURI.substr(lbryProtoQ.length);
let page = path.indexOf('?') >= 0 ? path.substring(0, path.indexOf('?')) : path; let page = path.indexOf('?') >= 0 ? path.substring(0, path.indexOf('?')) : path;
if (Object.values(PAGES).includes(page)) { if (Object.values(PAGES).includes(page)) {
deepLinkingURI = deepLinkingURI.replace(lbryProtoQ, '#/$/'); deepLinkingURI = deepLinkingURI.replace(lbryProtoQ, '#/$/');
@ -190,13 +186,9 @@ export default appState => {
window = null; window = null;
}); });
window.webContents.setWindowOpenHandler((details) => { window.webContents.on('new-window', (event, url) => {
// Only open http and https links to prevent event.preventDefault();
// security issues. shell.openExternal(url);
if (['https:', 'http:'].includes(new URL(details.url).protocol)) {
shell.openExternal(details.url);
}
return { action: 'deny' };
}); });
window.webContents.on('update-target-url', (event, url) => { window.webContents.on('update-target-url', (event, url) => {

View file

@ -4,7 +4,7 @@
import '@babel/polyfill'; import '@babel/polyfill';
import SemVer from 'semver'; import SemVer from 'semver';
import https from 'https'; import https from 'https';
import { app, dialog, ipcMain, session, shell, BrowserWindow } from 'electron'; import { app, dialog, ipcMain, session, shell } from 'electron';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
import Lbry from 'lbry'; import Lbry from 'lbry';
import LbryFirstInstance from './LbryFirstInstance'; import LbryFirstInstance from './LbryFirstInstance';
@ -17,17 +17,6 @@ import startSandbox from './startSandbox';
import installDevtools from './installDevtools'; import installDevtools from './installDevtools';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { diskSpaceLinux, diskSpaceWindows, diskSpaceMac } from '../ui/util/diskspace';
const { download } = require('electron-dl');
const mime = require('mime');
const remote = require('@electron/remote/main');
const os = require('os');
const sudo = require('sudo-prompt');
const probe = require('ffmpeg-probe');
const MAX_IPC_SEND_BUFFER_SIZE = 500000000; // large files crash when serialized for ipc message
remote.initialize();
const filePath = path.join(process.resourcesPath, 'static', 'upgradeDisabled'); const filePath = path.join(process.resourcesPath, 'static', 'upgradeDisabled');
let upgradeDisabled; let upgradeDisabled;
try { try {
@ -37,18 +26,11 @@ try {
upgradeDisabled = false; upgradeDisabled = false;
} }
autoUpdater.autoDownload = !upgradeDisabled; autoUpdater.autoDownload = !upgradeDisabled;
autoUpdater.allowPrerelease = false;
const UPDATE_STATE_INIT = 0; // This is set to true if an auto update has been downloaded through the Electron
const UPDATE_STATE_CHECKING = 1; // auto-update system and is ready to install. If the user declined an update earlier,
const UPDATE_STATE_UPDATES_FOUND = 2; // it will still install on shutdown.
const UPDATE_STATE_NO_UPDATES_FOUND = 3; let autoUpdateDownloaded = false;
const UPDATE_STATE_DOWNLOADING = 4;
const UPDATE_STATE_DOWNLOADED = 5;
let updateState = UPDATE_STATE_INIT;
let updateDownloadItem;
const isAutoUpdateSupported = ['win32', 'darwin'].includes(process.platform) || !!process.env.APPIMAGE;
// This is used to keep track of whether we are showing the special dialog // This is used to keep track of whether we are showing the special dialog
// that we show on Windows after you decline an upgrade and close the app later. // that we show on Windows after you decline an upgrade and close the app later.
@ -70,7 +52,7 @@ if (isDev && process.platform === 'win32') {
app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [ app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [
path.resolve(process.argv[1]), path.resolve(process.argv[1]),
]); ]);
} else if (process.platform !== 'linux') { } else {
app.setAsDefaultProtocolClient(PROTOCOL); app.setAsDefaultProtocolClient(PROTOCOL);
} }
@ -241,8 +223,7 @@ app.on('activate', () => {
app.on('will-quit', event => { app.on('will-quit', event => {
if ( if (
process.platform === 'win32' && process.platform === 'win32' &&
updateState === UPDATE_STATE_DOWNLOADED && autoUpdateDownloaded &&
isAutoUpdateSupported &&
!appState.autoUpdateAccepted && !appState.autoUpdateAccepted &&
!showingAutoUpdateCloseAlert !showingAutoUpdateCloseAlert
) { ) {
@ -302,118 +283,27 @@ app.on('before-quit', () => {
appState.isQuitting = true; appState.isQuitting = true;
}); });
// Get the content of a file as a raw buffer of bytes. ipcMain.on('upgrade', (event, installerPath) => {
// Useful to convert a file path to a File instance. app.on('quit', () => {
// Example: console.log('Launching upgrade installer at', installerPath);
// const result = await ipcMain.invoke('get-file-from-path', 'path/to/file'); // This gets triggered called after *all* other quit-related events, so
// const file = new File([result.buffer], result.name); // we'll only get here if we're fully prepared and quitting for real.
// NOTE: if path points to a folder, an empty shell.openPath(installerPath);
// file will be given.
ipcMain.handle('get-file-from-path', (event, path, readContents = true) => {
return new Promise((resolve, reject) => {
fs.stat(path, (error, stats) => {
if (error) {
reject(error);
return;
}
// Separate folders considering "\" and "/"
// as separators (cross platform)
const folders = path.split(/[\\/]/);
const name = folders[folders.length - 1];
if (stats.isDirectory()) {
resolve({
name,
mime: undefined,
path,
buffer: new ArrayBuffer(0),
});
return;
}
if (!readContents) {
resolve({
name,
mime: mime.getType(name) || undefined,
path,
buffer: new ArrayBuffer(0),
});
return;
}
// Encoding null ensures data results in a Buffer.
fs.readFile(path, { encoding: null }, (err, data) => {
if (err) {
reject(err);
return;
}
resolve({
name,
mime: mime.getType(name) || undefined,
path,
buffer: data,
});
});
});
}); });
// what to do if no shutdown in a long time?
console.log('Update downloaded to', installerPath);
console.log('The app will close and you will be prompted to install the latest version of LBRY.');
console.log('After the install is complete, please reopen the app.');
app.quit();
}); });
ipcMain.handle('get-file-details-from-path', async (event, path) => { autoUpdater.on('update-downloaded', () => {
const isFfMp4 = (ffprobeResults) => { autoUpdateDownloaded = true;
return ffprobeResults &&
ffprobeResults.format &&
ffprobeResults.format.format_name &&
ffprobeResults.format.format_name.includes('mp4');
};
const folders = path.split(/[\\/]/);
const name = folders[folders.length - 1];
let duration = 0, size = 0, mimeType;
try {
await fs.promises.stat(path);
let ffprobeResults;
try {
ffprobeResults = await probe(path);
duration = ffprobeResults.format.duration;
size = ffprobeResults.format.size;
} catch (e) {
}
let fileReadResult;
if (size < MAX_IPC_SEND_BUFFER_SIZE) {
try {
fileReadResult = await fs.promises.readFile(path);
} catch (e) {
}
}
// TODO: use mmmagic to inspect file and get mime type
mimeType = isFfMp4(ffprobeResults) ? 'video/mp4' : mime.getType(name);
const fileData = {name, mime: mimeType || undefined, path, duration: duration, size, buffer: fileReadResult };
return fileData;
} catch (e) {
// no stat
return { error: 'no file' };
}
}); });
ipcMain.on('get-disk-space', async (event) => { ipcMain.on('autoUpdateAccepted', () => {
try { appState.autoUpdateAccepted = true;
const { data_dir } = await Lbry.settings_get(); autoUpdater.quitAndInstall();
let diskSpace;
switch (os.platform()) {
case 'linux':
diskSpace = await diskSpaceLinux(data_dir);
break;
case 'darwin':
diskSpace = await diskSpaceMac(data_dir);
break;
case 'win32':
diskSpace = await diskSpaceWindows(data_dir);
break;
default:
throw new Error('unknown platform');
}
rendererWindow.webContents.send('send-disk-space', { diskSpace });
} catch (e) {
rendererWindow.webContents.send('send-disk-space', { error: e.message || e });
console.log('Failed to get disk space', e);
}
}); });
ipcMain.on('version-info-requested', () => { ipcMain.on('version-info-requested', () => {
@ -508,162 +398,3 @@ process.on('uncaughtException', error => {
if (daemon) daemon.quit(); if (daemon) daemon.quit();
app.exit(1); app.exit(1);
}); });
// Auto updater
autoUpdater.on('download-progress', () => {
updateState = UPDATE_STATE_DOWNLOADING;
});
autoUpdater.on('update-downloaded', () => {
updateState = UPDATE_STATE_DOWNLOADED;
// If this download was trigger by
// autoUpdateAccepted it means, the user
// wants to install the new update but
// needed to downloaded the files first.
if (appState.autoUpdateAccepted) {
autoUpdater.quitAndInstall();
}
});
autoUpdater.on('update-available', () => {
if (updateState === UPDATE_STATE_DOWNLOADING) {
return;
}
updateState = UPDATE_STATE_UPDATES_FOUND;
});
autoUpdater.on('update-not-available', () => {
updateState = UPDATE_STATE_NO_UPDATES_FOUND;
});
autoUpdater.on('error', () => {
if (updateState === UPDATE_STATE_DOWNLOADING) {
updateState = UPDATE_STATE_UPDATES_FOUND;
return;
}
updateState = UPDATE_STATE_INIT;
});
// Manual (.deb) update
ipcMain.on('cancel-download-upgrade', () => {
if (updateDownloadItem) {
// Cancel the download and execute the onCancel
// callback set in the options.
updateDownloadItem.cancel();
}
});
ipcMain.on('download-upgrade', (event, params) => {
if (updateState !== UPDATE_STATE_UPDATES_FOUND) {
return;
}
if (isAutoUpdateSupported) {
updateState = UPDATE_STATE_DOWNLOADING;
autoUpdater.downloadUpdate();
return;
}
const { url, options } = params;
const dir = fs.mkdtempSync(app.getPath('temp') + path.sep);
updateState = UPDATE_STATE_DOWNLOADING;
// Grab the download item's handler to allow
// cancelling the operation if required.
options.onStarted = function(downloadItem) {
updateDownloadItem = downloadItem;
};
options.onCancel = function() {
updateState = UPDATE_STATE_UPDATES_FOUND;
updateDownloadItem = undefined;
};
options.onProgress = function(p) {
rendererWindow.webContents.send('download-progress-update', p);
};
options.onCompleted = function(c) {
updateState = UPDATE_STATE_DOWNLOADED;
updateDownloadItem = undefined;
rendererWindow.webContents.send('download-update-complete', c);
};
options.directory = dir;
const win = BrowserWindow.getFocusedWindow();
download(win, url, options).catch(e => {
updateState = UPDATE_STATE_UPDATES_FOUND;
console.log('e', e);
});
});
// Update behavior
ipcMain.on('autoUpdateAccepted', () => {
appState.autoUpdateAccepted = true;
// quitAndInstall can only be called if the
// update has been downloaded. Since the user
// can disable auto updates, we have to make
// sure it has been downloaded first.
if (updateState === UPDATE_STATE_DOWNLOADED) {
autoUpdater.quitAndInstall();
return;
}
if (updateState !== UPDATE_STATE_UPDATES_FOUND) {
return;
}
// If the update hasn't been downloaded,
// start downloading it. After it's done, the
// event 'update-downloaded' will be triggered,
// where we will be able to resume the
// update installation.
updateState = UPDATE_STATE_DOWNLOADING;
autoUpdater.downloadUpdate();
});
ipcMain.on('check-for-updates', (event, autoDownload) => {
if (![UPDATE_STATE_INIT, UPDATE_STATE_NO_UPDATES_FOUND].includes(updateState)) {
return;
}
updateState = UPDATE_STATE_CHECKING;
// If autoDownload is true, checkForUpdates will begin the
// download automatically.
if (autoDownload) {
updateState = UPDATE_STATE_DOWNLOADING;
}
autoUpdater.autoDownload = autoDownload;
autoUpdater.checkForUpdates();
});
ipcMain.on('upgrade', (event, installerPath) => {
// what to do if no shutdown in a long time?
console.log('Update downloaded to', installerPath);
console.log('The app will close and you will be prompted to install the latest version of LBRY.');
console.log('After the install is complete, please reopen the app.');
// Prevent .deb package from opening with archive manager (Ubuntu >= 20)
if (process.platform === 'linux' && !process.env.APPIMAGE) {
sudo.exec(`dpkg -i ${installerPath}`, { name: app.name }, (err, stdout, stderr) => {
if (err || stderr) {
rendererWindow.webContents.send('upgrade-installing-error');
return;
}
// Re-launch the application when the installation finishes.
app.relaunch();
app.quit();
});
return;
}
app.on('quit', () => {
console.log('Launching upgrade installer at', installerPath);
// This gets triggered called after *all* other quit-related events, so
// we'll only get here if we're fully prepared and quitting for real.
shell.openPath(installerPath);
});
app.quit();
});

View file

@ -1,5 +1,4 @@
import { app, Menu, shell } from 'electron'; import { app, Menu, shell } from 'electron';
import { ZOOM } from 'util/zoomWindow';
export default () => { export default () => {
const template = [ const template = [
@ -23,38 +22,6 @@ export default () => {
label: 'View', label: 'View',
submenu: [ submenu: [
{ role: 'reload' }, { 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', label: 'Developer',
submenu: [{ role: 'forcereload' }, { role: 'toggledevtools' }], submenu: [{ role: 'forcereload' }, { role: 'toggledevtools' }],

View file

@ -1,4 +1,5 @@
// import express from 'express'; import express from 'express';
import unpackByOutpoint from './unpackByOutpoint';
// Polyfills and `lbry-redux` // Polyfills and `lbry-redux`
global.fetch = require('node-fetch'); global.fetch = require('node-fetch');
@ -7,31 +8,31 @@ if (typeof global.fetch === 'object') {
global.fetch = global.fetch.default; global.fetch = global.fetch.default;
} }
// const Lbry = require('lbry'); const Lbry = require('lbry');
delete global.window; delete global.window;
export default async function startSandbox() { export default async function startSandbox() {
// const port = 5278; const port = 5278;
// const sandbox = express(); const sandbox = express();
// sandbox.get('/set/:outpoint', async (req, res) => { sandbox.get('/set/:outpoint', async (req, res) => {
// const { outpoint } = req.params; const { outpoint } = req.params;
//
// const resolvedPath = await unpackByOutpoint(Lbry, outpoint); const resolvedPath = await unpackByOutpoint(Lbry, outpoint);
//
// sandbox.use(`/sandbox/${outpoint}/`, express.static(resolvedPath)); sandbox.use(`/sandbox/${outpoint}/`, express.static(resolvedPath));
//
// res.send(`/sandbox/${outpoint}/`); res.send(`/sandbox/${outpoint}/`);
// }); });
//
// sandbox sandbox
// .listen(port, 'localhost', () => console.log(`Sandbox listening on port ${port}.`)) .listen(port, 'localhost', () => console.log(`Sandbox listening on port ${port}.`))
// .on('error', err => { .on('error', err => {
// if (err.code === 'EADDRINUSE') { if (err.code === 'EADDRINUSE') {
// console.log( console.log(
// `Server already listening at localhost:${port}. This is probably another LBRY app running. If not, games in the app will not work.` `Server already listening at localhost:${port}. This is probably another LBRY app running. If not, games in the app will not work.`
// ); );
// } }
// }); });
} }

View file

@ -0,0 +1,23 @@
import fs from 'fs';
import path from 'path';
import { unpackDirectory } from 'lbry-format';
async function unpackByOutpoint(lbry, outpoint) {
const { items: claimFiles } = await lbry.file_list({ outpoint, full_status: true, page: 1, page_size: 1 });
if (claimFiles && claimFiles.length) {
const claimFileInfo = claimFiles[0];
const packFilePath = path.resolve(claimFileInfo.download_path);
const unpackPath = path.normalize(path.join(claimFileInfo.download_directory, claimFileInfo.claim_name));
if (!fs.existsSync(unpackPath)) {
await unpackDirectory(unpackPath, {
fileName: packFilePath,
});
}
return unpackPath;
}
}
export default unpackByOutpoint;

View file

@ -17,9 +17,6 @@ export const FETCH_CHANNEL_LIST_STARTED = 'FETCH_CHANNEL_LIST_STARTED';
export const FETCH_CHANNEL_LIST_COMPLETED = 'FETCH_CHANNEL_LIST_COMPLETED'; export const FETCH_CHANNEL_LIST_COMPLETED = 'FETCH_CHANNEL_LIST_COMPLETED';
export const CREATE_CHANNEL_STARTED = 'CREATE_CHANNEL_STARTED'; export const CREATE_CHANNEL_STARTED = 'CREATE_CHANNEL_STARTED';
export const CREATE_CHANNEL_COMPLETED = 'CREATE_CHANNEL_COMPLETED'; export const CREATE_CHANNEL_COMPLETED = 'CREATE_CHANNEL_COMPLETED';
export const PUBLISH_STARTED = 'PUBLISH_STARTED';
export const PUBLISH_COMPLETED = 'PUBLISH_COMPLETED';
export const PUBLISH_FAILED = 'PUBLISH_FAILED';
export const SET_PLAYING_URI = 'SET_PLAYING_URI'; export const SET_PLAYING_URI = 'SET_PLAYING_URI';
export const SET_CONTENT_POSITION = 'SET_CONTENT_POSITION'; export const SET_CONTENT_POSITION = 'SET_CONTENT_POSITION';
export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED'; export const SET_CONTENT_LAST_VIEWED = 'SET_CONTENT_LAST_VIEWED';
@ -29,10 +26,8 @@ export const CLEAR_CONTENT_HISTORY_ALL = 'CLEAR_CONTENT_HISTORY_ALL';
// Subscriptions // Subscriptions
export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE'; export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE'; export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
export const CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS = export const CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS = 'CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS';
'CHANNEL_SUBSCRIPTION_ENABLE_NOTIFICATIONS'; export const CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS = 'CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS';
export const CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS =
'CHANNEL_SUBSCRIPTION_DISABLE_NOTIFICATIONS';
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS'; export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST'; export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST';
export const UPDATE_SUBSCRIPTION_UNREADS = 'UPDATE_SUBSCRIPTION_UNREADS'; export const UPDATE_SUBSCRIPTION_UNREADS = 'UPDATE_SUBSCRIPTION_UNREADS';
@ -88,9 +83,6 @@ export const SYNC_APPLY_FAILED = 'SYNC_APPLY_FAILED';
export const SYNC_APPLY_BAD_PASSWORD = 'SYNC_APPLY_BAD_PASSWORD'; export const SYNC_APPLY_BAD_PASSWORD = 'SYNC_APPLY_BAD_PASSWORD';
export const SYNC_RESET = 'SYNC_RESET'; export const SYNC_RESET = 'SYNC_RESET';
// Lbry.tv
export const UPDATE_UPLOAD_PROGRESS = 'UPDATE_UPLOAD_PROGRESS';
// User // User
export const GENERATE_AUTH_TOKEN_FAILURE = 'GENERATE_AUTH_TOKEN_FAILURE'; 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_STARTED = 'GENERATE_AUTH_TOKEN_STARTED';

View file

@ -16,6 +16,7 @@ export { doGenerateAuthToken } from './redux/actions/auth';
export { doFetchCostInfoForUri } from './redux/actions/cost_info'; export { doFetchCostInfoForUri } from './redux/actions/cost_info';
export { doBlackListedOutpointsSubscribe } from './redux/actions/blacklist'; export { doBlackListedOutpointsSubscribe } from './redux/actions/blacklist';
export { doFilteredOutpointsSubscribe } from './redux/actions/filtered'; export { doFilteredOutpointsSubscribe } from './redux/actions/filtered';
// export { doFetchFeaturedUris, doFetchTrendingUris } from './redux/actions/homepage';
export { doFetchViewCount, doFetchSubCount } from './redux/actions/stats'; export { doFetchViewCount, doFetchSubCount } from './redux/actions/stats';
export { export {
doCheckSync, doCheckSync,
@ -32,6 +33,7 @@ export { authReducer } from './redux/reducers/auth';
export { costInfoReducer } from './redux/reducers/cost_info'; export { costInfoReducer } from './redux/reducers/cost_info';
export { blacklistReducer } from './redux/reducers/blacklist'; export { blacklistReducer } from './redux/reducers/blacklist';
export { filteredReducer } from './redux/reducers/filtered'; export { filteredReducer } from './redux/reducers/filtered';
// export { homepageReducer } from './redux/reducers/homepage';
export { statsReducer } from './redux/reducers/stats'; export { statsReducer } from './redux/reducers/stats';
export { syncReducer } from './redux/reducers/sync'; export { syncReducer } from './redux/reducers/sync';
@ -43,18 +45,15 @@ export {
selectAllCostInfoByUri, selectAllCostInfoByUri,
selectFetchingCostInfo, selectFetchingCostInfo,
} from './redux/selectors/cost_info'; } from './redux/selectors/cost_info';
export { export { selectBlackListedOutpoints, selectBlacklistedOutpointMap } from './redux/selectors/blacklist';
selectBlackListedOutpoints,
selectBlacklistedOutpointMap,
} from './redux/selectors/blacklist';
export { selectFilteredOutpoints, selectFilteredOutpointMap } from './redux/selectors/filtered'; export { selectFilteredOutpoints, selectFilteredOutpointMap } from './redux/selectors/filtered';
export { // export {
selectViewCount, // selectFeaturedUris,
selectViewCountForUri, // selectFetchingFeaturedUris,
// makeSelectViewCountForUri, // deprecated // selectTrendingUris,
selectSubCountForUri, // selectFetchingTrendingUris,
// makeSelectSubCountForUri, // deprecated // } from './redux/selectors/homepage';
} from './redux/selectors/stats'; export { selectViewCount, selectViewCountForUri, selectSubCountForUri } from './redux/selectors/stats';
export { selectBanStateForUri } from './redux/selectors/ban'; export { selectBanStateForUri } from './redux/selectors/ban';
export { export {
selectHasSyncedWallet, selectHasSyncedWallet,

View file

@ -1,6 +1,7 @@
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import Lbry from 'lbry'; import Lbry from 'lbry';
import querystring from 'querystring'; import querystring from 'querystring';
import analytics from 'analytics';
const Lbryio = { const Lbryio = {
enabled: true, enabled: true,
@ -14,7 +15,7 @@ const EXCHANGE_RATE_TIMEOUT = 20 * 60 * 1000;
const INTERNAL_APIS_DOWN = 'internal_apis_down'; const INTERNAL_APIS_DOWN = 'internal_apis_down';
// We can't use env's because they aren't passed into node_modules // We can't use env's because they aren't passed into node_modules
Lbryio.setLocalApi = endpoint => { Lbryio.setLocalApi = (endpoint) => {
Lbryio.CONNECTION_STRING = endpoint.replace(/\/*$/, '/'); // exactly one slash at the end; Lbryio.CONNECTION_STRING = endpoint.replace(/\/*$/, '/'); // exactly one slash at the end;
}; };
@ -36,8 +37,8 @@ Lbryio.call = (resource, action, params = {}, method = 'get') => {
return Promise.reject(INTERNAL_APIS_DOWN); return Promise.reject(INTERNAL_APIS_DOWN);
} }
if (response) if (response) {
return response.json().then(json => { return response.json().then((json) => {
let error; let error;
if (json.error) { if (json.error) {
error = new Error(json.error); error = new Error(json.error);
@ -48,14 +49,15 @@ Lbryio.call = (resource, action, params = {}, method = 'get') => {
return Promise.reject(error); return Promise.reject(error);
}); });
} }
}
function makeRequest(url, options) { function makeRequest(url, options) {
return fetch(url, options).then(checkAndParse); return fetch(url, options).then(checkAndParse);
} }
return Lbryio.getAuthToken().then(token => { return Lbryio.getAuthToken().then((token) => {
const fullParams = { auth_token: token, ...params }; const fullParams = { auth_token: token, ...params };
Object.keys(fullParams).forEach(key => { Object.keys(fullParams).forEach((key) => {
const value = fullParams[key]; const value = fullParams[key];
if (typeof value === 'object') { if (typeof value === 'object') {
fullParams[key] = JSON.stringify(value); fullParams[key] = JSON.stringify(value);
@ -80,18 +82,21 @@ Lbryio.call = (resource, action, params = {}, method = 'get') => {
url = `${Lbryio.CONNECTION_STRING}${resource}/${action}`; url = `${Lbryio.CONNECTION_STRING}${resource}/${action}`;
} }
return makeRequest(url, options).then(response => response.data); return makeRequest(url, options).then((response) => {
sendCallAnalytics(resource, action, params);
return response.data;
});
}); });
}; };
Lbryio.authToken = null; Lbryio.authToken = null;
Lbryio.getAuthToken = () => Lbryio.getAuthToken = () =>
new Promise(resolve => { new Promise((resolve) => {
if (Lbryio.authToken) { if (Lbryio.authToken) {
resolve(Lbryio.authToken); resolve(Lbryio.authToken);
} else if (Lbryio.overrides.getAuthToken) { } else if (Lbryio.overrides.getAuthToken) {
Lbryio.overrides.getAuthToken().then(token => { Lbryio.overrides.getAuthToken().then((token) => {
resolve(token); resolve(token);
}); });
} else if (typeof window !== 'undefined') { } else if (typeof window !== 'undefined') {
@ -122,7 +127,7 @@ Lbryio.authenticate = (domain, language) => {
language: language || 'en', language: language || 'en',
}; };
return new Promise(resolve => { return new Promise((resolve) => {
resolve(params); resolve(params);
}); });
} }
@ -130,15 +135,15 @@ Lbryio.authenticate = (domain, language) => {
if (Lbryio.authenticationPromise === null) { if (Lbryio.authenticationPromise === null) {
Lbryio.authenticationPromise = new Promise((resolve, reject) => { Lbryio.authenticationPromise = new Promise((resolve, reject) => {
Lbryio.getAuthToken() Lbryio.getAuthToken()
.then(token => { .then((token) => {
if (!token || token.length > 60) { if (!token || token.length > 60) {
return false; return false;
} }
// check that token works // check that token works
return Lbryio.getCurrentUser() return Lbryio.getCurrentUser()
.then(user => user) .then((user) => user)
.catch(error => { .catch((error) => {
if (error === INTERNAL_APIS_DOWN) { if (error === INTERNAL_APIS_DOWN) {
throw new Error('Internal APIS down'); throw new Error('Internal APIS down');
} }
@ -146,14 +151,14 @@ Lbryio.authenticate = (domain, language) => {
return false; return false;
}); });
}) })
.then(user => { .then((user) => {
if (user) { if (user) {
return user; return user;
} }
return Lbry.status() return Lbry.status()
.then( .then(
status => (status) =>
new Promise((res, rej) => { new Promise((res, rej) => {
const appId = const appId =
domain && domain !== 'lbry.tv' domain && domain !== 'lbry.tv'
@ -169,7 +174,7 @@ Lbryio.authenticate = (domain, language) => {
}, },
'post' 'post'
) )
.then(response => { .then((response) => {
if (!response.auth_token) { if (!response.auth_token) {
throw new Error('auth_token was not set in the response'); throw new Error('auth_token was not set in the response');
} }
@ -188,10 +193,10 @@ Lbryio.authenticate = (domain, language) => {
Lbryio.authToken = response.auth_token; Lbryio.authToken = response.auth_token;
return res(response); return res(response);
}) })
.catch(error => rej(error)); .catch((error) => rej(error));
}) })
) )
.then(newUser => { .then((newUser) => {
if (!newUser) { if (!newUser) {
return Lbryio.getCurrentUser(); return Lbryio.getCurrentUser();
} }
@ -211,10 +216,7 @@ Lbryio.getStripeToken = () =>
: 'pk_live_e8M4dRNnCCbmpZzduEUZBgJO'; : 'pk_live_e8M4dRNnCCbmpZzduEUZBgJO';
Lbryio.getExchangeRates = () => { Lbryio.getExchangeRates = () => {
if ( if (!Lbryio.exchangeLastFetched || Date.now() - Lbryio.exchangeLastFetched > EXCHANGE_RATE_TIMEOUT) {
!Lbryio.exchangeLastFetched ||
Date.now() - Lbryio.exchangeLastFetched > EXCHANGE_RATE_TIMEOUT
) {
Lbryio.exchangePromise = new Promise((resolve, reject) => { Lbryio.exchangePromise = new Promise((resolve, reject) => {
Lbryio.call('lbc', 'exchange_rate', {}, 'get', true) Lbryio.call('lbc', 'exchange_rate', {}, 'get', true)
.then(({ lbc_usd: LBC_USD, lbc_btc: LBC_BTC, btc_usd: BTC_USD }) => { .then(({ lbc_usd: LBC_USD, lbc_btc: LBC_BTC, btc_usd: BTC_USD }) => {
@ -235,4 +237,23 @@ Lbryio.setOverride = (methodName, newMethod) => {
Lbryio.overrides[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; export default Lbryio;

View file

@ -1,10 +1,11 @@
// @flow
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import { Lbryio } from 'lbryinc'; import { Lbryio } from 'lbryinc';
import { selectClaimForUri } from 'redux/selectors/claims'; import { selectClaimForUri } from 'redux/selectors/claims';
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export function doFetchCostInfoForUri(uri) { export function doFetchCostInfoForUri(uri: string) {
return (dispatch, getState) => { return (dispatch: Dispatch, getState: GetState) => {
const state = getState(); const state = getState();
const claim = selectClaimForUri(state, uri); const claim = selectClaimForUri(state, uri);

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

View file

@ -2,6 +2,9 @@
import { Lbryio } from 'lbryinc'; import { Lbryio } from 'lbryinc';
import * as ACTIONS from 'constants/action_types'; 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) => { export const doFetchViewCount = (claimIdCsv: string) => (dispatch: Dispatch) => {
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_STARTED }); dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_STARTED });
@ -10,23 +13,56 @@ export const doFetchViewCount = (claimIdCsv: string) => (dispatch: Dispatch) =>
const viewCounts = result; const viewCounts = result;
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_COMPLETED, data: { claimIdCsv, viewCounts } }); dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_COMPLETED, data: { claimIdCsv, viewCounts } });
}) })
.catch(error => { .catch((error) => {
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_FAILED, data: error }); dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_FAILED, data: error });
}); });
}; };
export const doFetchSubCount = (claimId: string) => (dispatch: Dispatch) => { 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 }); dispatch({ type: ACTIONS.FETCH_SUB_COUNT_STARTED });
return Lbryio.call('subscription', 'sub_count', { claim_id: claimId }) return Lbryio.call('subscription', 'sub_count', { claim_id: claimIds.join(',') })
.then((result: Array<number>) => { .then((result: Array<number>) => {
const subCount = result[0]; const subCounts = result;
dispatch({ dispatch({
type: ACTIONS.FETCH_SUB_COUNT_COMPLETED, type: ACTIONS.FETCH_SUB_COUNT_COMPLETED,
data: { claimId, subCount }, data: { claimIdCsv, subCounts, fetchDate: now },
}); });
}) })
.catch(error => { .catch((error) => {
dispatch({ type: ACTIONS.FETCH_SUB_COUNT_FAILED, data: 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);
};

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

View file

@ -8,15 +8,18 @@ const defaultState = {
fetchingSubCount: false, fetchingSubCount: false,
subCountError: undefined, subCountError: undefined,
subCountById: {}, subCountById: {},
subCountLastFetchedById: {},
}; };
export const statsReducer = handleActions( export const statsReducer = handleActions(
{ {
[ACTIONS.FETCH_VIEW_COUNT_STARTED]: state => ({ ...state, fetchingViewCount: true }), [ACTIONS.FETCH_VIEW_COUNT_STARTED]: (state) => ({ ...state, fetchingViewCount: true }),
[ACTIONS.FETCH_VIEW_COUNT_FAILED]: (state, action) => ({ [ACTIONS.FETCH_VIEW_COUNT_FAILED]: (state, action) => ({
...state, ...state,
viewCountError: action.data, viewCountError: action.data,
}), }),
[ACTIONS.FETCH_VIEW_COUNT_COMPLETED]: (state, action) => { [ACTIONS.FETCH_VIEW_COUNT_COMPLETED]: (state, action) => {
const { claimIdCsv, viewCounts } = action.data; const { claimIdCsv, viewCounts } = action.data;
@ -35,20 +38,43 @@ export const statsReducer = handleActions(
viewCountById, viewCountById,
}; };
}, },
[ACTIONS.FETCH_SUB_COUNT_STARTED]: state => ({ ...state, fetchingSubCount: true }),
[ACTIONS.FETCH_SUB_COUNT_STARTED]: (state) => ({ ...state, fetchingSubCount: true }),
[ACTIONS.FETCH_SUB_COUNT_FAILED]: (state, action) => ({ [ACTIONS.FETCH_SUB_COUNT_FAILED]: (state, action) => ({
...state, ...state,
subCountError: action.data, subCountError: action.data,
}), }),
[ACTIONS.FETCH_SUB_COUNT_COMPLETED]: (state, action) => {
const { claimId, subCount } = action.data;
const subCountById = { ...state.subCountById, [claimId]: subCount }; [ACTIONS.FETCH_SUB_COUNT_COMPLETED]: (state, action) => {
return { 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, ...state,
fetchingSubCount: false, fetchingSubCount: false,
subCountById, subCountLastFetchedById,
}; };
if (dataChanged) {
newState.subCountById = subCountById;
}
return newState;
}, },
}, },
defaultState defaultState

View file

@ -1,7 +1,4 @@
import { createSelector } from 'reselect'; const selectState = (state) => state.auth || {};
const selectState = state => state.auth || {}; export const selectAuthToken = (state) => selectState(state).authToken;
export const selectIsAuthenticating = (state) => selectState(state).authenticating;
export const selectAuthToken = createSelector(selectState, state => state.authToken);
export const selectIsAuthenticating = createSelector(selectState, state => state.authenticating);

View file

@ -5,7 +5,7 @@
// involve moving it from 'extras' to 'ui' (big change). // involve moving it from 'extras' to 'ui' (big change).
import { createCachedSelector } from 're-reselect'; import { createCachedSelector } from 're-reselect';
import { selectClaimForUri, makeSelectIsBlacklisted } from 'redux/selectors/claims'; import { selectClaimForUri } from 'redux/selectors/claims';
import { selectMutedChannels } from 'redux/selectors/blocked'; import { selectMutedChannels } from 'redux/selectors/blocked';
import { selectModerationBlockList } from 'redux/selectors/comments'; import { selectModerationBlockList } from 'redux/selectors/comments';
import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc'; import { selectBlacklistedOutpointMap, selectFilteredOutpointMap } from 'lbryinc';
@ -18,8 +18,7 @@ export const selectBanStateForUri = createCachedSelector(
selectFilteredOutpointMap, selectFilteredOutpointMap,
selectMutedChannels, selectMutedChannels,
selectModerationBlockList, selectModerationBlockList,
(state, uri) => makeSelectIsBlacklisted(uri)(state), (claim, blackListedOutpointMap, filteredOutpointMap, mutedChannelUris, personalBlocklist) => {
(claim, blackListedOutpointMap, filteredOutpointMap, mutedChannelUris, personalBlocklist, isBlacklisted) => {
const banState = {}; const banState = {};
if (!claim) { if (!claim) {
@ -28,10 +27,6 @@ export const selectBanStateForUri = createCachedSelector(
const channelClaim = getChannelFromClaim(claim); const channelClaim = getChannelFromClaim(claim);
if (isBlacklisted) {
banState['blacklisted'] = true;
}
// This will be replaced once blocking is done at the wallet server level. // This will be replaced once blocking is done at the wallet server level.
if (blackListedOutpointMap) { if (blackListedOutpointMap) {
if ( if (

View file

@ -1,15 +1,10 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
export const selectState = state => state.blacklist || {}; export const selectState = (state) => state.blacklist || {};
export const selectBlackListedOutpoints = createSelector( export const selectBlackListedOutpoints = (state) => selectState(state).blackListedOutpoints;
selectState,
state => state.blackListedOutpoints
);
export const selectBlacklistedOutpointMap = createSelector( export const selectBlacklistedOutpointMap = createSelector(selectBlackListedOutpoints, (outpoints) =>
selectBlackListedOutpoints,
outpoints =>
outpoints outpoints
? outpoints.reduce((acc, val) => { ? outpoints.reduce((acc, val) => {
const outpoint = `${val.txid}:${val.nout}`; const outpoint = `${val.txid}:${val.nout}`;

View file

@ -1,15 +1,10 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
export const selectState = state => state.filtered || {}; export const selectState = (state) => state.filtered || {};
export const selectFilteredOutpoints = createSelector( export const selectFilteredOutpoints = (state) => selectState(state).filteredOutpoints;
selectState,
state => state.filteredOutpoints
);
export const selectFilteredOutpointMap = createSelector( export const selectFilteredOutpointMap = createSelector(selectFilteredOutpoints, (outpoints) =>
selectFilteredOutpoints,
outpoints =>
outpoints outpoints
? outpoints.reduce((acc, val) => { ? outpoints.reduce((acc, val) => {
const outpoint = `${val.txid}:${val.nout}`; const outpoint = `${val.txid}:${val.nout}`;

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

View file

@ -1,11 +1,11 @@
// @flow // @flow
import { createSelector } from 'reselect';
import { selectClaimIdForUri } from 'redux/selectors/claims'; import { selectClaimIdForUri } from 'redux/selectors/claims';
type State = { claims: any }; type State = { claims: any, stats: any };
const selectState = state => state.stats || {};
export const selectViewCount = createSelector(selectState, state => state.viewCountById); const selectState = (state: State) => state.stats || {};
export const selectSubCount = createSelector(selectState, state => state.subCountById); export const selectViewCount = (state: State) => selectState(state).viewCountById;
export const selectSubCount = (state: State) => selectState(state).subCountById;
export const selectViewCountForUri = (state: State, uri: string) => { export const selectViewCountForUri = (state: State, uri: string) => {
const claimId = selectClaimIdForUri(state, uri); const claimId = selectClaimIdForUri(state, uri);

View file

@ -1,40 +1,13 @@
import { createSelector } from 'reselect'; const selectState = (state) => state.sync || {};
const selectState = state => state.sync || {}; export const selectHasSyncedWallet = (state) => selectState(state).hasSyncedWallet;
export const selectSyncHash = (state) => selectState(state).syncHash;
export const selectHasSyncedWallet = createSelector(selectState, state => state.hasSyncedWallet); export const selectSyncData = (state) => selectState(state).syncData;
export const selectSetSyncErrorMessage = (state) => selectState(state).setSyncErrorMessage;
export const selectSyncHash = createSelector(selectState, state => state.syncHash); export const selectGetSyncErrorMessage = (state) => selectState(state).getSyncErrorMessage;
export const selectGetSyncIsPending = (state) => selectState(state).getSyncIsPending;
export const selectSyncData = createSelector(selectState, state => state.syncData); export const selectSetSyncIsPending = (state) => selectState(state).setSyncIsPending;
export const selectHashChanged = (state) => selectState(state).hashChanged;
export const selectSetSyncErrorMessage = createSelector( export const selectSyncApplyIsPending = (state) => selectState(state).syncApplyIsPending;
selectState, export const selectSyncApplyErrorMessage = (state) => selectState(state).syncApplyErrorMessage;
state => state.setSyncErrorMessage export const selectSyncApplyPasswordError = (state) => selectState(state).syncApplyPasswordError;
);
export const selectGetSyncErrorMessage = createSelector(
selectState,
state => state.getSyncErrorMessage
);
export const selectGetSyncIsPending = createSelector(selectState, state => state.getSyncIsPending);
export const selectSetSyncIsPending = createSelector(selectState, state => state.setSyncIsPending);
export const selectHashChanged = createSelector(selectState, state => state.hashChanged);
export const selectSyncApplyIsPending = createSelector(
selectState,
state => state.syncApplyIsPending
);
export const selectSyncApplyErrorMessage = createSelector(
selectState,
state => state.syncApplyErrorMessage
);
export const selectSyncApplyPasswordError = createSelector(
selectState,
state => state.syncApplyPasswordError
);

View file

@ -5,10 +5,10 @@ import { parseURI } from 'util/lbryURI';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { makeSelectClaimForUri } from 'redux/selectors/claims'; import { makeSelectClaimForUri } from 'redux/selectors/claims';
import { selectPlayingUri, selectPrimaryUri } from 'redux/selectors/content'; import { selectPlayingUri, selectPrimaryUri } from 'redux/selectors/content';
import { makeSelectClientSetting, selectDaemonSettings } from 'redux/selectors/settings'; import { selectClientSetting, selectDaemonSettings } from 'redux/selectors/settings';
import { history } from 'ui/store'; 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 recsysId = 'lighthouse-v0';
const getClaimIdsFromUris = (uris) => { const getClaimIdsFromUris = (uris) => {
@ -73,7 +73,7 @@ const recsys = {
* Called from recommendedContent component * Called from recommendedContent component
*/ */
onRecsLoaded: function (claimId, uris) { onRecsLoaded: function (claimId, uris) {
if (window.store) { if (window && window.store) {
const state = window.store.getState(); const state = window.store.getState();
if (!recsys.entries[claimId]) { if (!recsys.entries[claimId]) {
recsys.createRecsysEntry(claimId); recsys.createRecsysEntry(claimId);
@ -92,7 +92,7 @@ const recsys = {
* @param: parentUuid: string (optional) * @param: parentUuid: string (optional)
*/ */
createRecsysEntry: function (claimId, parentUuid) { createRecsysEntry: function (claimId, parentUuid) {
if (window.store && claimId) { if (window && window.store && claimId) {
const state = window.store.getState(); const state = window.store.getState();
const user = selectUser(state); const user = selectUser(state);
const userId = user ? user.id : null; const userId = user ? user.id : null;
@ -173,7 +173,7 @@ const recsys = {
* if so, send the Entry. * if so, send the Entry.
*/ */
onPlayerDispose: function (claimId, isEmbedded) { onPlayerDispose: function (claimId, isEmbedded) {
if (window.store) { if (window && window.store) {
const state = window.store.getState(); const state = window.store.getState();
const playingUri = selectPlayingUri(state); const playingUri = selectPlayingUri(state);
const primaryUri = selectPrimaryUri(state); const primaryUri = selectPrimaryUri(state);
@ -196,7 +196,7 @@ const recsys = {
// * more events until player is disposed. Don't send unless floatingPlayer playingUri // * more events until player is disposed. Don't send unless floatingPlayer playingUri
// */ // */
// onLeaveFilePage: function (primaryUri) { // onLeaveFilePage: function (primaryUri) {
// if (window.store) { // if (window && window.store) {
// const state = window.store.getState(); // const state = window.store.getState();
// const claim = makeSelectClaimForUri(primaryUri)(state); // const claim = makeSelectClaimForUri(primaryUri)(state);
// const claimId = claim ? claim.claim_id : null; // const claimId = claim ? claim.claim_id : null;
@ -222,14 +222,14 @@ const recsys = {
* Send all claimIds that aren't currently playing. * Send all claimIds that aren't currently playing.
*/ */
onNavigate: function () { onNavigate: function () {
if (window.store) { if (window && window.store) {
const state = window.store.getState(); const state = window.store.getState();
const playingUri = selectPlayingUri(state); const playingUri = selectPlayingUri(state);
const actualPlayingUri = playingUri && playingUri.uri; const actualPlayingUri = playingUri && playingUri.uri;
const claim = makeSelectClaimForUri(actualPlayingUri)(state); const claim = makeSelectClaimForUri(actualPlayingUri)(state);
const playingClaimId = claim ? claim.claim_id : null; const playingClaimId = claim ? claim.claim_id : null;
// const primaryUri = selectPrimaryUri(state); // 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. // When leaving page, if floating player is enabled, play will continue.
Object.keys(recsys.entries).forEach((claimId) => { Object.keys(recsys.entries).forEach((claimId) => {
const shouldSkip = recsys.entries[claimId].parentUuid && !recsys.entries[claimId].recClaimIds; const shouldSkip = recsys.entries[claimId].parentUuid && !recsys.entries[claimId].recClaimIds;

37
flow-typed/Claim.js vendored
View file

@ -145,49 +145,12 @@ declare type PurchaseReceipt = {
type: 'purchase', type: 'purchase',
}; };
declare type ClaimErrorCensor = {
address: string,
amount: string,
canonical_url: string,
claim_id: string,
claim_op: string,
confirmations: number,
has_signing_key: boolean,
height: number,
meta: {
activation_height: number,
claims_in_channel: number,
creation_height: number,
creation_timestamp: number,
effective_amount: string,
expiration_height: number,
is_controlling: boolean,
reposted: number,
support_amount: string,
take_over_height: number,
},
name: string,
normalized_name: string,
nout: number,
permanent_url: string,
short_url: string,
timestamp: number,
txid: string,
type: string,
value: {
public_key: string,
public_key_id: string,
},
value_type: string,
}
declare type ClaimActionResolveInfo = { declare type ClaimActionResolveInfo = {
[string]: { [string]: {
stream: ?StreamClaim, stream: ?StreamClaim,
channel: ?ChannelClaim, channel: ?ChannelClaim,
claimsInChannel: ?number, claimsInChannel: ?number,
collection: ?CollectionClaim, collection: ?CollectionClaim,
errorCensor: ?ClaimErrorCensor,
}, },
} }

View file

@ -24,8 +24,9 @@ declare type CollectionGroup = {
} }
declare type CollectionEditParams = { declare type CollectionEditParams = {
uris?: Array<string>, claims?: Array<Claim>,
remove?: boolean, remove?: boolean,
claimIds?: Array<string>,
replace?: boolean, replace?: boolean,
order?: { from: number, to: number }, order?: { from: number, to: number },
type?: string, type?: string,

10
flow-typed/Comment.js vendored
View file

@ -169,8 +169,10 @@ declare type CommentAbandonParams = {
comment_id: string, comment_id: string,
creator_channel_id?: string, creator_channel_id?: string,
creator_channel_name?: string, creator_channel_name?: string,
channel_id?: string, signature?: string,
hexdata?: string, signing_ts?: string,
mod_channel_id?: string,
mod_channel_name?: string,
}; };
declare type CommentCreateParams = { declare type CommentCreateParams = {
@ -204,9 +206,11 @@ declare type ModerationBlockParams = {
// Creator that Moderator is delegated from. Used for delegated moderation // Creator that Moderator is delegated from. Used for delegated moderation
creator_channel_id?: string, creator_channel_id?: string,
creator_channel_name?: 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 // Blocks identity from comment universally, requires Admin rights on commentron instance
block_all?: boolean, 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 // If true will delete all comments of the offender, requires Admin rights on commentron for universal delete
delete_all?: boolean, delete_all?: boolean,
// The usual signature stuff // The usual signature stuff

6
flow-typed/Redux.js vendored
View file

@ -1,6 +0,0 @@
// @flow
/* eslint-disable no-use-before-define */
declare type GetState = () => any;
declare type ThunkAction = (dispatch: Dispatch, getState: GetState) => any;
declare type Dispatch = (action: {} | Promise<*> | Array<{}> | ThunkAction) => any; // Need to refer to ThunkAction
/* eslint-enable */

View file

@ -1,13 +0,0 @@
declare type CommentServerDetails = {
name: string,
url: string,
}
declare type WalletServerDetails = {
};
declare type DiskSpace = {
total: number,
free: number,
};

View file

@ -1,10 +0,0 @@
// @flow
declare type FileData = {
file?: Blob,
path: string,
duration?: number,
size?: number,
mimeType: string,
error?: string,
}

View file

@ -1,9 +0,0 @@
// @flow
declare type FileWithPath = {
file: File,
// The full path will only be available in
// the application. For browser, the name
// of the file will be used.
path: string,
}

View file

@ -22,7 +22,6 @@ declare type RowDataItem = {
channelIds?: Array<string>, channelIds?: Array<string>,
limitClaimsPerChannel?: number, limitClaimsPerChannel?: number,
pageSize?: number, pageSize?: number,
languages?: Array<string>,
}, },
route?: string, route?: string,
hideForUnauth?: boolean, hideForUnauth?: boolean,

View file

@ -52,3 +52,26 @@ declare type PublishParams = {
nsfw: boolean, nsfw: boolean,
tags: Array<Tag>, 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,
};

6
flow-typed/redux.js vendored Normal file
View file

@ -0,0 +1,6 @@
// @flow
/* eslint-disable no-use-before-define */
declare type GetState = () => any;
declare type Dispatch = any;
/* eslint-enable */

View file

@ -29,10 +29,8 @@ declare type SearchOptions = {
declare type SearchState = { declare type SearchState = {
options: SearchOptions, options: SearchOptions,
resultsByQuery: {}, resultsByQuery: {},
results: Array<string>,
hasReachedMaxResultsLength: {}, hasReachedMaxResultsLength: {},
searching: boolean, searching: boolean,
mentionQuery: string,
}; };
declare type SearchSuccess = { declare type SearchSuccess = {
@ -43,7 +41,6 @@ declare type SearchSuccess = {
size: number, size: number,
uris: Array<string>, uris: Array<string>,
recsys: string, recsys: string,
query: string,
}, },
}; };

6
flow-typed/web-file.js vendored Normal file
View file

@ -0,0 +1,6 @@
// @flow
declare type WebFile = File & {
path?: string,
title?: string,
}

View file

@ -1,6 +1,6 @@
{ {
"name": "lbry", "name": "lbry",
"version": "0.53.9", "version": "0.51.2",
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.", "description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
"keywords": [ "keywords": [
"lbry" "lbry"
@ -20,50 +20,60 @@
}, },
"main": "./dist/electron/main.js", "main": "./dist/electron/main.js",
"scripts": { "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:electron": "node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.electron.config.js",
"compile": "cross-env NODE_ENV=production yarn compile:electron", "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",
"copyenv": "copyfiles ./.env* web/",
"dev": "yarn dev:electron", "dev": "yarn dev:electron",
"dev:electron": "cross-env NODE_ENV=development node ./electron/devServer.js", "dev:electron": "cross-env NODE_ENV=development node ./electron/devServer.js",
"dev:web": "yarn copyenv && cd web && yarn dev",
"dev:web-server": "cross-env NODE_ENV=development yarn compile:web && concurrently \"cross-env NODE_ENV=development yarn compile:web --watch\" \"cd web && yarn dev:server\"",
"dev:internal-apis": "LBRY_API_URL='http://localhost:8080' yarn dev:electron",
"dev:iatv": "LBRY_API_URL='http://localhost:15400' SDK_API_URL='http://localhost:15100' yarn dev:web",
"run:web-server": "cross-env NODE_ENV=production yarn compile:web && cd web && yarn dev:server",
"pack": "electron-builder --dir", "pack": "electron-builder --dir",
"dist": "electron-builder", "dist": "electron-builder",
"build": "cross-env NODE_ENV=production yarn compile:electron && electron-builder build", "build": "cross-env NODE_ENV=production yarn compile:electron && electron-builder build",
"build:dir": "yarn build -- --dir -c.compression=store -c.mac.identity=null", "build:dir": "yarn build -- --dir -c.compression=store -c.mac.identity=null",
"crossenv": "cross-env", "crossenv": "./node_modules/cross-env/dist/bin/cross-env",
"flow": "flow", "flow": "flow",
"lint": "eslint 'ui/**/*.{js,jsx}' && eslint 'electron/**/*.js' && flow", "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 'electron/**/*.js'", "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", "format": "prettier 'src/**/*.{js,jsx,scss,json}' --write",
"flow-defs": "flow-typed install", "flow-defs": "flow-typed install",
"precommit": "lint-staged", "precommit": "lint-staged",
"postinstall": "electron-builder install-app-deps && node ./build/downloadDaemon.js", "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'" "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": { "dependencies": {
"@electron/remote": "^2.0.1",
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@mui/material": "^5.2.1",
"@ungap/from-entries": "^0.2.1", "@ungap/from-entries": "^0.2.1",
"auto-launch": "^5.0.5", "auto-launch": "^5.0.5",
"electron-dl": "^3.2.0", "electron-dl": "^1.11.0",
"electron-log": "^4.4.8", "electron-log": "^2.2.12",
"electron-notarize": "^1.0.0", "electron-notarize": "^1.0.0",
"electron-updater": "^4.2.4", "electron-updater": "^4.2.4",
"express": "^4.17.1", "express": "^4.17.1",
"ffmpeg-probe": "^1.0.6",
"humanize-duration": "^3.27.0", "humanize-duration": "^3.27.0",
"if-env": "^1.0.4",
"match-sorter": "^6.3.0", "match-sorter": "^6.3.0",
"mime": "^3.0.0",
"node-html-parser": "^5.1.0",
"parse-duration": "^1.0.0", "parse-duration": "^1.0.0",
"player.js": "^0.1.0",
"proxy-polyfill": "0.1.6", "proxy-polyfill": "0.1.6",
"re-reselect": "^4.0.0", "re-reselect": "^4.0.0",
"react-beautiful-dnd": "^13.1.0", "react-datetime-picker": "^3.2.1",
"react-datetime-picker": "^3.4.3", "react-plastic": "^1.1.1",
"react-top-loading-bar": "^2.0.1",
"remove-markdown": "^0.3.0",
"rss": "^1.2.2",
"source-map-explorer": "^2.5.2", "source-map-explorer": "^2.5.2",
"sudo-prompt": "^9.2.1", "tempy": "^0.6.0",
"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": { "devDependencies": {
"@babel/core": "^7.0.0", "@babel/core": "^7.0.0",
@ -74,17 +84,17 @@
"@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-flow-strip-types": "^7.2.3", "@babel/plugin-transform-flow-strip-types": "^7.2.3",
"@babel/plugin-transform-runtime": "^7.4.3", "@babel/plugin-transform-runtime": "^7.4.3",
"@babel/polyfill": "^7.12.1", "@babel/polyfill": "^7.2.5",
"@babel/preset-env": "^7.12.11", "@babel/preset-env": "^7.12.11",
"@babel/preset-flow": "^7.12.1", "@babel/preset-flow": "^7.12.1",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@babel/register": "^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", "@hot-loader/react-dom": "^16.13",
"@meetfranz/electron-cookies": "^3.0.2", "@reach/auto-id": "^0.13.0",
"@reach/combobox": "^0.12.1", "@reach/combobox": "^0.12.1",
"@reach/menu-button": "0.8.6", "@reach/menu-button": "0.7.4",
"@reach/rect": "^0.16.0", "@reach/rect": "^0.13.0",
"@reach/tabs": "^0.1.5", "@reach/tabs": "^0.1.5",
"@reach/tooltip": "^0.12.1", "@reach/tooltip": "^0.12.1",
"@reach/utils": "^0.12.1", "@reach/utils": "^0.12.1",
@ -92,17 +102,21 @@
"@sentry/webpack-plugin": "^1.10.0", "@sentry/webpack-plugin": "^1.10.0",
"@types/three": "^0.103.2", "@types/three": "^0.103.2",
"adm-zip": "^0.4.13", "adm-zip": "^0.4.13",
"async-exit-hook": "^2.0.1",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
"babel-loader": "^8.0.5", "babel-loader": "^8.0.5",
"babel-plugin-add-module-exports": "^1.0.4", "babel-plugin-add-module-exports": "^1.0.4",
"babel-plugin-import-glob": "^2.0.0", "babel-plugin-import-glob": "^2.0.0",
"babel-plugin-transform-imports": "^1.5.1", "babel-plugin-transform-imports": "^1.5.1",
"babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-plugin-transform-object-rest-spread": "^6.26.0",
"bluebird": "^3.5.1",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"codemirror": "^5.39.2", "codemirror": "^5.39.2",
"concurrently": "^4.1.2",
"connected-react-router": "^6.8.0", "connected-react-router": "^6.8.0",
"copy-webpack-plugin": "^6.4.1", "copy-webpack-plugin": "^5.1.2",
"copyfiles": "^2.4.1",
"country-data": "^0.0.31", "country-data": "^0.0.31",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
@ -113,10 +127,11 @@
"decompress": "^4.2.1", "decompress": "^4.2.1",
"del": "^3.0.0", "del": "^3.0.0",
"devtron": "^1.4.0", "devtron": "^1.4.0",
"dom-scroll-into-view": "^1.2.1",
"dotenv-defaults": "^2.0.1", "dotenv-defaults": "^2.0.1",
"dotenv-webpack": "^1.8.0", "dotenv-webpack": "^1.8.0",
"electron": "17.2.0", "electron": "9.4.0",
"electron-builder": "^22.10.5", "electron-builder": "^22.9.1",
"electron-devtools-installer": "^3.1.1", "electron-devtools-installer": "^3.1.1",
"electron-is-dev": "^0.3.0", "electron-is-dev": "^0.3.0",
"electron-webpack": "^2.8.2", "electron-webpack": "^2.8.2",
@ -138,19 +153,22 @@
"eslint-plugin-standard": "^4.0.1", "eslint-plugin-standard": "^4.0.1",
"file-loader": "^4.2.0", "file-loader": "^4.2.0",
"flow-bin": "^0.97.0", "flow-bin": "^0.97.0",
"flow-typed": "^3.7.0", "flow-typed": "^2.3.0",
"formik": "^0.10.4", "formik": "^0.10.4",
"hast-util-sanitize": "^3.0.2", "hast-util-sanitize": "^3.0.2",
"history": "^4.9.0", "history": "^4.9.0",
"husky": "^3.1.0", "husky": "^3.1.0",
"imagesloaded": "^4.1.4", "imagesloaded": "^4.1.4",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git",
"lint-staged": "^7.0.2", "lint-staged": "^7.0.2",
"localforage": "^1.7.1", "localforage": "^1.7.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.14",
"mammoth": "^1.4.16", "mammoth": "^1.4.16",
"moment": "^2.29.2", "moment": "^2.22.0",
"node-fetch": "^2.6.7", "node-abi": "^2.5.1",
"node-fetch": "^2.6.1",
"node-libs-browser": "^2.1.0",
"node-loader": "^0.6.0", "node-loader": "^0.6.0",
"node-wget": "^0.4.3", "node-wget": "^0.4.3",
"nodemon": "^1.19.1", "nodemon": "^1.19.1",
@ -165,6 +183,7 @@
"rc-progress": "^2.0.6", "rc-progress": "^2.0.6",
"react": "^16.8.2", "react": "^16.8.2",
"react-awesome-lightbox": "^1.7.3", "react-awesome-lightbox": "^1.7.3",
"react-confetti": "^4.0.1",
"react-dom": "^16.8.2", "react-dom": "^16.8.2",
"react-draggable": "^3.3.0", "react-draggable": "^3.3.0",
"react-google-recaptcha": "^2.0.1", "react-google-recaptcha": "^2.0.1",
@ -175,6 +194,7 @@
"react-router": "^5.1.0", "react-router": "^5.1.0",
"react-router-dom": "^5.1.0", "react-router-dom": "^5.1.0",
"react-simplemde-editor": "^4.1.3", "react-simplemde-editor": "^4.1.3",
"react-spring": "^8.0.20",
"reakit": "^1.0.0-beta.13", "reakit": "^1.0.0-beta.13",
"redux": "^3.6.0", "redux": "^3.6.0",
"redux-persist": "^5.10.0", "redux-persist": "^5.10.0",
@ -191,17 +211,23 @@
"sass": "^1.29.0", "sass": "^1.29.0",
"sass-loader": "^7.1.0", "sass-loader": "^7.1.0",
"semver": "^5.3.0", "semver": "^5.3.0",
"stream-to-blob-url": "^2.1.1",
"strip-markdown": "^3.0.3", "strip-markdown": "^3.0.3",
"style-loader": "^0.23.1", "style-loader": "^0.23.1",
"terser-webpack-plugin": "^4.2.3", "terser-webpack-plugin": "^1.2.3",
"three-full": "^28.0.2", "three": "^0.125.0",
"three-full": "^17.1.0",
"tiny-relative-date": "^1.3.0",
"tree-kill": "^1.1.0",
"unist-util-visit": "^2.0.3", "unist-util-visit": "^2.0.3",
"uuid": "^8.3.2", "uuid": "^8.3.2",
"video.js": "^7.14.3", "vast-client": "^3.1.1",
"video.js": "^7.13.3",
"videojs-contrib-quality-levels": "^2.0.9", "videojs-contrib-quality-levels": "^2.0.9",
"videojs-event-tracking": "^1.0.1", "videojs-event-tracking": "^1.0.1",
"villain-react": "^1.0.9", "villain-react": "^1.0.9",
"webpack": "^4.44.2", "wavesurfer.js": "^2.2.1",
"webpack": "^4.28.4",
"webpack-bundle-analyzer": "^3.1.0", "webpack-bundle-analyzer": "^3.1.0",
"webpack-cli": "^3.3.10", "webpack-cli": "^3.3.10",
"webpack-config-utils": "^2.3.1", "webpack-config-utils": "^2.3.1",
@ -210,17 +236,21 @@
"webpack-hot-middleware": "^2.24.3", "webpack-hot-middleware": "^2.24.3",
"webpack-merge": "^4.2.1", "webpack-merge": "^4.2.1",
"webpack-node-externals": "^1.7.2", "webpack-node-externals": "^1.7.2",
"y18n": "^4.0.1",
"yarnhook": "^0.2.0" "yarnhook": "^0.2.0"
}, },
"engines": { "engines": {
"node": ">=16.13", "node": ">=7",
"yarn": "^1.3" "yarn": "^1.3"
}, },
"lbrySettings": { "lbrySettings": {
"lbrynetDaemonVersion": "0.113.0", "lbrynetDaemonVersion": "0.99.0",
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip", "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
"lbrynetDaemonDir": "static/daemon", "lbrynetDaemonDir": "static/daemon",
"lbrynetDaemonFileName": "lbrynet" "lbrynetDaemonFileName": "lbrynet",
}, "LBRYFirstVersion": "0.0.20",
"packageManager": "yarn@3.2.0" "LBRYFirstUrlTemplate": "https://github.com/lbryio/lbry-first/releases/download/vLBRYFIRSTVER/lbry-first_OSNAME_amd64.zip",
"LBRYFirstDir": "static/lbry-first",
"LBRYFirstFileName": "lbry-first"
}
} }

View file

@ -55,19 +55,30 @@
"Deposit": "Deposit", "Deposit": "Deposit",
"Language": "Language", "Language": "Language",
"English": "English", "English": "English",
"Arabic": "Arabic",
"Chinese": "Chinese", "Chinese": "Chinese",
"Croatian": "Croatian",
"Czech": "Czech",
"Dutch": "Dutch",
"French": "French", "French": "French",
"German": "German", "German": "German",
"Japanese": "Japanese", "Greek": "Greek",
"Russian": "Russian", "Hindi": "Hindi",
"Spanish": "Spanish",
"Indonesian": "Indonesian", "Indonesian": "Indonesian",
"Italian": "Italian", "Italian": "Italian",
"Dutch": "Dutch", "Japanese": "Japanese",
"Turkish": "Turkish",
"Polish": "Polish",
"Malay": "Malay",
"Khmer": "Khmer", "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%.", "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", "LBRY Terms of Service": "LBRY Terms of Service",
"Enter a thumbnail URL": "Enter a thumbnail URL", "Enter a thumbnail URL": "Enter a thumbnail URL",
@ -97,6 +108,7 @@
"About --[tab title in Channel Page]--": "About", "About --[tab title in Channel Page]--": "About",
"About --[link title in Sidebar or Footer]--": "About", "About --[link title in Sidebar or Footer]--": "About",
"Community Guidelines": "Community Guidelines", "Community Guidelines": "Community Guidelines",
"FAQ and Support": "FAQ and Support",
"Share Channel": "Share Channel", "Share Channel": "Share Channel",
"Go to page:": "Go to page:", "Go to page:": "Go to page:",
"Enter a URL for your thumbnail.": "Enter a URL for your thumbnail.", "Enter a URL for your thumbnail.": "Enter a URL for your thumbnail.",
@ -110,7 +122,7 @@
"Balance": "Balance", "Balance": "Balance",
"Full History": "Full History", "Full History": "Full History",
"Refresh": "Refresh", "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", "Amount": "Amount",
"Recipient address": "Recipient address", "Recipient address": "Recipient address",
"Send": "Send", "Send": "Send",
@ -172,10 +184,10 @@
"Confirm External Resource": "Confirm External Resource", "Confirm External Resource": "Confirm External Resource",
"Continue": "Continue", "Continue": "Continue",
"This file has been shared with you by other people.": "This file has been shared with you by other people.", "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", "Yes": "Yes",
"No": "No", "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", "View file": "View file",
"Files": "Files", "Files": "Files",
"Channels": "Channels", "Channels": "Channels",
@ -238,9 +250,9 @@
"release notes": "release notes", "release notes": "release notes",
"Read the FAQ": "Read the FAQ", "Read the FAQ": "Read the FAQ",
"Our FAQ answers many common questions.": "Our FAQ answers many common questions.", "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", "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", "View your log": "View your log",
"support": "support", "support": "support",
"Open Log": "Open Log", "Open Log": "Open Log",
@ -323,7 +335,7 @@
"Please wait for thumbnail to finish uploading": "Please wait for thumbnail to finish uploading", "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.", "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.", "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", "API connection string": "API connection string",
"Method": "Method", "Method": "Method",
"Parameters": "Parameters", "Parameters": "Parameters",
@ -331,7 +343,7 @@
"Error message": "Error message", "Error message": "Error message",
"Error data": "Error data", "Error data": "Error data",
"Error": "Error", "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": "Customize",
"Customize Your Homepage": "Customize Your Homepage", "Customize Your Homepage": "Customize Your Homepage",
"Tags You Follow": "Tags You Follow", "Tags You Follow": "Tags You Follow",
@ -378,7 +390,6 @@
"Read more about why we do this.": "Read more about why we do this.", "Read more about why we do this.": "Read more about why we do this.",
"Submit Phone Number": "Submit Phone Number", "Submit Phone Number": "Submit Phone Number",
"Standard messaging rates apply. Having trouble?": "Standard messaging rates apply. Having trouble?", "Standard messaging rates apply. Having trouble?": "Standard messaging rates apply. Having trouble?",
"Join LBRY Chat": "Join LBRY Chat",
"Blockchain Sync": "Blockchain Sync", "Blockchain Sync": "Blockchain Sync",
"No rewards available": "No rewards available", "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.", "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.", "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", "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.", "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", "Read Odysee Basics FAQ": "Read Odysee Basics FAQ",
"View all LBRY FAQs": "View all LBRY FAQs", "View all Odysee FAQs": "View all Odysee FAQs",
"Find assistance": "Find assistance", "Find assistance": "Find assistance",
"Email Us": "Email Us", "Email Us": "Email Us",
"Today": "Today", "Today": "Today",
@ -468,24 +479,9 @@
"New --[clears Publish Form]--": "New", "New --[clears Publish Form]--": "New",
"Loading": "Loading", "Loading": "Loading",
"This file is in your library.": "This file is in your library.", "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.", "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", "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", "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.", "Sorry, we can't preview this file.": "Sorry, we can't preview this file.",
"View File": "View File", "View File": "View File",
"Close": "Close", "Close": "Close",
@ -517,7 +513,7 @@
"Not enough Credits": "Not enough Credits", "Not enough Credits": "Not enough Credits",
"Decrease amount to account for transaction fee": "Decrease amount to account for transaction fee", "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.", "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",
"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.", "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 channel": "Your YouTube channel",
"Your YouTube channels": "Your YouTube channels", "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.", "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.", "%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", "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.", "%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.", "Failed to load %language% translations.": "Failed to load %language% translations.",
"contact support": "contact support", "contact support": "contact support",
"If you continue to have issues, please %support%.": "If you continue to have issues, please %support%.", "If you continue to have issues, please %support%.": "If you continue to have issues, please %support%.",
"lbry.tv Account": "lbry.tv Account", "odysee.com Account": "odysee.com 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.", "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", "Paid content cannot be embedded": "Paid content cannot be embedded",
"This content cannot be embedded": "This 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.", "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", "Use custom wallet servers": "Use custom wallet servers",
"Remove custom wallet server": "Remove custom wallet server", "Remove custom wallet server": "Remove custom wallet server",
"Add": "Add", "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", "lbry.tv": "lbry.tv",
"Bid position must be a number.": "Bid position must be a number.", "Bid position must be a number.": "Bid position must be a number.",
"Copy": "Copy", "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?", "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", "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.", "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.", "Yes, but only with Odysee, Inc.": "Yes, but only with Odysee, 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.", "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 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.", "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", "Let's go": "Let's go",
"Do you agree to the %terms%?": "Do you agree to the %terms%?", "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.", "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.", "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.", "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%\"?", "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.", "%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% creator": "Nice! You are currently following %followingCount% creator",
"Nice! You are currently following %followingCount% creators": "Nice! You are currently following %followingCount% creators", "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", "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 blank": "Amount cannot be blank",
"Amount cannot be more than available": "Amount cannot be more than available", "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", "Amount to unlock": "Amount to unlock",
"Unlock tips": "Unlock tips", "Unlock tips": "Unlock tips",
"Support This Claim": "Support This Claim", "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 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 $%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 %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 as a tip, Mahalo!": "You sent %amount% LBRY Credits as a tip, Mahalo!",
"You sent %amount% LBRY Credits": "You sent %amount% LBRY Credits", "You sent %amount% LBRY Credits": "You sent %amount% LBRY Credits",
"No stats found": "No stats found", "No stats found": "No stats found",
@ -1055,15 +1056,15 @@
"Description of your issue or feature request": "Description of your issue or feature request", "Description of your issue or feature request": "Description of your issue or feature request",
"Submitting...": "Submitting...", "Submitting...": "Submitting...",
"Submit Report": "Submit Report", "Submit Report": "Submit Report",
"Developer?": "Developer?", "Developer? Or looking for more?": "Developer? Or looking for more?",
"You can also:": "You can also:", "You can also:": "You can also:",
"Submit an issue on GitHub": "Submit an issue on GitHub", "Submit an issue on GitHub": "Submit an issue on GitHub",
"Most viewed recent content": "Most viewed recent content", "Most viewed recent content": "Most viewed recent content",
"Most viewed content all time": "Most viewed content all time", "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.", "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", "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", "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.", "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", "Discover Channels": "Discover Channels",
@ -1083,11 +1084,11 @@
"Trending For Your Tags": "Trending For Your Tags", "Trending For Your Tags": "Trending For Your Tags",
"Latest From @lbry": "Latest From @lbry", "Latest From @lbry": "Latest From @lbry",
"Latest From @lbrycast": "Latest From @lbrycast", "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%", "Log in to %domain%": "Log in to %domain%",
"download the app": "download the app", "odysee collects usage information for itself only (%more_information%).": "odysee 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%).", "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?",
"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?",
"more --[value for \"more_information\"]--": "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.", "%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.", "Please %sign_in_link% to comment.": "Please %sign_in_link% to comment.",
@ -1111,7 +1112,7 @@
"minute": "minute", "minute": "minute",
"hours": "hours", "hours": "hours",
"hour": "hour", "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", "Connected": "Connected",
"Not connected": "Not connected", "Not connected": "Not connected",
"this link": "this link", "this link": "this link",
@ -1293,8 +1294,19 @@
"Select a file to upload": "Select a file to upload", "Select a file to upload": "Select a file to upload",
"Select file to upload": "Select file to upload", "Select file to upload": "Select file to upload",
"Url copied.": "Url copied.", "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...", "Uploading...": "Uploading...",
"Creating...": "Creating...", "Creating...": "Creating...",
"Stopped. Duplicate session detected.": "Stopped. Duplicate session detected.",
"Use a URL": "Use a URL", "Use a URL": "Use a URL",
"Edit Cover Image": "Edit Cover Image", "Edit Cover Image": "Edit Cover Image",
"Cover Image": "Cover Image", "Cover Image": "Cover Image",
@ -1400,6 +1412,7 @@
"Cheese": "Cheese", "Cheese": "Cheese",
"Cooking": "Cooking", "Cooking": "Cooking",
"Big Hits": "Big Hits", "Big Hits": "Big Hits",
"Education": "Education",
"Enlightenment": "Enlightenment", "Enlightenment": "Enlightenment",
"Gaming": "Gaming", "Gaming": "Gaming",
"Game": "Game", "Game": "Game",
@ -1410,6 +1423,7 @@
"Movies": "Movies", "Movies": "Movies",
"News": "News", "News": "News",
"News & Politics": "News & Politics", "News & Politics": "News & Politics",
"Pop Culture": "Pop Culture",
"Finance 2.0": "Finance 2.0", "Finance 2.0": "Finance 2.0",
"The Universe": "The Universe", "The Universe": "The Universe",
"Wild West": "Wild West", "Wild West": "Wild West",
@ -1432,7 +1446,6 @@
"Log in to %SITE_NAME%": "Log in to %SITE_NAME%", "Log in to %SITE_NAME%": "Log in to %SITE_NAME%",
"Log in": "Log in", "Log in": "Log in",
"Not Yet": "Not Yet", "Not Yet": "Not Yet",
"Preparing...": "Preparing...",
"Confirm Upload": "Confirm Upload", "Confirm Upload": "Confirm Upload",
"Confirm Edit": "Confirm Edit", "Confirm Edit": "Confirm Edit",
"Create Livestream": "Create Livestream", "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.", "You have no lists! Create one from any playable content.": "You have no lists! Create one from any playable content.",
"Pick": "Pick", "Pick": "Pick",
"You have unpublished lists! %pick% one and publish it!": "You have unpublished lists! %pick% one and publish it!", "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", "Publish Something": "Publish Something",
"Repost Something": "Repost Something", "Repost Something": "Repost Something",
"Watch on %SITE_NAME%": "Watch on %SITE_NAME%", "Watch on %SITE_NAME%": "Watch on %SITE_NAME%",
@ -1653,6 +1665,7 @@
"Most Supported": "Most Supported", "Most Supported": "Most Supported",
"Search Results": "Search Results", "Search Results": "Search Results",
"View All Results": "View All Results", "View All Results": "View All Results",
"View All Playlists": "View All Playlists",
"View competing uploads for %name%": "View competing uploads for %name%", "View competing uploads for %name%": "View competing uploads for %name%",
"Homepage": "Homepage", "Homepage": "Homepage",
"Currently winning": "Currently winning", "Currently winning": "Currently winning",
@ -1663,7 +1676,7 @@
"POWERED BY %lbry_link%": "POWERED BY %lbry_link%", "POWERED BY %lbry_link%": "POWERED BY %lbry_link%",
"Learn more about LBRY Credits on %DOMAIN%": "Learn more about LBRY Credits on %DOMAIN%", "Learn more about LBRY Credits on %DOMAIN%": "Learn more about LBRY Credits on %DOMAIN%",
"Results boosted by %lbc%": "Results boosted by %lbc%", "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 on notifications": "Turn on notifications",
"Turn off notifications": "Turn off notifications", "Turn off notifications": "Turn off notifications",
"Notifications turned off for %channel%.": "Notifications turned off for %channel%.", "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", "Enter a name or %domain% URL": "Enter a name or %domain% URL",
"Winning amount: %amount%": "Winning amount: %amount%", "Winning amount: %amount%": "Winning amount: %amount%",
"Download or get the app": "Download or get the app", "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", "Repost Here": "Repost Here",
"Publish Here": "Publish Here", "Publish Here": "Publish Here",
"Close sidebar - hide channels you are following.": "Close sidebar - hide channels you are following.", "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", "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.", "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", "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": "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.", "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.", "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", "Alternative coins": "Alternative coins",
"Failed to initiate swap.": "Failed to initiate swap.", "Failed to initiate swap.": "Failed to initiate swap.",
"Failed to query swap status.": "Failed to query swap status.", "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.", "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.", "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", "The amount needs to be higher": "The amount needs to be higher",
@ -2022,7 +2036,8 @@
"Replay video available": "Replay video available", "Replay video available": "Replay video available",
"Check for Replays": "Check for Replays", "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", "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", "Latest": "Latest",
"Channel Not Found": "Channel Not Found", "Channel Not Found": "Channel Not Found",
"Channel not found": "Channel not found", "Channel not found": "Channel not found",
@ -2163,6 +2178,8 @@
"Bank Accounts": "Bank Accounts", "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.", "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", "Payment Methods": "Payment Methods",
"Total Received Tips": "Total Received Tips",
"Withdrawn": "Withdrawn",
"Incoming": "Incoming", "Incoming": "Incoming",
"Outgoing": "Outgoing", "Outgoing": "Outgoing",
"Credits --[transactions tab]--": "Credits", "Credits --[transactions tab]--": "Credits",
@ -2178,149 +2195,52 @@
"Card Last 4": "Card Last 4", "Card Last 4": "Card Last 4",
"Search blocked channel name": "Search blocked channel name", "Search blocked channel name": "Search blocked channel name",
"Discuss": "Discuss", "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%", "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", "Show more livestreams": "Show more livestreams",
"Creator": "Creator", "Creator": "Creator",
"From comments": "From comments", "From comments": "From comments",
"From search": "From search", "From search": "From search",
"Manage tags": "Manage tags", "Manage tags": "Manage tags",
"No Reposts": "No Reposts", "Notification Delivery": "Notification Delivery",
"You haven't reposted anything yet. Do it.": "You haven't reposted anything yet. Do it.", "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 youd like to be emailed about.": "Choose which topics youd 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...", "Claiming credits...": "Claiming credits...",
"%totalComments% comments": "%totalComments% comments",
"(%lbc_balance% available)": "(%lbc_balance% available)",
"Sending...": "Sending...", "Sending...": "Sending...",
"Trending for #Art": "Trending for #Art",
"Trending for #Btc": "Trending for #Btc",
"Trending for #Music": "Trending for #Music",
"You sent %lbc% as a tip, Mahalo!": "You sent %lbc% as a tip, Mahalo!",
"Export All": "Export All",
"Default share url (%name%)": "Default share url (%name%)",
"Custom share url": "Custom share url",
"Share url": "Share url",
"Odysee Connect": "Odysee Connect",
"Log in to %CLOUD_CONNECT_SITE_NAME%": "Log in to %CLOUD_CONNECT_SITE_NAME%",
"Cloud Connect": "Cloud Connect",
"Connect your wallet to Odysee": "Connect your wallet to Odysee",
"Minimum time gap in seconds between comments.": "Minimum time gap in seconds between comments.",
"Enabling a minimum amount to comment will force all comments to have tips associated with them. This can help prevent spam.": "Enabling a minimum amount to comment will force all comments to have tips associated with them. This can help prevent spam.",
"Comments containing these words will be blocked.": "Comments containing these words will be blocked.",
"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", "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",
"Enable Data Hosting": "Enable Data Hosting",
"Data over the limit will be deleted within 30 minutes. This will make the Yrbl cry a little bit.": "Data over the limit will be deleted within 30 minutes. This will make the Yrbl cry a little bit.",
"Choose %asset%": "Choose %asset%", "Choose %asset%": "Choose %asset%",
"Showing %filtered% results of %total%": "Showing %filtered% results of %total%", "Showing %filtered% results of %total%": "Showing %filtered% results of %total%",
"filtered": "filtered", "Failed to synchronize settings. Wait a while before retrying.": "Failed to synchronize settings. Wait a while before retrying.",
"View All Playlists": "View All Playlists", "You are offline. Check your internet connection.": "You are offline. Check your internet connection.",
"Your wallet is not currently using a cloud sync service. You are in control of backing up your wallet.": "Your wallet is not currently using a cloud sync service. You are in control of backing up your wallet.", "A new version of Odysee is available.": "A new version of Odysee is available.",
"Sending": "Sending", "Reset stream": "Reset stream",
"You sent %lbc%": "You sent %lbc%", "Live stream successfully reset.": "Live stream successfully reset.",
"Buy LBC": "Buy LBC", "There was an error resetting the live stream.": "There was an error resetting the live stream.",
"This is information like error logging, performance tracking, and usage statistics. It includes your IP address and basic system details, but no other identifying information (unless you connect to a cloud service)": "This is information like error logging, performance tracking, and usage statistics. It includes your IP address and basic system details, but no other identifying information (unless you connect to a cloud service)", "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%.",
"Use official LBRY wallet servers": "Use official LBRY wallet servers",
"Enable Prerelease Updates": "Enable Prerelease Updates",
"Enable Upgrade to Test Builds": "Enable Upgrade to Test Builds",
"Prereleases may break things and we may not be able to fix them for you.": "Prereleases may break things and we may not be able to fix them for you.",
"A channel is required to repost on LBRY": "A channel is required to repost on LBRY",
"Admin": "Admin",
"Stickers": "Stickers", "Stickers": "Stickers",
"Different Sticker": "Different Sticker", "Different Sticker": "Different Sticker",
"LBC": "LBC", "Cash": "Cash",
"Add a Card": "Add a Card", "Switch to Cash": "Switch to Cash",
" To Tip Creators": " To Tip Creators", "Switch to Credits": "Switch to Credits",
"Nothing found": "Nothing found", "Cookies": "Cookies",
"From Comments": "From Comments", "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!",
"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 of $%input_amount% LBC in USB is lower than price of $%price_amount%": "Amount of $%input_amount% LBC in USB is lower than price of $%price_amount%",
"Hosting for content you have downloaded": "Hosting for content you have downloaded",
"Hosting content selected by the network": "Hosting content selected by the network",
"Remove all unavailable claims": "Remove all unavailable claims",
"Drag": "Drag",
"Move Top": "Move Top",
"Move Bottom": "Move Bottom",
"Move Up": "Move Up",
"Move Down": "Move Down",
"Trending for #Game": "Trending for #Game",
"Remove custom comment server": "Remove custom comment server",
"Use Https": "Use Https",
"Server URL": "Server URL",
"Use https": "Use https",
"Custom Servers": "Custom Servers",
"Add A Server": "Add A Server",
"Autoplay Next is off.": "Autoplay Next is off.",
"Shuffle is on.": "Shuffle is on.",
"Shuffle is off.": "Shuffle is off.",
"Loop is on.": "Loop is on.",
"Loop is off.": "Loop is off.",
"View History Hosting lets you choose how much storage to use helping content you've consumed.": "View History Hosting lets you choose how much storage to use helping content you've consumed.",
"Clean Now": "Clean Now",
"Enable Automatic Hosting": "Enable Automatic Hosting",
"Download and serve arbitrary data on the network.": "Download and serve arbitrary data on the network.",
"View History Hosting": "View History Hosting",
"Disable automatic updates": "Disable automatic updates",
"Preven't new updates to be downloaded automatically in the background (we will keep notifying you if there is an update)": "Preven't new updates to be downloaded automatically in the background (we will keep notifying you if there is an update)",
"Unlimited View Hosting": "Unlimited View Hosting",
"Choose View Hosting Limit": "Choose View Hosting Limit",
"View Hosting Limit (GB)": "View Hosting Limit (GB)",
"%free% of %total% available": "%free% of %total% available",
"Short (< 4 min)": "Short (< 4 min)",
"Medium (4 - 20 min)": "Medium (4 - 20 min)",
"Rename List": "Rename List",
"New Name": "New Name",
"Rename": "Rename",
"New Collection Name": "New Collection Name",
"In %collection%": "In %collection%",
"Add to %collection%": "Add to %collection%",
"Show this channel your appreciation by sending a donation of Credits. ": "Show this channel your appreciation by sending a donation of Credits. ",
"You've entered the land of content freedom! Let's make sure everything is ship shape.": "You've entered the land of content freedom! Let's make sure everything is ship shape.",
"By continuing, you agree to the %terms%": "By continuing, you agree to the %terms%",
"Privacy": "Privacy",
"LBRY takes privacy and choice seriously. Is it ok if we monitor performance and help creators track their views?": "LBRY takes privacy and choice seriously. Is it ok if we monitor performance and help creators track their views?",
"Yes, share with LBRY": "Yes, share with LBRY",
"Search Uploads": "Search Uploads",
"This refundable boost will improve the discoverability of this %claimTypeText% while active. ": "This refundable boost will improve the discoverability of this %claimTypeText% while active. ",
"Show less": "Show less",
"Elements": "Elements",
"Icons": "Icons",
"Go to": "Go to",
"Clearing...": "Clearing...",
"Clear Views": "Clear Views",
"Show Video View Progress": "Show Video View Progress",
"Display view progress on thumbnail. This setting will not hide any blockchain activity or downloads.": "Display view progress on thumbnail. This setting will not hide any blockchain activity or downloads.",
"Content Hosting": "Content Hosting",
"Hosting": "Hosting",
"Viewed Hosting": "Viewed Hosting",
"Auto Hosting": "Auto Hosting",
"Help creators and improve the P2P data network by hosting content.": "Help creators and improve the P2P data network by hosting content.",
"I'm happy with my settings": "I'm happy with my settings",
"We've noticed you already have some settings.": "We've noticed you already have some settings.",
"You choose how much data to host.": "You choose how much data to host.",
"Go back": "Go back",
"Custom Hosting": "Custom Hosting",
"Automatic Hosting (GB)": "Automatic Hosting (GB)",
"* 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.": "* 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.",
"Help improve the P2P data network (and make LBRY users happy) by hosting data.": "Help improve the P2P data network (and make LBRY users happy) by hosting data.",
"View History Hosting lets you choose how much storage to use hosting content you've consumed.": "View History Hosting lets you choose how much storage to use hosting content you've consumed.",
"Automatic Hosting downloads a small portion of content active on the network.": "Automatic Hosting downloads a small portion of content active on the network.",
"Publishes --[legend, storage category]--": "Publishes",
"Auto Hosting --[legend, storage category]--": "Auto Hosting",
"View Hosting --[legend, storage category]--": "View Hosting",
"%spaceUsed% of %limit% GB": "%spaceUsed% of %limit% GB",
"%spaceUsed% of %limit% Free GB": "%spaceUsed% of %limit% Free GB",
"Disabled": "Disabled",
"Free --[legend, unused disk space]--": "Free",
"Top content in %language%": "Top content in %language%",
"Apply": "Apply",
"Disable background": "Disable background",
"Installing, please wait...": "Installing, please wait...",
"There was an error during installation. Please, try again.": "There was an error during installation. Please, try again.",
"Odysee Connect --[Section in Help Page]--": "Odysee Connect",
"Your hub has blocked this content because it subscribes to the following blocking channel:": "Your hub has blocked this content because it subscribes to the following blocking channel:",
"Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.": "Your hub has blocked access to this content do to a complaint received under the US Digital Millennium Copyright Act.",
"Autoplay Next is on.": "Autoplay Next is on.",
"This will be visible in a few minutes after you submit this form.": "This will be visible in a few minutes after you submit this form.",
"Anon --[used in <%anonymous% Reposted>]--": "Anon",
"Your update is now pending. It will take a few minutes to appear for other users.": "Your update is now pending. It will take a few minutes to appear for other users.",
"--end--": "--end--" "--end--": "--end--"
} }

View file

@ -1,3 +0,0 @@
owner: lbryio
repo: lbry-desktop
provider: github

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,102 +0,0 @@
<svg width="2859" height="3858" viewBox="0 0 2859 3858" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.997" d="M229.331 2573.92C227.658 2571.29 287.004 2544.38 324.218 2448.2C337.11 2414.69 347.357 2369.08 337.943 2323.16C334.776 2307.96 329.583 2292.41 320.457 2279.96C313.372 2270.39 303.743 2262.04 292.932 2259.23C281.648 2255.61 267.954 2257.93 254.626 2263.16C231.995 2270.93 211.286 2285.24 190.078 2302.35C167.021 2283.54 134.978 2268.82 104.768 2273.7C81.902 2276.54 60.511 2295.24 49.4462 2318.86C3.74465 2410.06 71.315 2519.88 150.29 2625.09C150.29 2625.09 150.29 2625.09 150.29 2625.09C230.276 2725.89 340.979 2812.76 472.956 2860.92C580.106 2900.19 698.127 2914.49 816.398 2905.34C858.558 2901.63 900.297 2891.12 931.227 2867C941.601 2859.12 950.867 2849.65 958.548 2839.11C967.767 2826.46 974.702 2812.27 978.534 2797.4C985.145 2772.44 984.014 2745.1 975.509 2720.73V2720.73C967.343 2698.01 953.089 2677.33 934.061 2665.58C910.149 2650.23 881.933 2641.07 853.199 2637.76C819.885 2634.11 785.627 2639.42 757.021 2654.45C719.662 2673.22 691.325 2709.24 671.796 2748.4C639.342 2814.04 629.429 2890.35 625.585 2963.42C620.787 3054.16 660.729 3141.58 719.642 3201.36C770.676 3252.96 831.322 3289.74 893.723 3308.69C908.697 3313.56 926.857 3313.53 945.412 3314.04C973.343 3314.51 1002.22 3313.39 1029.19 3312.46C1112.71 3309.54 1178.34 3307.56 1247.24 3311.4C1252.48 3312.55 1259.26 3308.03 1265.44 3302.94C1274.49 3294.73 1281.43 3283.17 1287.71 3271.73C1306.29 3237.38 1318.13 3197.66 1326.75 3167.88C1349.83 3064.98 1363.32 3012.13 1366.36 3012.73C1369.41 3013.33 1361.94 3067.36 1344.36 3171.88C1344.36 3171.88 1344.36 3171.88 1344.36 3171.88C1337.33 3202.62 1327.04 3243.92 1308.44 3282.33C1302.39 3294.99 1294.5 3308.84 1282.76 3320.8C1274.53 3329.67 1261.97 3337.01 1246.23 3337.34C1178.13 3335.81 1114.64 3339.62 1030.69 3344.52C1003.79 3346.11 974.453 3347.89 945.212 3348.03C926.769 3348.32 905.587 3348.33 883.825 3342.43C815.106 3322.91 748.321 3284.25 692.192 3228.84C625.184 3162.88 579.614 3066.05 583.303 2961.59C585.929 2885.84 596.46 2803.96 631.931 2729.13C653.524 2683.25 688.026 2640.3 736.117 2614.43C773.614 2594.93 816.524 2587.77 858.202 2592.39C893.307 2596.11 928.083 2607.44 958.736 2626.81C987.785 2645.66 1008.37 2674.19 1019.07 2705.51V2705.51C1030.67 2738.69 1032.2 2775.02 1023.26 2809.17C1017.73 2829.86 1008.36 2849.2 995.965 2866.3C985.637 2880.55 973.211 2893.24 959.166 2903.86C917.766 2934.55 867.899 2948.06 819.932 2951.28C695.515 2960.79 571.075 2945 457.507 2902.73C317.198 2850.29 201.177 2757.52 118.223 2650.2V2650.2C32.6442 2545.24 -32.8981 2417.48 17.6801 2303.93C33.4423 2270.48 64.451 2246.14 100.199 2240.83C136.634 2236.45 169.491 2255.33 190.178 2280.49C205.009 2262.04 222.599 2245.98 243.989 2236.31C261.83 2229.19 281.993 2227.28 301.035 2233.04C318.973 2239.12 332.793 2251.36 341.852 2265.2C353.144 2282.35 358.941 2301.28 361.69 2318.87C370.075 2371.61 356.822 2420.33 340.993 2455.25C291.994 2563.91 225.032 2567.17 229.331 2573.92V2573.92Z" fill="black"/>
<path d="M1160.62 2299.49C1160.62 2299.49 1166.66 2341.8 1118.3 2468.75C1069.94 2595.69 1066.92 2885.85 1042.74 2949.32C1018.56 3012.79 885.57 3224.37 915.795 3411.76C946.02 3599.15 1094.12 3865.13 1275.47 3798.64C1456.82 3732.14 1420.55 3744.23 1420.55 3744.23C1420.55 3744.23 1946.46 3859.09 2294.05 3659.6C2372.63 3695.87 2420.99 3726.1 2538.87 3656.58C2656.74 3587.06 2717.19 3396.65 2714.17 3266.68C2711.15 3136.71 2523.75 2852.6 2502.6 2792.15C2481.44 2731.7 2402.85 2435.5 2363.56 2311.58C2324.27 2187.66 2354.5 2166.5 2354.5 2166.5L1160.62 2299.49Z" fill="#DE5700" stroke="black" stroke-width="46.0804"/>
<path d="M2798.72 15.367C2698.4 16.4866 2391.12 184.492 2306.37 277.783C2219.35 373.579 2165.64 521.033 2148.97 571.513C2050.88 508.595 1907.17 507.155 1907.17 507.155C1907.17 507.155 1991.8 479.955 2082.48 455.775C1925.31 401.371 1722.8 449.727 1722.8 449.727C1722.8 449.727 1813.47 349.989 1895.08 277.45C1768.13 310.697 1620.04 383.233 1529.36 437.638C1553.54 401.368 1595.86 362.076 1680.49 244.199C1447.75 301.626 1160.61 528.318 1060.87 612.947C1072.96 537.385 1103.19 489.025 1139.46 404.395C1002.07 499.513 937.628 607.905 907.715 681.853C876.541 624.261 794.11 483.695 690.543 400.173C558.035 293.312 45.0981 126.611 113.489 254.844C181.88 383.077 164.781 558.322 207.525 827.611C250.27 1096.9 634.972 1250.78 634.972 1250.78C637.191 1249.99 639.395 1249.09 641.592 1248.17C619.011 1319.85 598.866 1424.54 583.679 1597.01C511.014 1706.01 478.959 1727.38 470.41 1767.99C540.938 1733.8 575.13 1708.15 626.423 1695.32C581.542 1770.13 579.407 1872.71 598.642 2035.14C634.974 1943.24 632.832 1945.37 671.302 1917.59C681.988 2026.59 705.503 2086.43 733.287 2120.63C743.973 2060.78 776.031 2037.28 776.031 2037.28C776.031 2037.28 797.404 2167.65 859.383 2257.41C872.206 2178.34 887.165 2165.51 887.165 2165.51C887.165 2165.51 1135.08 2490.37 1767.7 2456.17C2400.31 2421.98 2579.84 1985.98 2579.84 1985.98C2579.84 1985.98 2592.66 2041.56 2592.66 2062.93C2639.68 1994.54 2669.6 1853.48 2669.6 1853.48C2669.6 1853.48 2703.8 1934.69 2695.25 1985.98C2733.72 1921.86 2742.26 1763.72 2725.17 1691.05C2785.01 1755.17 2836.31 1776.54 2836.31 1776.54C2836.31 1776.54 2703.79 1545.72 2699.52 1507.25C2695.25 1468.78 2793.56 1562.81 2793.56 1562.81C2793.56 1562.81 2731.97 1460.26 2716.62 1432.45C2659.41 1328.78 2605.12 1139.43 2528.87 1011.72C2592.86 970.123 2850.88 788.773 2843.33 573.68C2834.72 328.21 2786.46 176.103 2827.99 51.0384C2836.43 25.6346 2824.3 15.0816 2798.72 15.367V15.367Z" fill="#DE5700"/>
<path d="M1160.62 2299.49C1160.62 2299.49 1166.66 2341.8 1118.3 2468.75C1069.94 2595.69 1066.92 2885.85 1042.74 2949.32C1018.56 3012.79 885.57 3224.37 915.795 3411.76C946.02 3599.15 1094.12 3865.13 1275.47 3798.64C1456.82 3732.14 1420.55 3744.23 1420.55 3744.23C1420.55 3744.23 1946.46 3859.09 2294.05 3659.6C2372.63 3695.87 2420.99 3726.1 2538.87 3656.58C2656.74 3587.06 2717.19 3396.65 2714.17 3266.68C2711.15 3136.71 2523.75 2852.6 2502.6 2792.15C2481.44 2731.7 2402.85 2435.5 2363.56 2311.58C2324.27 2187.66 2354.5 2166.5 2354.5 2166.5L1160.62 2299.49Z" fill="#DE5700"/>
<path d="M1940.31 2212.63L1557.07 2255.32C1406.21 2289.92 1288.96 2332.21 1288.96 2332.21C1288.96 2332.21 1220.57 2746.83 1233.39 2862.24C1246.22 2977.65 1173.55 3152.9 1212.02 3328.15C1245.61 3481.2 1311.83 3644.02 1558.64 3765.38C1710.04 3782.34 1960.24 3792 2176.82 3713.48C2280.67 3629.51 2354.73 3520.47 2383.21 3435.02C2447.33 3242.67 2370.39 2909.27 2340.47 2840.87C2310.55 2772.48 2344.75 2558.76 2178.04 2315.11C2132.1 2247.96 2043.91 2219.17 1940.31 2212.63Z" fill="#F7EDE9"/>
<path opacity="0.6" d="M2798.72 15.367C2698.4 16.4866 2391.12 184.492 2306.37 277.783C2219.35 373.579 2165.64 521.033 2148.97 571.513C2050.88 508.595 1907.17 507.155 1907.17 507.155C1907.17 507.155 1991.8 479.955 2082.48 455.775C1925.31 401.371 1722.8 449.727 1722.8 449.727C1722.8 449.727 1813.47 349.989 1895.08 277.45C1768.13 310.697 1620.04 383.233 1529.36 437.638C1553.54 401.368 1595.86 362.076 1680.49 244.199C1447.75 301.626 1160.61 528.318 1060.87 612.947C1072.96 537.385 1103.19 489.025 1139.46 404.395C1002.07 499.513 937.628 607.905 907.715 681.853C876.541 624.261 794.11 483.695 690.543 400.173C558.035 293.312 45.0981 126.611 113.489 254.844C181.88 383.077 164.781 558.322 207.525 827.611C250.27 1096.9 634.972 1250.78 634.972 1250.78C637.191 1249.99 639.395 1249.09 641.592 1248.17C619.011 1319.85 598.866 1424.54 583.679 1597.01C511.014 1706.01 478.959 1727.38 470.41 1767.99C540.938 1733.8 575.13 1708.15 626.423 1695.32C581.542 1770.13 579.407 1872.71 598.642 2035.14C634.974 1943.24 632.832 1945.37 671.302 1917.59C681.988 2026.59 705.503 2086.43 733.287 2120.63C743.973 2060.78 776.031 2037.28 776.031 2037.28C776.031 2037.28 797.404 2167.65 859.383 2257.41C872.206 2178.34 887.165 2165.51 887.165 2165.51C887.165 2165.51 1135.08 2490.37 1767.7 2456.17C2400.31 2421.98 2579.84 1985.98 2579.84 1985.98C2579.84 1985.98 2592.66 2041.56 2592.66 2062.93C2639.68 1994.54 2669.6 1853.48 2669.6 1853.48C2669.6 1853.48 2703.8 1934.69 2695.25 1985.98C2733.72 1921.86 2742.26 1763.72 2725.17 1691.05C2785.01 1755.17 2836.31 1776.54 2836.31 1776.54C2836.31 1776.54 2703.79 1545.72 2699.52 1507.25C2695.25 1468.78 2793.56 1562.81 2793.56 1562.81C2793.56 1562.81 2731.97 1460.26 2716.62 1432.45C2659.41 1328.78 2605.12 1139.43 2528.87 1011.72C2592.86 970.123 2850.88 788.773 2843.33 573.68C2834.72 328.21 2786.46 176.103 2827.99 51.0384C2836.43 25.6346 2824.3 15.0816 2798.72 15.367V15.367Z" fill="url(#paint0_radial_2_3)"/>
<path d="M1785.69 1777.75C1684.07 1778.04 1631.38 1822.48 1594.58 1842.79C1435.61 2029.15 1115.77 1926.47 942.736 2050.1C912.815 2071.47 871.139 2186.88 871.139 2186.88L873.322 2195.17C881.063 2170.83 887.165 2165.51 887.165 2165.51C887.165 2165.51 1135.08 2490.37 1767.7 2456.17C2277.77 2428.6 2493.17 2139.93 2557.52 2029.09C2551.52 1973.12 2534.07 1898.88 2483.67 1896.22C2402.45 1891.95 2066.9 1864.16 1964.32 1808.59C1922.64 1786.02 1855.22 1777.56 1785.69 1777.75V1777.75Z" fill="#F7EDE9"/>
<path opacity="0.993" d="M1961.57 1985.15C1963.63 1987.09 1933.18 2033.96 1856.33 2063.83V2063.83C1839.85 2070.21 1822.61 2075.45 1804.72 2079.62C1766.98 2088.41 1729.87 2091.61 1698.49 2092.27C1674.97 2092.78 1655.68 2091.74 1642.07 2090.22C1634.99 2089.43 1629.58 2088.53 1625.85 2087.62C1623.95 2087.15 1622.51 2086.68 1621.54 2086.24C1621.04 2086.01 1620.67 2085.78 1620.42 2085.57C1620.15 2085.33 1620.02 2085.11 1620.03 2084.89C1620.05 2084.67 1620.2 2084.47 1620.49 2084.27C1620.76 2084.08 1621.15 2083.91 1621.67 2083.73C1622.67 2083.4 1624.14 2083.08 1626.06 2082.78C1629.77 2082.2 1635.24 2081.66 1642.21 2081.09C1655.61 2079.99 1674.74 2078.77 1697.58 2076.59C1728.3 2073.63 1764.16 2069.35 1800.38 2060.94C1817.57 2056.95 1834.05 2052.22 1849.95 2046.59C1918.62 2022.43 1958.6 1982.34 1961.57 1985.15V1985.15Z" fill="black"/>
<path d="M1934.67 1959.27C1935.77 1956.91 1954.37 1960.97 1967.17 1981.57C1979.98 2002.16 1975.41 2020.64 1972.82 2020.59C1970 2020.53 1967.84 2002.79 1958.14 1987.19C1948.44 1971.58 1933.47 1961.83 1934.67 1959.27V1959.27Z" fill="black"/>
<path d="M1263.38 1639.08C1266.22 1756.73 1183.41 1844.17 1098.66 1852.16C951.388 1866.04 891.515 1767.76 901.935 1653.12C912.302 1539.07 1011.03 1433.96 1109.23 1430.52C1193.46 1427.58 1260.47 1518.48 1263.38 1639.08Z" fill="black"/>
<g filter="url(#filter0_f_2_3)">
<path d="M1109.1 1636.63C1138.87 1598.95 1137.08 1543.74 1105.1 1513.3C1073.11 1482.87 1023.06 1488.73 993.289 1526.41C963.521 1564.08 965.315 1619.29 997.297 1649.73C1029.28 1680.17 1079.34 1674.3 1109.1 1636.63Z" fill="#BEBEBE"/>
</g>
<path d="M1109.1 1636.63C1138.87 1598.95 1137.08 1543.74 1105.1 1513.3C1073.11 1482.87 1023.06 1488.73 993.289 1526.41C963.521 1564.08 965.315 1619.29 997.297 1649.73C1029.28 1680.17 1079.34 1674.3 1109.1 1636.63Z" fill="#DEDEDE"/>
<g filter="url(#filter1_f_2_3)">
<path d="M1220.38 1725.27C1218.97 1703.96 1200.95 1686.44 1180.12 1686.13C1159.3 1685.82 1143.56 1702.84 1144.97 1724.14C1146.37 1745.45 1164.4 1762.98 1185.22 1763.29C1206.05 1763.6 1221.79 1746.58 1220.38 1725.27Z" fill="#BEBEBE"/>
</g>
<path d="M1217.34 1725.22C1216.05 1705.95 1199.48 1690.11 1180.33 1689.83C1161.18 1689.54 1146.71 1704.93 1148.01 1724.2C1149.3 1743.46 1165.87 1759.31 1185.02 1759.59C1204.17 1759.87 1218.64 1744.48 1217.34 1725.22Z" fill="#DEDEDE"/>
<path d="M2111.74 1483.68C2108.91 1601.33 2191.71 1688.77 2276.47 1696.76C2423.74 1710.64 2483.61 1612.36 2473.19 1497.72C2462.82 1383.67 2364.09 1278.56 2265.89 1275.13C2181.66 1272.18 2114.65 1363.09 2111.74 1483.68Z" fill="black"/>
<g filter="url(#filter2_f_2_3)">
<path d="M2266.02 1481.23C2236.25 1443.55 2238.05 1388.34 2270.03 1357.9C2302.01 1327.47 2352.07 1333.34 2381.83 1371.01C2411.6 1408.68 2409.81 1463.9 2377.83 1494.33C2345.85 1524.77 2295.79 1518.9 2266.02 1481.23Z" fill="#BEBEBE"/>
</g>
<path d="M2266.02 1481.23C2236.25 1443.55 2238.05 1388.34 2270.03 1357.9C2302.01 1327.47 2352.07 1333.34 2381.83 1371.01C2411.6 1408.68 2409.81 1463.9 2377.83 1494.33C2345.85 1524.77 2295.79 1518.9 2266.02 1481.23Z" fill="#DEDEDE"/>
<g filter="url(#filter3_f_2_3)">
<path d="M2154.74 1569.87C2156.15 1548.56 2174.17 1531.04 2195 1530.73C2215.82 1530.42 2231.57 1547.44 2230.16 1568.75C2228.75 1590.05 2210.72 1607.58 2189.9 1607.89C2169.07 1608.2 2153.33 1591.18 2154.74 1569.87Z" fill="#BEBEBE"/>
</g>
<path d="M2157.78 1569.82C2159.08 1550.55 2175.65 1534.71 2194.79 1534.43C2213.94 1534.15 2228.41 1549.53 2227.12 1568.8C2225.82 1588.06 2209.25 1603.91 2190.1 1604.19C2170.96 1604.47 2156.49 1589.08 2157.78 1569.82Z" fill="#DEDEDE"/>
<path d="M1662.35 1829.49C1645.46 1787.85 1850.52 1752.32 1854.28 1791.71C1858.37 1834.59 1804.4 1912.61 1777.2 1917.14C1750 1921.68 1680.39 1873.96 1662.35 1829.49V1829.49Z" fill="black"/>
<g opacity="0.8">
<g filter="url(#filter4_f_2_3)">
<path d="M1786.9 1793.91C1788.82 1786.22 1801.15 1781.29 1814.44 1782.91C1827.74 1784.53 1836.97 1792.08 1835.05 1799.77C1833.13 1807.46 1820.8 1812.39 1807.51 1810.77C1794.21 1809.15 1784.99 1801.61 1786.9 1793.91Z" fill="#BEBEBE"/>
</g>
<path d="M1788.84 1794.15C1790.59 1787.19 1801.91 1782.76 1814.14 1784.25C1826.36 1785.74 1834.85 1792.58 1833.11 1799.54C1831.36 1806.49 1820.04 1810.92 1807.81 1809.43C1795.59 1807.94 1787.1 1801.1 1788.84 1794.15Z" fill="#DEDEDE"/>
</g>
<path d="M2741.41 163.935C2735.43 163.644 2728.58 173.489 2718.76 194.994C2673.88 293.306 2462.29 385.213 2381.08 483.525C2299.87 581.837 2284.9 697.243 2374.67 851.122C2421.52 931.442 2482.9 980.277 2532.12 1009.56C2600.59 964.628 2850.77 785.686 2843.33 573.68C2841.17 511.985 2836.51 456.205 2831.39 405.253C2769.94 259.579 2759.63 164.818 2741.41 163.935V163.935Z" fill="url(#paint1_radial_2_3)"/>
<path d="M181.665 291.229C163.173 289.362 174.995 366.697 171.814 505.727C180.547 595.591 187.448 701.123 207.525 827.611C246.788 1074.97 573.781 1224.58 626.915 1247.37C670.403 1199.53 739.457 1106.83 766.704 960.629C806.777 745.606 754.64 615.556 641.83 530.754C529.019 445.951 288.043 418.155 211.526 319.567C197.179 301.081 187.769 291.846 181.665 291.229V291.229Z" fill="url(#paint2_radial_2_3)"/>
<path d="M2798.72 15.367C2698.4 16.4866 2391.12 184.492 2306.37 277.783C2219.35 373.579 2165.64 521.033 2148.97 571.513C2050.88 508.595 1907.17 507.155 1907.17 507.155C1907.17 507.155 1991.8 479.955 2082.48 455.775C1925.31 401.371 1722.8 449.727 1722.8 449.727C1722.8 449.727 1813.47 349.989 1895.08 277.45C1768.13 310.697 1620.04 383.233 1529.36 437.638C1553.54 401.368 1595.86 362.076 1680.49 244.199C1447.75 301.626 1160.61 528.318 1060.87 612.947C1072.96 537.385 1103.19 489.025 1139.46 404.395C1002.07 499.513 937.628 607.905 907.715 681.853C876.541 624.261 794.11 483.695 690.543 400.173C558.035 293.312 45.0981 126.611 113.489 254.844C181.88 383.077 164.781 558.322 207.525 827.611C250.27 1096.9 634.972 1250.78 634.972 1250.78C637.191 1249.99 639.395 1249.09 641.592 1248.17C619.011 1319.85 598.866 1424.54 583.679 1597.01C511.014 1706.01 478.959 1727.38 470.41 1767.99C540.938 1733.8 575.13 1708.15 626.423 1695.32C581.542 1770.13 579.407 1872.71 598.642 2035.14C634.974 1943.24 632.832 1945.37 671.302 1917.59C681.988 2026.59 705.503 2086.43 733.287 2120.63C743.973 2060.78 776.031 2037.28 776.031 2037.28C776.031 2037.28 797.404 2167.65 859.383 2257.41C872.206 2178.34 887.165 2165.51 887.165 2165.51C887.165 2165.51 1135.08 2490.37 1767.7 2456.17C2400.31 2421.98 2579.84 1985.98 2579.84 1985.98C2579.84 1985.98 2592.66 2041.56 2592.66 2062.93C2639.68 1994.54 2669.6 1853.48 2669.6 1853.48C2669.6 1853.48 2703.8 1934.69 2695.25 1985.98C2733.72 1921.86 2742.26 1763.72 2725.17 1691.05C2785.01 1755.17 2836.31 1776.54 2836.31 1776.54C2836.31 1776.54 2703.79 1545.72 2699.52 1507.25C2695.25 1468.78 2793.56 1562.81 2793.56 1562.81C2793.56 1562.81 2731.97 1460.26 2716.62 1432.45C2659.41 1328.78 2605.12 1139.43 2528.87 1011.72C2592.86 970.123 2850.88 788.773 2843.33 573.68C2834.72 328.21 2786.46 176.103 2827.99 51.0384C2836.43 25.6346 2824.3 15.0816 2798.72 15.367V15.367Z" stroke="black" stroke-width="30.7203" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1166.07 3775.9C1091.27 3651.94 1054.81 3567.11 1049.59 3511.42C1044.78 3460.13 1038.37 3398.15 1075.24 3428.61C1085.92 3383.72 1083.79 3358.08 1111.57 3370.9C1139.36 3383.72 1158.59 3411.51 1158.59 3411.51C1158.59 3411.51 1154.32 3347.39 1182.1 3368.76C1233.19 3399.52 1324.44 3688.35 1313.81 3770.16C1302.14 3860.06 1231.96 3879.6 1166.07 3775.9V3775.9Z" fill="url(#paint3_linear_2_3)" stroke="black" stroke-width="23.0402"/>
<path d="M2556.53 3668.47C2609.53 3533.74 2631.28 3444 2627.1 3388.22C2623.26 3336.85 2619.21 3274.67 2587.96 3310.87C2569.91 3268.41 2567.73 3242.76 2542.48 3260.06C2517.23 3277.35 2502.92 3307.96 2502.92 3307.96C2502.92 3307.96 2496.4 3244.03 2472.59 3269.75C2427.37 3308.62 2385.74 3608.66 2409.91 3687.53C2436.47 3774.21 2508.93 3781.73 2556.53 3668.47V3668.47Z" fill="url(#paint4_linear_2_3)" stroke="black" stroke-width="23.0402"/>
<path d="M1562.1 3063.57C1524.1 3059.67 1486.65 3074.05 1455.28 3094.55C1440.72 3104.25 1426.76 3115.03 1414.48 3127.52C1438.67 3156.34 1465.72 3183.02 1496.29 3205.05C1502.66 3208.89 1510.78 3214.39 1518.34 3210.58C1525.16 3205.3 1523.61 3195.64 1524.22 3188.07C1530.46 3197.06 1541.83 3202.2 1552.66 3201.65C1562.44 3199.92 1561.73 3187.56 1560.44 3180.1C1559.69 3173.07 1556.6 3166.42 1556.17 3159.41C1562.43 3160.56 1568.02 3169.72 1575.98 3167.49C1584.76 3164.88 1580.38 3153.58 1580.28 3146.94C1575.9 3118.83 1568.65 3091.24 1562.1 3063.57V3063.57Z" fill="url(#paint5_linear_2_3)"/>
<path d="M2302.03 3008.83C2271.36 3023.52 2235.53 3023.15 2203.06 3015.16C2175.5 3008.19 2149 2996.64 2125.3 2980.94C2128.65 3015.23 2131.75 3049.73 2139.6 3083.34C2140.74 3090.5 2145.31 3102 2154.43 3096.05C2163.06 3091.33 2167.94 3082.23 2175.33 3076.17C2175.86 3081.7 2172.59 3088.04 2172.97 3094.1C2172.43 3103.97 2175.25 3117.51 2186.59 3119.85C2200.16 3121.29 2212.08 3112.19 2221.11 3103.04C2227.36 3097.53 2223.84 3112.39 2227.54 3115.27C2229.88 3123.69 2240.07 3132.21 2248.05 3124.67C2257.97 3116 2262.87 3103.26 2269.34 3092.1C2283.08 3065.57 2293.72 3037.5 2302.03 3008.83V3008.83Z" fill="url(#paint6_linear_2_3)"/>
<path d="M1517.64 2663.48C1517.64 2663.48 1517.64 2663.48 1517.67 2663.48C1520.97 2664.02 1526.42 2740.85 1532.65 2874.63C1534.91 2900.48 1540.5 2925.66 1547.39 2954.9C1554.58 2985.32 1562.49 3014.75 1571.38 3047.9C1578.08 3072.85 1585.59 3099.84 1591.55 3126.74C1593.42 3135.07 1595.31 3143.95 1596.25 3153.37C1596.57 3156.1 1596.76 3159.46 1596.39 3163.39C1597.28 3165.14 1595.58 3169.34 1590.43 3175.07V3175.07C1580.39 3180.9 1569.4 3179.92 1561.47 3173.37V3173.37C1559.01 3171.29 1557.89 3169.89 1557.57 3169.88C1559.13 3171.17 1561.37 3170.83 1565.41 3169.42H1565.41C1567.06 3167.09 1567.82 3166.38 1567.48 3165.67C1567.51 3167 1568.74 3169.46 1569.97 3173.33V3173.33C1574.02 3183.37 1572.31 3196.44 1561.97 3206.72V3206.72C1550.18 3212.97 1538.44 3211.07 1531.04 3204.36V3204.36C1528.22 3202.07 1526.49 3200.26 1525.34 3199.66H1525.34C1524.94 3200.04 1525.8 3199.59 1528.79 3199.47V3199.47C1531.49 3196.56 1532.68 3197.17 1530.81 3199.19C1530.12 3201.61 1529.26 3205.67 1526.9 3210.83C1526.22 3212.23 1525.38 3213.53 1524.39 3214.71C1520.87 3218.89 1515.47 3221.56 1508.72 3222C1497.43 3220.63 1489.74 3215.57 1485.08 3211.33C1426.38 3166.73 1381.27 3109.36 1347.87 3050.36C1312.57 2987.93 1289.97 2924.54 1275.1 2870.05C1253.47 2738.94 1243.01 2664.06 1246.21 2663.48C1249.41 2662.9 1265.97 2736.63 1291.73 2866.13V2866.13C1308.09 2918.97 1331.53 2980.15 1366.49 3039.63C1399.64 3096.09 1443.21 3150.79 1498.89 3192.79C1504.65 3196.18 1508.06 3199.72 1509.67 3198.89C1507.79 3199.52 1506.9 3199.67 1506.47 3200.08C1506.35 3200.2 1506.26 3200.33 1506.2 3200.5C1506.94 3199.44 1507.34 3196.53 1508.73 3192.3C1508.67 3188.65 1512.14 3183.04 1520.37 3177.93V3177.93C1527.93 3176.03 1533.99 3177.15 1537.72 3180.15H1537.72C1541.56 3182.71 1544.07 3185.1 1545.79 3186.59V3186.59C1547.9 3187.76 1550.06 3189.47 1548.1 3188.32H1548.1C1549.88 3189.46 1549.02 3184.96 1548.23 3180.69C1547.29 3178.15 1545.76 3174.34 1544.81 3168.99C1543.68 3162.9 1546.08 3156.29 1552.14 3150.81V3150.81C1559.11 3147.32 1565.42 3147.49 1569.99 3150.72V3150.72C1573.53 3153.21 1575.53 3155.29 1576.51 3156.24C1576.77 3156.9 1578.18 3157.59 1576.82 3156.92C1573.94 3160.94 1572.68 3161.93 1573.89 3160.86C1574.1 3159.67 1573.92 3157.84 1573.76 3155.45C1573.11 3147.76 1571.43 3139.75 1569.7 3131.46C1564.19 3105.34 1557.05 3078.42 1550.71 3053.27C1542.36 3020.15 1534.87 2989.98 1528.34 2959.17C1522.03 2929.53 1516.99 2902.93 1515.44 2875.5C1513.55 2741.07 1514.34 2664.11 1517.6 2663.48C1517.63 2663.47 1517.63 2663.48 1517.63 2663.48L1517.64 2663.48Z" fill="black"/>
<path d="M2054.36 2616.63C2054.36 2616.63 2054.36 2616.63 2054.39 2616.62C2056.19 2615.89 2070.04 2637.19 2094.89 2678.94C2109.22 2707.44 2132.01 2758.35 2129.26 2813.73C2126.35 2866.49 2129.91 2919.11 2136.27 2983.73C2138.7 3008.27 2141.56 3033.66 2145.95 3058.66C2147.37 3066.7 2148.83 3074.55 2150.94 3081.92C2153.48 3086.92 2152.68 3089.64 2151.85 3087.1V3087.1C2150.35 3086.42 2149.64 3086.43 2147.76 3086.96C2148.59 3086.81 2149.83 3084.69 2152.36 3081.82C2154.18 3079.26 2156.95 3075.55 2161.56 3071.71V3071.71C2167.24 3067.27 2175.05 3066.8 2183.03 3071.14V3071.14C2189.11 3077.02 2191.46 3083.31 2190.86 3088.36C2190.8 3093.49 2190.09 3097.39 2189.91 3100.1C2189.14 3103.56 2191.72 3107.99 2194.93 3108.22C2193.44 3110.41 2195.34 3107.08 2199.65 3103.9C2200.36 3103.37 2201.14 3102.85 2201.98 3102.36C2204.18 3100.03 2207.36 3096.8 2212.18 3093.66V3093.66C2217.35 3090.16 2224.54 3089.74 2232.22 3093.32V3093.32C2239.64 3099.62 2242.27 3105.64 2242.32 3108.84V3108.84C2243.94 3113.56 2244.7 3117.24 2245.92 3119.33H2245.92C2246.6 3118.44 2244.84 3119 2239.24 3117.9C2240.48 3118.95 2242.2 3113.83 2247.17 3108.21C2313.25 3000.93 2319.46 2871.49 2314.9 2781.07C2297.57 2668.78 2289.5 2605.08 2292.54 2604.55C2295.58 2604.02 2309.48 2666.75 2331.2 2779.09C2339.6 2871.29 2335.62 3005.12 2266.47 3120.62C2263.84 3126.17 2258.15 3134.43 2246.52 3139.76V3139.76C2235.07 3140.6 2227.55 3136.27 2225.41 3129.88C2222.7 3124.33 2221.47 3119.55 2220.39 3116.09C2218.14 3112.39 2218.74 3110.5 2219.99 3112.94V3112.94C2222.91 3113.55 2223.8 3114.03 2224.18 3113.43C2222.87 3114.01 2221.12 3116.14 2218.22 3118.84C2217.57 3119.81 2216.8 3120.78 2215.9 3121.71C2210.47 3127.42 2200.54 3132.07 2187.13 3129.99C2174.17 3124.43 2166.53 3112.21 2166.89 3098.57C2167.2 3093.95 2167.9 3090.58 2167.82 3088.37C2167.22 3087.43 2167.96 3087.26 2169.25 3089.56L2169.25 3089.56C2172.81 3090.59 2174.25 3090.97 2175.57 3089.9C2174.45 3090.47 2173.01 3092.84 2170.38 3095.95C2168.41 3098.82 2165.16 3102.7 2159.8 3106.38C2152.56 3110.22 2144.7 3109.78 2138.18 3105.33L2138.18 3105.33C2130.69 3097.78 2128.79 3091.01 2129.09 3088.03V3088.03C2126.7 3079.31 2125.14 3070.69 2123.79 3062.43C2119.5 3036.46 2116.88 3010.52 2114.8 2985.7C2109.33 2920.88 2107.01 2866.34 2111.71 2812.49C2116.41 2763.32 2096.79 2712.68 2084.96 2683.93C2063.44 2641.21 2052.84 2617.95 2054.33 2616.66V2616.66C2054.36 2616.65 2054.36 2616.65 2054.36 2616.65L2054.36 2616.63Z" fill="black"/>
<path d="M1565.2 3063.14C1564.91 3065.73 1555.96 3066.2 1541.37 3069.17C1525.48 3072.41 1508.74 3077.63 1488.07 3087.53C1480.4 3091.19 1473 3095.12 1465.92 3099.1C1438.07 3119.19 1417.43 3130.85 1415.06 3127.79C1412.69 3124.73 1429.23 3107.59 1456.24 3085.16C1463.45 3080.3 1471.31 3075.72 1479.69 3071.69C1501.77 3061.09 1522.77 3056.66 1540.11 3056.79C1556.81 3056.91 1565.43 3061.05 1565.2 3063.14V3063.14Z" fill="black"/>
<path d="M2297.82 3016.57C2298.41 3019.14 2289.95 3024.6 2272.7 3028.14C2254.99 3031.77 2231.89 3032.36 2206.84 3026.95C2194.91 3024.37 2183.64 3020.67 2173.35 3016.16C2144.06 2998.71 2126.51 2984.91 2128.57 2981.06C2130.62 2977.21 2151.75 2984.02 2181.57 2998.09C2191.34 3001.25 2201.5 3004.04 2212.24 3006.34C2255.38 3015.56 2296.57 3011.08 2297.82 3016.57V3016.57Z" fill="black"/>
<defs>
<filter id="filter0_f_2_3" x="951.462" y="1473.2" width="199.468" height="216.64" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="10.3119" result="effect1_foregroundBlur_2_3"/>
</filter>
<filter id="filter1_f_2_3" x="1135.42" y="1676.67" width="94.502" height="96.079" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="4.72782" result="effect1_foregroundBlur_2_3"/>
</filter>
<filter id="filter2_f_2_3" x="2224.19" y="1317.8" width="199.468" height="216.64" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="10.3119" result="effect1_foregroundBlur_2_3"/>
</filter>
<filter id="filter3_f_2_3" x="2145.2" y="1521.27" width="94.5021" height="96.079" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="4.72782" result="effect1_foregroundBlur_2_3"/>
</filter>
<filter id="filter4_f_2_3" x="1781.61" y="1777.56" width="58.7359" height="38.5579" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2.52106" result="effect1_foregroundBlur_2_3"/>
</filter>
<radialGradient id="paint0_radial_2_3" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(1760.99 1829.81) rotate(-0.182296) scale(306.481 287.176)">
<stop stop-color="#CC3100"/>
<stop offset="1" stop-color="#DE6B00"/>
</radialGradient>
<radialGradient id="paint1_radial_2_3" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(2373.33 907.327) rotate(63.7909) scale(608.272 241.85)">
<stop stop-color="#FF927A"/>
<stop offset="1" stop-color="#F7C199"/>
</radialGradient>
<radialGradient id="paint2_radial_2_3" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(785.008 1066.98) rotate(-157.738) scale(359.552 439.05)">
<stop stop-color="#FF927A"/>
<stop offset="1" stop-color="#F7C199"/>
</radialGradient>
<linearGradient id="paint3_linear_2_3" x1="1257.34" y1="3813.75" x2="1136.44" y2="3481.28" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
<linearGradient id="paint4_linear_2_3" x1="2481.02" y1="3700.43" x2="2528.01" y2="3388.4" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
<linearGradient id="paint5_linear_2_3" x1="1465.89" y1="3071.73" x2="1527.85" y2="3160.89" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
<linearGradient id="paint6_linear_2_3" x1="2205.57" y1="3007.9" x2="2200.48" y2="3085.58" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 27 KiB

View file

@ -1,73 +0,0 @@
<svg width="2959" height="3706" viewBox="0 0 2959 3706" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.997" d="M316.789 2931.12C315.488 2929.29 342.101 2913.78 372.838 2868.23C384.205 2851.41 395.682 2829.37 402.48 2802.88C407.785 2782.32 410.27 2759.1 408.909 2733.66C407.69 2710.88 403.393 2686.99 395.976 2661.83V2661.83C382.3 2615.37 359.257 2568.62 326.569 2519.31C298.838 2477.58 265.182 2435.96 225.052 2395.73C208.608 2379.25 191.087 2363.05 172.444 2347.71C157.209 2335.18 141.225 2323.1 124.379 2312.5C111.765 2304.63 99.0872 2297.4 86.1465 2292.93C81.4965 2291.35 77.2099 2290.09 73.3454 2289.68C70.3614 2289.5 68.2282 2289.3 67.36 2289.79C67.103 2290.02 66.986 2289.93 67.7268 2289.46C67.8742 2288.79 67.916 2289.67 67.402 2291.25V2291.25C66.8491 2293.83 67.2734 2297.48 67.8385 2301.99C68.763 2308.14 70.6571 2314.59 72.9592 2321.43V2321.43C79.957 2342.07 90.3586 2362.14 101.941 2382.98C118.976 2413.51 138.274 2443.25 159.063 2473.54C239.086 2574.41 349.687 2660.96 481.224 2708.82C588.471 2748.15 706.505 2762.42 824.643 2753.29C866.817 2749.58 908.547 2739.08 939.471 2714.96C946.162 2709.88 952.392 2704.14 958.034 2697.88C971.642 2682.77 981.817 2664.63 986.785 2645.36C993.4 2620.4 992.269 2593.07 983.767 2568.69C975.601 2545.97 961.347 2525.28 942.318 2513.53C918.401 2498.17 890.181 2489.01 861.448 2485.7C828.124 2482.05 793.861 2487.36 765.252 2502.39C748.435 2511.06 733.146 2523.06 719.946 2537.05C703.762 2554.16 690.691 2574.62 680.006 2596.35C647.54 2662.01 637.627 2738.34 633.783 2811.39C631.492 2858.76 639.748 2905.63 657.74 2947.16C674.348 2985.79 699.212 3020.25 727.85 3049.36C755.245 3077.18 786.301 3100.41 818.625 3119.3C845.995 3135.23 873.674 3148.13 901.953 3156.71C916.938 3161.58 935.105 3161.55 953.657 3162.06C981.591 3162.53 1010.48 3161.41 1037.44 3160.48C1120.97 3157.55 1186.59 3155.58 1255.49 3159.41C1258.01 3159.67 1260.93 3159.01 1263.71 3157.85C1267.08 3156.47 1270.42 3153.91 1273.73 3150.93C1282.8 3142.7 1289.74 3131.12 1296.02 3119.67C1304.49 3103.94 1311.23 3087.27 1316.96 3071.71C1324.16 3052.16 1330.13 3032.79 1335.12 3015.45C1347.23 2973.37 1356.5 2932.2 1361.78 2909.03C1368.62 2876.64 1372.92 2860.42 1374.61 2860.7C1376.3 2860.97 1375.37 2877.73 1371.82 2910.71V2910.71C1368.73 2935.64 1362.81 2975.3 1352.55 3019.95C1348.43 3037.86 1343.3 3057.89 1336.77 3078.48C1331.6 3094.81 1325.21 3112.65 1316.67 3130.22C1310.63 3142.89 1302.73 3156.75 1290.99 3168.72C1286.56 3173.24 1281.1 3177.52 1274.47 3180.82C1268.55 3183.74 1261.74 3185.39 1254.47 3185.26C1186.38 3183.73 1122.9 3187.53 1038.93 3192.43C1012.03 3194.02 982.698 3195.8 953.459 3195.94C935.016 3196.23 913.838 3196.25 892.086 3190.34C860.467 3181.34 829.909 3167.88 800.35 3151.26C765.137 3131.53 731.042 3106.84 700.477 3076.77C668.369 3045.23 640.218 3007.16 620.629 2963.48C599.562 2916.19 589.495 2863.25 591.598 2809.55C594.222 2733.83 604.742 2651.95 640.214 2577.11C652.15 2552.07 667.567 2527.86 687.265 2506.54C703.474 2489.04 722.685 2473.88 744.379 2462.43C781.862 2442.93 824.765 2435.76 866.445 2440.38C901.541 2444.1 936.315 2455.43 966.972 2474.79C996.013 2493.63 1016.6 2522.16 1027.31 2553.48V2553.48C1038.91 2586.65 1040.44 2622.98 1031.5 2657.13C1024.34 2683.95 1010.73 2708.48 992.439 2728.83C984.857 2737.27 976.473 2744.98 967.414 2751.83C926.027 2782.52 876.166 2796.04 828.18 2799.26C703.899 2808.78 579.433 2793.06 465.731 2750.76C325.525 2698.22 209.253 2605.43 125.942 2497.69C104.813 2466.19 84.9637 2434.73 67.2313 2402.07C55.3916 2380.34 44.3348 2357.9 36.3566 2333.64V2333.64C33.6731 2325.54 31.3734 2316.86 30.0211 2307.57C28.9492 2300.97 28.6637 2293.32 29.9722 2284.91C30.8048 2278.53 33.7282 2271.54 39.1215 2264.8C44.6302 2258.8 51.1436 2255 57.9177 2253.4C65.6381 2251.65 72.5521 2251.65 78.3106 2252.64C85.8795 2253.76 92.7209 2255.75 98.8267 2258.08C115.658 2264.44 130.588 2273.11 144.014 2282.01C162.336 2294.05 179.275 2307.32 195.03 2320.84C214.373 2337.42 232.368 2354.74 249.101 2372.26C289.906 2414.94 323.84 2459.09 351.487 2503.24C384.143 2555.26 406.809 2605.59 419.621 2655.32C426.578 2682.37 430.13 2708.29 430.416 2732.98C430.737 2760.6 426.738 2785.78 419.794 2807.86C410.802 2836.34 396.954 2859.18 383.542 2876.09C346.533 2922.69 316.167 2930.24 316.792 2931.12L316.789 2931.12Z" fill="black"/>
<path d="M1168.86 2147.45C1168.86 2147.45 1174.91 2189.77 1126.55 2316.71C1078.19 2443.66 1075.16 2733.81 1050.98 2797.29C1026.8 2860.76 893.814 3072.33 924.039 3259.73C954.264 3447.12 1102.37 3713.1 1283.71 3646.6C1465.06 3580.11 1428.79 3592.2 1428.79 3592.2C1428.79 3592.2 1954.71 3707.05 2302.29 3507.57C2380.88 3543.84 2429.23 3574.06 2547.11 3504.55C2664.99 3435.03 2725.44 3244.61 2722.41 3114.65C2719.39 2984.68 2532 2700.57 2510.84 2640.12C2489.68 2579.67 2411.1 2283.46 2371.81 2159.54C2332.51 2035.62 2362.74 2014.46 2362.74 2014.46L1168.86 2147.45Z" fill="#DE5700" stroke="black" stroke-width="46.0804"/>
<path d="M2910.99 15.367C2810.67 16.4868 2399.36 32.4575 2314.62 125.748C2227.59 221.545 2173.88 368.999 2157.21 419.478C2059.12 356.561 1915.41 355.12 1915.41 355.12C1915.41 355.12 2000.05 327.92 2090.72 303.741C1933.55 249.336 1731.05 297.692 1731.05 297.692C1731.05 297.692 1816.06 254.536 1897.66 181.997C1770.72 215.244 1628.28 231.198 1537.61 285.603C1534.34 256.983 1479.51 292.115 1564.25 174.208C1331.52 231.635 1168.86 376.283 1069.11 460.912C1081.2 385.351 1085.97 396.401 1122.24 311.772C984.85 406.889 945.872 455.871 915.959 529.819C884.785 472.227 802.353 331.66 698.786 248.138C566.279 141.277 -48.0541 314.703 20.3368 442.936C88.7277 571.169 253.044 550.321 295.788 819.61C338.532 1088.9 643.216 1098.74 643.216 1098.74C645.435 1097.95 647.639 1097.05 649.836 1096.13C627.255 1167.82 607.109 1272.5 591.923 1444.97C519.258 1553.97 513.208 1597.36 504.66 1637.96C575.188 1603.77 583.374 1556.11 634.667 1543.29C589.785 1618.09 587.651 1720.68 606.886 1883.1C643.218 1791.2 641.076 1793.34 679.546 1765.56C690.232 1874.55 713.747 1934.4 741.53 1968.59C752.217 1908.75 784.275 1885.25 784.275 1885.25C784.275 1885.25 805.648 2015.62 867.627 2105.38C880.451 2026.3 895.409 2013.48 895.409 2013.48C895.409 2013.48 1143.32 2338.33 1775.94 2304.14C2408.55 2269.94 2588.08 1833.95 2588.08 1833.95C2588.08 1833.95 2600.91 1889.52 2600.91 1910.89C2647.93 1842.5 2677.85 1701.44 2677.85 1701.44C2677.85 1701.44 2712.04 1782.65 2703.49 1833.95C2741.96 1769.83 2750.51 1611.68 2733.41 1539.02C2793.25 1603.13 2844.55 1624.51 2844.55 1624.51C2844.55 1624.51 2712.04 1393.69 2707.76 1355.22C2703.49 1316.75 2801.81 1410.78 2801.81 1410.78C2801.81 1410.78 2740.21 1308.23 2724.86 1280.41C2667.66 1176.75 2613.36 987.392 2537.11 859.689C2601.1 818.088 2795.1 718.757 2787.56 503.664C2778.95 258.194 2898.72 176.103 2940.26 51.0386C2948.69 25.6348 2936.56 15.0816 2910.99 15.367V15.367Z" fill="#DE5700"/>
<path d="M1168.86 2147.45C1168.86 2147.45 1174.91 2189.77 1126.55 2316.71C1078.19 2443.66 1075.16 2733.81 1050.98 2797.29C1026.8 2860.76 893.814 3072.33 924.039 3259.73C954.264 3447.12 1102.37 3713.1 1283.71 3646.6C1465.06 3580.11 1428.79 3592.2 1428.79 3592.2C1428.79 3592.2 1954.71 3707.05 2302.29 3507.57C2380.88 3543.84 2429.23 3574.06 2547.11 3504.55C2664.99 3435.03 2725.44 3244.61 2722.41 3114.65C2719.39 2984.68 2532 2700.57 2510.84 2640.12C2489.68 2579.67 2411.1 2283.46 2371.81 2159.54C2332.51 2035.62 2362.74 2014.46 2362.74 2014.46L1168.86 2147.45Z" fill="#DE5700"/>
<path d="M1948.55 2060.6L1565.32 2103.28C1414.45 2137.89 1297.2 2180.18 1297.2 2180.18C1297.2 2180.18 1228.81 2594.8 1241.64 2710.21C1254.46 2825.62 1181.79 3000.87 1220.26 3176.12C1253.86 3329.16 1320.07 3491.98 1566.89 3613.34C1718.29 3630.31 1968.48 3639.97 2185.07 3561.45C2288.91 3477.48 2362.98 3368.43 2391.46 3282.98C2455.58 3090.63 2378.64 2757.23 2348.72 2688.84C2318.79 2620.45 2352.99 2406.72 2186.29 2163.08C2140.34 2095.93 2052.15 2067.14 1948.55 2060.6V2060.6Z" fill="#F7EDE9"/>
<path opacity="0.6" d="M2910.99 15.367C2810.67 16.4868 2399.36 32.4575 2314.62 125.748C2227.59 221.545 2173.88 368.999 2157.21 419.478C2059.12 356.561 1915.41 355.12 1915.41 355.12C1915.41 355.12 2000.05 327.92 2090.72 303.741C1933.55 249.336 1731.05 297.692 1731.05 297.692C1731.05 297.692 1816.06 254.536 1897.66 181.997C1770.72 215.244 1628.28 231.198 1537.61 285.603C1538.99 259.97 1477.23 293.202 1564.25 174.208C1331.52 231.635 1168.86 376.283 1069.11 460.912C1081.2 385.351 1085.97 396.401 1122.24 311.772C984.85 406.889 945.872 455.871 915.959 529.819C884.785 472.227 802.353 331.66 698.786 248.138C566.279 141.277 -48.0541 314.703 20.3368 442.936C88.7277 571.169 253.044 550.321 295.788 819.61C338.532 1088.9 643.216 1098.74 643.216 1098.74C645.435 1097.95 647.639 1097.05 649.836 1096.13C627.255 1167.82 607.109 1272.5 591.923 1444.97C519.258 1553.97 514.209 1598.36 505.661 1638.96C576.188 1604.77 583.374 1556.11 634.667 1543.29C589.785 1618.09 587.651 1720.68 606.886 1883.1C643.218 1791.2 641.076 1793.34 679.546 1765.56C690.232 1874.55 713.747 1934.4 741.53 1968.59C752.217 1908.75 784.275 1885.25 784.275 1885.25C784.275 1885.25 805.648 2015.62 867.627 2105.38C880.451 2026.3 895.409 2013.48 895.409 2013.48C895.409 2013.48 1143.32 2338.33 1775.94 2304.14C2408.55 2269.94 2588.08 1833.95 2588.08 1833.95C2588.08 1833.95 2600.91 1889.52 2600.91 1910.89C2647.93 1842.5 2677.85 1701.44 2677.85 1701.44C2677.85 1701.44 2712.04 1782.65 2703.49 1833.95C2741.96 1769.83 2750.51 1611.68 2733.41 1539.02C2793.25 1603.13 2844.55 1624.51 2844.55 1624.51C2844.55 1624.51 2712.04 1393.69 2707.76 1355.22C2703.49 1316.75 2801.81 1410.78 2801.81 1410.78C2801.81 1410.78 2740.21 1308.23 2724.86 1280.41C2667.66 1176.75 2613.36 987.392 2537.11 859.689C2601.1 818.088 2801.11 718.757 2793.56 503.664C2784.96 258.194 2898.72 176.103 2940.26 51.0386C2948.69 25.6348 2936.56 15.0816 2910.99 15.367V15.367Z" fill="url(#paint0_radial_28_46)"/>
<path d="M1793.93 1625.72C1692.31 1626.01 1639.62 1670.45 1602.82 1690.75C1443.85 1877.11 1124.01 1774.44 950.98 1898.07C921.059 1919.44 879.383 2034.85 879.383 2034.85L881.565 2043.13C889.307 2018.8 895.409 2013.48 895.409 2013.48C895.409 2013.48 1143.32 2338.33 1775.94 2304.14C2286.01 2276.56 2501.41 1987.9 2565.77 1877.06C2559.76 1821.08 2542.31 1746.84 2491.91 1744.19C2410.7 1739.91 2075.15 1712.12 1972.56 1656.56C1930.89 1633.98 1863.46 1625.52 1793.93 1625.72V1625.72Z" fill="#F7EDE9"/>
<path d="M1670.59 1677.46C1653.7 1635.82 1858.76 1600.28 1862.52 1639.68C1866.61 1682.55 1812.65 1760.58 1785.45 1765.11C1758.24 1769.64 1688.63 1721.93 1670.59 1677.46V1677.46Z" fill="black"/>
<g opacity="0.8" filter="url(#filter0_f_28_46)">
<path d="M1795.15 1641.88C1797.06 1634.19 1809.39 1629.26 1822.69 1630.88C1835.98 1632.49 1845.21 1640.04 1843.29 1647.73C1841.38 1655.43 1829.05 1660.35 1815.75 1658.74C1802.45 1657.12 1793.23 1649.57 1795.15 1641.88Z" fill="#BEBEBE"/>
</g>
<path opacity="0.8" d="M1797.09 1642.11C1798.83 1635.16 1810.16 1630.73 1822.38 1632.22C1834.6 1633.7 1843.1 1640.55 1841.35 1647.5C1839.61 1654.46 1828.28 1658.89 1816.06 1657.4C1803.83 1655.91 1795.34 1649.07 1797.09 1642.11Z" fill="#DEDEDE"/>
<path d="M2839.03 126.979C2750.83 205.43 2490.04 199.914 2389.32 331.49C2311.81 432.749 2293.15 545.208 2382.91 699.087C2429.76 779.407 2491.15 828.243 2540.37 857.529C2656.85 814.594 2791.93 737.805 2791.56 525.669C2791.4 431.967 2868.09 265.271 2862.97 214.318C2865.39 180.456 2897.45 75.0209 2839.03 126.979V126.979Z" fill="url(#paint1_radial_28_46)"/>
<path d="M135.341 546.877C204.087 610.736 275.71 693.122 295.788 819.61C335.05 1066.97 582.025 1072.54 635.159 1095.33C678.647 1047.5 747.7 954.793 774.947 808.594C815.021 593.571 743.572 440.231 630.27 356.086C544.242 292.197 465.345 399.217 373.009 410.597C226.391 428.666 137.346 442.883 104.936 460.272C73.1798 477.31 100.208 513.662 135.341 546.877V546.877Z" fill="url(#paint2_radial_28_46)"/>
<path d="M2910.99 15.367C2810.67 16.4868 2399.36 32.4575 2314.62 125.748C2227.59 221.545 2173.88 368.999 2157.21 419.478C2059.12 356.561 1915.41 355.12 1915.41 355.12C1915.41 355.12 2000.05 327.92 2090.72 303.741C1933.55 249.336 1731.05 297.692 1731.05 297.692C1731.05 297.692 1816.06 254.536 1897.66 181.997C1770.72 215.244 1628.28 231.198 1537.61 285.603C1544.1 286.111 1479.62 292.085 1564.25 174.208C1331.52 231.635 1168.86 376.283 1069.11 460.912C1081.2 385.351 1085.97 396.401 1122.24 311.772C984.85 406.889 945.872 455.871 915.959 529.819C884.785 472.227 802.353 331.66 698.786 248.138C566.279 141.277 -48.0541 314.703 20.3368 442.936C88.7277 571.169 253.044 550.321 295.788 819.61C338.532 1088.9 643.216 1098.74 643.216 1098.74C645.435 1097.95 647.639 1097.05 649.836 1096.13C627.255 1167.82 607.109 1272.5 591.923 1444.97C519.258 1553.97 513.208 1598.36 504.66 1638.96C575.188 1604.77 583.374 1556.11 634.667 1543.29C589.785 1618.09 587.651 1720.68 606.886 1883.1C643.218 1791.2 641.076 1793.34 679.546 1765.56C690.232 1874.55 713.747 1934.4 741.53 1968.59C752.217 1908.75 784.275 1885.25 784.275 1885.25C784.275 1885.25 805.648 2015.62 867.627 2105.38C880.451 2026.3 895.409 2013.48 895.409 2013.48C895.409 2013.48 1143.32 2338.33 1775.94 2304.14C2408.55 2269.94 2588.08 1833.95 2588.08 1833.95C2588.08 1833.95 2600.91 1889.52 2600.91 1910.89C2647.93 1842.5 2677.85 1701.44 2677.85 1701.44C2677.85 1701.44 2712.04 1782.65 2703.49 1833.95C2741.96 1769.83 2750.51 1611.68 2733.41 1539.02C2793.25 1603.13 2844.55 1624.51 2844.55 1624.51C2844.55 1624.51 2712.04 1393.69 2707.76 1355.22C2703.49 1316.75 2801.81 1410.78 2801.81 1410.78C2801.81 1410.78 2740.21 1308.23 2724.86 1280.41C2667.66 1176.75 2613.36 987.392 2537.11 859.689C2601.1 818.088 2797.11 782.772 2789.56 567.679C2780.95 322.209 2898.72 176.103 2940.26 51.0386C2948.69 25.6348 2936.56 15.0816 2910.99 15.367V15.367Z" stroke="black" stroke-width="30.7203" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1174.32 3623.87C1099.51 3499.91 1063.06 3415.08 1057.84 3359.39C1053.03 3308.09 1046.62 3246.12 1083.48 3276.57C1094.17 3231.69 1092.03 3206.04 1119.82 3218.87C1147.6 3231.69 1166.83 3259.47 1166.83 3259.47C1166.83 3259.47 1162.56 3195.36 1190.34 3216.73C1241.43 3247.48 1332.69 3536.32 1322.06 3618.13C1310.38 3708.02 1240.21 3727.57 1174.32 3623.87V3623.87Z" fill="url(#paint3_linear_28_46)" stroke="black" stroke-width="23.0402"/>
<path d="M2564.78 3516.43C2617.78 3381.7 2639.52 3291.97 2635.35 3236.19C2631.5 3184.81 2627.45 3122.63 2596.2 3158.83C2578.16 3116.37 2575.97 3090.73 2550.72 3108.02C2525.48 3125.31 2511.17 3155.92 2511.17 3155.92C2511.17 3155.92 2504.65 3092 2480.83 3117.72C2435.61 3156.59 2393.99 3456.62 2418.16 3535.5C2444.71 3622.17 2517.17 3629.7 2564.78 3516.43V3516.43Z" fill="url(#paint4_linear_28_46)" stroke="black" stroke-width="23.0402"/>
<path d="M1547.64 2230.3C1547.64 2230.3 1409.61 2394.34 1345.59 2428.35C1281.58 2462.36 1537.64 2590.38 1537.64 2590.38C1537.64 2590.38 1687.67 2386.34 1693.67 2308.32L1699.68 2230.3H1547.64Z" fill="#F7EDE9"/>
<path d="M1552.45 2225.31C1571.48 2258.43 1605.03 2280.42 1639.99 2293.91C1656.36 2300.07 1673.27 2305.1 1690.57 2307.77C1699.84 2271.3 1705.7 2233.76 1705.72 2196.08C1705.12 2188.67 1704.84 2178.86 1697.33 2174.95C1689.06 2172.5 1682.13 2179.41 1675.63 2183.33C1679.28 2173.01 1676.81 2160.78 1670.04 2152.31C1662.92 2145.38 1653.3 2153.18 1648.01 2158.59C1642.73 2163.3 1639.14 2169.69 1633.7 2174.14C1630.98 2168.39 1635.15 2158.49 1628.69 2153.34C1621.45 2147.74 1614.83 2157.89 1609.5 2161.85C1589.24 2181.83 1571.08 2203.83 1552.45 2225.31V2225.31Z" fill="url(#paint5_linear_28_46)"/>
<path d="M1253.67 2495.08C1253.67 2495.08 1253.7 2495.06 1253.71 2494.98C1253.73 2494.91 1253.75 2494.77 1253.73 2494.55C1253.85 2494.31 1254.06 2494 1254.36 2493.63C1254.95 2492.9 1255.9 2491.91 1257.19 2490.66C1259.68 2488.27 1263.48 2484.94 1268.52 2480.67C1277.89 2472.76 1291.17 2462 1308.59 2447.97C1332.04 2429.09 1377.21 2393.23 1416.14 2359.67C1435.84 2342.71 1453.06 2323.43 1472.83 2300.71C1493.32 2277.11 1512.59 2253.5 1534.3 2226.92C1550.64 2206.9 1568.16 2185.04 1586.51 2164.5C1592.19 2158.11 1598.29 2151.39 1605.38 2145.13C1607.41 2143.27 1610.03 2141.16 1613.44 2139.16C1614.53 2138.47 1616.19 2137.74 1618.44 2137.08C1620 2136.39 1622.68 2136.37 1626.4 2137.18C1631.32 2138.25 1636.42 2142 1640.22 2148.66C1642.23 2154.09 1642.53 2158.57 1641.93 2161.68C1641.68 2164.89 1641.2 2166.62 1641.38 2166.88C1641.51 2164.86 1639.93 2163.24 1636.43 2160.78C1633.57 2160.8 1632.55 2160.6 1632.18 2161.3C1633.23 2160.49 1634.51 2158.06 1636.94 2154.8C1639.08 2151.42 1642.54 2147.44 1647.9 2143.9C1654.96 2140.17 1662.36 2139.65 1668.71 2141.79C1674.35 2143.23 1679.64 2147.46 1683.14 2154.38C1685.12 2160.15 1685.36 2164.93 1684.86 2168.28C1684.65 2171.9 1684.19 2174.36 1684.38 2175.65C1684.92 2175.76 1684.05 2175.31 1682.21 2172.96C1680.34 2172.13 1679.39 2171.73 1679.02 2171.98C1678.97 2172.08 1679.56 2171.81 1680.8 2171.48C1683.17 2170.62 1686.96 2168.95 1692.53 2167.86C1693.69 2167.59 1695.01 2167.47 1696.48 2167.52C1698.3 2167.58 1700.34 2167.9 1702.57 2168.52C1707.01 2170.25 1710.27 2172.9 1712.21 2176.09C1717.7 2186.05 1718.08 2195.25 1717.36 2201.51C1715.45 2275.24 1695.21 2345.41 1666.81 2407C1636.86 2472 1598.69 2527.26 1563.18 2571.11C1524.08 2619.42 1486.97 2655.52 1460.54 2678.9C1446.05 2691.72 1434.5 2700.95 1426.26 2707C1421.97 2710.14 1418.65 2712.38 1416.29 2713.78C1415.08 2714.49 1414.14 2714.98 1413.46 2715.25C1413.12 2715.38 1412.84 2715.46 1412.63 2715.49C1412.53 2715.5 1412.44 2715.5 1412.37 2715.49C1412.3 2715.47 1412.24 2715.44 1412.21 2715.4C1412.17 2715.35 1412.14 2715.31 1412.16 2715.22C1412.16 2715.15 1412.17 2715.06 1412.21 2714.96C1412.28 2714.77 1412.41 2714.51 1412.62 2714.21C1413.02 2713.6 1413.68 2712.78 1414.6 2711.74C1416.38 2709.72 1419.19 2706.81 1422.87 2703.08C1430.3 2695.57 1440.14 2685.87 1453.75 2671.8C1477.95 2646.79 1512.96 2609.16 1549.7 2560.56C1583.1 2516.35 1619.21 2461.46 1647.22 2398.15C1673.71 2338.25 1692.69 2270.9 1694.25 2201.13C1693.64 2194.48 1694.52 2189.65 1692.9 2188.82C1693.08 2189.33 1693.81 2189.75 1695.21 2190.46C1695.85 2190.55 1696.22 2190.64 1696.36 2190.69C1696.47 2190.73 1696.44 2190.74 1696.24 2190.7C1694.94 2190.72 1692.35 2192.09 1688.11 2193.43C1686.33 2194.1 1683.84 2194.74 1680.61 2195.05C1677.54 2195.49 1673.78 2194.67 1669.64 2192.37C1663.69 2187.36 1661.05 2181.78 1661.31 2177V2177C1661.15 2172.39 1661.62 2168.95 1661.82 2166.69C1661.73 2164.15 1662.06 2162.83 1661.8 2163.16C1662.81 2164.08 1662.41 2164.31 1661.87 2163.8C1660.62 2163.58 1659.43 2163.35 1659.67 2163.67C1658.82 2163.83 1657.66 2165.67 1655.61 2168.16V2168.16C1654.1 2170.4 1651.9 2173.87 1648.11 2177.76C1643.82 2182.24 1637.07 2184.15 1629.07 2182.43C1622.17 2178.82 1618.62 2173.59 1618.58 2168V2168C1618.53 2163.66 1619.06 2160.83 1619.25 2159.47C1619.14 2157.69 1619.49 2157.05 1619.48 2158C1620.8 2159.19 1620.41 2159.57 1619.62 2158.83C1622.22 2159.29 1623.69 2159.41 1624.13 2159.02C1624.75 2158.78 1624.9 2158.78 1624.53 2158.91C1623.44 2159.44 1622.06 2160.64 1620.22 2162.16C1614.36 2167.19 1608.83 2173.23 1603.11 2179.48C1585.13 2199.21 1567.45 2220.73 1550.73 2240.56C1528.73 2266.69 1508.62 2290.39 1487.43 2313.69C1467.11 2336.07 1448.51 2355.66 1427.18 2372.92C1384.59 2407.27 1342.02 2437.75 1315.01 2456.55C1296.77 2469.25 1282.19 2478.95 1271.97 2485.38C1266.36 2488.91 1262.02 2491.49 1258.97 2493.12C1257.38 2493.97 1256.14 2494.55 1255.26 2494.88C1254.81 2495.05 1254.45 2495.15 1254.19 2495.19C1253.98 2495.1 1253.85 2495.08 1253.77 2495.07C1253.7 2495.07 1253.67 2495.08 1253.67 2495.08L1253.67 2495.08Z" fill="black"/>
<path d="M1550.29 2223.04C1552.02 2222.06 1555.96 2226.15 1563.08 2233.04C1570.64 2240.36 1579.91 2248.83 1593.5 2258.19C1600.35 2262.92 1607.58 2267.34 1615.33 2271.58C1622.75 2275.64 1630.22 2279.3 1637.63 2282.7C1670.11 2293.53 1691.56 2303.44 1690.46 2307.15C1689.37 2310.85 1665.88 2307.47 1631.98 2298.68C1623.76 2295.61 1615.41 2291.88 1607.22 2287.42C1598.68 2282.76 1590.69 2277.53 1583.42 2271.78C1569.42 2260.68 1560.14 2248.95 1554.8 2239.31C1549.55 2229.84 1548.78 2223.89 1550.29 2223.04V2223.04Z" fill="black"/>
<path d="M2182.1 2821.65C2148.09 2821.91 2115.82 2806.33 2089.83 2785.3C2067.85 2767.28 2048.77 2745.55 2034 2721.26C2022.45 2753.72 2010.59 2786.27 2003.4 2820.03C2001.39 2826.99 2000.64 2839.35 2011.42 2837.84C2021.24 2837.24 2029.53 2831.07 2038.79 2828.73C2036.92 2833.96 2031.26 2838.31 2029.04 2843.95C2024.34 2852.66 2021.14 2866.11 2030.41 2873.05C2042.08 2880.13 2056.74 2876.96 2068.8 2872.51C2076.8 2870.19 2067.29 2882.14 2069.42 2886.32C2067.95 2894.94 2073.56 2906.98 2083.99 2903.55C2096.65 2899.92 2106.5 2890.47 2117.11 2883.12C2140.83 2864.95 2162.39 2844.07 2182.1 2821.65V2821.65Z" fill="url(#paint6_linear_28_46)"/>
<path d="M2124.7 2361.37C2124.7 2361.37 2124.69 2361.37 2124.7 2361.37C2126.63 2361.25 2130.12 2386.35 2134.88 2434.81C2135.35 2453.78 2134.73 2477.74 2130.57 2503.41C2127.18 2524.43 2120.99 2548.65 2108.67 2571.61C2096.65 2593.93 2086.27 2617.36 2075.16 2644.03C2064.04 2670.75 2053.24 2698.81 2042.73 2728.46C2034.5 2751.7 2026.3 2775.9 2019.64 2800.39C2017.5 2808.27 2015.49 2816 2014.27 2823.56C2013.89 2825.98 2013.58 2828.06 2013.57 2829.79C2014.02 2831.42 2013.52 2831.12 2012.88 2828.64C2013.06 2828.52 2012.5 2828.22 2011.21 2827.25C2009.87 2826.84 2009.31 2826.6 2009.25 2826.77C2010.06 2826.98 2012.08 2825.6 2015.59 2824.07C2018.33 2822.53 2022.41 2820.34 2028.22 2818.83C2030.63 2818.04 2034.14 2818.05 2038.54 2819.25C2043.23 2821.27 2046.38 2824.16 2047.89 2827.45C2050.9 2835.35 2050.35 2842.05 2047.66 2846.36C2045.41 2850.98 2043.12 2854.21 2041.81 2856.58C2040.65 2859.24 2039.62 2860.95 2040.01 2861.51C2039.43 2861.99 2041.07 2863.68 2042.89 2866.07C2042.2 2865.98 2042.89 2865.88 2043.64 2865.99C2044.95 2866.01 2046.72 2865.44 2049 2864.68C2049.85 2864.39 2050.77 2864.08 2051.77 2863.76C2054.75 2862.59 2059 2861.02 2064.7 2860.23C2070.87 2859.25 2077.55 2861.93 2082.98 2868.44L2082.98 2868.44C2085.37 2872.89 2086.2 2876.48 2085.97 2878.82V2878.82C2086.05 2881.94 2085.82 2884.6 2085.52 2886.78C2084.98 2891.73 2084.11 2895.39 2084.32 2897.81C2084.65 2898.69 2084.3 2898.52 2083.76 2896.92C2082.02 2894.82 2080.59 2893.78 2078.89 2893.67C2078.04 2893.95 2078.68 2893.42 2080.68 2893.01V2893.01C2083.59 2892.01 2086.71 2890.26 2090.18 2888.27L2090.18 2888.27C2140.92 2854.85 2181.84 2810.27 2213.89 2764.4C2248.17 2715.46 2272.77 2664.59 2290.65 2620.8C2322.67 2511.91 2342.43 2450.89 2345.4 2451.7C2348.38 2452.52 2334.32 2515.13 2306.25 2625.93C2289.7 2671.41 2265.85 2724.43 2231.15 2776.22C2198.69 2824.56 2156.17 2871.74 2102.37 2907.71C2098.38 2910.13 2093.86 2912.59 2088.63 2914.6C2086.06 2915.87 2081.86 2916.84 2076.18 2916.55C2071.56 2916.09 2067.33 2913.5 2064.07 2908.9C2061.84 2904.53 2061.06 2900.99 2061.27 2898.63C2061.18 2892.46 2062.1 2887.6 2062.59 2884.02C2062.78 2882.14 2062.96 2880.71 2062.88 2879.75C2062.52 2878.92 2062.91 2879.21 2063.57 2880.99L2063.57 2880.99C2065.95 2882.79 2066.56 2883.6 2067.15 2883.22C2065.73 2883.19 2063.23 2884.37 2059.46 2885.58C2058.49 2885.95 2057.43 2886.34 2056.29 2886.71C2053.25 2887.71 2049.62 2888.62 2045.35 2889.06C2039.02 2889.69 2032.31 2887.55 2026.58 2882.45C2021.64 2878.11 2017.75 2871.14 2016.92 2862.1C2017.1 2855.05 2019.1 2849.42 2021.62 2845.41C2023.87 2841.36 2025.93 2838.6 2026.8 2836.58C2026.66 2835.47 2027.4 2835.63 2027.59 2838.26C2027.8 2839.09 2028.89 2839.83 2030.96 2840.94C2032.77 2841.23 2033.43 2841.45 2033.17 2841.25C2031.91 2841.29 2029.6 2842.82 2025.89 2844.53C2022.89 2846.28 2018.3 2848.41 2011.88 2849.46C2008.88 2849.97 2005.24 2849.48 2001.23 2847.77C1996.95 2845.35 1994.14 2842.37 1992.76 2839.32C1990.7 2834.14 1990.32 2830.66 1990.83 2829.24C1990.95 2825.7 1991.39 2822.55 1991.89 2819.79C1993.44 2810.89 1995.69 2802.43 1997.98 2794.38C2005.13 2769.04 2013.8 2744.45 2022.47 2721.1C2033.56 2691.25 2045.05 2663 2056.94 2636.2C2056.94 2636.2 2056.94 2636.2 2056.94 2636.2C2068.74 2609.56 2080.15 2585.7 2093.32 2563.01C2105.09 2542.96 2111.7 2520.53 2115.92 2500.68C2121.01 2476.68 2122.93 2453.52 2123.78 2435.1C2122.46 2387.18 2122.76 2361.68 2124.68 2361.37L2124.7 2361.37Z" fill="black"/>
<path d="M2175 2826.87C2174.44 2829.44 2164.46 2830.8 2147.34 2826.66C2129.78 2822.41 2108.61 2813.13 2088.24 2797.58C2078.48 2790.13 2069.81 2781.92 2062.37 2773.42C2043.37 2745.24 2033.41 2725.37 2036.91 2722.76C2040.4 2720.15 2056.55 2735.24 2077.5 2760.58C2085.04 2767.64 2093.09 2774.53 2101.89 2781.22C2120.61 2795.44 2137.36 2804.74 2152.67 2812.56C2166.96 2819.86 2175.65 2823.9 2175 2826.87V2826.87Z" fill="black"/>
<path opacity="0.969" d="M2271.02 1481.65C2270.98 1481.7 2270.96 1481.76 2270.93 1481.82C2270.3 1482.5 2269.52 1483.07 2268.45 1483.4C2266.31 1484.06 2263.43 1484.13 2259.51 1483.48C2251.72 1482.17 2240.55 1478.45 2225.78 1472.61C2195.05 1460.45 2150 1440.05 2094.71 1417.03C2083.82 1413.11 2066.88 1406.63 2047.14 1395.61C2042.15 1394.09 2032.43 1386.77 2019.12 1373.26C2005.68 1360.16 2000.71 1338.54 2004.65 1309.48V1309.48C2015.17 1285.72 2024.38 1273.62 2031.14 1272.29C2040.81 1265.93 2048.46 1262.35 2053.94 1260.86C2072.42 1254.93 2088.82 1252.2 2103.52 1250.56C2122.55 1247.86 2141.12 1246.81 2159.25 1246.97C2184.24 1247.19 2208.15 1249.72 2230.89 1253.25C2276.78 1260.23 2318.45 1271.46 2353.6 1283.35C2444.6 1317.86 2493.15 1346 2484.81 1358.31C2477.63 1368.89 2429.3 1366.68 2351.7 1352.28C2319.35 1347.49 2279.06 1341.07 2239.34 1336.79C2204.14 1333.35 2168.81 1329.86 2134.1 1332.29C2133.42 1332.34 2132.74 1332.39 2132.06 1332.44C2117.45 1333.99 2104.62 1334.71 2092.5 1336.9C2088.86 1337.69 2086.18 1337.74 2084.54 1337.85C2083.85 1338.92 2083.97 1336.16 2085.92 1331.01C2085.96 1323.35 2084.56 1319.35 2081.58 1317.6C2079.4 1317.18 2079.97 1318.08 2081.88 1320.29C2089.26 1327.57 2100.53 1333.47 2111.5 1339.38C2169.79 1370.45 2212.91 1401.57 2241.33 1428.53C2254.76 1441.26 2263.67 1452.08 2268.92 1460.88C2271.53 1465.26 2272.96 1468.94 2273.54 1472.03C2273.83 1473.57 2273.79 1474.92 2273.58 1476.1C2273.39 1476.64 2273.09 1477.16 2272.82 1477.63V1477.63C2283.36 1455.58 2295.93 1429.3 2306.47 1407.26C2306.56 1407.07 2306.56 1407.08 2306.47 1407.26C2329.9 1358.27 2248.02 1529.51 2271.45 1480.52C2271.32 1480.9 2271.19 1481.31 2271.02 1481.65L2271.02 1481.65Z" fill="black"/>
<path opacity="0.997" d="M2062.08 1928.03C2066.08 1963.78 2031.67 1988.95 1996.69 1993.07C1952.17 1998.32 1895.66 1971.73 1812.67 1981.01C1754.15 1987.55 1699.65 2020.18 1652.86 2030.03C1581.12 2045.14 1530.79 2037.18 1525.3 1988C1516.22 1906.75 1629.02 1827.46 1777.25 1810.9C1925.48 1794.33 2053 1846.78 2062.08 1928.03V1928.03Z" fill="black"/>
<path opacity="0.969" d="M928.618 1507.23C938.738 1515.86 985.438 1505.63 1058.08 1479.04C1088.78 1468.85 1127.78 1455.32 1166.09 1444.19C1201.06 1434.49 1236.16 1424.44 1272.09 1420.85C1274.07 1420.72 1276.03 1420.58 1277.96 1420.44C1290.49 1419.5 1301.76 1418.33 1312.8 1418.39C1316.66 1418.53 1319.6 1418.1 1321.7 1417.99C1322.82 1418.82 1322.93 1416.45 1321.25 1412.65C1320.85 1406.53 1321.81 1403.64 1324 1402.45C1325.64 1402.66 1324.84 1404.21 1323.13 1406.79C1316.37 1415.86 1305.6 1423.98 1295.62 1431.73C1242.64 1472.89 1205.17 1511.48 1181.64 1543.57C1170.52 1558.73 1163.7 1571.17 1160.11 1581.02C1158.32 1585.91 1157.66 1589.9 1157.68 1593.16C1157.68 1594.78 1158.04 1596.16 1158.48 1597.34C1158.81 1597.86 1159.26 1598.34 1159.64 1598.77C1143.14 1572.13 1123.51 1540.47 1107.01 1513.83C1106.88 1513.63 1106.88 1513.63 1107.01 1513.83C1067.71 1450.43 1200.7 1665.03 1161.4 1601.63C1161.56 1602.08 1161.71 1602.62 1161.9 1603C1162.06 1603.17 1162.23 1603.33 1162.41 1603.48C1162.93 1603.91 1163.53 1604.25 1164.29 1604.43C1166.32 1604.89 1168.93 1604.66 1172.5 1603.49C1179.61 1601.16 1189.55 1595.86 1202.88 1587.73C1230.59 1570.82 1271.17 1543.07 1322 1510.73C1332.09 1505.08 1348.78 1495.65 1367.94 1481.3C1372.95 1479.1 1382.55 1469.79 1395.45 1453.3H1395.45C1408.68 1437.14 1411.42 1412.6 1403.47 1380.91V1380.91C1388.85 1356.01 1377.31 1343.97 1369.85 1343.56C1358.65 1338.09 1350.13 1335.4 1344.28 1334.74C1324.43 1331.51 1307.46 1331.4 1292.46 1332.26C1269.62 1332.69 1247.91 1335.32 1227.11 1339.44C1205.39 1343.75 1184.84 1349.64 1165.34 1356.17C1120.73 1370.95 1081.19 1389.4 1048.22 1407.42C960.49 1459.08 916.235 1496.67 928.618 1507.23V1507.23Z" fill="black"/>
<path d="M2570.51 1270.25C2570.51 1270.25 2611.95 1201.33 2707.84 1167.02C2803.74 1132.7 2924.6 1340.93 2570.51 1270.25Z" fill="#71E0E0"/>
<path opacity="0.997" d="M2773.75 1203.7C2773.35 1218.61 2757.87 1223.28 2737.72 1222.74C2717.56 1222.2 2700.36 1216.65 2700.76 1201.74C2701.16 1186.83 2717.82 1175.18 2737.98 1175.72C2758.13 1176.26 2774.15 1188.79 2773.75 1203.7V1203.7Z" fill="white"/>
<path d="M2715.89 1392.5C2715.89 1392.5 2772.4 1373.32 2843.17 1399.83C2913.95 1426.34 2882.64 1602.24 2715.89 1392.5Z" fill="#71E0E0"/>
<path opacity="0.997" d="M2862.94 1452.19C2855.56 1460.45 2844.55 1455.68 2833.39 1445.72C2822.23 1435.75 2815.15 1424.36 2822.52 1416.1C2829.89 1407.85 2844.91 1409.23 2856.08 1419.2C2867.24 1429.16 2870.31 1443.94 2862.94 1452.19V1452.19Z" fill="white"/>
<path d="M841.958 1500.18C841.958 1500.18 771.339 1461.71 671.266 1480.64C571.194 1499.57 572.326 1740.33 841.958 1500.18Z" fill="#71E0E0"/>
<path opacity="0.997" d="M632.987 1545.62C640.877 1558.28 656.592 1554.48 673.702 1543.82C690.813 1533.15 702.84 1519.65 694.949 1507C687.059 1494.34 666.792 1492.73 649.681 1503.39C632.57 1514.06 625.096 1532.97 632.987 1545.62V1545.62Z" fill="white"/>
<path d="M778.439 1679.19C778.439 1679.19 719.99 1691.24 672.36 1749.92C624.73 1808.6 740.752 1944.48 778.439 1679.19Z" fill="#71E0E0"/>
<path opacity="0.997" d="M681.813 1805.09C692.35 1808.48 699.433 1798.79 704.016 1784.55C708.599 1770.31 708.946 1756.9 698.41 1753.51C687.874 1750.12 675.617 1758.91 671.033 1773.16C666.45 1787.4 671.277 1801.7 681.813 1805.09V1805.09Z" fill="white"/>
<path d="M1833.6 1920.21C1830.99 1920.21 1828.37 1920.21 1825.74 1920.25C1816.01 1920.32 1806.14 1921.2 1796.18 1922.94C1702.44 1939.32 1636.6 1955.14 1607.38 2036.68C1621.25 2035.85 1636.48 2033.62 1652.86 2030.17C1699.65 2020.32 1754.15 1987.69 1812.67 1981.15C1895.67 1971.87 1952.17 1998.46 1996.69 1993.21C2001.8 1992.61 2006.89 1991.55 2011.86 1990.08C1974.97 1931.79 1909.4 1920.36 1833.6 1920.22V1920.21Z" fill="#DF6747"/>
<defs>
<filter id="filter0_f_28_46" x="1790.28" y="1625.95" width="57.8831" height="37.7055" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2.30794" result="effect1_foregroundBlur_28_46"/>
</filter>
<radialGradient id="paint0_radial_28_46" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(1769.23 1677.78) rotate(-0.182296) scale(306.481 287.176)">
<stop stop-color="#CC3100"/>
<stop offset="1" stop-color="#DE6B00"/>
</radialGradient>
<radialGradient id="paint1_radial_28_46" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(2381.57 755.292) rotate(63.7909) scale(608.272 241.85)">
<stop stop-color="#FF927A"/>
<stop offset="1" stop-color="#F7C199"/>
</radialGradient>
<radialGradient id="paint2_radial_28_46" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(793.252 914.947) rotate(-157.738) scale(359.552 439.05)">
<stop stop-color="#FF927A"/>
<stop offset="1" stop-color="#F7C199"/>
</radialGradient>
<linearGradient id="paint3_linear_28_46" x1="1265.58" y1="3661.71" x2="1144.68" y2="3329.24" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
<linearGradient id="paint4_linear_28_46" x1="2489.26" y1="3548.4" x2="2536.25" y2="3236.36" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
<linearGradient id="paint5_linear_28_46" x1="1615.27" y1="2298.63" x2="1651.45" y2="2196.26" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
<linearGradient id="paint6_linear_28_46" x1="2095.19" y1="2779.79" x2="2057.55" y2="2847.94" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 32 KiB

View file

@ -1,109 +0,0 @@
<svg width="2859" height="3858" viewBox="0 0 2859 3858" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_35_87)">
<path opacity="0.997" d="M229.331 2573.92C227.658 2571.29 287.004 2544.38 324.218 2448.2C337.11 2414.69 347.357 2369.08 337.943 2323.16C334.776 2307.96 329.583 2292.41 320.457 2279.96C313.372 2270.39 303.743 2262.04 292.932 2259.23C281.648 2255.61 267.954 2257.93 254.626 2263.16C231.995 2270.93 211.286 2285.24 190.078 2302.35C167.021 2283.54 134.978 2268.82 104.768 2273.7C81.902 2276.54 60.511 2295.24 49.4462 2318.86C3.74465 2410.06 71.315 2519.88 150.29 2625.09C150.29 2625.09 150.29 2625.09 150.29 2625.09C230.276 2725.89 340.979 2812.76 472.956 2860.92C580.106 2900.19 698.127 2914.49 816.398 2905.34C858.558 2901.63 900.297 2891.12 931.227 2867C941.601 2859.12 950.867 2849.65 958.548 2839.11C967.767 2826.46 974.702 2812.27 978.534 2797.4C985.145 2772.44 984.014 2745.1 975.509 2720.73V2720.73C967.343 2698.01 953.089 2677.33 934.061 2665.58C910.149 2650.23 881.933 2641.07 853.199 2637.76C819.885 2634.11 785.627 2639.42 757.021 2654.45C719.662 2673.22 691.325 2709.24 671.796 2748.4C639.342 2814.04 629.429 2890.35 625.585 2963.42C620.787 3054.16 660.729 3141.58 719.642 3201.36C770.676 3252.96 831.322 3289.74 893.723 3308.69C908.697 3313.56 926.857 3313.53 945.412 3314.04C973.343 3314.51 1002.22 3313.39 1029.19 3312.46C1112.71 3309.54 1178.34 3307.56 1247.24 3311.4C1252.48 3312.55 1259.26 3308.03 1265.44 3302.94C1274.49 3294.73 1281.43 3283.17 1287.71 3271.73C1306.29 3237.38 1318.13 3197.66 1326.75 3167.88C1349.83 3064.98 1363.32 3012.13 1366.36 3012.73C1369.41 3013.33 1361.94 3067.36 1344.36 3171.88C1344.36 3171.88 1344.36 3171.88 1344.36 3171.88C1337.33 3202.62 1327.04 3243.92 1308.44 3282.33C1302.39 3294.99 1294.5 3308.84 1282.76 3320.8C1274.53 3329.67 1261.97 3337.01 1246.23 3337.34C1178.13 3335.81 1114.64 3339.62 1030.69 3344.52C1003.79 3346.11 974.453 3347.89 945.212 3348.03C926.769 3348.32 905.587 3348.33 883.825 3342.43C815.106 3322.91 748.321 3284.25 692.192 3228.84C625.184 3162.88 579.614 3066.05 583.303 2961.59C585.929 2885.84 596.46 2803.96 631.931 2729.13C653.524 2683.25 688.026 2640.3 736.117 2614.43C773.614 2594.93 816.524 2587.77 858.202 2592.39C893.307 2596.11 928.083 2607.44 958.736 2626.81C987.785 2645.66 1008.37 2674.19 1019.07 2705.51V2705.51C1030.67 2738.69 1032.2 2775.02 1023.26 2809.17C1017.73 2829.86 1008.36 2849.2 995.965 2866.3C985.637 2880.55 973.211 2893.24 959.166 2903.86C917.766 2934.55 867.899 2948.06 819.932 2951.28C695.515 2960.79 571.075 2945 457.507 2902.73C317.198 2850.29 201.177 2757.52 118.223 2650.2V2650.2C32.6442 2545.24 -32.8981 2417.48 17.6801 2303.93C33.4423 2270.48 64.451 2246.14 100.199 2240.83C136.634 2236.45 169.491 2255.33 190.178 2280.49C205.009 2262.04 222.599 2245.98 243.989 2236.31C261.83 2229.19 281.993 2227.28 301.035 2233.04C318.973 2239.12 332.793 2251.36 341.852 2265.2C353.144 2282.35 358.941 2301.28 361.69 2318.87C370.075 2371.61 356.822 2420.33 340.993 2455.25C291.994 2563.91 225.032 2567.17 229.331 2573.92V2573.92Z" fill="black"/>
<path d="M1160.62 2299.49C1160.62 2299.49 1166.66 2341.8 1118.3 2468.75C1069.94 2595.69 1066.92 2885.85 1042.74 2949.32C1018.56 3012.79 885.57 3224.37 915.795 3411.76C946.02 3599.15 1094.12 3865.13 1275.47 3798.64C1456.82 3732.14 1420.55 3744.23 1420.55 3744.23C1420.55 3744.23 1946.46 3859.09 2294.05 3659.6C2372.63 3695.87 2420.99 3726.1 2538.87 3656.58C2656.74 3587.06 2717.19 3396.65 2714.17 3266.68C2711.15 3136.71 2523.75 2852.6 2502.6 2792.15C2481.44 2731.7 2402.86 2435.5 2363.56 2311.58C2324.27 2187.66 2354.5 2166.5 2354.5 2166.5L1160.62 2299.49Z" fill="#DE5700" stroke="black" stroke-width="46.0804"/>
<path d="M2798.72 15.367C2698.4 16.4866 2391.12 184.492 2306.37 277.783C2219.35 373.579 2165.64 521.033 2148.97 571.513C2050.88 508.595 1907.17 507.155 1907.17 507.155C1907.17 507.155 1991.8 479.955 2082.48 455.775C1925.31 401.371 1722.8 449.727 1722.8 449.727C1722.8 449.727 1813.47 349.989 1895.08 277.45C1768.13 310.697 1620.04 383.233 1529.36 437.638C1553.54 401.368 1595.86 362.076 1680.49 244.199C1447.75 301.626 1160.61 528.318 1060.87 612.947C1072.96 537.385 1103.19 489.025 1139.46 404.395C1002.07 499.513 937.628 607.905 907.715 681.853C876.542 624.261 794.11 483.695 690.543 400.173C558.035 293.312 45.0983 126.611 113.489 254.844C181.88 383.077 164.781 558.322 207.526 827.611C250.27 1096.9 634.972 1250.78 634.972 1250.78C637.192 1249.99 639.395 1249.09 641.592 1248.17C619.011 1319.85 598.866 1424.54 583.679 1597.01C511.014 1706.01 478.959 1727.38 470.41 1767.99C540.938 1733.8 575.13 1708.15 626.423 1695.32C581.542 1770.13 579.407 1872.71 598.642 2035.14C634.975 1943.24 632.833 1945.37 671.303 1917.59C681.988 2026.59 705.503 2086.43 733.287 2120.63C743.973 2060.78 776.031 2037.28 776.031 2037.28C776.031 2037.28 797.404 2167.65 859.383 2257.41C872.207 2178.34 887.165 2165.51 887.165 2165.51C887.165 2165.51 1135.08 2490.37 1767.7 2456.17C2400.31 2421.98 2579.84 1985.98 2579.84 1985.98C2579.84 1985.98 2592.66 2041.56 2592.66 2062.93C2639.68 1994.54 2669.6 1853.48 2669.6 1853.48C2669.6 1853.48 2703.8 1934.69 2695.25 1985.98C2733.72 1921.86 2742.26 1763.72 2725.17 1691.05C2785.01 1755.17 2836.31 1776.54 2836.31 1776.54C2836.31 1776.54 2703.79 1545.72 2699.52 1507.25C2695.25 1468.78 2793.56 1562.81 2793.56 1562.81C2793.56 1562.81 2731.97 1460.26 2716.62 1432.45C2659.41 1328.78 2605.12 1139.43 2528.87 1011.72C2592.86 970.123 2850.88 788.773 2843.33 573.68C2834.72 328.21 2786.46 176.103 2827.99 51.0384C2836.43 25.6346 2824.3 15.0816 2798.72 15.367V15.367Z" fill="#DE5700"/>
<path d="M1160.62 2299.49C1160.62 2299.49 1166.66 2341.8 1118.3 2468.75C1069.94 2595.69 1066.92 2885.85 1042.74 2949.32C1018.56 3012.79 885.57 3224.37 915.795 3411.76C946.02 3599.15 1094.12 3865.13 1275.47 3798.64C1456.82 3732.14 1420.55 3744.23 1420.55 3744.23C1420.55 3744.23 1946.46 3859.09 2294.05 3659.6C2372.63 3695.87 2420.99 3726.1 2538.87 3656.58C2656.74 3587.06 2717.19 3396.65 2714.17 3266.68C2711.15 3136.71 2523.75 2852.6 2502.6 2792.15C2481.44 2731.7 2402.86 2435.5 2363.56 2311.58C2324.27 2187.66 2354.5 2166.5 2354.5 2166.5L1160.62 2299.49Z" fill="#DE5700"/>
<path d="M1940.31 2212.63L1557.07 2255.32C1406.21 2289.92 1288.96 2332.21 1288.96 2332.21C1288.96 2332.21 1220.57 2746.83 1233.39 2862.24C1246.22 2977.65 1173.55 3152.9 1212.02 3328.15C1245.61 3481.2 1311.83 3644.02 1558.64 3765.38C1710.04 3782.34 1960.24 3792 2176.82 3713.48C2280.67 3629.51 2354.73 3520.47 2383.22 3435.02C2447.33 3242.67 2370.39 2909.27 2340.47 2840.87C2310.55 2772.48 2344.75 2558.76 2178.04 2315.11C2132.1 2247.96 2043.91 2219.17 1940.31 2212.63Z" fill="#F7EDE9"/>
<path opacity="0.6" d="M2798.72 15.367C2698.4 16.4866 2391.12 184.492 2306.37 277.783C2219.35 373.579 2165.64 521.033 2148.97 571.513C2050.88 508.595 1907.17 507.155 1907.17 507.155C1907.17 507.155 1991.8 479.955 2082.48 455.775C1925.31 401.371 1722.8 449.727 1722.8 449.727C1722.8 449.727 1813.47 349.989 1895.08 277.45C1768.13 310.697 1620.04 383.233 1529.36 437.638C1553.54 401.368 1595.86 362.076 1680.49 244.199C1447.75 301.626 1160.61 528.318 1060.87 612.947C1072.96 537.385 1103.19 489.025 1139.46 404.395C1002.07 499.513 937.628 607.905 907.715 681.853C876.542 624.261 794.11 483.695 690.543 400.173C558.035 293.312 45.0983 126.611 113.489 254.844C181.88 383.077 164.781 558.322 207.526 827.611C250.27 1096.9 634.972 1250.78 634.972 1250.78C637.192 1249.99 639.395 1249.09 641.592 1248.17C619.011 1319.85 598.866 1424.54 583.679 1597.01C511.014 1706.01 478.959 1727.38 470.41 1767.99C540.938 1733.8 575.13 1708.15 626.423 1695.32C581.542 1770.13 579.407 1872.71 598.642 2035.14C634.975 1943.24 632.833 1945.37 671.303 1917.59C681.988 2026.59 705.503 2086.43 733.287 2120.63C743.973 2060.78 776.031 2037.28 776.031 2037.28C776.031 2037.28 797.404 2167.65 859.383 2257.41C872.207 2178.34 887.165 2165.51 887.165 2165.51C887.165 2165.51 1135.08 2490.37 1767.7 2456.17C2400.31 2421.98 2579.84 1985.98 2579.84 1985.98C2579.84 1985.98 2592.66 2041.56 2592.66 2062.93C2639.68 1994.54 2669.6 1853.48 2669.6 1853.48C2669.6 1853.48 2703.8 1934.69 2695.25 1985.98C2733.72 1921.86 2742.26 1763.72 2725.17 1691.05C2785.01 1755.17 2836.31 1776.54 2836.31 1776.54C2836.31 1776.54 2703.79 1545.72 2699.52 1507.25C2695.25 1468.78 2793.56 1562.81 2793.56 1562.81C2793.56 1562.81 2731.97 1460.26 2716.62 1432.45C2659.41 1328.78 2605.12 1139.43 2528.87 1011.72C2592.86 970.123 2850.88 788.773 2843.33 573.68C2834.72 328.21 2786.46 176.103 2827.99 51.0384C2836.43 25.6346 2824.3 15.0816 2798.72 15.367V15.367Z" fill="url(#paint0_radial_35_87)"/>
<path d="M1785.69 1777.75C1684.07 1778.04 1631.38 1822.48 1594.58 1842.79C1435.61 2029.15 1115.77 1926.47 942.736 2050.1C912.815 2071.47 871.139 2186.88 871.139 2186.88L873.322 2195.17C881.064 2170.83 887.165 2165.51 887.165 2165.51C887.165 2165.51 1135.08 2490.37 1767.7 2456.17C2277.77 2428.6 2493.17 2139.93 2557.52 2029.09C2551.52 1973.12 2534.07 1898.88 2483.67 1896.22C2402.45 1891.95 2066.9 1864.16 1964.32 1808.59C1922.64 1786.02 1855.22 1777.56 1785.69 1777.75V1777.75Z" fill="#F7EDE9"/>
<path opacity="0.993" d="M1961.57 1985.15C1963.63 1987.09 1933.18 2033.96 1856.33 2063.83V2063.83C1839.85 2070.21 1822.61 2075.45 1804.72 2079.62C1766.98 2088.41 1729.87 2091.61 1698.49 2092.27C1674.97 2092.78 1655.68 2091.74 1642.07 2090.22C1635 2089.43 1629.58 2088.53 1625.85 2087.62C1623.95 2087.15 1622.51 2086.68 1621.54 2086.24C1621.04 2086.01 1620.67 2085.78 1620.42 2085.57C1620.15 2085.33 1620.02 2085.11 1620.03 2084.89C1620.05 2084.67 1620.2 2084.47 1620.49 2084.27C1620.76 2084.08 1621.15 2083.91 1621.67 2083.73C1622.67 2083.4 1624.14 2083.08 1626.06 2082.78C1629.77 2082.2 1635.24 2081.66 1642.21 2081.09C1655.61 2079.99 1674.75 2078.77 1697.58 2076.59C1728.3 2073.63 1764.16 2069.35 1800.38 2060.94C1817.58 2056.95 1834.05 2052.22 1849.95 2046.59C1918.62 2022.43 1958.6 1982.34 1961.57 1985.15V1985.15Z" fill="black"/>
<path d="M1934.67 1959.27C1935.77 1956.91 1954.37 1960.97 1967.17 1981.57C1979.98 2002.16 1975.41 2020.64 1972.82 2020.59C1970 2020.53 1967.84 2002.79 1958.14 1987.19C1948.44 1971.58 1933.47 1961.83 1934.67 1959.27V1959.27Z" fill="black"/>
<path d="M1263.38 1639.08C1266.22 1756.73 1183.41 1844.17 1098.66 1852.16C951.388 1866.04 891.515 1767.76 901.936 1653.12C912.303 1539.07 1011.03 1433.96 1109.23 1430.52C1193.46 1427.58 1260.47 1518.48 1263.38 1639.08Z" fill="black"/>
<g filter="url(#filter0_f_35_87)">
<path d="M1109.1 1636.63C1138.87 1598.95 1137.08 1543.74 1105.1 1513.3C1073.11 1482.87 1023.06 1488.73 993.29 1526.41C963.522 1564.08 965.316 1619.29 997.297 1649.73C1029.28 1680.17 1079.34 1674.3 1109.1 1636.63Z" fill="#BEBEBE"/>
</g>
<path d="M1109.1 1636.63C1138.87 1598.95 1137.08 1543.74 1105.1 1513.3C1073.11 1482.87 1023.06 1488.73 993.29 1526.41C963.522 1564.08 965.316 1619.29 997.297 1649.73C1029.28 1680.17 1079.34 1674.3 1109.1 1636.63Z" fill="#DEDEDE"/>
<g filter="url(#filter1_f_35_87)">
<path d="M1220.38 1725.27C1218.97 1703.96 1200.95 1686.44 1180.12 1686.13C1159.3 1685.82 1143.56 1702.84 1144.97 1724.14C1146.38 1745.45 1164.4 1762.98 1185.22 1763.29C1206.05 1763.6 1221.79 1746.58 1220.38 1725.27Z" fill="#BEBEBE"/>
</g>
<path d="M1217.34 1725.22C1216.05 1705.95 1199.48 1690.11 1180.33 1689.83C1161.18 1689.54 1146.71 1704.93 1148.01 1724.2C1149.3 1743.46 1165.87 1759.31 1185.02 1759.59C1204.17 1759.87 1218.64 1744.48 1217.34 1725.22Z" fill="#DEDEDE"/>
<path d="M2111.74 1483.68C2108.91 1601.33 2191.71 1688.77 2276.47 1696.76C2423.74 1710.64 2483.61 1612.36 2473.19 1497.72C2462.82 1383.67 2364.09 1278.56 2265.89 1275.13C2181.66 1272.18 2114.65 1363.09 2111.74 1483.68Z" fill="black"/>
<g filter="url(#filter2_f_35_87)">
<path d="M2266.02 1481.23C2236.25 1443.55 2238.05 1388.34 2270.03 1357.9C2302.01 1327.47 2352.07 1333.34 2381.83 1371.01C2411.6 1408.68 2409.81 1463.9 2377.83 1494.33C2345.85 1524.77 2295.79 1518.9 2266.02 1481.23Z" fill="#BEBEBE"/>
</g>
<path d="M2266.02 1481.23C2236.25 1443.55 2238.05 1388.34 2270.03 1357.9C2302.01 1327.47 2352.07 1333.34 2381.83 1371.01C2411.6 1408.68 2409.81 1463.9 2377.83 1494.33C2345.85 1524.77 2295.79 1518.9 2266.02 1481.23Z" fill="#DEDEDE"/>
<g filter="url(#filter3_f_35_87)">
<path d="M2154.74 1569.87C2156.15 1548.56 2174.17 1531.04 2195 1530.73C2215.83 1530.42 2231.57 1547.44 2230.16 1568.75C2228.75 1590.05 2210.72 1607.58 2189.9 1607.89C2169.07 1608.2 2153.33 1591.18 2154.74 1569.87Z" fill="#BEBEBE"/>
</g>
<path d="M2157.78 1569.82C2159.08 1550.55 2175.65 1534.71 2194.79 1534.43C2213.94 1534.15 2228.41 1549.53 2227.12 1568.8C2225.82 1588.06 2209.25 1603.91 2190.1 1604.19C2170.96 1604.47 2156.49 1589.08 2157.78 1569.82Z" fill="#DEDEDE"/>
<path d="M1662.35 1829.49C1645.46 1787.85 1850.52 1752.32 1854.28 1791.71C1858.37 1834.59 1804.4 1912.61 1777.2 1917.14C1750 1921.68 1680.39 1873.96 1662.35 1829.49V1829.49Z" fill="black"/>
<g opacity="0.8">
<g filter="url(#filter4_f_35_87)">
<path d="M1786.9 1793.91C1788.82 1786.22 1801.15 1781.29 1814.45 1782.91C1827.74 1784.53 1836.97 1792.08 1835.05 1799.77C1833.13 1807.46 1820.8 1812.39 1807.51 1810.77C1794.21 1809.15 1784.99 1801.61 1786.9 1793.91Z" fill="#BEBEBE"/>
</g>
<path d="M1788.84 1794.15C1790.59 1787.19 1801.91 1782.76 1814.14 1784.25C1826.36 1785.74 1834.85 1792.58 1833.11 1799.54C1831.36 1806.49 1820.04 1810.92 1807.81 1809.43C1795.59 1807.94 1787.1 1801.1 1788.84 1794.15Z" fill="#DEDEDE"/>
</g>
<path d="M2741.41 163.935C2735.43 163.645 2728.58 173.489 2718.76 194.994C2673.88 293.306 2462.29 385.213 2381.08 483.525C2299.87 581.837 2284.9 697.243 2374.67 851.122C2421.52 931.442 2482.9 980.277 2532.12 1009.56C2600.59 964.628 2850.77 785.686 2843.33 573.68C2841.17 511.985 2836.51 456.206 2831.39 405.253C2769.94 259.579 2759.63 164.818 2741.41 163.935V163.935Z" fill="url(#paint1_radial_35_87)"/>
<path d="M181.665 291.229C163.173 289.362 174.995 366.697 171.814 505.727C180.547 595.591 187.448 701.123 207.526 827.611C246.789 1074.97 573.781 1224.58 626.915 1247.37C670.403 1199.53 739.457 1106.83 766.704 960.629C806.777 745.606 754.64 615.556 641.83 530.754C529.02 445.951 288.043 418.155 211.526 319.567C197.179 301.081 187.769 291.846 181.665 291.229V291.229Z" fill="url(#paint2_radial_35_87)"/>
<path d="M2798.72 15.367C2698.4 16.4866 2391.12 184.492 2306.37 277.783C2219.35 373.579 2165.64 521.033 2148.97 571.513C2050.88 508.595 1907.17 507.155 1907.17 507.155C1907.17 507.155 1991.8 479.955 2082.48 455.775C1925.31 401.371 1722.8 449.727 1722.8 449.727C1722.8 449.727 1813.47 349.989 1895.08 277.45C1768.13 310.697 1620.04 383.233 1529.36 437.638C1553.54 401.368 1595.86 362.076 1680.49 244.199C1447.75 301.626 1160.61 528.318 1060.87 612.947C1072.96 537.385 1103.19 489.025 1139.46 404.395C1002.07 499.513 937.628 607.905 907.715 681.853C876.542 624.261 794.11 483.695 690.543 400.173C558.035 293.312 45.0983 126.611 113.489 254.844C181.88 383.077 164.781 558.322 207.526 827.611C250.27 1096.9 634.972 1250.78 634.972 1250.78C637.192 1249.99 639.395 1249.09 641.592 1248.17C619.011 1319.85 598.866 1424.54 583.679 1597.01C511.014 1706.01 478.959 1727.38 470.41 1767.99C540.938 1733.8 575.13 1708.15 626.423 1695.32C581.542 1770.13 579.407 1872.71 598.642 2035.14C634.975 1943.24 632.833 1945.37 671.303 1917.59C681.988 2026.59 705.503 2086.43 733.287 2120.63C743.973 2060.78 776.031 2037.28 776.031 2037.28C776.031 2037.28 797.404 2167.65 859.383 2257.41C872.207 2178.34 887.165 2165.51 887.165 2165.51C887.165 2165.51 1135.08 2490.37 1767.7 2456.17C2400.31 2421.98 2579.84 1985.98 2579.84 1985.98C2579.84 1985.98 2592.66 2041.56 2592.66 2062.93C2639.68 1994.54 2669.6 1853.48 2669.6 1853.48C2669.6 1853.48 2703.8 1934.69 2695.25 1985.98C2733.72 1921.86 2742.26 1763.72 2725.17 1691.05C2785.01 1755.17 2836.31 1776.54 2836.31 1776.54C2836.31 1776.54 2703.79 1545.72 2699.52 1507.25C2695.25 1468.78 2793.56 1562.81 2793.56 1562.81C2793.56 1562.81 2731.97 1460.26 2716.62 1432.45C2659.41 1328.78 2605.12 1139.43 2528.87 1011.72C2592.86 970.123 2850.88 788.773 2843.33 573.68C2834.72 328.21 2786.46 176.103 2827.99 51.0384C2836.43 25.6346 2824.3 15.0816 2798.72 15.367V15.367Z" stroke="black" stroke-width="30.7203" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1166.07 3775.9C1091.27 3651.94 1054.81 3567.11 1049.59 3511.42C1044.78 3460.13 1038.37 3398.15 1075.24 3428.61C1085.93 3383.72 1083.79 3358.08 1111.57 3370.9C1139.36 3383.72 1158.59 3411.51 1158.59 3411.51C1158.59 3411.51 1154.32 3347.39 1182.1 3368.76C1233.19 3399.52 1324.44 3688.35 1313.81 3770.16C1302.14 3860.06 1231.96 3879.6 1166.07 3775.9V3775.9Z" fill="url(#paint3_linear_35_87)" stroke="black" stroke-width="23.0402"/>
<path d="M2556.53 3668.47C2609.53 3533.74 2631.28 3444 2627.1 3388.22C2623.26 3336.85 2619.21 3274.67 2587.96 3310.87C2569.91 3268.41 2567.73 3242.76 2542.48 3260.06C2517.23 3277.35 2502.92 3307.96 2502.92 3307.96C2502.92 3307.96 2496.4 3244.03 2472.59 3269.75C2427.37 3308.62 2385.74 3608.66 2409.91 3687.53C2436.47 3774.21 2508.93 3781.73 2556.53 3668.47V3668.47Z" fill="url(#paint4_linear_35_87)" stroke="black" stroke-width="23.0402"/>
<path d="M1562.1 3063.57C1524.1 3059.67 1486.65 3074.05 1455.28 3094.55C1440.72 3104.25 1426.76 3115.03 1414.48 3127.52C1438.67 3156.34 1465.72 3183.02 1496.29 3205.05C1502.66 3208.89 1510.79 3214.39 1518.35 3210.58C1525.16 3205.3 1523.61 3195.64 1524.22 3188.07C1530.46 3197.06 1541.84 3202.2 1552.66 3201.65C1562.44 3199.92 1561.73 3187.56 1560.44 3180.1C1559.69 3173.07 1556.6 3166.42 1556.17 3159.41C1562.43 3160.56 1568.02 3169.72 1575.98 3167.49C1584.76 3164.88 1580.38 3153.58 1580.28 3146.94C1575.9 3118.83 1568.65 3091.24 1562.1 3063.57V3063.57Z" fill="url(#paint5_linear_35_87)"/>
<path d="M1517.64 2663.48C1517.64 2663.48 1517.64 2663.48 1517.67 2663.48C1520.97 2664.02 1526.42 2740.85 1532.65 2874.63C1534.91 2900.48 1540.5 2925.66 1547.39 2954.9C1554.58 2985.32 1562.49 3014.75 1571.38 3047.9C1578.08 3072.85 1585.59 3099.84 1591.55 3126.74C1593.42 3135.07 1595.31 3143.95 1596.25 3153.37C1596.57 3156.1 1596.76 3159.46 1596.39 3163.39C1597.28 3165.14 1595.58 3169.34 1590.43 3175.07V3175.07C1580.39 3180.9 1569.4 3179.92 1561.47 3173.37V3173.37C1559.01 3171.29 1557.89 3169.89 1557.57 3169.88C1559.13 3171.17 1561.37 3170.83 1565.41 3169.42H1565.41C1567.07 3167.09 1567.82 3166.38 1567.48 3165.67C1567.51 3167 1568.74 3169.46 1569.97 3173.33V3173.33C1574.02 3183.37 1572.31 3196.44 1561.97 3206.72V3206.72C1550.18 3212.97 1538.44 3211.07 1531.04 3204.36V3204.36C1528.22 3202.07 1526.49 3200.26 1525.34 3199.66H1525.34C1524.94 3200.04 1525.8 3199.59 1528.79 3199.47V3199.47C1531.49 3196.56 1532.68 3197.17 1530.81 3199.19C1530.12 3201.61 1529.26 3205.67 1526.9 3210.83C1526.22 3212.23 1525.38 3213.53 1524.39 3214.71C1520.87 3218.89 1515.47 3221.56 1508.72 3222C1497.43 3220.63 1489.74 3215.57 1485.08 3211.33C1426.38 3166.73 1381.27 3109.36 1347.87 3050.36C1312.57 2987.93 1289.98 2924.54 1275.1 2870.05C1253.47 2738.94 1243.01 2664.06 1246.21 2663.48C1249.41 2662.9 1265.97 2736.63 1291.73 2866.13V2866.13C1308.09 2918.97 1331.53 2980.15 1366.49 3039.63C1399.64 3096.09 1443.21 3150.79 1498.89 3192.79C1504.65 3196.18 1508.06 3199.72 1509.67 3198.89C1507.79 3199.52 1506.9 3199.67 1506.47 3200.08C1506.35 3200.2 1506.26 3200.33 1506.2 3200.5C1506.94 3199.44 1507.34 3196.53 1508.73 3192.3C1508.67 3188.65 1512.14 3183.04 1520.37 3177.93V3177.93C1527.93 3176.03 1533.99 3177.15 1537.72 3180.15H1537.72C1541.56 3182.71 1544.07 3185.1 1545.79 3186.59V3186.59C1547.9 3187.76 1550.06 3189.47 1548.1 3188.32H1548.1C1549.89 3189.46 1549.02 3184.96 1548.23 3180.69C1547.29 3178.15 1545.76 3174.34 1544.81 3168.99C1543.68 3162.9 1546.08 3156.29 1552.14 3150.81V3150.81C1559.11 3147.32 1565.42 3147.49 1569.99 3150.72V3150.72C1573.53 3153.21 1575.53 3155.29 1576.51 3156.24C1576.77 3156.9 1578.18 3157.59 1576.82 3156.92C1573.94 3160.94 1572.68 3161.93 1573.89 3160.86C1574.1 3159.67 1573.92 3157.84 1573.76 3155.45C1573.11 3147.76 1571.43 3139.75 1569.7 3131.46C1564.19 3105.34 1557.05 3078.42 1550.71 3053.27C1542.36 3020.15 1534.87 2989.98 1528.34 2959.17C1522.03 2929.53 1516.99 2902.93 1515.44 2875.5C1513.55 2741.07 1514.34 2664.11 1517.6 2663.48C1517.63 2663.47 1517.63 2663.48 1517.63 2663.48L1517.64 2663.48Z" fill="black"/>
<path d="M1565.2 3063.14C1564.91 3065.73 1555.96 3066.2 1541.37 3069.17C1525.48 3072.41 1508.74 3077.63 1488.07 3087.53C1480.4 3091.19 1473 3095.12 1465.92 3099.1C1438.07 3119.19 1417.43 3130.85 1415.06 3127.79C1412.69 3124.73 1429.23 3107.59 1456.25 3085.16C1463.45 3080.3 1471.31 3075.72 1479.69 3071.69C1501.77 3061.09 1522.77 3056.66 1540.11 3056.79C1556.81 3056.91 1565.43 3061.05 1565.2 3063.14V3063.14Z" fill="black"/>
<path d="M2047.1 3038.3C2015.08 3027.25 1989.62 3002.03 1971.75 2973.82C1956.72 2949.74 1945.57 2923.17 1939.25 2895.65C1918.3 2922.05 1897.04 2948.41 1879.78 2977.47C1875.73 2983.29 1871.18 2994.54 1881.77 2996.72C1891.18 2999.42 1900.88 2996.43 1910.31 2997.32C1906.93 3001.57 1900.26 3003.74 1896.41 3008.26C1889.29 3014.81 1882.1 3026.28 1888.65 3035.81C1897.4 3046.28 1912.16 3048.19 1924.86 3048.06C1933.1 3048.55 1920.45 3056.52 1921.15 3061.12C1917.09 3068.66 1918.61 3081.73 1929.47 3082C1942.49 3082.82 1954.68 3077.29 1966.93 3073.96C1994.86 3064.92 2021.6 3052.63 2047.1 3038.3V3038.3Z" fill="url(#paint6_linear_35_87)"/>
<path d="M2136.43 2590.6C2136.43 2590.6 2136.43 2590.6 2136.46 2590.61C2138.25 2591.35 2133.68 2615.94 2123.09 2662.55C2113.97 2692.52 2095.65 2744.12 2055.96 2781.07C2017.94 2816.07 1984.67 2855.47 1945.22 2905.25C1930.26 2924.17 1915.03 2943.97 1901.15 2964.57C1896.69 2971.21 1892.4 2977.73 1888.9 2984.37C1887.31 2989.65 1884.88 2991 1886.02 2988.64C1885.41 2987.11 1884.89 2986.63 1883.18 2985.7C1883.88 2986.16 1886.21 2985.54 1889.98 2985.28C1893.03 2984.76 1897.54 2984.08 1903.47 2984.6C1910.57 2985.44 1916.49 2990.55 1919.26 2999.16C1919.62 3007.51 1917.02 3013.57 1913.15 3016.69C1909.61 3020.25 1906.45 3022.49 1904.48 3024.27C1901.57 3026.15 1900.4 3031.06 1902.55 3033.46C1899.98 3033.96 1903.61 3032.95 1908.88 3033.72C1909.75 3033.84 1910.66 3034.02 1911.6 3034.26C1914.77 3034.15 1919.24 3034.1 1924.84 3035.26C1930.94 3036.4 1936.38 3041.11 1939.46 3048.98C1940.49 3058.56 1938.28 3064.62 1936.14 3066.9C1934.09 3071.33 1932.13 3074.45 1931.58 3076.77C1932.68 3076.62 1931.02 3075.79 1927.76 3071.11C1927.93 3072.71 1932.65 3070.31 1940.05 3069.84C2060.52 3040.57 2153.12 2954.06 2211.42 2887.43C2275.45 2796.57 2313.04 2746.24 2315.58 2747.99C2318.13 2749.74 2285.39 2803.44 2224.48 2897.39C2167.72 2967.95 2073.72 3059.09 1945.44 3091.98C1939.78 3094.05 1930.07 3095.88 1918.09 3091.52C1909.3 3084.14 1906.86 3075.86 1909.67 3069.89C1911.51 3064.11 1913.88 3059.89 1915.46 3056.71C1916.37 3052.55 1918.09 3051.64 1917.32 3054.22C1919 3056.69 1919.32 3057.64 1919.99 3057.49C1918.66 3056.99 1915.95 3057.25 1912.03 3057.13C1910.91 3057.36 1909.69 3057.5 1908.41 3057.54C1900.62 3057.76 1890.34 3054.11 1882.13 3043.31C1876.61 3030.37 1879.45 3016.48 1889 3007.16C1892.37 3004.14 1895.17 3002.25 1896.61 3000.66C1896.82 2999.58 1897.47 2999.97 1896.83 3002.48C1898.68 3005.68 1899.46 3006.96 1901.14 3007.12C1899.94 3006.74 1897.3 3007.4 1893.29 3007.76C1889.92 3008.39 1884.94 3008.85 1878.59 3007.7C1870.78 3005.36 1865.44 2999.58 1863.79 2991.91C1863.56 2981.4 1866.8 2975.32 1869.05 2973.44C1873.27 2965.66 1878.02 2958.53 1882.67 2951.79C1897.28 2930.57 1913.07 2910.54 1928.48 2891.68C1968.69 2842.39 2004.17 2802.5 2044.22 2767.98C2081.07 2736.75 2101.47 2687.54 2112.56 2659.14C2126.21 2614.17 2134.44 2590.46 2136.39 2590.6C2136.42 2590.61 2136.42 2590.61 2136.42 2590.61L2136.43 2590.6Z" fill="black"/>
<path d="M2038.8 3040.8C2037.48 3043.01 2027.68 3040.96 2012.89 3031.42C1997.72 3021.64 1980.73 3005.97 1966.44 2984.73C1959.64 2974.61 1954.07 2964.16 1949.75 2953.83C1940.62 2921.19 1937.42 2899.28 1941.52 2898.01C1945.61 2896.74 1956.14 2916.23 1967.96 2946.88C1972.82 2955.89 1978.21 2964.93 1984.35 2974.02C2009.03 3010.53 2041.64 3036.08 2038.8 3040.8V3040.8Z" fill="black"/>
<path d="M1643 3039.5V3096H1697.5V3039.5C1697.5 3039.5 1697.5 2974.5 1764.5 2974.5C1831.5 2974.5 1823.5 3039.5 1823.5 3039.5V3096H1874V3039.5C1869.33 3003.67 1850 2922 1764.5 2922C1678.44 2922 1643.67 3001 1643 3039.5Z" fill="#C4C4C4" stroke="black" stroke-width="14"/>
<rect x="1604.5" y="3089.5" width="333" height="269" fill="#F7CF45" stroke="black" stroke-width="15"/>
</g>
<defs>
<filter id="filter0_f_35_87" x="951.462" y="1473.2" width="199.468" height="216.64" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="10.3119" result="effect1_foregroundBlur_35_87"/>
</filter>
<filter id="filter1_f_35_87" x="1135.42" y="1676.67" width="94.5019" height="96.079" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="4.72782" result="effect1_foregroundBlur_35_87"/>
</filter>
<filter id="filter2_f_35_87" x="2224.19" y="1317.8" width="199.468" height="216.64" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="10.3119" result="effect1_foregroundBlur_35_87"/>
</filter>
<filter id="filter3_f_35_87" x="2145.2" y="1521.27" width="94.5019" height="96.079" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="4.72782" result="effect1_foregroundBlur_35_87"/>
</filter>
<filter id="filter4_f_35_87" x="1781.61" y="1777.56" width="58.7359" height="38.5579" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2.52106" result="effect1_foregroundBlur_35_87"/>
</filter>
<radialGradient id="paint0_radial_35_87" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(1760.99 1829.81) rotate(-0.182296) scale(306.481 287.176)">
<stop stop-color="#CC3100"/>
<stop offset="1" stop-color="#DE6B00"/>
</radialGradient>
<radialGradient id="paint1_radial_35_87" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(2373.33 907.327) rotate(63.7909) scale(608.272 241.85)">
<stop stop-color="#FF927A"/>
<stop offset="1" stop-color="#F7C199"/>
</radialGradient>
<radialGradient id="paint2_radial_35_87" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(785.008 1066.98) rotate(-157.738) scale(359.552 439.05)">
<stop stop-color="#FF927A"/>
<stop offset="1" stop-color="#F7C199"/>
</radialGradient>
<linearGradient id="paint3_linear_35_87" x1="1257.34" y1="3813.75" x2="1136.44" y2="3481.28" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
<linearGradient id="paint4_linear_35_87" x1="2481.02" y1="3700.43" x2="2528.01" y2="3388.4" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
<linearGradient id="paint5_linear_35_87" x1="1465.89" y1="3071.73" x2="1527.85" y2="3160.89" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
<linearGradient id="paint6_linear_35_87" x1="1978.5" y1="2970.47" x2="1922.1" y2="3021.61" gradientUnits="userSpaceOnUse">
<stop stop-color="#F79041"/>
<stop offset="1" stop-color="#F7C199"/>
</linearGradient>
<clipPath id="clip0_35_87">
<rect width="2858.86" height="3857.48" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 28 KiB

View file

@ -1,6 +1,21 @@
<!DOCTYPE html> <!DOCTYPE html>
<html dir="ltr"> <html dir="ltr">
<head> <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 charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Pragma" content="no-cache" /> <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/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="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> <style>
@font-face { @font-face {

View file

@ -1,44 +1,69 @@
// @flow // @flow
/*
Removed Watchman (internal view tracking) code.
This file may eventually implement cantina
Refer to 0cc0e213a5c5bf9e2a76316df5d9da4b250a13c3 for initial integration commit
refer to ___ for removal commit.
*/
import { Lbryio } from 'lbryinc'; import { Lbryio } from 'lbryinc';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import MatomoTracker from '@datapunt/matomo-tracker-js'; import * as RENDER_MODES from 'constants/file_render_modes';
import { history } from './store'; import { SDK_API_PATH } from 'config';
import Native from 'native';
import ElectronCookies from '@meetfranz/electron-cookies'; // --- GA ---
import { generateInitialUrl } from 'util/url'; // - Events: 500 max (cannot be deleted).
import { MATOMO_ID, MATOMO_URL } from 'config'; // - 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;
// async function getUserBandwidth() {
// userDownloadBandwidthInBitsPerSecond = await getConnectionSpeed();
// }
// get user bandwidth every minute, starting after an initial one minute wait
// setInterval(getUserBandwidth, 1000 * 60);
const isProduction = process.env.NODE_ENV === 'production'; const isProduction = process.env.NODE_ENV === 'production';
const devInternalApis = process.env.LBRY_API_URL && process.env.LBRY_API_URL.includes('dev'); const devInternalApis = process.env.LBRY_API_URL && process.env.LBRY_API_URL.includes('dev');
export const SHARE_INTERNAL = 'shareInternal'; export const SHARE_INTERNAL = 'shareInternal';
const SHARE_THIRD_PARTY = 'shareThirdParty';
if (isProduction) { const WATCHMAN_BACKEND_ENDPOINT = 'https://watchman.na-backend.odysee.com/reports/playback';
ElectronCookies.enable({ const SEND_DATA_TO_WATCHMAN_INTERVAL = 10; // in seconds
origin: 'https://lbry.tv',
});
}
type Analytics = { type Analytics = {
appStartTime: number,
eventStartTime: any,
error: (string) => Promise<any>, error: (string) => Promise<any>,
sentryError: ({} | string, {}) => Promise<any>, sentryError: ({} | string, {}) => Promise<any>,
pageView: (string, ?string) => void,
setUser: (Object) => void, setUser: (Object) => void,
toggleInternal: (boolean, ?boolean) => void, toggleInternal: (boolean, ?boolean) => void,
apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>, apiLogView: (string, string, string, ?number, ?() => void) => Promise<any>,
apiLogPublish: (ChannelClaim | StreamClaim) => void, apiLogPublish: (ChannelClaim | StreamClaim) => void,
apiSyncTags: ({}) => void, apiSyncTags: ({}) => void,
tagFollowEvent: (string, boolean, ?string) => void, tagFollowEvent: (string, boolean, ?string) => void,
playerLoadedEvent: (?boolean) => void, playerLoadedEvent: (string, ?boolean) => void,
playerStartedEvent: (?boolean) => void, playerVideoStartedEvent: (?boolean) => void,
videoStartEvent: (string, number, string, number, string, any, number) => void, videoStartEvent: (string, number, string, number, string, any, number) => void,
videoIsPlaying: (boolean, any) => void, videoIsPlaying: (boolean, any) => void,
videoBufferEvent: ( videoBufferEvent: (
@ -53,13 +78,17 @@ type Analytics = {
readyState: number, readyState: number,
} }
) => Promise<any>, ) => Promise<any>,
adsFetchedEvent: () => void,
emailProvidedEvent: () => void, emailProvidedEvent: () => void,
emailVerifiedEvent: () => void, emailVerifiedEvent: () => void,
rewardEligibleEvent: () => 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, purchaseEvent: (number) => void,
readyEvent: (number) => void,
openUrlEvent: (string) => void, openUrlEvent: (string) => void,
reportEvent: (string, any) => void,
}; };
type LogPublishParams = { type LogPublishParams = {
@ -69,13 +98,145 @@ type LogPublishParams = {
channel_claim_id?: string, channel_claim_id?: string,
}; };
let internalAnalyticsEnabled: boolean = false; let internalAnalyticsEnabled: boolean = IS_WEB || false;
if (window.localStorage.getItem(SHARE_INTERNAL) === 'true') internalAnalyticsEnabled = true; // let thirdPartyAnalyticsEnabled: boolean = IS_WEB || false;
const isGaAllowed = internalAnalyticsEnabled && isProduction;
/**
* Determine the mobile device type viewing the data
* This function returns one of 'and' (Android), 'ios', or 'web'.
*
* @returns {String}
*/
function getDeviceType() {
// We may not care what the device is if it's in a web browser. Commenting out for now.
// if (!IS_WEB) {
// return 'elt';
// }
// const userAgent = navigator.userAgent || navigator.vendor || window.opera;
//
// if (/android/i.test(userAgent)) {
// return 'adr';
// }
//
// // iOS detection from: http://stackoverflow.com/a/9039885/177710
// if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
// return 'ios';
// }
// default as web, this can be optimized
if (!IS_WEB) {
return 'dsk';
}
return 'web';
}
// variables initialized for watchman
let amountOfBufferEvents = 0;
let amountOfBufferTimeInMS = 0;
let videoType, userId, claimUrl, playerPoweredBy, videoPlayer, bitrateAsBitsPerSecond;
let lastSentTime;
// calculate data for backend, send them, and reset buffer data for next interval
async function sendAndResetWatchmanData() {
if (!userId) {
return 'Can only be used with a user id';
}
if (!videoPlayer) {
return 'Video player not initialized';
}
let timeSinceLastIntervalSend = new Date() - lastSentTime;
lastSentTime = new Date();
let protocol;
if (videoType === 'application/x-mpegURL') {
protocol = 'hls';
// get bandwidth if it exists from the texttrack (so it's accurate if user changes quality)
// $FlowFixMe
bitrateAsBitsPerSecond = videoPlayer.textTracks?.().tracks_[0]?.activeCues[0]?.value?.bandwidth;
} else {
protocol = 'stb';
}
// current position in video in MS
const positionInVideo = Math.round(videoPlayer.currentTime()) * 1000;
// get the duration marking the time in the video for relative position calculation
const totalDurationInSeconds = Math.round(videoPlayer.duration());
// build object for watchman backend
const objectToSend = {
rebuf_count: amountOfBufferEvents,
rebuf_duration: amountOfBufferTimeInMS,
url: claimUrl.replace('lbry://', ''),
device: getDeviceType(),
duration: timeSinceLastIntervalSend,
protocol,
player: playerPoweredBy,
user_id: userId.toString(),
position: Math.round(positionInVideo),
rel_position: Math.round((positionInVideo / (totalDurationInSeconds * 1000)) * 100),
bitrate: bitrateAsBitsPerSecond,
bandwidth: undefined,
// ...(userDownloadBandwidthInBitsPerSecond && {bandwidth: userDownloadBandwidthInBitsPerSecond}), // add bandwidth if populated
};
// post to watchman
await sendWatchmanData(objectToSend);
// reset buffer data
amountOfBufferEvents = 0;
amountOfBufferTimeInMS = 0;
}
let watchmanInterval;
// clear watchman interval and mark it as null (when video paused)
function stopWatchmanInterval() {
clearInterval(watchmanInterval);
watchmanInterval = null;
}
// creates the setInterval that will run send to watchman on recurring basis
function startWatchmanIntervalIfNotRunning() {
if (!watchmanInterval) {
// instantiate the first time to calculate duration from
lastSentTime = new Date();
// only set an interval if analytics are enabled and is prod
if (isProduction && IS_WEB) {
watchmanInterval = setInterval(sendAndResetWatchmanData, 1000 * SEND_DATA_TO_WATCHMAN_INTERVAL);
}
}
}
// post data to the backend
async function sendWatchmanData(body) {
try {
const response = await fetch(WATCHMAN_BACKEND_ENDPOINT, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
return response;
} catch (err) {}
}
const analytics: Analytics = { const analytics: Analytics = {
appStartTime: 0,
eventStartTime: {},
// receive buffer events from tracking plugin and save buffer amounts and times for backend call // receive buffer events from tracking plugin and save buffer amounts and times for backend call
videoBufferEvent: async (claim, data) => { videoBufferEvent: async (claim, data) => {
// stub amountOfBufferEvents = amountOfBufferEvents + 1;
amountOfBufferTimeInMS = amountOfBufferTimeInMS + data.bufferDuration;
},
onDispose: () => {
stopWatchmanInterval();
}, },
/** /**
* Is told whether video is being started or paused, and adjusts interval accordingly * Is told whether video is being started or paused, and adjusts interval accordingly
@ -83,11 +244,40 @@ const analytics: Analytics = {
* @param {object} passedPlayer - VideoJS Player object * @param {object} passedPlayer - VideoJS Player object
*/ */
videoIsPlaying: (isPlaying, passedPlayer) => { videoIsPlaying: (isPlaying, passedPlayer) => {
// stub let playerIsSeeking = false;
// have to use this because videojs pauses/unpauses during seek
// sometimes the seeking function isn't populated yet so check for it as well
if (passedPlayer && passedPlayer.seeking) {
playerIsSeeking = passedPlayer.seeking();
}
// if being paused, and not seeking, send existing data and stop interval
if (!isPlaying && !playerIsSeeking) {
sendAndResetWatchmanData();
stopWatchmanInterval();
// if being told to pause, and seeking, send and restart interval
} else if (!isPlaying && playerIsSeeking) {
sendAndResetWatchmanData();
stopWatchmanInterval();
startWatchmanIntervalIfNotRunning();
// is being told to play, and seeking, don't do anything,
// assume it's been started already from pause
} else if (isPlaying && playerIsSeeking) {
// start but not a seek, assuming a start from paused content
} else if (isPlaying && !playerIsSeeking) {
startWatchmanIntervalIfNotRunning();
}
}, },
videoStartEvent: (claimId, timeToStartVideo, poweredBy, passedUserId, canonicalUrl, passedPlayer, videoBitrate) => { videoStartEvent: (claimId, timeToStartVideo, poweredBy, passedUserId, canonicalUrl, passedPlayer, videoBitrate) => {
// sendPromMetric('time_to_start', duration); // populate values for watchman when video starts
sendMatomoEvent('Media', 'TimeToStart', claimId, timeToStartVideo); userId = passedUserId;
claimUrl = canonicalUrl;
playerPoweredBy = poweredBy;
videoType = passedPlayer.currentSource().type;
videoPlayer = passedPlayer;
bitrateAsBitsPerSecond = videoBitrate;
sendPromMetric('time_to_start', timeToStartVideo);
}, },
error: (message) => { error: (message) => {
return new Promise((resolve) => { return new Promise((resolve) => {
@ -113,36 +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) => { setUser: (userId) => {
if (internalAnalyticsEnabled && userId) { if (isGaAllowed && userId && window.gtag) {
window._paq.push(['setUserId', String(userId)]); window.gtag('set', { user_id: userId });
Native.getAppVersionInfo().then(({ localVersion }) => {
sendMatomoEvent('Version', 'Desktop-Version', localVersion);
});
} }
}, },
toggleInternal: (enabled: boolean): void => { toggleInternal: (enabled: boolean): void => {
internalAnalyticsEnabled = enabled; // Always collect analytics on Odysee for now.
window.localStorage.setItem(SHARE_INTERNAL, enabled);
}, },
toggleThirdParty: (enabled: boolean): void => { toggleThirdParty: (enabled: boolean): void => {
window.localStorage.setItem(SHARE_THIRD_PARTY, enabled); // Always collect analytics on Odysee for now.
}, },
apiLogView: (uri, outpoint, claimId, timeToStart) => { apiLogView: (uri, outpoint, claimId, timeToStart) => {
@ -159,7 +330,8 @@ const analytics: Analytics = {
claim_id: claimId, claim_id: claimId,
}; };
if (timeToStart) { // lbry.tv streams from AWS so we don't care about the time to start
if (timeToStart && !IS_WEB) {
params.time_to_start = timeToStart; params.time_to_start = timeToStart;
} }
@ -198,73 +370,128 @@ const analytics: Analytics = {
} }
}, },
adsFetchedEvent: () => { adsFetchedEvent: () => {
sendMatomoEvent('Media', 'AdsFetched'); sendGaEvent('ad_fetched');
}, },
adsReceivedEvent: (response) => { playerLoadedEvent: (renderMode, embedded) => {
sendMatomoEvent('Media', 'AdsReceived', JSON.stringify(response)); 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) => { playerVideoStartedEvent: (embedded) => {
sendMatomoEvent('Media', 'AdsError', JSON.stringify(response)); sendGaEvent('player', {
}, [GA_DIMENSIONS.ACTION]: 'started_video',
playerLoadedEvent: (embedded) => { [GA_DIMENSIONS.TYPE]: embedded ? 'embedded' : 'onsite',
sendMatomoEvent('Player', 'Loaded', embedded ? 'embedded' : 'onsite'); });
},
playerStartedEvent: (embedded) => {
sendMatomoEvent('Player', 'Started', embedded ? 'embedded' : 'onsite');
}, },
tagFollowEvent: (tag, following) => { tagFollowEvent: (tag, following) => {
sendMatomoEvent('Tag', following ? 'Tag-Follow' : 'Tag-Unfollow', tag); sendGaEvent('tags', {
}, [GA_DIMENSIONS.ACTION]: following ? 'follow' : 'unfollow',
channelBlockEvent: (uri, blocked, location) => { [GA_DIMENSIONS.VALUE]: tag,
sendMatomoEvent(blocked ? 'Channel-Hidden' : 'Channel-Unhidden', uri); });
}, },
emailProvidedEvent: () => { emailProvidedEvent: () => {
sendMatomoEvent('Engagement', 'Email-Provided'); sendGaEvent('engagement', {
[GA_DIMENSIONS.TYPE]: 'email_provided',
});
}, },
emailVerifiedEvent: () => { emailVerifiedEvent: () => {
sendMatomoEvent('Engagement', 'Email-Verified'); sendGaEvent('engagement', {
[GA_DIMENSIONS.TYPE]: 'email_verified',
});
}, },
rewardEligibleEvent: () => { rewardEligibleEvent: () => {
sendMatomoEvent('Engagement', 'Reward-Eligible'); sendGaEvent('engagement', {
[GA_DIMENSIONS.TYPE]: 'reward_eligible',
});
}, },
openUrlEvent: (url: string) => { openUrlEvent: (url: string) => {
sendMatomoEvent('Engagement', 'Open-Url', url); sendGaEvent('engagement', {
[GA_DIMENSIONS.TYPE]: 'open_url',
url,
});
}, },
trendingAlgorithmEvent: (trendingAlgorithm: string) => { trendingAlgorithmEvent: (trendingAlgorithm: string) => {
sendMatomoEvent('Engagement', 'Trending-Algorithm', trendingAlgorithm); sendGaEvent('engagement', {
[GA_DIMENSIONS.TYPE]: 'trending_algorithm',
trending_algorithm: trendingAlgorithm,
});
}, },
startupEvent: () => { initAppStartTime: (startTime: number) => {
sendMatomoEvent('Startup', 'Startup'); analytics.appStartTime = startTime;
}, },
readyEvent: (timeToReady: number) => { startupEvent: (time: number) => {
sendMatomoEvent('Startup', 'App-Ready', 'Time', timeToReady); 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) => { 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) { function sendGaEvent(event: string, params?: { [string]: string | number }) {
if (internalAnalyticsEnabled) { if (isGaAllowed && window.gtag) {
const event = { category, action, name, value }; window.gtag('event', event, params);
MatomoInstance.trackEvent(event);
} }
} }
const MatomoInstance = new MatomoTracker({ function sendPromMetric(name: string, value?: number) {
urlBase: MATOMO_URL, if (IS_WEB) {
siteId: MATOMO_ID, // optional, default value: `1` let url = new URL(SDK_API_PATH + '/metric/ui');
}); const params = { name: name, value: value ? value.toString() : '' };
url.search = new URLSearchParams(params).toString();
analytics.pageView(generateInitialUrl(window.location.hash)); return fetch(url, { method: 'post' }).catch(function (error) {});
}
// Listen for url changes and report }
// This will include search queries
history.listen((location) => { // Activate
const { pathname, search } = location; if (isGaAllowed && window.gtag) {
window.gtag('consent', 'update', {
const page = `${pathname}${search}`; ad_storage: 'granted',
analytics.pageView(page, search); analytics_storage: 'granted',
}); });
}
export default analytics; export default analytics;

View file

@ -1,8 +1,3 @@
import { connect } from 'react-redux';
import IframeReact from './view'; import IframeReact from './view';
const select = state => ({}); export default IframeReact;
const perform = () => ({});
export default connect(select, perform)(IframeReact);

View file

@ -1,6 +1,3 @@
import { connect } from 'react-redux';
import AbandonedChannelPreview from './view'; import AbandonedChannelPreview from './view';
const select = (state, props) => ({}); export default AbandonedChannelPreview;
export default connect(select)(AbandonedChannelPreview);

View file

@ -5,31 +5,21 @@ import { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user';
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectUnclaimedRewards } from 'redux/selectors/rewards'; import { selectUnclaimedRewards } from 'redux/selectors/rewards';
import { doFetchChannelListMine, doFetchCollectionListMine, doResolveUris } from 'redux/actions/claims'; import { doFetchChannelListMine, doFetchCollectionListMine, doResolveUris } from 'redux/actions/claims';
import { selectMyChannelUrls, selectMyChannelClaimIds } from 'redux/selectors/claims'; import { selectMyChannelUrls } from 'redux/selectors/claims';
import * as SETTINGS from 'constants/settings'; import * as SETTINGS from 'constants/settings';
import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { import { selectClientSetting, selectLanguage, selectLoadedLanguages, selectThemePath } from 'redux/selectors/settings';
makeSelectClientSetting,
selectLanguage,
selectLoadedLanguages,
selectThemePath,
} from 'redux/selectors/settings';
import { import {
selectIsUpgradeAvailable, selectIsUpgradeAvailable,
selectAutoUpdateDownloaded, selectAutoUpdateDownloaded,
selectModal, selectModal,
selectActiveChannelClaim, selectActiveChannelClaim,
selectIsUpdateModalDisplayed, selectIsReloadRequired,
} from 'redux/selectors/app'; } 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 { doSyncLoop } from 'redux/actions/sync';
import { import { doDownloadUpgradeRequested, doSignIn, doSetActiveChannel, doSetIncognito } from 'redux/actions/app';
doDownloadUpgradeRequested,
doSignIn,
doGetAndPopulatePreferences,
doSetActiveChannel,
doSetIncognito,
} from 'redux/actions/app';
import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/comments'; import { doFetchModBlockedList, doFetchCommentModAmIList } from 'redux/actions/comments';
import App from './view'; import App from './view';
@ -38,20 +28,20 @@ const select = (state) => ({
accessToken: selectAccessToken(state), accessToken: selectAccessToken(state),
theme: selectThemePath(state), theme: selectThemePath(state),
language: selectLanguage(state), language: selectLanguage(state),
syncEnabled: makeSelectClientSetting(SETTINGS.ENABLE_SYNC)(state), syncEnabled: selectClientSetting(state, SETTINGS.ENABLE_SYNC),
languages: selectLoadedLanguages(state), languages: selectLoadedLanguages(state),
autoUpdateDownloaded: selectAutoUpdateDownloaded(state), autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state), isUpgradeAvailable: selectIsUpgradeAvailable(state),
isReloadRequired: selectIsReloadRequired(state),
syncError: selectGetSyncErrorMessage(state), syncError: selectGetSyncErrorMessage(state),
uploadCount: selectUploadCount(state),
rewards: selectUnclaimedRewards(state), rewards: selectUnclaimedRewards(state),
isAuthenticated: selectUserVerifiedEmail(state), isAuthenticated: selectUserVerifiedEmail(state),
currentModal: selectModal(state), currentModal: selectModal(state),
syncFatalError: selectSyncFatalError(state), syncFatalError: selectSyncFatalError(state),
activeChannelClaim: selectActiveChannelClaim(state), activeChannelClaim: selectActiveChannelClaim(state),
myChannelUrls: selectMyChannelUrls(state), myChannelUrls: selectMyChannelUrls(state),
myChannelClaimIds: selectMyChannelClaimIds(state),
subscriptions: selectSubscriptions(state), subscriptions: selectSubscriptions(state),
isUpdateModalDisplayed: selectIsUpdateModalDisplayed(state),
}); });
const perform = (dispatch) => ({ const perform = (dispatch) => ({
@ -61,8 +51,6 @@ const perform = (dispatch) => ({
setLanguage: (language) => dispatch(doSetLanguage(language)), setLanguage: (language) => dispatch(doSetLanguage(language)),
signIn: () => dispatch(doSignIn()), signIn: () => dispatch(doSignIn()),
requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()), requestDownloadUpgrade: () => dispatch(doDownloadUpgradeRequested()),
updatePreferences: () => dispatch(doGetAndPopulatePreferences()),
getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()),
syncLoop: (noInterval) => dispatch(doSyncLoop(noInterval)), syncLoop: (noInterval) => dispatch(doSyncLoop(noInterval)),
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)), setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()), setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()),

View file

@ -1,37 +1,54 @@
// @flow // @flow
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import React, { useEffect, useRef, useState, useLayoutEffect } from 'react'; import React, { useEffect, useRef, useState, useLayoutEffect } from 'react';
import { lazyImport } from 'util/lazyImport';
import classnames from 'classnames'; import classnames from 'classnames';
import analytics from 'analytics'; import analytics from 'analytics';
import { setSearchUserId } from 'redux/actions/search';
import { buildURI, parseURI } from 'util/lbryURI';
import { SIMPLE_SITE } from 'config';
import Router from 'component/router/index'; import Router from 'component/router/index';
import ModalRouter from 'modal/modalRouter';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import { openContextMenu } from 'util/context-menu'; import { openContextMenu } from 'util/context-menu';
import useKonamiListener from 'util/enhanced-layout'; import useKonamiListener from 'util/enhanced-layout';
import Yrbl from 'component/yrbl';
import FileRenderFloating from 'component/fileRenderFloating'; import FileRenderFloating from 'component/fileRenderFloating';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import usePrevious from 'effects/use-previous'; import usePrevious from 'effects/use-previous';
import Nag from 'component/common/nag';
import REWARDS from 'rewards'; import REWARDS from 'rewards';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import Spinner from 'component/spinner';
import LANGUAGES from 'constants/languages'; import LANGUAGES from 'constants/languages';
import useZoom from 'effects/use-zoom'; import YoutubeWelcome from 'web/component/youtubeReferralWelcome';
import useHistoryNav from 'effects/use-history-nav'; import {
useDegradedPerformance,
STATUS_OK,
STATUS_DEGRADED,
STATUS_FAILING,
STATUS_DOWN,
} from 'web/effects/use-degraded-performance';
import LANGUAGE_MIGRATIONS from 'constants/language-migrations'; import LANGUAGE_MIGRATIONS from 'constants/language-migrations';
import FileDrop from 'component/fileDrop'; const FileDrop = lazyImport(() => import('component/fileDrop' /* webpackChunkName: "fileDrop" */));
import ModalRouter from 'modal/modalRouter'; const NagContinueFirstRun = lazyImport(() => import('component/nagContinueFirstRun' /* webpackChunkName: "nagCFR" */));
import Nag from 'component/common/nag'; const OpenInAppLink = lazyImport(() => import('web/component/openInAppLink' /* webpackChunkName: "openInAppLink" */));
const NagDataCollection = lazyImport(() => import('web/component/nag-data-collection' /* webpackChunkName: "nagDC" */));
import SyncFatalError from 'component/syncFatalError'; const NagDegradedPerformance = lazyImport(() =>
import Yrbl from 'component/yrbl'; 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-sunset" */));
const SyncFatalError = lazyImport(() => import('component/syncFatalError' /* webpackChunkName: "syncFatalError" */));
// **************************************************************************** // ****************************************************************************
export const MAIN_WRAPPER_CLASS = 'main-wrapper'; export const MAIN_WRAPPER_CLASS = 'main-wrapper';
export const IS_MAC = navigator.userAgent.indexOf('Mac OS X') !== -1; 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 imaLibraryPath = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js';
const MOUSE_BACK_BTN = 3; const securePrivacyScriptUrl = 'https://app.secureprivacy.ai/script/6194129b66262906dd4a5f43.js';
const MOUSE_FORWARD_BTN = 4;
type Props = { type Props = {
language: string, language: string,
@ -39,13 +56,7 @@ type Props = {
theme: string, theme: string,
user: ?{ id: string, has_verified_email: boolean, is_reward_approved: boolean }, user: ?{ id: string, has_verified_email: boolean, is_reward_approved: boolean },
location: { pathname: string, hash: string, search: string }, location: { pathname: string, hash: string, search: string },
history: { history: { push: (string) => void },
goBack: () => void,
goForward: () => void,
index: number,
length: number,
push: (string) => void,
},
fetchAccessToken: () => void, fetchAccessToken: () => void,
fetchChannelListMine: () => void, fetchChannelListMine: () => void,
fetchCollectionListMine: () => void, fetchCollectionListMine: () => void,
@ -54,9 +65,8 @@ type Props = {
onSignedIn: () => void, onSignedIn: () => void,
setLanguage: (string) => void, setLanguage: (string) => void,
isUpgradeAvailable: boolean, isUpgradeAvailable: boolean,
isReloadRequired: boolean,
autoUpdateDownloaded: boolean, autoUpdateDownloaded: boolean,
updatePreferences: () => Promise<any>,
getWalletSyncPref: () => Promise<any>,
uploadCount: number, uploadCount: number,
balance: ?number, balance: ?number,
syncError: ?string, syncError: ?string,
@ -68,15 +78,14 @@ type Props = {
syncLoop: (?boolean) => void, syncLoop: (?boolean) => void,
currentModal: any, currentModal: any,
syncFatalError: boolean, syncFatalError: boolean,
activeChannelId: ?string, activeChannelClaim: ?ChannelClaim,
myChannelClaimIds: ?Array<string>, myChannelUrls: ?Array<string>,
subscriptions: Array<Subscription>, subscriptions: Array<Subscription>,
setActiveChannelIfNotSet: () => void, setActiveChannelIfNotSet: () => void,
setIncognito: (boolean) => void, setIncognito: (boolean) => void,
fetchModBlockedList: () => void, fetchModBlockedList: () => void,
resolveUris: (Array<string>) => void, resolveUris: (Array<string>) => void,
fetchModAmIList: () => void, fetchModAmIList: () => void,
isUpdateModalDisplayed: boolean,
}; };
function App(props: Props) { function App(props: Props) {
@ -89,6 +98,7 @@ function App(props: Props) {
signIn, signIn,
autoUpdateDownloaded, autoUpdateDownloaded,
isUpgradeAvailable, isUpgradeAvailable,
isReloadRequired,
requestDownloadUpgrade, requestDownloadUpgrade,
uploadCount, uploadCount,
history, history,
@ -96,57 +106,116 @@ function App(props: Props) {
language, language,
languages, languages,
setLanguage, setLanguage,
updatePreferences,
getWalletSyncPref,
rewards, rewards,
setReferrer, setReferrer,
isAuthenticated, isAuthenticated,
syncLoop, syncLoop,
currentModal, currentModal,
syncFatalError, syncFatalError,
myChannelClaimIds, myChannelUrls,
activeChannelId, activeChannelClaim,
setActiveChannelIfNotSet, setActiveChannelIfNotSet,
setIncognito, setIncognito,
fetchModBlockedList, fetchModBlockedList,
resolveUris, resolveUris,
subscriptions, subscriptions,
fetchModAmIList, fetchModAmIList,
isUpdateModalDisplayed,
} = props; } = props;
const appRef = useRef(); const appRef = useRef();
const isEnhancedLayout = useKonamiListener(); const isEnhancedLayout = useKonamiListener();
const [hasSignedIn, setHasSignedIn] = useState(false); const [hasSignedIn, setHasSignedIn] = useState(false);
const [readyForSync, setReadyForSync] = useState(false);
const [readyForPrefs, setReadyForPrefs] = useState(false);
const hasVerifiedEmail = user && Boolean(user.has_verified_email); const hasVerifiedEmail = user && Boolean(user.has_verified_email);
const isRewardApproved = user && user.is_reward_approved; const isRewardApproved = user && user.is_reward_approved;
const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail); const previousHasVerifiedEmail = usePrevious(hasVerifiedEmail);
const previousRewardApproved = usePrevious(isRewardApproved); const previousRewardApproved = usePrevious(isRewardApproved);
const { pathname, search } = props.location;
const [showAnalyticsNag, setShowAnalyticsNag] = usePersistedState('analytics-nag', true);
const [lbryTvApiStatus, setLbryTvApiStatus] = useState(STATUS_OK);
const { pathname, hash, search } = props.location;
const [upgradeNagClosed, setUpgradeNagClosed] = useState(false); const [upgradeNagClosed, setUpgradeNagClosed] = useState(false);
const [resolvedSubscriptions, setResolvedSubscriptions] = useState(false); const [resolvedSubscriptions, setResolvedSubscriptions] = useState(false);
// const [retryingSync, setRetryingSync] = useState(false); const [retryingSync, setRetryingSync] = useState(false);
const [langRenderKey, setLangRenderKey] = useState(0);
const [sidebarOpen] = usePersistedState('sidebar', true); const [sidebarOpen] = usePersistedState('sidebar', true);
const showUpgradeButton = (autoUpdateDownloaded || isUpgradeAvailable) && !upgradeNagClosed; const [seenSunsestMessage, setSeenSunsetMessage] = usePersistedState('lbrytv-sunset', false);
const showUpgradeButton =
(autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable)) && !upgradeNagClosed;
// referral claiming // referral claiming
const referredRewardAvailable = rewards && rewards.some((reward) => reward.reward_type === REWARDS.TYPE_REFEREE); const referredRewardAvailable = rewards && rewards.some((reward) => reward.reward_type === REWARDS.TYPE_REFEREE);
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
const rawReferrerParam = urlParams.get('r'); const rawReferrerParam = urlParams.get('r');
const fromLbrytvParam = urlParams.get('sunset');
const sanitizedReferrerParam = rawReferrerParam && rawReferrerParam.replace(':', '#'); const sanitizedReferrerParam = rawReferrerParam && rawReferrerParam.replace(':', '#');
const shouldHideNag = pathname.startsWith(`/$/${PAGES.EMBED}`) || pathname.startsWith(`/$/${PAGES.AUTH_VERIFY}`);
const userId = user && user.id; const userId = user && user.id;
const useCustomScrollbar = !IS_MAC; const useCustomScrollbar = !IS_MAC;
const hasMyChannels = myChannelClaimIds && myChannelClaimIds.length > 0; const hasMyChannels = myChannelUrls && myChannelUrls.length > 0;
const hasNoChannels = myChannelClaimIds && myChannelClaimIds.length === 0; const hasNoChannels = myChannelUrls && myChannelUrls.length === 0;
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language]; const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
const hasActiveChannelClaim = activeChannelId !== undefined; const hasActiveChannelClaim = activeChannelClaim !== undefined;
const isPersonalized = hasVerifiedEmail; const isPersonalized = !IS_WEB || hasVerifiedEmail;
const renderFiledrop = !IS_WEB || isAuthenticated;
const isOnline = navigator.onLine;
let uri;
try {
const newpath = buildURI(parseURI(pathname.slice(1).replace(/:/g, '#')));
uri = newpath + hash;
} catch (e) {}
function handleAnalyticsDismiss() {
setShowAnalyticsNag(false);
}
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(() => { useEffect(() => {
if (userId) { if (userId) {
analytics.setUser(userId); analytics.setUser(userId);
setSearchUserId(userId);
} }
}, [userId]); }, [userId]);
@ -154,28 +223,12 @@ function App(props: Props) {
if (!uploadCount) return; if (!uploadCount) return;
const handleBeforeUnload = (event) => { const handleBeforeUnload = (event) => {
event.preventDefault(); event.preventDefault();
event.returnValue = __('There are pending uploads.'); // without setting this to something it doesn't work event.returnValue = 'magic'; // without setting this to something it doesn't work
}; };
window.addEventListener('beforeunload', handleBeforeUnload); window.addEventListener('beforeunload', handleBeforeUnload);
return () => window.removeEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload);
}, [uploadCount]); }, [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 // allows user to pause miniplayer using the spacebar without the page scrolling down
useEffect(() => { useEffect(() => {
const handleKeyPress = (e) => { const handleKeyPress = (e) => {
@ -187,16 +240,6 @@ function App(props: Props) {
return () => window.removeEventListener('keydown', handleKeyPress); 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(() => { useEffect(() => {
if (referredRewardAvailable && sanitizedReferrerParam && isRewardApproved) { if (referredRewardAvailable && sanitizedReferrerParam && isRewardApproved) {
setReferrer(sanitizedReferrerParam, true); setReferrer(sanitizedReferrerParam, true);
@ -274,33 +317,103 @@ function App(props: Props) {
} }
}, [previousRewardApproved, isRewardApproved]); }, [previousRewardApproved, isRewardApproved]);
// Load IMA3 SDK for aniview
useEffect(() => { useEffect(() => {
if (updatePreferences && getWalletSyncPref && readyForPrefs) { const script = document.createElement('script');
getWalletSyncPref() script.src = imaLibraryPath;
.then(() => updatePreferences()) script.async = true;
.then(() => { // $FlowFixMe
setReadyForSync(true); 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]); }
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. // ready for sync syncs, however after signin when hasVerifiedEmail, that syncs too.
useEffect(() => { useEffect(() => {
// signInSyncPref is cleared after sharedState loop. // signInSyncPref is cleared after sharedState loop.
if (readyForSync && hasVerifiedEmail) { const syncLoopWithoutInterval = () => syncLoop(true);
if (hasSignedIn && hasVerifiedEmail) {
// In case we are syncing. // In case we are syncing.
syncLoop(); syncLoop();
window.addEventListener('focus', syncLoopWithoutInterval);
} }
}, [readyForSync, hasVerifiedEmail, syncLoop]); return () => {
window.removeEventListener('focus', syncLoopWithoutInterval);
// 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 }, [hasSignedIn, hasVerifiedEmail, syncLoop]);
// This will no longer work if desktop users no longer get a user object from lbryinc
useEffect(() => {
if (user) {
setReadyForPrefs(true);
}
}, [user, setReadyForPrefs]);
useEffect(() => { useEffect(() => {
if (syncError && isAuthenticated && !pathname.includes(PAGES.AUTH_WALLET_PASSWORD) && !currentModal) { if (syncError && isAuthenticated && !pathname.includes(PAGES.AUTH_WALLET_PASSWORD) && !currentModal) {
@ -327,32 +440,56 @@ function App(props: Props) {
} }
}, [sidebarOpen, isPersonalized, resolvedSubscriptions, subscriptions, resolveUris, setResolvedSubscriptions]); }, [sidebarOpen, isPersonalized, resolvedSubscriptions, subscriptions, resolveUris, setResolvedSubscriptions]);
useEffect(() => { useDegradedPerformance(setLbryTvApiStatus, user);
// When language is changed or translations are fetched, we render.
setLangRenderKey(Date.now());
}, [language, languages]);
if (syncFatalError) { // Require an internal-api user on lbry.tv
return <SyncFatalError />; // 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
if (user === undefined) {
return (
<div className="main--empty">
<Spinner delayed />
</div>
);
}
if (isOnline && lbryTvApiStatus === STATUS_DOWN) {
// TODO: Rename `SyncFatalError` since it has nothing to do with syncing.
return (
<React.Suspense fallback={null}>
<SyncFatalError lbryTvApiStatus={lbryTvApiStatus} />
</React.Suspense>
);
} }
return ( return (
<div <div
className={classnames(MAIN_WRAPPER_CLASS, { className={classnames(MAIN_WRAPPER_CLASS, {
// @if TARGET='app'
[`${MAIN_WRAPPER_CLASS}--mac`]: IS_MAC, [`${MAIN_WRAPPER_CLASS}--mac`]: IS_MAC,
// @endif
[`${MAIN_WRAPPER_CLASS}--scrollbar`]: useCustomScrollbar, [`${MAIN_WRAPPER_CLASS}--scrollbar`]: useCustomScrollbar,
})} })}
ref={appRef} ref={appRef}
key={langRenderKey} onContextMenu={IS_WEB ? undefined : (e) => openContextMenu(e)}
onContextMenu={(e) => openContextMenu(e)}
> >
{IS_WEB && lbryTvApiStatus === STATUS_DOWN ? (
<Yrbl
className="main--empty"
title={__('odysee.com is currently down')}
subtitle={__('My wheel broke, but the good news is that someone from LBRY is working on it.')}
/>
) : (
<React.Fragment>
<Router /> <Router />
<ModalRouter /> <ModalRouter />
<FileDrop /> <React.Suspense fallback={null}>{renderFiledrop && <FileDrop />}</React.Suspense>
<FileRenderFloating /> <FileRenderFloating />
<React.Suspense fallback={null}>
{isEnhancedLayout && <Yrbl className="yrbl--enhanced" />} {isEnhancedLayout && <Yrbl className="yrbl--enhanced" />}
{showUpgradeButton && !isUpdateModalDisplayed && ( {/* @if TARGET='app' */}
{showUpgradeButton && (
<Nag <Nag
message={__('An upgrade is available.')} message={__('An upgrade is available.')}
actionText={__('Install Now')} actionText={__('Install Now')}
@ -360,6 +497,21 @@ function App(props: Props) {
onClose={() => setUpgradeNagClosed(true)} onClose={() => setUpgradeNagClosed(true)}
/> />
)} )}
{/* @endif */}
<YoutubeWelcome />
{!SIMPLE_SITE && !shouldHideNag && <OpenInAppLink uri={uri} />}
{!shouldHideNag && <NagContinueFirstRun />}
{fromLbrytvParam && !seenSunsestMessage && !shouldHideNag && (
<NagSunset email={hasVerifiedEmail} onClose={() => setSeenSunsetMessage(true)} />
)}
{!SIMPLE_SITE && lbryTvApiStatus === STATUS_OK && showAnalyticsNag && !shouldHideNag && (
<NagDataCollection onClose={handleAnalyticsDismiss} />
)}
{getStatusNag()}
</React.Suspense>
</React.Fragment>
)}
</div> </div>
); );
} }

View file

@ -1,21 +0,0 @@
import { connect } from 'react-redux';
import StorageViz from './view';
import {
selectViewBlobSpace,
selectViewHostingLimit,
selectAutoBlobSpace,
selectPrivateBlobSpace,
selectAutoHostingLimit,
} from 'redux/selectors/settings';
import { selectDiskSpace } from 'redux/selectors/app';
const select = (state) => ({
diskSpace: selectDiskSpace(state),
viewHostingLimit: selectViewHostingLimit(state),
autoHostingLimit: selectAutoHostingLimit(state),
viewBlobSpace: selectViewBlobSpace(state),
autoBlobSpace: selectAutoBlobSpace(state),
privateBlobSpace: selectPrivateBlobSpace(state),
});
export default connect(select)(StorageViz);

View file

@ -1,130 +0,0 @@
// @flow
import * as React from 'react';
import I18nMessage from 'component/i18nMessage';
import { ipcRenderer } from 'electron';
type Props = {
// --- select ---
diskSpace: DiskSpace, // KB
viewHostingLimit: number, // MB
autoHostingLimit: number,
viewBlobSpace: number,
autoBlobSpace: number,
privateBlobSpace: number,
};
function StorageViz(props: Props) {
const { diskSpace, viewHostingLimit, autoHostingLimit, viewBlobSpace, autoBlobSpace, privateBlobSpace } = props;
React.useEffect(() => {
ipcRenderer.send('get-disk-space');
}, []);
if (!diskSpace || !diskSpace.total) {
return (
<div className={'storage__wrapper'}>
<div className={'storage__bar'}>
<div className="help">{__('Cannot get disk space information.')}</div>
</div>
</div>
);
}
const totalMB = diskSpace && Math.floor(diskSpace.total / 1024);
const freeMB = diskSpace && Math.floor(diskSpace.free / 1024);
const otherMB = totalMB - (freeMB + viewBlobSpace + autoBlobSpace + privateBlobSpace);
const autoFree = autoHostingLimit - autoBlobSpace;
const viewFree = viewHostingLimit > 0 ? viewHostingLimit - viewBlobSpace : freeMB - autoFree;
const unallocFree = freeMB - viewFree - autoFree;
const viewLimit =
viewHostingLimit === 0
? freeMB - (autoHostingLimit - autoBlobSpace) + viewBlobSpace
: viewHostingLimit + viewBlobSpace;
const getPercent = (val, lim = totalMB) => (val / lim) * 100;
const getGB = (val) => (Number(val) / 1024).toFixed(2);
const otherPercent = getPercent(otherMB);
const privatePercent = getPercent(privateBlobSpace);
const autoLimitPercent = getPercent(autoHostingLimit);
const viewLimitPercent = getPercent(viewLimit);
const viewUsedPercentOfLimit = getPercent(viewBlobSpace, viewLimit);
const autoUsedPercentOfLimit = getPercent(autoBlobSpace, autoHostingLimit);
return (
<div className={'storage__wrapper'}>
<div className={'storage__bar'}>
<div className={'storage__other'} style={{ width: `${otherPercent}%` }} />
<div className={'storage__private'} style={{ width: `${privatePercent}%` }} />
<div className={'storage__auto'} style={{ width: `${autoLimitPercent}%` }}>
<div className={'storage__auto--used'} style={{ width: `${autoUsedPercentOfLimit}%` }} />
<div className={'storage__auto--free'} />
</div>
<div className={'storage__viewed'} style={{ width: `${viewLimitPercent}%` }}>
<div className={'storage__viewed--used'} style={{ width: `${viewUsedPercentOfLimit}%` }} />
<div className={'storage__viewed--free'} />
</div>
{viewHostingLimit !== 0 && <div style={{ 'background-color': 'unset' }} />}
</div>
<div className={'storage__legend-wrapper'}>
<div className={'storage__legend-item'}>
<div className={'storage__legend-item-swatch storage__legend-item-swatch--private'} />
<div className={'storage__legend-item-label'}>
<label>{__('Publishes --[legend, storage category]--')}</label>
<div className={'help'}>{`${getGB(privateBlobSpace)} GB`}</div>
</div>
</div>
<div className={'storage__legend-item'}>
<div className={'storage__legend-item-swatch storage__legend-item-swatch--auto'} />
<div className={'storage__legend-item-label'}>
<label>{__('Auto Hosting --[legend, storage category]--')}</label>
<div className={'help'}>
{autoHostingLimit === 0 ? (
__('Disabled')
) : (
<I18nMessage
tokens={{
spaceUsed: getGB(autoBlobSpace),
limit: getGB(autoHostingLimit),
}}
>
%spaceUsed% of %limit% GB
</I18nMessage>
)}
</div>
</div>
</div>
<div className={'storage__legend-item'}>
<div className={'storage__legend-item-swatch storage__legend-item-swatch--viewed'} />
<div className={'storage__legend-item-label'}>
<label>{__('View Hosting --[legend, storage category]--')}</label>
<div className={'help'}>
{viewHostingLimit === 1 ? (
__('Disabled')
) : (
<I18nMessage
tokens={{
spaceUsed: getGB(viewBlobSpace),
limit: viewHostingLimit !== 0 ? getGB(viewHostingLimit) : getGB(viewFree),
}}
>
%spaceUsed% of %limit% Free GB
</I18nMessage>
)}
</div>
</div>
</div>
{viewHostingLimit !== 0 && (
<div className={'storage__legend-item'}>
<div className={'storage__legend-item-swatch storage__legend-item-swatch--free'} />
<div className={'storage__legend-item-label'}>
<label>{__('Free --[legend, unused disk space]--')}</label>
<div className={'help'}>{`${getGB(unallocFree)} GB`}</div>
</div>
</div>
)}
</div>
</div>
);
}
export default StorageViz;

View file

@ -1,5 +1,6 @@
// @flow // @flow
import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList, ComboboxOption } from '@reach/combobox'; import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList, ComboboxOption } from '@reach/combobox';
// import '@reach/combobox/styles.css'; --> 'scss/third-party.scss'
import { matchSorter } from 'match-sorter'; import { matchSorter } from 'match-sorter';
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
@ -117,7 +118,7 @@ export default function BlockList(props: Props) {
return ( return (
<> <>
<div className="help--notice">{help}</div> <div className="help--notice">{help}</div>
<div className="section" style={{ zIndex: '4' }}> <div className="section">
<SearchList <SearchList
list={localList} list={localList}
placeholder={__('e.g. odysee')} placeholder={__('e.g. odysee')}

View file

@ -5,6 +5,7 @@ import Icon from 'component/common/icon';
import classnames from 'classnames'; import classnames from 'classnames';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { formatLbryUrlForWeb } from 'util/url'; import { formatLbryUrlForWeb } from 'util/url';
import * as PAGES from 'constants/pages';
import useCombinedRefs from 'effects/use-combined-refs'; import useCombinedRefs from 'effects/use-combined-refs';
type Props = { type Props = {
@ -33,6 +34,7 @@ type Props = {
onMouseLeave: ?(any) => any, onMouseLeave: ?(any) => any,
pathname: string, pathname: string,
emailVerified: boolean, emailVerified: boolean,
requiresAuth: ?boolean,
myref: any, myref: any,
dispatch: any, dispatch: any,
'aria-label'?: string, 'aria-label'?: string,
@ -64,6 +66,7 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
iconColor, iconColor,
activeClass, activeClass,
emailVerified, emailVerified,
requiresAuth,
myref, myref,
dispatch, // <button> doesn't know what to do with dispatch dispatch, // <button> doesn't know what to do with dispatch
pathname, pathname,
@ -72,7 +75,7 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
...otherProps ...otherProps
} = props; } = props;
const disable = disabled; const disable = disabled || (user === null && requiresAuth);
const combinedClassName = classnames( const combinedClassName = classnames(
'button', 'button',
@ -180,6 +183,31 @@ const Button = forwardRef<any, {}>((props: Props, ref: any) => {
} }
} }
if (requiresAuth && !emailVerified) {
let redirectUrl = `/$/${PAGES.AUTH}?redirect=${pathname}`;
if (authSrc) {
redirectUrl += `&src=${authSrc}`;
}
return (
<NavLink
exact
onClick={(e) => {
e.stopPropagation();
}}
to={redirectUrl}
title={title || defaultTooltip}
disabled={disable}
className={combinedClassName}
activeClassName={activeClass}
aria-label={ariaLabel}
>
{content}
</NavLink>
);
}
return path ? ( return path ? (
<NavLink <NavLink
exact exact

View file

@ -1,12 +1,14 @@
// @flow // @flow
import { SIMPLE_SITE } from 'config';
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import MarkdownPreview from 'component/common/markdown-preview'; import MarkdownPreview from 'component/common/markdown-preview';
import ClaimTags from 'component/claimTags'; import ClaimTags from 'component/claimTags';
import CreditAmount from 'component/common/credit-amount'; import CreditAmount from 'component/common/credit-amount';
import Button from 'component/button';
import * as PAGES from 'constants/pages';
import DateTime from 'component/dateTime'; import DateTime from 'component/dateTime';
import YoutubeBadge from 'component/youtubeBadge'; import YoutubeBadge from 'component/youtubeBadge';
import SUPPORTED_LANGUAGES from 'constants/supported_languages'; import SUPPORTED_LANGUAGES from 'constants/supported_languages';
import { formatNumber } from 'util/number';
type Props = { type Props = {
claim: ChannelClaim, claim: ChannelClaim,
@ -75,7 +77,7 @@ function ChannelAbout(props: Props) {
</div> </div>
<label>{__('Total Uploads')}</label> <label>{__('Total Uploads')}</label>
<div className="media__info-text">{formatNumber(claim.meta.claims_in_channel || 0, 2, true)}</div> <div className="media__info-text">{claim.meta.claims_in_channel}</div>
<label>{__('Last Updated')}</label> <label>{__('Last Updated')}</label>
<div className="media__info-text"> <div className="media__info-text">
@ -92,13 +94,22 @@ function ChannelAbout(props: Props) {
<div className="media__info-text media__info-text--constrained">{claim.claim_id}</div> <div className="media__info-text media__info-text--constrained">{claim.claim_id}</div>
</div> </div>
<label>{__('Staked LBRY Credits')}</label> <label>{__('Staked Credits')}</label>
<div className="media__info-text"> <div className="media__info-text">
<CreditAmount <CreditAmount
badge={false} badge={false}
amount={parseFloat(claim.amount) + parseFloat(claim.meta.support_amount)} amount={parseFloat(claim.amount) + parseFloat(claim.meta.support_amount)}
precision={8} precision={8}
/>{' '} />{' '}
{SIMPLE_SITE && (
<Button
button="link"
label={__('view other claims at lbry://%name%', {
name: claim.name,
})}
navigate={`/$/${PAGES.TOP}?name=${claim.name}`}
/>
)}
</div> </div>
<YoutubeBadge channelClaimId={claimId} /> <YoutubeBadge channelClaimId={claimId} />

View file

@ -11,11 +11,11 @@ type Props = {
isBlockingOrUnBlocking: boolean, isBlockingOrUnBlocking: boolean,
isToggling: boolean, isToggling: boolean,
doCommentModUnBlock: (string, boolean) => void, doCommentModUnBlock: (string, boolean) => void,
doCommentModBlock: (string, ?Number, boolean) => void, doCommentModBlock: (string, ?string, ?Number, boolean) => void,
doCommentModUnBlockAsAdmin: (string, string) => void, doCommentModUnBlockAsAdmin: (string, string) => void,
doCommentModBlockAsAdmin: (string, string) => void, doCommentModBlockAsAdmin: (string, ?string, ?string) => void,
doCommentModUnBlockAsModerator: (string, string, string) => void, doCommentModUnBlockAsModerator: (string, string, string) => void,
doCommentModBlockAsModerator: (string, string, string) => void, doCommentModBlockAsModerator: (string, ?string, string, ?string) => void,
}; };
function ChannelBlockButton(props: Props) { function ChannelBlockButton(props: Props) {
@ -41,7 +41,7 @@ function ChannelBlockButton(props: Props) {
if (isBlocked) { if (isBlocked) {
doCommentModUnBlock(uri, false); doCommentModUnBlock(uri, false);
} else { } else {
doCommentModBlock(uri, undefined, false); doCommentModBlock(uri, undefined, undefined, false);
} }
break; break;
@ -50,7 +50,7 @@ function ChannelBlockButton(props: Props) {
if (isBlocked) { if (isBlocked) {
doCommentModUnBlockAsModerator(uri, creatorUri, ''); doCommentModUnBlockAsModerator(uri, creatorUri, '');
} else { } else {
doCommentModBlockAsModerator(uri, creatorUri, ''); doCommentModBlockAsModerator(uri, undefined, creatorUri, undefined);
} }
} }
break; break;
@ -59,7 +59,7 @@ function ChannelBlockButton(props: Props) {
if (isBlocked) { if (isBlocked) {
doCommentModUnBlockAsAdmin(uri, ''); doCommentModUnBlockAsAdmin(uri, '');
} else { } else {
doCommentModBlockAsAdmin(uri, ''); doCommentModBlockAsAdmin(uri, undefined, undefined);
} }
break; break;
} }

View file

@ -12,7 +12,7 @@ import * as SETTINGS from 'constants/settings';
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked'; import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { selectUserVerifiedEmail } from 'redux/selectors/user'; import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { makeSelectClientSetting, selectShowMatureContent } from 'redux/selectors/settings'; import { selectClientSetting, selectShowMatureContent } from 'redux/selectors/settings';
import ChannelContent from './view'; import ChannelContent from './view';
@ -31,7 +31,7 @@ const select = (state, props) => {
claim, claim,
isAuthenticated: selectUserVerifiedEmail(state), isAuthenticated: selectUserVerifiedEmail(state),
showMature: selectShowMatureContent(state), showMature: selectShowMatureContent(state),
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state), tileLayout: selectClientSetting(state, SETTINGS.TILE_LAYOUT),
}; };
}; };

View file

@ -1,4 +1,5 @@
// @flow // @flow
import { SHOW_ADS, SIMPLE_SITE } from 'config';
import * as CS from 'constants/claim_search'; import * as CS from 'constants/claim_search';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
@ -6,7 +7,9 @@ import HiddenNsfwClaims from 'component/hiddenNsfwClaims';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import Button from 'component/button'; import Button from 'component/button';
import ClaimListDiscover from 'component/claimListDiscover'; import ClaimListDiscover from 'component/claimListDiscover';
import Ads from 'web/component/ads';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import LivestreamLink from 'component/livestreamLink';
import { Form, FormField } from 'component/common/form'; import { Form, FormField } from 'component/common/form';
import { DEBOUNCE_WAIT_DURATION_MS } from 'constants/search'; import { DEBOUNCE_WAIT_DURATION_MS } from 'constants/search';
import { lighthouse } from 'redux/actions/search'; import { lighthouse } from 'redux/actions/search';
@ -43,6 +46,7 @@ function ChannelContent(props: Props) {
channelIsBlocked, channelIsBlocked,
channelIsBlackListed, channelIsBlackListed,
claim, claim,
isAuthenticated,
defaultPageSize = CS.PAGE_SIZE, defaultPageSize = CS.PAGE_SIZE,
defaultInfiniteScroll = true, defaultInfiniteScroll = true,
showMature, showMature,
@ -114,15 +118,18 @@ function ChannelContent(props: Props) {
{!fetching && Boolean(claimsInChannel) && !channelIsBlocked && !channelIsBlackListed && ( {!fetching && Boolean(claimsInChannel) && !channelIsBlocked && !channelIsBlackListed && (
<HiddenNsfwClaims uri={uri} /> <HiddenNsfwClaims uri={uri} />
)} )}
<LivestreamLink uri={uri} />
{!fetching && channelIsBlackListed && ( {!fetching && channelIsBlackListed && (
<section className="card card--section"> <section className="card card--section">
<p> <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> </p>
<div className="section__actions"> <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> </div>
</section> </section>
)} )}
@ -145,12 +152,14 @@ function ChannelContent(props: Props) {
hideAdvancedFilter={!showFilters} hideAdvancedFilter={!showFilters}
tileLayout={tileLayout} tileLayout={tileLayout}
uris={searchResults} uris={searchResults}
streamType={SIMPLE_SITE ? CS.CONTENT_ALL : undefined}
channelIds={[claimId]} channelIds={[claimId]}
claimType={claimType} claimType={claimType}
feeAmount={CS.FEE_AMOUNT_ANY} feeAmount={CS.FEE_AMOUNT_ANY}
defaultOrderBy={CS.ORDER_BY_NEW} defaultOrderBy={CS.ORDER_BY_NEW}
pageSize={defaultPageSize} pageSize={defaultPageSize}
infiniteScroll={defaultInfiniteScroll} infiniteScroll={defaultInfiniteScroll}
injectedItem={SHOW_ADS && !isAuthenticated && IS_WEB && <Ads type="video" />}
meta={ meta={
showFilters && ( showFilters && (
<Form onSubmit={() => {}} className="wunderbar--inline"> <Form onSubmit={() => {}} className="wunderbar--inline">

View file

@ -1,7 +1,9 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import CommentsList from 'component/commentsList';
import Empty from 'component/common/empty'; import Empty from 'component/common/empty';
import { lazyImport } from 'util/lazyImport';
const CommentsList = lazyImport(() => import('component/commentsList' /* webpackChunkName: "comments" */));
type Props = { type Props = {
uri: string, uri: string,
@ -17,7 +19,9 @@ function ChannelDiscussion(props: Props) {
} }
return ( return (
<section className="section"> <section className="section">
<React.Suspense fallback={null}>
<CommentsList uri={uri} linkedCommentId={linkedCommentId} commentsAreExpanded /> <CommentsList uri={uri} linkedCommentId={linkedCommentId} commentsAreExpanded />
</React.Suspense>
</section> </section>
); );
} }

View file

@ -3,9 +3,10 @@ import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { FormField, FormFieldAreaAdvanced } from 'component/common/form'; import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import TagsSearch from 'component/tagsSearch'; import TagsSearch from 'component/tagsSearch';
import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field';
import ErrorText from 'component/common/error-text'; import ErrorText from 'component/common/error-text';
import ChannelThumbnail from 'component/channelThumbnail'; import ChannelThumbnail from 'component/channelThumbnail';
import { isNameValid, parseURI } from 'util/lbryURI'; import { isNameValid, parseURI } from 'util/lbryURI';
@ -19,6 +20,7 @@ import analytics from 'analytics';
import LbcSymbol from 'component/common/lbc-symbol'; import LbcSymbol from 'component/common/lbc-symbol';
import SUPPORTED_LANGUAGES from 'constants/supported_languages'; import SUPPORTED_LANGUAGES from 'constants/supported_languages';
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp'; import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
import { SIMPLE_SITE } from 'config';
import { sortLanguageMap } from 'util/default-languages'; import { sortLanguageMap } from 'util/default-languages';
import ThumbnailBrokenImage from 'component/selectThumbnail/thumbnail-broken.png'; import ThumbnailBrokenImage from 'component/selectThumbnail/thumbnail-broken.png';
import Gerbil from 'component/channelThumbnail/gerbil.png'; import Gerbil from 'component/channelThumbnail/gerbil.png';
@ -26,9 +28,6 @@ import Gerbil from 'component/channelThumbnail/gerbil.png';
const LANG_NONE = 'none'; const LANG_NONE = 'none';
const MAX_TAG_SELECT = 5; const MAX_TAG_SELECT = 5;
const MAX_NAME_LEN = 128;
const MAX_TITLE_LEN = 255;
const MAX_DESCRIPTION_LEN = 2056;
type Props = { type Props = {
claim: ChannelClaim, claim: ChannelClaim,
@ -94,11 +93,10 @@ function ChannelForm(props: Props) {
const [nameError, setNameError] = React.useState(undefined); const [nameError, setNameError] = React.useState(undefined);
const [bidError, setBidError] = React.useState(''); const [bidError, setBidError] = React.useState('');
const [isUpload, setIsUpload] = React.useState({ cover: false, thumbnail: false }); const [isUpload, setIsUpload] = React.useState({ cover: false, thumbnail: false });
const [coverError, setCoverError] = React.useState(false);
const [thumbError, setThumbError] = React.useState(false); const [thumbError, setThumbError] = React.useState(false);
const { claim_id: claimId } = claim || {}; const { claim_id: claimId } = claim || {};
const [params, setParams]: [any, (any) => void] = React.useState(getChannelParams()); const [params, setParams]: [any, (any) => void] = React.useState(getChannelParams());
const [coverError, setCoverError] = React.useState(false);
const [coverPreview, setCoverPreview] = React.useState(params.coverUrl);
const { channelName } = parseURI(uri); const { channelName } = parseURI(uri);
const name = params.name; const name = params.name;
const isNewChannel = !uri; const isNewChannel = !uri;
@ -207,8 +205,7 @@ function ChannelForm(props: Props) {
setThumbError(false); setThumbError(false);
} }
function handleCoverChange(coverUrl: string, uploadSelected: boolean, preview: ?string) { function handleCoverChange(coverUrl: string, uploadSelected: boolean) {
setCoverPreview(preview || '');
setParams({ ...params, coverUrl }); setParams({ ...params, coverUrl });
setIsUpload({ ...isUpload, cover: uploadSelected }); setIsUpload({ ...isUpload, cover: uploadSelected });
setCoverError(false); setCoverError(false);
@ -261,7 +258,7 @@ function ChannelForm(props: Props) {
} }
}, [hasClaimedInitialRewards, claimInitialRewards]); }, [hasClaimedInitialRewards, claimInitialRewards]);
const coverSrc = coverError ? ThumbnailBrokenImage : coverPreview; const coverSrc = coverError ? ThumbnailBrokenImage : params.coverUrl;
let thumbnailPreview; let thumbnailPreview;
if (!params.thumbnailUrl) { if (!params.thumbnailUrl) {
@ -275,7 +272,7 @@ function ChannelForm(props: Props) {
// TODO clear and bail after submit // TODO clear and bail after submit
return ( return (
<> <>
<div className={classnames({ 'card--disabled': disabled })}> <div className={classnames('main--contained', { 'card--disabled': disabled })}>
<header className="channel-cover"> <header className="channel-cover">
<div className="channel__quick-actions"> <div className="channel__quick-actions">
<Button <Button
@ -283,7 +280,7 @@ function ChannelForm(props: Props) {
title={__('Cover')} title={__('Cover')}
onClick={() => onClick={() =>
openModal(MODALS.IMAGE_UPLOAD, { openModal(MODALS.IMAGE_UPLOAD, {
onUpdate: (coverUrl, isUpload, preview) => handleCoverChange(coverUrl, isUpload, preview), onUpdate: (coverUrl, isUpload) => handleCoverChange(coverUrl, isUpload),
title: __('Edit Cover Image'), title: __('Edit Cover Image'),
helpText: __('(6.25:1)'), helpText: __('(6.25:1)'),
assetName: __('Cover Image'), assetName: __('Cover Image'),
@ -297,7 +294,7 @@ function ChannelForm(props: Props) {
{params.coverUrl && {params.coverUrl &&
(coverError && isUpload.cover ? ( (coverError && isUpload.cover ? (
<div className="channel-cover__custom--waiting"> <div className="channel-cover__custom--waiting">
<p>{__('Uploaded image will be visible in a few minutes.')}</p> <p>{__('Uploaded image will be visible in a few minutes after you submit this form.')}</p>
</div> </div>
) : ( ) : (
<img className="channel-cover__custom" src={coverSrc} onError={() => setCoverError(true)} /> <img className="channel-cover__custom" src={coverSrc} onError={() => setCoverError(true)} />
@ -325,6 +322,7 @@ function ChannelForm(props: Props) {
uri={uri} uri={uri}
thumbnailPreview={thumbnailPreview} thumbnailPreview={thumbnailPreview}
allowGifs allowGifs
showDelayedMessage={isUpload.thumbnail}
setThumbUploadError={setThumbError} setThumbUploadError={setThumbError}
thumbUploadError={thumbError} thumbUploadError={thumbError}
/> />
@ -335,7 +333,7 @@ function ChannelForm(props: Props) {
<div className="channel-cover__gradient" /> <div className="channel-cover__gradient" />
</header> </header>
<Tabs className="channelPage-wrapper"> <Tabs>
<TabList className="tabs__list--channel-page"> <TabList className="tabs__list--channel-page">
<Tab>{__('General')}</Tab> <Tab>{__('General')}</Tab>
<Tab>{__('Credit Details')}</Tab> <Tab>{__('Credit Details')}</Tab>
@ -362,7 +360,6 @@ function ChannelForm(props: Props) {
error={nameError} error={nameError}
disabled={!isNewChannel} disabled={!isNewChannel}
onChange={(e) => setParams({ ...params, name: e.target.value })} onChange={(e) => setParams({ ...params, name: e.target.value })}
maxLength={MAX_NAME_LEN}
/> />
</fieldset-group> </fieldset-group>
{!isNewChannel && <span className="form-field__help">{__('This field cannot be changed.')}</span>} {!isNewChannel && <span className="form-field__help">{__('This field cannot be changed.')}</span>}
@ -374,16 +371,15 @@ function ChannelForm(props: Props) {
placeholder={__('My Awesome Channel')} placeholder={__('My Awesome Channel')}
value={params.title} value={params.title}
onChange={(e) => setParams({ ...params, title: e.target.value })} onChange={(e) => setParams({ ...params, title: e.target.value })}
maxLength={MAX_TITLE_LEN}
/> />
<FormFieldAreaAdvanced <FormField
type="markdown" type="markdown"
name="content_description2" name="content_description2"
label={__('Description')} label={__('Description')}
placeholder={__('Description of your content')} placeholder={__('Description of your content')}
value={params.description} value={params.description}
onChange={(text) => setParams({ ...params, description: text })} onChange={(text) => setParams({ ...params, description: text })}
textAreaMaxLength={MAX_DESCRIPTION_LEN} textAreaMaxLength={FF_MAX_CHARS_IN_DESCRIPTION}
/> />
</> </>
} }
@ -418,7 +414,7 @@ function ChannelForm(props: Props) {
<Card <Card
body={ body={
<TagsSearch <TagsSearch
suggestMature suggestMature={!SIMPLE_SITE}
disableAutoFocus disableAutoFocus
disableControlTags disableControlTags
limitSelect={MAX_TAG_SELECT} limitSelect={MAX_TAG_SELECT}

View file

@ -0,0 +1,10 @@
import { connect } from 'react-redux';
import { selectClaimForUri, selectIsUriResolving } from 'redux/selectors/claims';
import ChannelMentionSuggestion from './view';
const select = (state, props) => ({
claim: selectClaimForUri(state, props.uri),
isResolvingUri: selectIsUriResolving(state, props.uri),
});
export default connect(select)(ChannelMentionSuggestion);

View file

@ -0,0 +1,32 @@
// @flow
import { ComboboxOption } from '@reach/combobox';
import ChannelThumbnail from 'component/channelThumbnail';
import React from 'react';
type Props = {
claim: ?Claim,
uri?: string,
isResolvingUri: boolean,
};
export default function ChannelMentionSuggestion(props: Props) {
const { claim, uri, isResolvingUri } = props;
return !claim ? null : (
<ComboboxOption value={uri}>
{isResolvingUri ? (
<div className="channel-mention__suggestion">
<div className="media__thumb media__thumb--resolving" />
</div>
) : (
<div className="channel-mention__suggestion">
<ChannelThumbnail xsmall uri={uri} />
<span className="channel-mention__suggestion-label">
<div className="channel-mention__suggestion-title">{(claim.value && claim.value.title) || claim.name}</div>
<div className="channel-mention__suggestion-name">{claim.name}</div>
</span>
</div>
)}
</ComboboxOption>
);
}

View file

@ -0,0 +1,27 @@
import { connect } from 'react-redux';
import { MAX_LIVESTREAM_COMMENTS } from 'constants/livestream';
import { selectShowMatureContent } from 'redux/selectors/settings';
import { selectSubscriptionUris } from 'redux/selectors/subscriptions';
import { withRouter } from 'react-router';
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 maxComments = props.isLivestream ? MAX_LIVESTREAM_COMMENTS : -1;
const data = selectChannelMentionData(state, props.uri, maxComments);
return {
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),
};
};
export default withRouter(connect(select, { doResolveUris })(ChannelMentionSuggestions));

View file

@ -0,0 +1,286 @@
// @flow
import { Combobox, ComboboxInput, ComboboxPopover, ComboboxList } from '@reach/combobox';
import { Form } from 'component/common/form';
import { parseURI, regexInvalidURI } from 'util/lbryURI';
import { SEARCH_OPTIONS } from 'constants/search';
import * as KEYCODES from 'constants/keycodes';
import ChannelMentionSuggestion from 'component/channelMentionSuggestion';
import ChannelMentionTopSuggestion from 'component/channelMentionTopSuggestion';
import React from 'react';
import Spinner from 'component/spinner';
import type { ElementRef } from 'react';
import useLighthouse from 'effects/use-lighthouse';
const INPUT_DEBOUNCE_MS = 1000;
const LIGHTHOUSE_MIN_CHARACTERS = 3;
type Props = {
inputRef: any,
mentionTerm: string,
noTopSuggestion?: boolean,
showMature: boolean,
isLivestream: boolean,
creatorUri: string,
commentorUris: Array<string>,
subscriptionUris: Array<string>,
unresolvedCommentors: Array<string>,
unresolvedSubscriptions: Array<string>,
canonicalCreator: string,
canonicalCommentors: Array<string>,
canonicalSubscriptions: Array<string>,
doResolveUris: (Array<string>) => void,
customSelectAction?: (string, number) => void,
};
export default function ChannelMentionSuggestions(props: Props) {
const {
unresolvedCommentors,
unresolvedSubscriptions,
canonicalCreator,
isLivestream,
creatorUri,
inputRef,
showMature,
noTopSuggestion,
mentionTerm,
doResolveUris,
customSelectAction,
} = props;
const comboboxInputRef: ElementRef<any> = React.useRef();
const comboboxListRef: ElementRef<any> = React.useRef();
const mainEl = document.querySelector('.channel-mention__suggestions');
const [debouncedTerm, setDebouncedTerm] = React.useState('');
const [mostSupported, setMostSupported] = React.useState('');
const [canonicalResults, setCanonicalResults] = React.useState([]);
const isRefFocused = (ref) => ref && ref.current === document.activeElement;
const subscriptionUris = props.subscriptionUris.filter((uri) => uri !== creatorUri);
const canonicalSubscriptions = props.canonicalSubscriptions.filter((uri) => uri !== canonicalCreator);
const commentorUris = props.commentorUris.filter((uri) => uri !== creatorUri && !subscriptionUris.includes(uri));
const canonicalCommentors = props.canonicalCommentors.filter(
(uri) => uri !== canonicalCreator && !canonicalSubscriptions.includes(uri)
);
const termToMatch = mentionTerm && mentionTerm.replace('@', '').toLowerCase();
const allShownUris = [creatorUri, ...subscriptionUris, ...commentorUris];
const allShownCanonical = [canonicalCreator, ...canonicalSubscriptions, ...canonicalCommentors];
const possibleMatches = allShownUris.filter((uri) => {
try {
// yuck a try catch in a filter?
const { channelName } = parseURI(uri);
return channelName && channelName.toLowerCase().includes(termToMatch);
} catch (e) {}
});
const searchSize = 5;
const additionalOptions = { isBackgroundSearch: false, [SEARCH_OPTIONS.CLAIM_TYPE]: SEARCH_OPTIONS.INCLUDE_CHANNELS };
const { results, loading } = useLighthouse(debouncedTerm, showMature, searchSize, additionalOptions, 0);
const stringifiedResults = JSON.stringify(results);
const hasMinLength = mentionTerm && mentionTerm.length >= LIGHTHOUSE_MIN_CHARACTERS;
const isTyping = debouncedTerm !== mentionTerm;
const showPlaceholder = isTyping || loading;
const isUriFromTermValid = !regexInvalidURI.test(mentionTerm.substring(1));
const handleSelect = React.useCallback(
(value, key) => {
if (customSelectAction) {
// Give them full results, as our resolved one might truncate the claimId.
customSelectAction(value || (results && results.find((r) => r.startsWith(value))) || '', Number(key));
}
},
[customSelectAction, results]
);
React.useEffect(() => {
const timer = setTimeout(() => {
if (isTyping) setDebouncedTerm(!hasMinLength ? '' : mentionTerm);
}, INPUT_DEBOUNCE_MS);
return () => clearTimeout(timer);
}, [hasMinLength, isTyping, mentionTerm]);
React.useEffect(() => {
if (!mainEl) return;
const header = document.querySelector('.header__navigation');
function handleReflow() {
const boxAtTopOfPage = header && mainEl.getBoundingClientRect().top <= header.offsetHeight;
const boxAtBottomOfPage = mainEl.getBoundingClientRect().bottom >= window.innerHeight;
if (boxAtTopOfPage) {
mainEl.setAttribute('flow-bottom', '');
}
if (mainEl.getAttribute('flow-bottom') !== null && boxAtBottomOfPage) {
mainEl.removeAttribute('flow-bottom');
}
}
handleReflow();
window.addEventListener('scroll', handleReflow);
return () => window.removeEventListener('scroll', handleReflow);
}, [mainEl]);
React.useEffect(() => {
if (!inputRef || !comboboxInputRef || !mentionTerm) return;
function handleKeyDown(event) {
const { keyCode } = event;
const activeElement = document.activeElement;
if (keyCode === KEYCODES.UP || keyCode === KEYCODES.DOWN) {
if (isRefFocused(comboboxInputRef)) {
const selectedId = activeElement && activeElement.getAttribute('aria-activedescendant');
const selectedItem = selectedId && document.querySelector(`li[id="${selectedId}"]`);
if (selectedItem) selectedItem.scrollIntoView({ block: 'nearest', inline: 'nearest' });
} else {
// $FlowFixMe
comboboxInputRef.current.focus();
}
} else {
if ((isRefFocused(comboboxInputRef) || isRefFocused(inputRef)) && keyCode === KEYCODES.TAB) {
event.preventDefault();
const activeValue = activeElement && activeElement.getAttribute('value');
if (activeValue) {
handleSelect(activeValue, keyCode);
} else if (possibleMatches.length) {
// $FlowFixMe
const suggest = allShownCanonical.find((matchUri) => possibleMatches.find((uri) => uri.includes(matchUri)));
if (suggest) handleSelect(suggest, keyCode);
} else if (results) {
handleSelect(mentionTerm, keyCode);
}
}
if (isRefFocused(comboboxInputRef)) {
// $FlowFixMe
inputRef.current.focus();
}
}
}
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [allShownCanonical, handleSelect, inputRef, mentionTerm, possibleMatches, results]);
React.useEffect(() => {
if (!stringifiedResults) return;
const arrayResults = JSON.parse(stringifiedResults);
if (arrayResults && arrayResults.length > 0) {
// $FlowFixMe
doResolveUris(arrayResults).then((response) => {
try {
// $FlowFixMe
const canonical_urls = Object.values(response).map(({ canonical_url }) => canonical_url);
setCanonicalResults(canonical_urls);
} catch (e) {}
});
}
}, [doResolveUris, stringifiedResults]);
// Only resolve commentors on Livestreams when actually mentioning/looking for it
React.useEffect(() => {
if (isLivestream && unresolvedCommentors && mentionTerm) doResolveUris(unresolvedCommentors);
}, [doResolveUris, isLivestream, mentionTerm, unresolvedCommentors]);
// Only resolve the subscriptions that match the mention term, instead of all
React.useEffect(() => {
if (isTyping) return;
const urisToResolve = [];
subscriptionUris.map(
(uri) =>
hasMinLength &&
possibleMatches.includes(uri) &&
unresolvedSubscriptions.includes(uri) &&
urisToResolve.push(uri)
);
if (urisToResolve.length > 0) doResolveUris(urisToResolve);
}, [doResolveUris, hasMinLength, isTyping, possibleMatches, subscriptionUris, unresolvedSubscriptions]);
const suggestionsRow = (
label: string,
suggestions: Array<string>,
canonical: Array<string>,
hasSuggestionsBelow: boolean
) => {
if (mentionTerm.length > 1 && suggestions !== results) {
suggestions = suggestions.filter((uri) => possibleMatches.includes(uri));
} else if (suggestions === results) {
suggestions = suggestions.filter((uri) => !allShownUris.includes(uri));
}
// $FlowFixMe
suggestions = suggestions
.map((matchUri) => canonical.find((uri) => matchUri.includes(uri)))
.filter((uri) => Boolean(uri));
if (canonical === canonicalResults) {
suggestions = suggestions.filter((uri) => uri !== mostSupported);
}
return !suggestions.length ? null : (
<>
<div className="channel-mention__label">{label}</div>
{suggestions.map((uri) => (
<ChannelMentionSuggestion key={uri} uri={uri} />
))}
{hasSuggestionsBelow && <hr className="channel-mention__top-separator" />}
</>
);
};
return isRefFocused(inputRef) || isRefFocused(comboboxInputRef) ? (
<Form onSubmit={() => handleSelect(mentionTerm)}>
<Combobox className="channel-mention" onSelect={handleSelect}>
<ComboboxInput ref={comboboxInputRef} className="channel-mention__input--none" value={mentionTerm} />
{mentionTerm && isUriFromTermValid && (
<ComboboxPopover portal={false} className="channel-mention__suggestions">
<ComboboxList ref={comboboxListRef}>
{creatorUri &&
suggestionsRow(
__('Creator'),
[creatorUri],
[canonicalCreator],
canonicalSubscriptions.length > 0 || commentorUris.length > 0 || !showPlaceholder
)}
{canonicalSubscriptions.length > 0 &&
suggestionsRow(
__('Following'),
subscriptionUris,
canonicalSubscriptions,
commentorUris.length > 0 || !showPlaceholder
)}
{commentorUris.length > 0 &&
suggestionsRow(__('From comments'), commentorUris, canonicalCommentors, !showPlaceholder)}
{hasMinLength &&
(showPlaceholder ? (
<Spinner type="small" />
) : (
results && (
<>
{!noTopSuggestion && (
<ChannelMentionTopSuggestion
query={debouncedTerm}
shownUris={allShownCanonical}
setMostSupported={(winningUri) => setMostSupported(winningUri)}
/>
)}
{suggestionsRow(__('From search'), results, canonicalResults, false)}
</>
)
))}
</ComboboxList>
</ComboboxPopover>
)}
</Combobox>
</Form>
) : null;
}

Some files were not shown because too many files have changed in this diff Show more