adds user ability to block channels
small changes blocked channels page most features done tweaks
This commit is contained in:
parent
223a68413f
commit
5ab165131f
21 changed files with 217 additions and 16 deletions
|
@ -89,6 +89,14 @@ const analytics: Analytics = {
|
|||
});
|
||||
}
|
||||
},
|
||||
channelBlockEvent: (uri, blocked, location) => {
|
||||
if (analyticsEnabled) {
|
||||
ReactGA.event({
|
||||
category: blocked ? 'Channel-Hidden' : 'Channel-Unhidden',
|
||||
action: uri,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize google analytics
|
||||
|
|
15
src/ui/component/blockButton/index.js
Normal file
15
src/ui/component/blockButton/index.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectChannelIsBlocked, doToggleBlockChannel, doToast } from 'lbry-redux';
|
||||
import BlockButton from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
channelIsBlocked: selectChannelIsBlocked(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
{
|
||||
toggleBlockChannel: doToggleBlockChannel,
|
||||
doToast,
|
||||
}
|
||||
)(BlockButton);
|
40
src/ui/component/blockButton/view.jsx
Normal file
40
src/ui/component/blockButton/view.jsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React, { useRef } from 'react';
|
||||
import Button from 'component/button';
|
||||
import useHover from 'util/use-hover';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
isSubscribed: boolean,
|
||||
toggleBlockChannel: (uri: string) => void,
|
||||
channelIsBlocked: boolean,
|
||||
doToast: ({ message: string, linkText: string, linkTarget: string }) => void,
|
||||
};
|
||||
|
||||
export default function BlockButton(props: Props) {
|
||||
const { uri, toggleBlockChannel, channelIsBlocked, doToast } = props;
|
||||
|
||||
const blockRef = useRef();
|
||||
const isHovering = useHover(blockRef);
|
||||
const blockLabel = channelIsBlocked ? __('Blocked') : __('Block');
|
||||
const blockedOverride = channelIsBlocked && isHovering && __('Unblock');
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={blockRef}
|
||||
iconColor="red"
|
||||
icon={blockedOverride ? ICONS.UNBLOCK : ICONS.BLOCK}
|
||||
button={'alt'}
|
||||
label={blockedOverride || blockLabel}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
if (!channelIsBlocked) {
|
||||
doToast({ message: `Blocked ${uri}`, linkText: 'Manage', linkTarget: `/${PAGES.BLOCKED}` });
|
||||
}
|
||||
toggleBlockChannel(uri);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -6,6 +6,7 @@ import {
|
|||
makeSelectFetchingChannelClaims,
|
||||
makeSelectClaimIsMine,
|
||||
makeSelectTotalPagesForChannel,
|
||||
selectChannelIsBlocked,
|
||||
} from 'lbry-redux';
|
||||
import { withRouter } from 'react-router';
|
||||
import ChannelPage from './view';
|
||||
|
@ -19,6 +20,7 @@ const select = (state, props) => {
|
|||
fetching: makeSelectFetchingChannelClaims(props.uri)(state),
|
||||
totalPages: makeSelectTotalPagesForChannel(props.uri, PAGE_SIZE)(state),
|
||||
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
channelIsBlocked: selectChannelIsBlocked(props.uri)(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -12,12 +12,13 @@ type Props = {
|
|||
fetching: boolean,
|
||||
params: { page: number },
|
||||
claimsInChannel: Array<StreamClaim>,
|
||||
channelIsBlocked: boolean,
|
||||
channelIsMine: boolean,
|
||||
fetchClaims: (string, number) => void,
|
||||
};
|
||||
|
||||
function ChannelContent(props: Props) {
|
||||
const { uri, fetching, claimsInChannel, totalPages, channelIsMine, fetchClaims } = props;
|
||||
const { uri, fetching, claimsInChannel, totalPages, channelIsMine, channelIsBlocked, fetchClaims } = props;
|
||||
const hasContent = Boolean(claimsInChannel && claimsInChannel.length);
|
||||
return (
|
||||
<Fragment>
|
||||
|
@ -27,21 +28,30 @@ function ChannelContent(props: Props) {
|
|||
</section>
|
||||
)}
|
||||
|
||||
{!fetching && !hasContent && (
|
||||
{!fetching && !hasContent && !channelIsBlocked && (
|
||||
<div className="card--section">
|
||||
<h2 className="help">{__("This channel hasn't uploaded anything.")}</h2>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!fetching && channelIsBlocked && (
|
||||
<div className="card--section">
|
||||
<h2 className="card__content help">{__('You have blocked this channel content.')}</h2>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!channelIsMine && <HiddenNsfwClaims className="card__subtitle" uri={uri} />}
|
||||
|
||||
{hasContent && <ClaimList header={false} uris={claimsInChannel.map(claim => claim.permanent_url)} />}
|
||||
|
||||
<Paginate
|
||||
onPageChange={page => fetchClaims(uri, page)}
|
||||
totalPages={totalPages}
|
||||
loading={fetching && !hasContent}
|
||||
/>
|
||||
{hasContent && !channelIsBlocked && (
|
||||
<ClaimList header={false} uris={claimsInChannel.map(claim => claim.permanent_url)} />
|
||||
)}
|
||||
{!channelIsBlocked && (
|
||||
<Paginate
|
||||
onPageChange={page => fetchClaims(uri, page)}
|
||||
totalPages={totalPages}
|
||||
loading={fetching && !hasContent}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -8,10 +8,13 @@ import {
|
|||
makeSelectThumbnailForUri,
|
||||
makeSelectTitleForUri,
|
||||
makeSelectClaimIsNsfw,
|
||||
selectBlockedChannels,
|
||||
selectChannelIsBlocked,
|
||||
} from 'lbry-redux';
|
||||
import { selectBlackListedOutpoints, selectFilteredOutpoints } from 'lbryinc';
|
||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||
import { makeSelectHasVisitedUri } from 'redux/selectors/content';
|
||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||
import ClaimPreview from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -25,7 +28,10 @@ const select = (state, props) => ({
|
|||
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
|
||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||
filteredOutpoints: selectFilteredOutpoints(state),
|
||||
blockedChannelUris: selectBlockedChannels(state),
|
||||
hasVisitedUri: makeSelectHasVisitedUri(props.uri)(state),
|
||||
channelIsBlocked: selectChannelIsBlocked(props.uri)(state),
|
||||
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
|
|
@ -13,12 +13,14 @@ import FileProperties from 'component/fileProperties';
|
|||
import ClaimTags from 'component/claimTags';
|
||||
import SubscribeButton from 'component/subscribeButton';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import BlockButton from 'component/blockButton';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
claim: ?Claim,
|
||||
obscureNsfw: boolean,
|
||||
showUserBlocked: boolean,
|
||||
claimIsMine: boolean,
|
||||
pending?: boolean,
|
||||
resolveUri: string => void,
|
||||
|
@ -39,6 +41,9 @@ type Props = {
|
|||
txid: string,
|
||||
nout: number,
|
||||
}>,
|
||||
blockedChannelUris: Array<string>,
|
||||
channelIsBlocked: boolean,
|
||||
isSubscribed: boolean,
|
||||
};
|
||||
|
||||
function ClaimPreview(props: Props) {
|
||||
|
@ -58,7 +63,11 @@ function ClaimPreview(props: Props) {
|
|||
type,
|
||||
blackListedOutpoints,
|
||||
filteredOutpoints,
|
||||
blockedChannelUris,
|
||||
hasVisitedUri,
|
||||
showUserBlocked,
|
||||
channelIsBlocked,
|
||||
isSubscribed,
|
||||
} = props;
|
||||
const haventFetched = claim === undefined;
|
||||
const abandoned = !isResolvingUri && !claim;
|
||||
|
@ -93,6 +102,10 @@ function ClaimPreview(props: Props) {
|
|||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
|
||||
);
|
||||
}
|
||||
// if showUserBlocked wasnt passed to claimPreview (for blocked page) hide user-blocked channels
|
||||
if (claim && !shouldHide && !showUserBlocked && blockedChannelUris.length && signingChannel) {
|
||||
shouldHide = blockedChannelUris.some(blockedUri => blockedUri === signingChannel.permanent_url);
|
||||
}
|
||||
|
||||
function handleContextMenu(e) {
|
||||
e.preventDefault();
|
||||
|
@ -152,7 +165,10 @@ function ClaimPreview(props: Props) {
|
|||
</div>
|
||||
{!hideActions && (
|
||||
<div>
|
||||
{isChannel && <SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />}
|
||||
{isChannel && !channelIsBlocked && (
|
||||
<SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
|
||||
)}
|
||||
{isChannel && !isSubscribed && <BlockButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />}
|
||||
{!isChannel && <FileProperties uri={uri} />}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -128,6 +128,17 @@ export const icons = {
|
|||
<line x1="12" y1="17" x2="12" y2="17" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.BLOCK]: buildIcon(
|
||||
<g>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="4.93" y1="4.93" x2="19.07" y2="19.07" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.UNBLOCK]: buildIcon(
|
||||
<g>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.LIGHT]: buildIcon(
|
||||
<g>
|
||||
<circle cx="12" cy="12" r="5" />
|
||||
|
|
|
@ -21,6 +21,7 @@ import WalletPage from 'page/wallet';
|
|||
import NavigationHistory from 'page/navigationHistory';
|
||||
import TagsPage from 'page/tags';
|
||||
import FollowingPage from 'page/following';
|
||||
import ListBlocked from 'page/listBlocked';
|
||||
|
||||
// Tell the browser we are handling scroll restoration
|
||||
if ('scrollRestoration' in history) {
|
||||
|
@ -64,6 +65,7 @@ function AppRouter(props: Props) {
|
|||
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
|
||||
<Route path={`/$/${PAGES.FOLLOWING}`} exact component={FollowingPage} />
|
||||
<Route path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
||||
<Route path={`/$/${PAGES.BLOCKED}`} exact component={ListBlocked} />
|
||||
{/* Below need to go at the end to make sure we don't match any of our pages first */}
|
||||
<Route path="/:claimName" exact component={ShowPage} />
|
||||
<Route path="/:claimName/:contentName" exact component={ShowPage} />
|
||||
|
|
|
@ -6,7 +6,7 @@ import { parseURI } from 'lbry-redux';
|
|||
import Button from 'component/button';
|
||||
import useHover from 'util/use-hover';
|
||||
|
||||
type SubscribtionArgs = {
|
||||
type SubscriptionArgs = {
|
||||
channelName: string,
|
||||
uri: string,
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ type Props = {
|
|||
isSubscribed: boolean,
|
||||
subscriptions: Array<string>,
|
||||
doChannelSubscribe: ({ channelName: string, uri: string }) => void,
|
||||
doChannelUnsubscribe: SubscribtionArgs => void,
|
||||
doChannelUnsubscribe: SubscriptionArgs => void,
|
||||
doOpenModal: (id: string) => void,
|
||||
showSnackBarOnSubscribe: boolean,
|
||||
doToast: ({ message: string }) => void,
|
||||
|
|
|
@ -71,3 +71,5 @@ export const DARK = 'Moon';
|
|||
export const LIBRARY = 'Folder';
|
||||
export const TAG = 'Tag';
|
||||
export const SUPPORT = 'TrendingUp';
|
||||
export const BLOCK = 'Slash';
|
||||
export const UNBLOCK = 'Circle';
|
||||
|
|
|
@ -20,3 +20,4 @@ export const SEARCH = 'search';
|
|||
export const TRANSACTIONS = 'transactions';
|
||||
export const TAGS = 'tags';
|
||||
export const WALLET = 'wallet';
|
||||
export const BLOCKED = 'blocked';
|
||||
|
|
|
@ -6,7 +6,9 @@ import {
|
|||
makeSelectCoverForUri,
|
||||
selectCurrentChannelPage,
|
||||
makeSelectClaimForUri,
|
||||
selectChannelIsBlocked,
|
||||
} from 'lbry-redux';
|
||||
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
|
||||
import ChannelPage from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -16,6 +18,8 @@ const select = (state, props) => ({
|
|||
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
page: selectCurrentChannelPage(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
|
||||
channelIsBlocked: selectChannelIsBlocked(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -3,6 +3,7 @@ import React, { useState } from 'react';
|
|||
import { parseURI } from 'lbry-redux';
|
||||
import Page from 'component/page';
|
||||
import SubscribeButton from 'component/subscribeButton';
|
||||
import BlockButton from 'component/blockButton';
|
||||
import ShareButton from 'component/shareButton';
|
||||
import { Tabs, TabList, Tab, TabPanels, TabPanel } from 'component/common/tabs';
|
||||
import { withRouter } from 'react-router';
|
||||
|
@ -29,10 +30,24 @@ type Props = {
|
|||
history: { push: string => void },
|
||||
match: { params: { attribute: ?string } },
|
||||
channelIsMine: boolean,
|
||||
isSubscribed: boolean,
|
||||
channelIsBlocked: boolean,
|
||||
};
|
||||
|
||||
function ChannelPage(props: Props) {
|
||||
const { uri, title, cover, history, location, page, channelIsMine, thumbnail, claim } = props;
|
||||
const {
|
||||
uri,
|
||||
title,
|
||||
cover,
|
||||
history,
|
||||
location,
|
||||
page,
|
||||
channelIsMine,
|
||||
thumbnail,
|
||||
claim,
|
||||
isSubscribed,
|
||||
channelIsBlocked,
|
||||
} = props;
|
||||
const { channelName } = parseURI(uri);
|
||||
const { search } = location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
|
@ -91,7 +106,8 @@ function ChannelPage(props: Props) {
|
|||
<Tab>{editing ? __('Editing Your Channel') : __('About')}</Tab>
|
||||
<div className="card__actions--inline">
|
||||
<ShareButton uri={uri} />
|
||||
<SubscribeButton uri={permanentUrl} />
|
||||
{!channelIsBlocked && <SubscribeButton uri={permanentUrl} />}
|
||||
{!isSubscribed && <BlockButton uri={permanentUrl} />}
|
||||
</div>
|
||||
</TabList>
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ type Props = {
|
|||
channelUri: string,
|
||||
viewCount: number,
|
||||
prepareEdit: ({}, string, {}) => void,
|
||||
openModal: (id: string, { uri: string, claimIsMine: boolean, isSupport: boolean }) => void,
|
||||
openModal: (id: string, { [key: string]: any }) => void,
|
||||
markSubscriptionRead: (string, string) => void,
|
||||
fetchViewCount: string => void,
|
||||
balance: number,
|
||||
|
|
12
src/ui/page/listBlocked/index.js
Normal file
12
src/ui/page/listBlocked/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectBlockedChannels } from 'lbry-redux';
|
||||
import ListBlocked from './view';
|
||||
|
||||
const select = state => ({
|
||||
uris: selectBlockedChannels(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
)(ListBlocked);
|
37
src/ui/page/listBlocked/view.jsx
Normal file
37
src/ui/page/listBlocked/view.jsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import ClaimList from 'component/claimList';
|
||||
import Page from 'component/page';
|
||||
|
||||
type Props = {
|
||||
uris: Array<string>,
|
||||
};
|
||||
|
||||
function ListBlocked(props: Props) {
|
||||
const { uris } = props;
|
||||
|
||||
return (
|
||||
<Page notContained>
|
||||
{uris && uris.length ? (
|
||||
<div className="card">
|
||||
<ClaimList
|
||||
header={<h1>{__('Your Blocked Channels')}</h1>}
|
||||
persistedStorageKey="block-list-published"
|
||||
uris={uris}
|
||||
defaultSort
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="main--empty">
|
||||
<section className="card card--section">
|
||||
<header className="card__header">
|
||||
<h2 className="card__title">{__('It looks like you have no blocked channels.')}</h2>
|
||||
</header>
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default ListBlocked;
|
|
@ -8,7 +8,7 @@ import {
|
|||
selectLanguages,
|
||||
selectosNotificationsEnabled,
|
||||
} from 'redux/selectors/settings';
|
||||
import { doWalletStatus, selectWalletIsEncrypted } from 'lbry-redux';
|
||||
import { doWalletStatus, selectWalletIsEncrypted, selectBlockedChannelsCount } from 'lbry-redux';
|
||||
import SettingsPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
|
@ -26,6 +26,7 @@ const select = state => ({
|
|||
osNotificationsEnabled: selectosNotificationsEnabled(state),
|
||||
autoDownload: makeSelectClientSetting(settings.AUTO_DOWNLOAD)(state),
|
||||
supportOption: makeSelectClientSetting(settings.SUPPORT_OPTION)(state),
|
||||
userBlockedChannelsCount: selectBlockedChannelsCount(state),
|
||||
hideBalance: makeSelectClientSetting(settings.HIDE_BALANCE)(state),
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// @flow
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import * as React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { FormField, FormFieldPrice, Form } from 'component/common/form';
|
||||
|
@ -44,6 +45,7 @@ type Props = {
|
|||
walletEncrypted: boolean,
|
||||
osNotificationsEnabled: boolean,
|
||||
supportOption: boolean,
|
||||
userBlockedChannelsCount?: number,
|
||||
hideBalance: boolean,
|
||||
};
|
||||
|
||||
|
@ -153,6 +155,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
setClientSetting,
|
||||
supportOption,
|
||||
hideBalance,
|
||||
userBlockedChannelsCount,
|
||||
} = this.props;
|
||||
|
||||
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
||||
|
@ -278,6 +281,16 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
|||
</Form>
|
||||
</section>
|
||||
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__('Blocked Channels')}</h2>
|
||||
<p className="card__subtitle card__help ">
|
||||
{__('You have')} {userBlockedChannelsCount} {__('blocked')}{' '}
|
||||
{userBlockedChannelsCount === 1 && __('channel')}
|
||||
{userBlockedChannelsCount !== 1 && __('channels')}.{' '}
|
||||
<Button button="link" label={__('Manage')} navigate={`/$/${PAGES.BLOCKED}`} />
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__('Notifications')}</h2>
|
||||
<Form>
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
notificationsReducer,
|
||||
tagsReducer,
|
||||
commentReducer,
|
||||
blockChannelReducer,
|
||||
publishReducer,
|
||||
} from 'lbry-redux';
|
||||
import {
|
||||
|
@ -47,6 +48,7 @@ export default history =>
|
|||
stats: statsReducer,
|
||||
subscriptions: subscriptionsReducer,
|
||||
tags: tagsReducer,
|
||||
blockedChannels: blockChannelReducer,
|
||||
user: userReducer,
|
||||
wallet: walletReducer,
|
||||
});
|
||||
|
|
|
@ -48,6 +48,7 @@ const appFilter = createFilter('app', ['hasClickedComment', 'searchOptionsExpand
|
|||
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
||||
const searchFilter = createFilter('search', ['options']);
|
||||
const tagsFilter = createFilter('tags', ['followedTags']);
|
||||
const blockedFilter = createFilter('blockedChannels', ['blockedChannels']);
|
||||
const whiteListedReducers = [
|
||||
// @if TARGET='app'
|
||||
'publish',
|
||||
|
@ -59,6 +60,7 @@ const whiteListedReducers = [
|
|||
'app',
|
||||
'search',
|
||||
'tags',
|
||||
'blockedChannels',
|
||||
];
|
||||
|
||||
const transforms = [
|
||||
|
@ -66,6 +68,7 @@ const transforms = [
|
|||
walletFilter,
|
||||
contentFilter,
|
||||
fileInfoFilter,
|
||||
blockedFilter,
|
||||
// @endif
|
||||
appFilter,
|
||||
searchFilter,
|
||||
|
|
Loading…
Reference in a new issue