adds user ability to block channels

small changes

blocked channels page

most features done

tweaks
This commit is contained in:
jessop 2019-07-08 16:54:58 -04:00
parent 223a68413f
commit 5ab165131f
21 changed files with 217 additions and 16 deletions

View file

@ -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

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

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

View file

@ -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),
};
};

View file

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

View file

@ -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 => ({

View file

@ -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>
)}

View file

@ -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" />

View file

@ -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} />

View file

@ -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,

View file

@ -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';

View file

@ -20,3 +20,4 @@ export const SEARCH = 'search';
export const TRANSACTIONS = 'transactions';
export const TAGS = 'tags';
export const WALLET = 'wallet';
export const BLOCKED = 'blocked';

View file

@ -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(

View file

@ -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>

View file

@ -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,

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

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

View file

@ -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),
});

View file

@ -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>

View file

@ -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,
});

View file

@ -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,