commit
7e2676d38a
29 changed files with 272 additions and 31 deletions
|
@ -124,7 +124,7 @@
|
|||
"jsmediatags": "^3.8.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#469d3b70cbe39f28aafee4e072fdfc5bac604c4b",
|
||||
"lbry-redux": "lbryio/lbry-redux#8f12baa88f6f057eb3b7d0cf04d6e4bb0eb11763",
|
||||
"lbryinc": "lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
|
|
|
@ -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
|
||||
|
|
16
src/ui/component/blockButton/index.js
Normal file
16
src/ui/component/blockButton/index.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectChannelIsBlocked, doToggleBlockChannel, doToast, makeSelectShortUrlForUri } from 'lbry-redux';
|
||||
import BlockButton from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
channelIsBlocked: selectChannelIsBlocked(props.uri)(state),
|
||||
shortUrl: makeSelectShortUrlForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
{
|
||||
toggleBlockChannel: doToggleBlockChannel,
|
||||
doToast,
|
||||
}
|
||||
)(BlockButton);
|
41
src/ui/component/blockButton/view.jsx
Normal file
41
src/ui/component/blockButton/view.jsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
// @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,
|
||||
shortUrl: 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, shortUrl, 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={ICONS.BLOCK}
|
||||
button={'alt'}
|
||||
label={blockedOverride || blockLabel}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
if (!channelIsBlocked) {
|
||||
doToast({ message: `Blocked ${shortUrl}`, 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="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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,24 +9,25 @@ type Props = {
|
|||
uri: string,
|
||||
className?: string,
|
||||
thumbnailPreview: ?string,
|
||||
obscure?: boolean,
|
||||
};
|
||||
|
||||
function ChannelThumbnail(props: Props) {
|
||||
const { thumbnail, uri, className, thumbnailPreview } = props;
|
||||
const { thumbnail, uri, className, thumbnailPreview, obscure } = props;
|
||||
|
||||
// Generate a random color class based on the first letter of the channel name
|
||||
const { channelName } = parseURI(uri);
|
||||
const initializer = channelName.charCodeAt(0) - 65; // will be between 0 and 57
|
||||
const colorClassName = `channel-thumbnail__default--${initializer % 4}`;
|
||||
|
||||
const showThumb = !obscure && !!thumbnail;
|
||||
return (
|
||||
<div
|
||||
className={classnames('channel-thumbnail', className, {
|
||||
[colorClassName]: !thumbnail,
|
||||
[colorClassName]: !showThumb,
|
||||
})}
|
||||
>
|
||||
{!thumbnail && <img className="channel-thumbnail__default" src={thumbnailPreview || Gerbil} />}
|
||||
{thumbnail && <img className="channel-thumbnail__custom" src={thumbnailPreview || thumbnail} />}
|
||||
{!showThumb && <img className="channel-thumbnail__default" src={thumbnailPreview || Gerbil} />}
|
||||
{showThumb && <img className="channel-thumbnail__custom" src={thumbnailPreview || thumbnail} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ type Props = {
|
|||
id?: string,
|
||||
// If using the default header, this is a unique ID needed to persist the state of the filter setting
|
||||
persistedStorageKey?: string,
|
||||
showHiddenByUser: boolean,
|
||||
};
|
||||
|
||||
export default function ClaimList(props: Props) {
|
||||
|
@ -43,6 +44,7 @@ export default function ClaimList(props: Props) {
|
|||
pageSize,
|
||||
page,
|
||||
id,
|
||||
showHiddenByUser,
|
||||
} = props;
|
||||
const [scrollBottomCbMap, setScrollBottomCbMap] = useState({});
|
||||
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
||||
|
@ -113,7 +115,7 @@ export default function ClaimList(props: Props) {
|
|||
<ul className="ul--no-style">
|
||||
{sortedUris.map((uri, index) => (
|
||||
<React.Fragment key={uri}>
|
||||
<ClaimPreview uri={uri} type={type} />
|
||||
<ClaimPreview uri={uri} type={type} showUserBlocked={showHiddenByUser} />
|
||||
{index === 4 && injectedItem && injectedItem}
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { connect } from 'react-redux';
|
||||
import { doClaimSearch, selectClaimSearchByQuery, selectFetchingClaimSearch, doToggleTagFollow } from 'lbry-redux';
|
||||
import {
|
||||
doClaimSearch,
|
||||
selectClaimSearchByQuery,
|
||||
selectFetchingClaimSearch,
|
||||
doToggleTagFollow,
|
||||
selectBlockedChannels,
|
||||
} from 'lbry-redux';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import ClaimListDiscover from './view';
|
||||
|
@ -10,6 +16,7 @@ const select = state => ({
|
|||
loading: selectFetchingClaimSearch(state),
|
||||
subscribedChannels: selectSubscriptions(state),
|
||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_NSFW)(state),
|
||||
hiddenUris: selectBlockedChannels(state),
|
||||
});
|
||||
|
||||
const perform = {
|
||||
|
|
|
@ -44,6 +44,7 @@ type Props = {
|
|||
claimSearchByQuery: {
|
||||
[string]: Array<string>,
|
||||
},
|
||||
hiddenUris: Array<string>,
|
||||
};
|
||||
|
||||
function ClaimListDiscover(props: Props) {
|
||||
|
@ -59,6 +60,7 @@ function ClaimListDiscover(props: Props) {
|
|||
showNsfw,
|
||||
history,
|
||||
location,
|
||||
hiddenUris,
|
||||
} = props;
|
||||
const didNavigateForward = history.action === 'PUSH';
|
||||
const { search, pathname } = location;
|
||||
|
@ -75,6 +77,7 @@ function ClaimListDiscover(props: Props) {
|
|||
no_totals: boolean,
|
||||
any_tags: Array<string>,
|
||||
channel_ids: Array<string>,
|
||||
not_channel_ids: Array<string>,
|
||||
not_tags: Array<string>,
|
||||
order_by: Array<string>,
|
||||
release_time?: string,
|
||||
|
@ -86,6 +89,7 @@ function ClaimListDiscover(props: Props) {
|
|||
no_totals: true,
|
||||
any_tags: (personalView && personalSort === SEARCH_SORT_YOU) || !personalView ? tags : [],
|
||||
channel_ids: personalSort === SEARCH_SORT_CHANNELS ? subscribedChannels.map(sub => sub.uri.split('#')[1]) : [],
|
||||
not_channel_ids: hiddenUris && hiddenUris.length ? hiddenUris.map(hiddenUri => hiddenUri.split('#')[1]) : [],
|
||||
not_tags: !showNsfw ? MATURE_TAGS : [],
|
||||
order_by:
|
||||
typeSort === TYPE_TRENDING
|
||||
|
|
|
@ -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,15 @@ function ClaimPreview(props: Props) {
|
|||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
|
||||
);
|
||||
}
|
||||
// block stream claims
|
||||
if (claim && !shouldHide && !showUserBlocked && blockedChannelUris.length && signingChannel) {
|
||||
shouldHide = blockedChannelUris.some(blockedUri => blockedUri === signingChannel.permanent_url);
|
||||
}
|
||||
// block channel claims if we can't control for them in claim search
|
||||
// e.g. fetchRecommendedSubscriptions
|
||||
if (claim && isChannel && !shouldHide && !showUserBlocked && blockedChannelUris.length && isChannel) {
|
||||
shouldHide = blockedChannelUris.some(blockedUri => blockedUri === claim.permanent_url);
|
||||
}
|
||||
|
||||
function handleContextMenu(e) {
|
||||
e.preventDefault();
|
||||
|
@ -144,15 +162,18 @@ function ClaimPreview(props: Props) {
|
|||
'claim-preview--pending': pending,
|
||||
})}
|
||||
>
|
||||
{isChannel ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />}
|
||||
{isChannel ? <ChannelThumbnail uri={uri} obscure={channelIsBlocked} /> : <CardMedia thumbnail={thumbnail} />}
|
||||
<div className="claim-preview-metadata">
|
||||
<div className="claim-preview-info">
|
||||
<div className="claim-preview-title">
|
||||
{claim ? <TruncatedText text={title || claim.name} lines={1} /> : <span>{__('Nothing here')}</span>}
|
||||
</div>
|
||||
{!hideActions && (
|
||||
<div>
|
||||
{isChannel && <SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />}
|
||||
<div className="card__actions--inline">
|
||||
{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';
|
||||
|
@ -14,6 +15,7 @@ import ChannelThumbnail from 'component/channelThumbnail';
|
|||
import ChannelEdit from 'component/channelEdit';
|
||||
import ClaimUri from 'component/claimUri';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import classnames from 'classnames';
|
||||
|
||||
const PAGE_VIEW_QUERY = `view`;
|
||||
const ABOUT_PAGE = `about`;
|
||||
|
@ -29,10 +31,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);
|
||||
|
@ -62,11 +78,18 @@ function ChannelPage(props: Props) {
|
|||
<Page>
|
||||
<div className="card">
|
||||
<header className="channel-cover">
|
||||
{!editing && cover && <img className="channel-cover__custom" src={cover} />}
|
||||
{!editing && cover && (
|
||||
<img
|
||||
className={classnames('channel-cover__custom', { 'channel__image--blurred': channelIsBlocked })}
|
||||
src={cover}
|
||||
/>
|
||||
)}
|
||||
{editing && <img className="channel-cover__custom" src={coverPreview} />}
|
||||
{/* component that offers select/upload */}
|
||||
<div className="channel__primary-info ">
|
||||
{!editing && <ChannelThumbnail className="channel__thumbnail--channel-page" uri={uri} />}
|
||||
{!editing && (
|
||||
<ChannelThumbnail className="channel__thumbnail--channel-page" uri={uri} obscure={channelIsBlocked} />
|
||||
)}
|
||||
{editing && (
|
||||
<ChannelThumbnail
|
||||
className="channel__thumbnail--channel-page"
|
||||
|
@ -91,7 +114,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>
|
||||
{uris && uris.length ? (
|
||||
<div className="card">
|
||||
<ClaimList
|
||||
header={<h1>{__('Your Blocked Channels')}</h1>}
|
||||
persistedStorageKey="block-list-published"
|
||||
uris={uris}
|
||||
defaultSort
|
||||
showHiddenByUser
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="main--empty">
|
||||
<section className="card card--section">
|
||||
<h2 className="card__title">{__('You aren’t blocking any channels')}</h2>
|
||||
<p className="card__subtitle">When you block a channel, all content from that channel will be hidden.</p>
|
||||
</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,
|
||||
blockedReducer,
|
||||
publishReducer,
|
||||
} from 'lbry-redux';
|
||||
import {
|
||||
|
@ -25,7 +26,6 @@ import contentReducer from 'redux/reducers/content';
|
|||
import settingsReducer from 'redux/reducers/settings';
|
||||
import subscriptionsReducer from 'redux/reducers/subscriptions';
|
||||
|
||||
|
||||
export default history =>
|
||||
combineReducers({
|
||||
router: connectRouter(history),
|
||||
|
@ -47,6 +47,7 @@ export default history =>
|
|||
stats: statsReducer,
|
||||
subscriptions: subscriptionsReducer,
|
||||
tags: tagsReducer,
|
||||
blocked: blockedReducer,
|
||||
user: userReducer,
|
||||
wallet: walletReducer,
|
||||
});
|
||||
|
|
|
@ -102,3 +102,7 @@ $metadata-z-index: 1;
|
|||
color: rgba($lbry-white, 0.75);
|
||||
margin-right: var(--spacing-large);
|
||||
}
|
||||
|
||||
.channel__image--blurred {
|
||||
filter: blur(16px);
|
||||
}
|
||||
|
|
|
@ -231,6 +231,14 @@ $border-color--dark: var(--dm-color-04);
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.claim-preview-actions {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
.claim-preview__button {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
.claim-preview-properties {
|
||||
align-items: flex-end;
|
||||
flex: 1;
|
||||
|
|
|
@ -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('blocked', ['blockedChannels']);
|
||||
const whiteListedReducers = [
|
||||
// @if TARGET='app'
|
||||
'publish',
|
||||
|
@ -59,6 +60,7 @@ const whiteListedReducers = [
|
|||
'app',
|
||||
'search',
|
||||
'tags',
|
||||
'blocked',
|
||||
];
|
||||
|
||||
const transforms = [
|
||||
|
@ -66,6 +68,7 @@ const transforms = [
|
|||
walletFilter,
|
||||
contentFilter,
|
||||
fileInfoFilter,
|
||||
blockedFilter,
|
||||
// @endif
|
||||
appFilter,
|
||||
searchFilter,
|
||||
|
|
|
@ -6653,9 +6653,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
|
|||
yargs "^13.2.2"
|
||||
zstd-codec "^0.1.1"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#469d3b70cbe39f28aafee4e072fdfc5bac604c4b:
|
||||
lbry-redux@lbryio/lbry-redux#8f12baa88f6f057eb3b7d0cf04d6e4bb0eb11763:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/469d3b70cbe39f28aafee4e072fdfc5bac604c4b"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/8f12baa88f6f057eb3b7d0cf04d6e4bb0eb11763"
|
||||
dependencies:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
|
Loading…
Add table
Reference in a new issue