diff --git a/static/app-strings.json b/static/app-strings.json index 728349365..b2b70ba0f 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -746,6 +746,7 @@ "gaming, crypto": "gaming, crypto", "Autocomplete": "Autocomplete", "Followed Tags": "Followed Tags", + "Followed Channels": "Followed Channels", "Manage Tags": "Manage Tags", "%selectTagsLabel% (%number% left)": "%selectTagsLabel% (%number% left)", "Matching": "Matching", diff --git a/ui/component/router/view.jsx b/ui/component/router/view.jsx index a02ec1f73..e70137af4 100644 --- a/ui/component/router/view.jsx +++ b/ui/component/router/view.jsx @@ -48,6 +48,9 @@ const ChannelsFollowingDiscoverPage = lazyImport(() => const ChannelsFollowingPage = lazyImport(() => import('page/channelsFollowing' /* webpackChunkName: "channelsFollowing" */) ); +const ChannelsFollowingManage = lazyImport(() => + import('page/channelsFollowingManage' /* webpackChunkName: "channelsFollowing" */) +); const ChannelsPage = lazyImport(() => import('page/channels' /* webpackChunkName: "channels" */)); const CheckoutPage = lazyImport(() => import('page/checkoutPage' /* webpackChunkName: "checkoutPage" */)); const CreatorDashboard = lazyImport(() => import('page/creatorDashboard' /* webpackChunkName: "creatorDashboard" */)); @@ -330,6 +333,12 @@ function AppRouter(props: Props) { path={`/$/${PAGES.CHANNELS_FOLLOWING_DISCOVER}`} component={ChannelsFollowingDiscoverPage} /> + diff --git a/ui/component/sideNavigation/view.jsx b/ui/component/sideNavigation/view.jsx index ba44bec7d..beb6bc86f 100644 --- a/ui/component/sideNavigation/view.jsx +++ b/ui/component/sideNavigation/view.jsx @@ -232,7 +232,6 @@ function SideNavigation(props: Props) { ); const [pulseLibrary, setPulseLibrary] = React.useState(false); - const [expandSubscriptions, setExpandSubscriptions] = React.useState(false); const [expandTags, setExpandTags] = React.useState(false); const isPersonalized = !IS_WEB || isAuthenticated; @@ -276,12 +275,7 @@ function SideNavigation(props: Props) { ); let displayedSubscriptions = filteredSubscriptions; - if ( - showSubscriptionSection && - !subscriptionFilter && - subscriptions.length > FOLLOWED_ITEM_INITIAL_LIMIT && - !expandSubscriptions - ) { + if (showSubscriptionSection && !subscriptionFilter && subscriptions.length > FOLLOWED_ITEM_INITIAL_LIMIT) { displayedSubscriptions = subscriptions.slice(0, FOLLOWED_ITEM_INITIAL_LIMIT); } @@ -337,12 +331,12 @@ function SideNavigation(props: Props) { )} - {!subscriptionFilter && subscriptions.length > FOLLOWED_ITEM_INITIAL_LIMIT && ( + {!subscriptionFilter && ( setExpandSubscriptions(!expandSubscriptions)} + navigate={`/$/${PAGES.CHANNELS_FOLLOWING_MANAGE}`} /> )} diff --git a/ui/constants/pages.js b/ui/constants/pages.js index a186df3c6..569ea2dec 100644 --- a/ui/constants/pages.js +++ b/ui/constants/pages.js @@ -63,6 +63,7 @@ exports.DEPRECATED__CHANNELS_FOLLOWING = 'following/channels'; exports.CHANNELS_FOLLOWING = 'following'; exports.DEPRECATED__CHANNELS_FOLLOWING_MANAGE = 'following/channels/manage'; exports.CHANNELS_FOLLOWING_DISCOVER = 'following/discover'; +exports.CHANNELS_FOLLOWING_MANAGE = 'following/manage'; exports.WALLET = 'wallet'; exports.CHANNELS = 'channels'; exports.EMBED = 'embed'; diff --git a/ui/page/channelsFollowing/view.jsx b/ui/page/channelsFollowing/view.jsx index d2f1b72e6..a440aa135 100644 --- a/ui/page/channelsFollowing/view.jsx +++ b/ui/page/channelsFollowing/view.jsx @@ -69,12 +69,20 @@ function ChannelsFollowingPage(props: Props) { defaultOrderBy={CS.ORDER_BY_NEW} channelIds={channelIds} meta={ - + <> + + + > } subSection={ ({ + subscribedChannelUris: selectSubscriptionUris(state), +}); + +export default connect(select)(ChannelsFollowingManage); diff --git a/ui/page/channelsFollowingManage/view.jsx b/ui/page/channelsFollowingManage/view.jsx new file mode 100644 index 000000000..a9547fe92 --- /dev/null +++ b/ui/page/channelsFollowingManage/view.jsx @@ -0,0 +1,81 @@ +// @flow +import React from 'react'; +import ClaimList from 'component/claimList'; +import ClaimListDiscover from 'component/claimListDiscover'; +import DebouncedInput from 'component/common/debounced-input'; +import Empty from 'component/common/empty'; +import Page from 'component/page'; +import Spinner from 'component/spinner'; +import * as CS from 'constants/claim_search'; +import * as ICONS from 'constants/icons'; +import { parseURI } from 'util/lbryURI'; + +function getFilteredUris(uris, filterQuery) { + if (filterQuery) { + const filterQueryLowerCase = filterQuery.toLowerCase(); + return uris.filter((uri) => uri.toLowerCase().includes(filterQueryLowerCase)); + } + return null; +} + +function parseIdFromUri(uri) { + try { + const { channelClaimId } = parseURI(uri); + return channelClaimId; + } catch { + return ''; + } +} + +// **************************************************************************** +// ChannelsFollowingManage +// **************************************************************************** + +type Props = { + subscribedChannelUris: Array, +}; + +export default function ChannelsFollowingManage(props: Props) { + const { subscribedChannelUris } = props; + const [uris, setUris] = React.useState([]); + const [filterQuery, setFilterQuery] = React.useState(''); + const filteredUris = getFilteredUris(uris, filterQuery); + const [channelIds, setChannelIds] = React.useState(null); + + React.useEffect(() => { + setUris(subscribedChannelUris); + setChannelIds(subscribedChannelUris.map((uri) => parseIdFromUri(uri))); + // eslint-disable-next-line react-hooks/exhaustive-deps, (lock list on mount so it doesn't shift when unfollow) + }, []); + + return ( + + + {__('Followed Channels')} + + + {channelIds === null ? ( + + + + ) : uris && uris.length === 0 ? ( + + ) : ( + <> + + {filteredUris && } + {!filteredUris && channelIds.length && ( + + )} + > + )} + + ); +} diff --git a/ui/scss/init/_gui.scss b/ui/scss/init/_gui.scss index 2b3dfc621..e0c6ab7cf 100644 --- a/ui/scss/init/_gui.scss +++ b/ui/scss/init/_gui.scss @@ -653,6 +653,13 @@ img { } } +.followManage-wrapper { + @extend .discoverPage-wrapper; + .claim-list { + margin-top: var(--spacing-m); + } +} + .uploadPage-wraper { &.card-stack { .card:not(:last-of-type) {