From 83dbe8ec7ce679574b7f38b5e1bf1c104069c609 Mon Sep 17 00:00:00 2001 From: Rafael Saes <76502841+saltrafael@users.noreply.github.com> Date: Wed, 13 Jul 2022 10:59:59 -0300 Subject: [PATCH] Playlists v2: Refactors, touch ups + Queue Mode (#1604) * Playlists v2 * Style pass * Change playlist items arrange icon * Playlist card body open by default * Refactor collectionEdit components * Paginate & Refactor bid field * Collection page changes * Add Thumbnail optional * Replace extra info for description on collection page * Playlist card right below video on medium screen * Allow editing private collections * Add edit option to menus * Allow deleting a public playlist but keeping a private version * Add queue to Save menu, remove edit option from Builtin pages, show queue on playlists page * Fix scroll to recent persisting on medium screen * Fix adding to queue from menu * Fixes for delete * PublishList: delay mounting Items tab to prevent lock-up (#1783) For a large list, the playlist publish form is unusable (super-slow typing) due to the entire list being mounted despite the tab is not active. The full solution is still to paginate it, but for now, don't mount the tab until it is selected. Add a spinner to indicate something is loading. It's not prefect, but it's throwaway code anyway. At least we can fill in the fields properly now. * Batch-resolve private collections (#1782) * makeSelectClaimForClaimId --> selectClaimForClaimId Move away from the problematic `makeSelect*`, especially in large loops. * Batch-resolve private collections 1758 This alleviates the lock-up that is caused by large number of invidual resolves. There will still be some minor stutter due to the large DOM that React needs to handle -- that is logged in 1758 and will be handled separately. At least the stutter is short (1-2s) and the app is still usable. Private list items are being resolve individually, super slow if the list is large (>100). Published lists doesn't have this issue. doFetchItemsInCollections contains most of the useful logic, but it isn't called for private/built-in lists because it's not an actual claim. Tweaked doFetchItemsInCollections to handle private (UUID-based) collections. * Use persisted state for floating player playlist card body - I find it annoying being open everytime * Fix removing edits from published playlist * Fix scroll on mobile * Allow going editing items from toast * Fix ClaimShareButton * Prevent edit/publish of builtin * Fix async inside forEach * Fix sync on queue edit * Fix autoplayCountdown replay * Fix deleting an item scrolling the playlist * CreatedAt fixes * Remove repost for now * Anon publish fixes * Fix mature case on floating Co-authored-by: infinite-persistence <64950861+infinite-persistence@users.noreply.github.com> --- extras/lbryinc/redux/actions/cost_info.js | 31 +- flow-typed/Collections.js | 34 +- flow-typed/content.js | 19 +- flow-typed/notification.js | 4 + static/app-strings.json | 46 +- ui/component/autoplayCountdown/index.js | 11 +- ui/component/autoplayCountdown/view.jsx | 33 +- ui/component/button/index.js | 2 + ui/component/button/view.jsx | 12 +- ui/component/buttonAddToQueue/index.js | 37 ++ ui/component/buttonAddToQueue/view.jsx | 97 ++++ ui/component/channelEdit/view.jsx | 11 +- ui/component/channelSelector/view.jsx | 17 +- ui/component/channelThumbnail/view.jsx | 21 +- ui/component/claimCollectionAdd/index.js | 23 - ui/component/claimCollectionAdd/view.jsx | 148 ----- .../claimCollectionAddButton/index.js | 11 +- .../claimCollectionAddButton/view.jsx | 39 +- ui/component/claimDescription/index.js | 17 + ui/component/claimDescription/view.jsx | 17 + ui/component/claimList/view.jsx | 115 +++- ui/component/claimListHeader/view.jsx | 2 +- ui/component/claimMenuList/index.js | 55 +- ui/component/claimMenuList/view.jsx | 215 ++++--- ui/component/claimPreview/index.js | 8 +- .../buttonRemoveFromCollection/index.js | 11 + .../buttonRemoveFromCollection/view.jsx | 40 ++ .../claim-preview-no-content.jsx | 0 .../claim-preview-no-mature.jsx | 16 +- ui/component/claimPreview/view.jsx | 123 +++- ui/component/claimPreviewSubtitle/view.jsx | 4 +- ui/component/claimPreviewTile/index.js | 7 + ui/component/claimPreviewTile/view.jsx | 40 +- ui/component/claimRepostButton/index.js | 18 + ui/component/claimRepostButton/view.jsx | 28 + ui/component/claimShareButton/index.js | 9 + ui/component/claimShareButton/view.jsx | 30 + ui/component/claimSupportButton/view.jsx | 27 +- ui/component/claimType/view.jsx | 2 +- ui/component/collectionActions/index.js | 45 -- ui/component/collectionActions/view.jsx | 200 ------- .../collectionContentSidebar/index.js | 38 -- .../collectionContentSidebar/view.jsx | 133 ----- ui/component/collectionEdit/index.js | 54 -- ui/component/collectionEdit/view.jsx | 511 ----------------- ui/component/collectionEditButtons/index.js | 6 +- ui/component/collectionEditButtons/view.jsx | 82 ++- ui/component/collectionGeneralTab/index.js | 17 + ui/component/collectionGeneralTab/view.jsx | 170 ++++++ ui/component/collectionItemsList/index.js | 28 + ui/component/collectionItemsList/view.jsx | 86 +++ ui/component/collectionMenuList/index.js | 26 +- ui/component/collectionMenuList/view.jsx | 103 ++-- .../collectionPreviewOverlay/index.js | 28 +- .../collectionPreviewOverlay/view.jsx | 51 +- .../collectionPreviewTile/collectionCount.jsx | 19 - .../collectionPrivate.jsx | 14 - ui/component/collectionPreviewTile/index.js | 59 -- ui/component/collectionPreviewTile/view.jsx | 172 ------ ui/component/collectionSelectItem/index.js | 21 - ui/component/collectionSelectItem/view.jsx | 62 -- ui/component/collectionsListMine/index.js | 19 - ui/component/collectionsListMine/view.jsx | 250 -------- ui/component/commentMenuList/index.js | 4 +- .../FreezeframeLite/classes.js | 0 .../FreezeframeLite/index.js | 4 +- .../FreezeframeLite/styles.scss | 0 .../FreezeframeLite/templates.js | 0 .../FreezeframeLite/utils.js | 2 +- ui/component/common/bid-help-text.jsx | 38 ++ ui/component/common/card.jsx | 123 ++-- .../claim-preview-loading.jsx | 16 +- .../common/collection-private-icon.jsx | 13 + ui/component/common/file-action-button.jsx | 46 ++ .../freezeframe-wrapper.jsx} | 25 +- ui/component/common/icon-custom.jsx | 209 ++++++- ui/component/common/section-divider.jsx | 10 + ui/component/common/truncated-text.jsx | 5 +- .../internal/claimDeleteButton/index.js | 9 + .../internal/claimDeleteButton/view.jsx | 25 + .../internal/claimPublishButton/index.js | 22 + .../internal/claimPublishButton/view.jsx | 46 ++ ui/component/fileActions/view.jsx | 55 +- ui/component/fileDownloadLink/index.js | 4 +- ui/component/fileReactions/view.jsx | 149 +++-- ui/component/fileRenderFloating/index.js | 40 +- ui/component/fileRenderFloating/view.jsx | 340 +++++++---- ui/component/fileRenderInitiator/view.jsx | 2 +- ui/component/fileThumbnail/index.js | 19 +- .../fileThumbnail/{ => internal}/thumb.jsx | 9 +- ui/component/fileThumbnail/placeholder.png | Bin 28818 -> 0 bytes ui/component/fileThumbnail/view.jsx | 61 +- ui/component/fileWatchLaterLink/index.js | 14 +- ui/component/fileWatchLaterLink/view.jsx | 68 ++- ui/component/formNewCollection/index.js | 9 + ui/component/formNewCollection/view.jsx | 120 ++++ ui/component/livestreamLayout/view.jsx | 5 +- ui/component/playlistCard/index.js | 55 ++ .../playlistCard/internal/loopButton/index.js | 18 + .../playlistCard/internal/loopButton/view.jsx | 28 + .../internal/shuffleButton/index.js | 18 + .../internal/shuffleButton/view.jsx | 29 + ui/component/playlistCard/view.jsx | 401 +++++++++++++ ui/component/playlistsMine/index.js | 20 - ui/component/playlistsMine/view.jsx | 182 ------ .../previewOverlayProperties/index.js | 4 +- .../previewOverlayProperties/view.jsx | 17 +- ui/component/publishBidField/index.js | 23 + ui/component/publishBidField/view.jsx | 60 ++ ui/component/router/index.js | 14 +- ui/component/router/view.jsx | 7 +- ui/component/selectThumbnail/view.jsx | 4 +- ui/component/settingContent/index.js | 11 +- ui/component/settingContent/view.jsx | 4 +- ui/component/shareButton/index.js | 14 - ui/component/shareButton/view.jsx | 24 - ui/component/sideNavigation/view.jsx | 10 +- ui/component/snackBar/view.jsx | 31 +- ui/component/socialShare/view.jsx | 2 +- ui/component/swipeableDrawer/index.js | 16 +- ui/component/swipeableDrawer/view.jsx | 58 +- ui/component/swipeableDrawerExpand/index.js | 2 +- ui/component/swipeableDrawerExpand/view.jsx | 53 +- ui/component/uriIndicator/view.jsx | 6 +- ui/component/userFirstChannel/view.jsx | 8 +- ui/component/viewers/videoViewer/index.js | 55 +- .../videoViewer/internal/videojs-events.jsx | 10 - .../viewers/videoViewer/internal/videojs.jsx | 3 - ui/component/viewers/videoViewer/view.jsx | 146 ++++- ui/component/walletSendTip/view.jsx | 8 +- ui/constants/action_types.js | 4 +- ui/constants/collections.js | 57 +- ui/constants/drawer_types.js | 2 + ui/constants/icons.js | 5 + ui/constants/modal_types.js | 1 + ui/constants/pageTitles.js | 3 +- ui/constants/pages.js | 1 + ui/constants/player.js | 3 + ui/constants/publish.js | 1 + ui/effects/use-play-next.js | 4 +- ui/effects/use-screensize.js | 2 +- ui/modal/modalAffirmPurchase/index.js | 2 +- ui/modal/modalAffirmPurchase/view.jsx | 11 +- ui/modal/modalClaimCollectionAdd/index.js | 6 +- .../internal/claimCollectionAdd/index.js | 21 + .../internal/collectionSelectItem/index.js | 21 + .../internal/collectionSelectItem/view.jsx | 71 +++ .../internal/claimCollectionAdd/view.jsx | 62 ++ ui/modal/modalClaimCollectionAdd/view.jsx | 9 +- ui/modal/modalCollectionCreate/index.jsx | 21 + .../internal/collectionCreate/index.js | 19 + .../internal/collectionCreate/view.jsx | 29 + ui/modal/modalRemoveCollection/index.js | 22 +- ui/modal/modalRemoveCollection/view.jsx | 54 +- ui/modal/modalRouter/index.js | 2 +- ui/modal/modalRouter/view.jsx | 11 +- ui/page/channel/view.jsx | 6 +- ui/page/collection/index.js | 51 +- .../internal/collectionActions/index.js | 18 + .../internal/deleteButton/index.js | 18 + .../internal/deleteButton/view.jsx | 31 + .../internal/playButton/index.js | 20 + .../internal/playButton/view.jsx | 29 + .../internal/publishButton/index.js | 16 + .../internal/publishButton/view.jsx | 36 ++ .../internal/report-button.jsx | 23 + .../internal/shuffleButton/index.js | 21 + .../internal/shuffleButton/view.jsx | 55 ++ .../internal/collectionActions/view.jsx | 88 +++ .../internal/collectionHeader/index.js | 37 ++ .../internal/collectionHeader/view.jsx | 127 +++++ .../internal/collectionPrivateEdit/index.js | 28 + .../internal/collectionPrivateEdit/view.jsx | 150 +++++ .../internal/collectionPublish/index.js | 40 ++ .../internal/collectionPublish/view.jsx | 338 +++++++++++ ui/page/collection/view.jsx | 223 ++------ ui/page/file/index.js | 18 +- ui/page/file/view.jsx | 75 ++- ui/page/lists/index.js | 3 - ui/page/lists/view.jsx | 22 - ui/page/playlists/index.js | 3 - .../internal/collectionsListMine/index.js | 24 + .../internal/builtin-playlists.jsx | 28 + .../internal/collectionListHeader/index.jsx | 163 ++++++ .../internal/filtered-text-label.jsx | 26 + .../internal/rightSideActions/index.js | 9 + .../internal/rightSideActions/view.jsx | 65 +++ .../internal/collectionPreview/index.js | 58 ++ .../internal/collection-item-count.jsx | 25 + .../internal/collection-public-icon.jsx | 13 + .../internal/collectionPreview/style.scss | 260 +++++++++ .../internal/collectionPreview/view.jsx | 162 ++++++ .../collectionsListMine/internal/label.jsx | 21 + .../internal/page-items-label.jsx | 24 + .../internal/table-header.jsx | 21 + .../internal/collectionsListMine/view.jsx | 228 ++++++++ ui/page/playlists/view.jsx | 22 +- ui/page/show/index.js | 12 +- ui/redux/actions/app.js | 17 +- ui/redux/actions/claims.js | 19 +- ui/redux/actions/collections.js | 237 +++++--- ui/redux/actions/content.js | 418 +++++++++++--- ui/redux/actions/file.js | 36 +- ui/redux/reducers/app.js | 15 +- ui/redux/reducers/claims.js | 10 +- ui/redux/reducers/collections.js | 92 +-- ui/redux/reducers/content.js | 21 +- ui/redux/selectors/app.js | 19 +- ui/redux/selectors/claims.js | 79 ++- ui/redux/selectors/collections.js | 535 ++++++++++++------ ui/redux/selectors/content.js | 59 +- ui/redux/selectors/publish.js | 39 +- ui/redux/selectors/settings.js | 4 +- ui/redux/selectors/user.js | 2 + ui/scss/component/_button.scss | 8 +- ui/scss/component/_card.scss | 187 +++++- ui/scss/component/_channel.scss | 14 + ui/scss/component/_claim-list.scss | 161 ++++-- ui/scss/component/_claim-search.scss | 53 +- ui/scss/component/_collection.scss | 49 +- ui/scss/component/_content.scss | 76 ++- ui/scss/component/_file-render.scss | 6 + ui/scss/component/_main.scss | 68 +-- ui/scss/component/_media.scss | 23 +- ui/scss/component/_swipeable-drawer.scss | 7 + ui/scss/component/_table.scss | 67 +++ ui/scss/component/_textarea-suggestions.scss | 10 +- ui/scss/component/menu-button.scss | 2 +- ui/scss/component/section.scss | 30 +- ui/scss/init/_breakpoints.scss | 4 + ui/scss/init/_gui.scss | 165 +++++- ui/scss/init/_mixins.scss | 6 + ui/scss/init/_vars.scss | 4 +- ui/util/claim.js | 40 +- ui/util/collections.js | 21 + ui/util/lbryURI.js | 5 + ui/util/publish.js | 45 ++ ui/util/time.js | 2 + ui/util/url.js | 16 + .../helper-functions.js => util/window.js} | 5 +- 240 files changed, 8225 insertions(+), 3939 deletions(-) create mode 100644 ui/component/buttonAddToQueue/index.js create mode 100644 ui/component/buttonAddToQueue/view.jsx delete mode 100644 ui/component/claimCollectionAdd/index.js delete mode 100644 ui/component/claimCollectionAdd/view.jsx create mode 100644 ui/component/claimDescription/index.js create mode 100644 ui/component/claimDescription/view.jsx create mode 100644 ui/component/claimPreview/internal/buttonRemoveFromCollection/index.js create mode 100644 ui/component/claimPreview/internal/buttonRemoveFromCollection/view.jsx rename ui/component/claimPreview/{ => internal}/claim-preview-no-content.jsx (100%) rename ui/component/claimPreview/{ => internal}/claim-preview-no-mature.jsx (56%) create mode 100644 ui/component/claimRepostButton/index.js create mode 100644 ui/component/claimRepostButton/view.jsx create mode 100644 ui/component/claimShareButton/index.js create mode 100644 ui/component/claimShareButton/view.jsx delete mode 100644 ui/component/collectionActions/index.js delete mode 100644 ui/component/collectionActions/view.jsx delete mode 100644 ui/component/collectionContentSidebar/index.js delete mode 100644 ui/component/collectionContentSidebar/view.jsx delete mode 100644 ui/component/collectionEdit/index.js delete mode 100644 ui/component/collectionEdit/view.jsx create mode 100644 ui/component/collectionGeneralTab/index.js create mode 100644 ui/component/collectionGeneralTab/view.jsx create mode 100644 ui/component/collectionItemsList/index.js create mode 100644 ui/component/collectionItemsList/view.jsx delete mode 100644 ui/component/collectionPreviewTile/collectionCount.jsx delete mode 100644 ui/component/collectionPreviewTile/collectionPrivate.jsx delete mode 100644 ui/component/collectionPreviewTile/index.js delete mode 100644 ui/component/collectionPreviewTile/view.jsx delete mode 100644 ui/component/collectionSelectItem/index.js delete mode 100644 ui/component/collectionSelectItem/view.jsx delete mode 100644 ui/component/collectionsListMine/index.js delete mode 100644 ui/component/collectionsListMine/view.jsx rename ui/component/{fileThumbnail => common}/FreezeframeLite/classes.js (100%) rename ui/component/{fileThumbnail => common}/FreezeframeLite/index.js (97%) rename ui/component/{fileThumbnail => common}/FreezeframeLite/styles.scss (100%) rename ui/component/{fileThumbnail => common}/FreezeframeLite/templates.js (100%) rename ui/component/{fileThumbnail => common}/FreezeframeLite/utils.js (93%) create mode 100644 ui/component/common/bid-help-text.jsx rename ui/component/{claimPreview => common}/claim-preview-loading.jsx (72%) create mode 100644 ui/component/common/collection-private-icon.jsx create mode 100644 ui/component/common/file-action-button.jsx rename ui/component/{fileThumbnail/FreezeframeWrapper.jsx => common/freezeframe-wrapper.jsx} (65%) create mode 100644 ui/component/common/section-divider.jsx create mode 100644 ui/component/fileActions/internal/claimDeleteButton/index.js create mode 100644 ui/component/fileActions/internal/claimDeleteButton/view.jsx create mode 100644 ui/component/fileActions/internal/claimPublishButton/index.js create mode 100644 ui/component/fileActions/internal/claimPublishButton/view.jsx rename ui/component/fileThumbnail/{ => internal}/thumb.jsx (64%) delete mode 100644 ui/component/fileThumbnail/placeholder.png create mode 100644 ui/component/formNewCollection/index.js create mode 100644 ui/component/formNewCollection/view.jsx create mode 100644 ui/component/playlistCard/index.js create mode 100644 ui/component/playlistCard/internal/loopButton/index.js create mode 100644 ui/component/playlistCard/internal/loopButton/view.jsx create mode 100644 ui/component/playlistCard/internal/shuffleButton/index.js create mode 100644 ui/component/playlistCard/internal/shuffleButton/view.jsx create mode 100644 ui/component/playlistCard/view.jsx delete mode 100644 ui/component/playlistsMine/index.js delete mode 100644 ui/component/playlistsMine/view.jsx create mode 100644 ui/component/publishBidField/index.js create mode 100644 ui/component/publishBidField/view.jsx delete mode 100644 ui/component/shareButton/index.js delete mode 100644 ui/component/shareButton/view.jsx create mode 100644 ui/constants/drawer_types.js create mode 100644 ui/constants/publish.js create mode 100644 ui/modal/modalClaimCollectionAdd/internal/claimCollectionAdd/index.js create mode 100644 ui/modal/modalClaimCollectionAdd/internal/claimCollectionAdd/internal/collectionSelectItem/index.js create mode 100644 ui/modal/modalClaimCollectionAdd/internal/claimCollectionAdd/internal/collectionSelectItem/view.jsx create mode 100644 ui/modal/modalClaimCollectionAdd/internal/claimCollectionAdd/view.jsx create mode 100644 ui/modal/modalCollectionCreate/index.jsx create mode 100644 ui/modal/modalCollectionCreate/internal/collectionCreate/index.js create mode 100644 ui/modal/modalCollectionCreate/internal/collectionCreate/view.jsx create mode 100644 ui/page/collection/internal/collectionActions/index.js create mode 100644 ui/page/collection/internal/collectionActions/internal/deleteButton/index.js create mode 100644 ui/page/collection/internal/collectionActions/internal/deleteButton/view.jsx create mode 100644 ui/page/collection/internal/collectionActions/internal/playButton/index.js create mode 100644 ui/page/collection/internal/collectionActions/internal/playButton/view.jsx create mode 100644 ui/page/collection/internal/collectionActions/internal/publishButton/index.js create mode 100644 ui/page/collection/internal/collectionActions/internal/publishButton/view.jsx create mode 100644 ui/page/collection/internal/collectionActions/internal/report-button.jsx create mode 100644 ui/page/collection/internal/collectionActions/internal/shuffleButton/index.js create mode 100644 ui/page/collection/internal/collectionActions/internal/shuffleButton/view.jsx create mode 100644 ui/page/collection/internal/collectionActions/view.jsx create mode 100644 ui/page/collection/internal/collectionHeader/index.js create mode 100644 ui/page/collection/internal/collectionHeader/view.jsx create mode 100644 ui/page/collection/internal/collectionPrivateEdit/index.js create mode 100644 ui/page/collection/internal/collectionPrivateEdit/view.jsx create mode 100644 ui/page/collection/internal/collectionPublish/index.js create mode 100644 ui/page/collection/internal/collectionPublish/view.jsx delete mode 100644 ui/page/lists/index.js delete mode 100644 ui/page/lists/view.jsx delete mode 100644 ui/page/playlists/index.js create mode 100644 ui/page/playlists/internal/collectionsListMine/index.js create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/builtin-playlists.jsx create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/collectionListHeader/index.jsx create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/collectionListHeader/internal/filtered-text-label.jsx create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/collectionListHeader/internal/rightSideActions/index.js create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/collectionListHeader/internal/rightSideActions/view.jsx create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/collectionPreview/index.js create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/collectionPreview/internal/collection-item-count.jsx create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/collectionPreview/internal/collection-public-icon.jsx create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/collectionPreview/style.scss create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/collectionPreview/view.jsx create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/label.jsx create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/page-items-label.jsx create mode 100644 ui/page/playlists/internal/collectionsListMine/internal/table-header.jsx create mode 100644 ui/page/playlists/internal/collectionsListMine/view.jsx create mode 100644 ui/util/collections.js rename ui/{component/fileRenderFloating/helper-functions.js => util/window.js} (94%) diff --git a/extras/lbryinc/redux/actions/cost_info.js b/extras/lbryinc/redux/actions/cost_info.js index d924625b5d4..f2e95be6e63 100644 --- a/extras/lbryinc/redux/actions/cost_info.js +++ b/extras/lbryinc/redux/actions/cost_info.js @@ -5,32 +5,33 @@ import { selectClaimForUri } from 'redux/selectors/claims'; // eslint-disable-next-line import/prefer-default-export export function doFetchCostInfoForUri(uri: string) { - return (dispatch: Dispatch, getState: GetState) => { + return async (dispatch: Dispatch, getState: GetState) => { const state = getState(); const claim = selectClaimForUri(state, uri); if (!claim) return; - function resolve(costInfo) { - dispatch({ - type: ACTIONS.FETCH_COST_INFO_COMPLETED, - data: { - uri, - costInfo, - }, - }); - } - const fee = claim.value ? claim.value.fee : undefined; + let costInfo; if (fee === undefined) { - resolve({ cost: 0, includesData: true }); + costInfo = { cost: 0, includesData: true }; } else if (fee.currency === 'LBC') { - resolve({ cost: fee.amount, includesData: true }); + costInfo = { cost: fee.amount, includesData: true }; } else { - Lbryio.getExchangeRates().then(({ LBC_USD }) => { - resolve({ cost: fee.amount / LBC_USD, includesData: true }); + await Lbryio.getExchangeRates().then(({ LBC_USD }) => { + costInfo = { cost: fee.amount / LBC_USD, includesData: true }; }); } + + dispatch({ + type: ACTIONS.FETCH_COST_INFO_COMPLETED, + data: { + uri, + costInfo, + }, + }); + + return costInfo; }; } diff --git a/flow-typed/Collections.js b/flow-typed/Collections.js index a6939307fd2..77920598b57 100644 --- a/flow-typed/Collections.js +++ b/flow-typed/Collections.js @@ -2,9 +2,16 @@ declare type Collection = { id: string, items: Array, name: string, + description?: string, + thumbnail?: { + url?: string, + }, type: string, + createdAt?: ?number, updatedAt: number, totalItems?: number, + itemCount?: number, + editsCleared?: boolean, sourceId?: string, // if copied, claimId of original collection }; @@ -17,11 +24,25 @@ declare type CollectionState = { saved: Array, isResolvingCollectionById: { [string]: boolean }, error?: string | null, + queue: Collection, }; declare type CollectionGroup = { [string]: Collection, -} +}; + +declare type CollectionList = Array; + +declare type CollectionCreateParams = { + name: string, + description?: string, + thumbnail?: { + url?: string, + }, + items: ?Array, + type: string, + sourceId?: string, // if copied, claimId of original collection +}; declare type CollectionEditParams = { uris?: Array, @@ -30,4 +51,13 @@ declare type CollectionEditParams = { order?: { from: number, to: number }, type?: string, name?: string, -} + description?: string, + thumbnail?: { + url?: string, + }, +}; + +declare type CollectionFetchParams = { + collectionId: string, + pageSize?: number, +}; diff --git a/flow-typed/content.js b/flow-typed/content.js index eb1a2124aea..bdc18092332 100644 --- a/flow-typed/content.js +++ b/flow-typed/content.js @@ -2,19 +2,16 @@ declare type ContentState = { primaryUri: ?string, - playingUri: { uri?: string }, + playingUri: { + uri?: string, + collection: PlayingCollection, + }, positions: { [string]: { [string]: number } }, // claimId: { outpoint: position } history: Array, recommendationId: { [string]: string }, // claimId: recommendationId recommendationParentId: { [string]: string }, // claimId: referrerId recommendationUrls: { [string]: Array }, // claimId: [lbryUrls...] recommendationClicks: { [string]: Array }, // "claimId": [clicked indices...] - loopList?: { collectionId: string, loop: boolean }, - shuffleList?: { collectionId: string, newUrls: Array | boolean }, - // TODO: it's confusing for newUrls to be a boolean --------- ^^^ - // It can/should be '?Array` instead -- set it to null, then clients - // can cast it to a boolean. That, or rename the variable to `shuffle` if you - // don't care about the URLs. lastViewedAnnouncement: ?string, // undefined = not seen in wallet. recsysEntries: { [ClaimId]: RecsysEntry }, // Persistent shadow copy. The main one resides in RecSys. }; @@ -29,6 +26,12 @@ declare type PlayingUri = { primaryUri?: string, pathname?: string, commentId?: string, - collectionId?: ?string, + collection: PlayingCollection, source?: string, }; + +declare type PlayingCollection = { + collectionId?: ?string, + loop?: ?boolean, + shuffle?: ?{ newUrls: Array }, +}; diff --git a/flow-typed/notification.js b/flow-typed/notification.js index 5fb611fb0bd..acf71b4543d 100644 --- a/flow-typed/notification.js +++ b/flow-typed/notification.js @@ -17,6 +17,10 @@ declare type ToastParams = { linkTarget?: string, isError?: boolean, duration?: 'default' | 'long', + actionText?: string, + action?: () => void, + secondaryActionText?: string, + secondaryAction?: () => void, }; declare type Toast = { diff --git a/static/app-strings.json b/static/app-strings.json index 65b4690bd5b..c1b7b3312c5 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -26,9 +26,24 @@ "Tags": "Tags", "Share": "Share", "Play": "Play", + "Play All": "Play All", + "Start Playing": "Start Playing", "Shuffle Play": "Shuffle Play", "Shuffle": "Shuffle", + "Play in Shuffle mode": "Play in Shuffle mode", "Loop": "Loop", + "Playlist": "Playlist", + "Visibility": "Visibility", + "Video Count": "Video Count", + "Last updated at": "Last updated at", + "You can add videos to your Playlists": "You can add videos to your Playlists", + "Do you want to find some content to save for later, or create a brand new playlist?": "Do you want to find some content to save for later, or create a brand new playlist?", + "Explore!": "Explore!", + "New Playlist": "New Playlist", + "Showing %filtered% results of %total%": "Showing %filtered% results of %total%", + "%playlist_item_count% item": "%playlist_item_count% item", + "%playlist_item_count% items": "%playlist_item_count% items", + "Published as: %playlist_channel%": "Published as: %playlist_channel%", "Report content": "Report content", "Report Content": "Report Content", "Report comment": "Report comment", @@ -1173,6 +1188,7 @@ "Paid": "Paid", "Start at": "Start at", "Include List ID": "Include List ID", + "Include Playlist ID": "Include Playlist ID", "Links": "Links", "Download Link": "Download Link", "Mature content is not supported.": "Mature content is not supported.", @@ -1775,6 +1791,8 @@ "How does this work?": "How does this work?", "Introducing Lists": "Introducing Lists", "You have no lists yet. Better start hoarding!": "You have no lists yet. Better start hoarding!", + "You have no Playlists yet. Better start hoarding!": "You have no Playlists yet. Better start hoarding!", + "Create a Playlist": "Create a Playlist", "Update your livestream": "Update your livestream", "Prepare an upcoming livestream": "Prepare an upcoming livestream", "Edit your post": "Edit your post", @@ -1958,15 +1976,17 @@ "Lists": "Lists", "Watch Later": "Watch Later", "Favorites": "Favorites", - "New List": "New List", - "New List Title": "New List Title", "Add to Lists": "Add to Lists", + "Add to Playlist": "Add to Playlist", "Playlists": "Playlists", "Edit List": "Edit List", + "Edit Playlist": "Edit Playlist", "Delete List": "Delete List", + "Delete Playlist": "Delete Playlist", "Private": "Private", "Public": "Public", "View List": "View List", + "View Playlist": "View Playlist", "Publish List": "Publish List", "Info": "Info", "Publishes": "Publishes", @@ -1979,7 +1999,7 @@ "Credits": "Credits", "Cannot publish empty list": "Cannot publish empty list", "MyAwesomeList": "MyAwesomeList", - "My Awesome List": "My Awesome List", + "My Awesome Playlist": "My Awesome Playlist", "This list has no items.": "This list has no items.", "1 item": "1 item", "%collectionCount% items": "%collectionCount% items", @@ -1989,6 +2009,7 @@ "URL Selected": "URL Selected", "Keep": "Keep", "Add this claim to a list": "Add this claim to a list", + "Add this video to a playlist": "Add this video to a playlist", "List is Empty": "List is Empty", "Confirm List Unpublish": "Confirm List Unpublish", "This will permanently delete the list.": "This will permanently delete the list.", @@ -2003,8 +2024,8 @@ "Remove from Watch Later": "Remove from Watch Later", "Add to Watch Later": "Add to Watch Later", "Added": "Added", - "Item removed from %name%": "Item removed from %name%", - "Item added to %name%": "Item added to %name%", + "Item removed from %playlist_name%": "Item removed from %playlist_name%", + "Item added to %playlist_name%": "Item added to %playlist_name%", "Item added to Watch Later": "Item added to Watch Later", "Item removed from Watch Later": "Item removed from Watch Later", "Item added to Favorites": "Item added to Favorites", @@ -2135,7 +2156,6 @@ "Sending...": "Sending...", "Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8": "Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8", "Choose %asset%": "Choose %asset%", - "Showing %filtered% results of %total%": "Showing %filtered% results of %total%", "filtered": "filtered", "Failed to synchronize settings. Wait a while before retrying.": "Failed to synchronize settings. Wait a while before retrying.", "You are offline. Check your internet connection.": "You are offline. Check your internet connection.", @@ -2307,5 +2327,19 @@ "Latest Content Link": "Latest Content Link", "Current Livestream Link": "Current Livestream Link", "Back to Odysee Premium": "Back to Odysee Premium", + "Add to Queue": "Add to Queue", + "Queue Mode": "Queue Mode", + "Item added to Queue": "Item added to Queue", + "In Queue": "In Queue", + "Now playing: --[Which Playlist is currently playing]--": "Now playing:", + "Private %lock_icon%": "Private %lock_icon%", + "Playlist is Empty": "Playlist is Empty", + "Queue": "Queue", + "Your Playlists": "Your Playlists", + "Default Playlists": "Default Playlists", + "Open": "Open", + "Support this content": "Support this content", + "Repost this content": "Repost this content", + "Share this content": "Share this content", "--end--": "--end--" } diff --git a/ui/component/autoplayCountdown/index.js b/ui/component/autoplayCountdown/index.js index 7cdccf6862e..c0bd5d956e4 100644 --- a/ui/component/autoplayCountdown/index.js +++ b/ui/component/autoplayCountdown/index.js @@ -1,8 +1,8 @@ import { connect } from 'react-redux'; -import { makeSelectClaimForUri } from 'redux/selectors/claims'; -import { withRouter } from 'react-router'; +import { makeSelectClaimForUri, selectClaimIsNsfwForUri } from 'redux/selectors/claims'; import AutoplayCountdown from './view'; import { selectModal } from 'redux/selectors/app'; +import { doOpenModal } from 'redux/actions/app'; /* AutoplayCountdown does not fetch it's own next content to play, it relies on being rendered. @@ -11,6 +11,11 @@ import { selectModal } from 'redux/selectors/app'; const select = (state, props) => ({ nextRecommendedClaim: makeSelectClaimForUri(props.nextRecommendedUri)(state), modal: selectModal(state), + isMature: selectClaimIsNsfwForUri(state, props.uri), }); -export default withRouter(connect(select, null)(AutoplayCountdown)); +const perform = { + doOpenModal, +}; + +export default connect(select, perform)(AutoplayCountdown); diff --git a/ui/component/autoplayCountdown/view.jsx b/ui/component/autoplayCountdown/view.jsx index 1527fdb9101..a2fcb74efad 100644 --- a/ui/component/autoplayCountdown/view.jsx +++ b/ui/component/autoplayCountdown/view.jsx @@ -6,33 +6,40 @@ import I18nMessage from 'component/i18nMessage'; import { withRouter } from 'react-router'; import debounce from 'util/debounce'; import * as ICONS from 'constants/icons'; +import * as MODALS from 'constants/modal_types'; const DEBOUNCE_SCROLL_HANDLER_MS = 150; const CLASSNAME_AUTOPLAY_COUNTDOWN = 'autoplay-countdown'; type Props = { - history: { push: (string) => void }, + uri?: string, nextRecommendedClaim: ?StreamClaim, nextRecommendedUri: string, modal: { id: string, modalProps: {} }, skipPaid: boolean, + skipMature: boolean, + isMature: boolean, doNavigate: () => void, doReplay: () => void, doPrevious: () => void, onCanceled: () => void, + doOpenModal: (id: string, props: {}) => void, }; function AutoplayCountdown(props: Props) { const { + uri, nextRecommendedUri, nextRecommendedClaim, - history: { push }, modal, skipPaid, + skipMature, + isMature, doNavigate, doReplay, doPrevious, onCanceled, + doOpenModal, } = props; const nextTitle = nextRecommendedClaim && nextRecommendedClaim.value && nextRecommendedClaim.value.title; @@ -44,6 +51,8 @@ function AutoplayCountdown(props: Props) { const [timerPaused, setTimerPaused] = React.useState(false); const anyModalPresent = modal !== undefined && modal !== null; const isTimerPaused = timerPaused || anyModalPresent; + const shouldSkipMature = skipMature && isMature; + const skipCurrentVideo = skipPaid || shouldSkipMature; function isAnyInputFocused() { const activeElement = document.activeElement; @@ -58,7 +67,9 @@ function AutoplayCountdown(props: Props) { } function getMsgPlayingNext() { - if (skipPaid) { + if (shouldSkipMature) { + return __('Skipping mature content in %seconds_left% seconds...', { seconds_left: timer }); + } else if (skipPaid) { return __('Playing next free content in %seconds_left% seconds...', { seconds_left: timer }); } else { return __('Playing in %seconds_left% seconds...', { seconds_left: timer }); @@ -89,7 +100,7 @@ function AutoplayCountdown(props: Props) { interval = setInterval(() => { const newTime = timer - 1; if (newTime === 0) { - if (skipPaid) setTimer(countdownTime); + if (skipCurrentVideo) setTimer(countdownTime); doNavigate(); } else { setTimer(timer - 1); @@ -100,7 +111,7 @@ function AutoplayCountdown(props: Props) { return () => { clearInterval(interval); }; - }, [timer, doNavigate, push, timerCanceled, isTimerPaused, nextRecommendedUri, skipPaid]); + }, [doNavigate, isTimerPaused, nextRecommendedUri, skipCurrentVideo, timer, timerCanceled]); if (timerCanceled || !nextRecommendedUri) { return null; @@ -138,7 +149,7 @@ function AutoplayCountdown(props: Props) { /> )} - {skipPaid && doPrevious && ( + {skipCurrentVideo && doPrevious && (
diff --git a/ui/component/button/index.js b/ui/component/button/index.js index f50b0e00171..02d8279baf4 100644 --- a/ui/component/button/index.js +++ b/ui/component/button/index.js @@ -2,11 +2,13 @@ import Button from './view'; import React, { forwardRef } from 'react'; import { connect } from 'react-redux'; import { selectUser, selectUserVerifiedEmail } from 'redux/selectors/user'; +import { selectHasChannels } from 'redux/selectors/claims'; const mapStateToProps = (state) => ({ pathname: state.router.location.pathname, emailVerified: selectUserVerifiedEmail(state), user: selectUser(state), + hasChannels: selectHasChannels(state), }); const ConnectedButton = connect(mapStateToProps)(Button); diff --git a/ui/component/button/view.jsx b/ui/component/button/view.jsx index 1f64428077c..85f4fd40f75 100644 --- a/ui/component/button/view.jsx +++ b/ui/component/button/view.jsx @@ -36,6 +36,8 @@ type Props = { pathname: string, emailVerified: boolean, requiresAuth: ?boolean, + requiresChannel: ?boolean, + hasChannels: boolean, myref: any, dispatch: any, 'aria-label'?: string, @@ -69,6 +71,8 @@ const Button = forwardRef((props: Props, ref: any) => { activeClass, emailVerified, requiresAuth, + requiresChannel, + hasChannels, myref, dispatch, //