Additional pop up menu options

This commit is contained in:
saltrafael 2021-06-11 17:49:18 -03:00 committed by jessopb
parent 524370711c
commit 2aaa9f358b
16 changed files with 336 additions and 150 deletions

View file

@ -6,8 +6,8 @@ type Props = {
uri: string, uri: string,
isBlocked: boolean, isBlocked: boolean,
isBlockingOrUnBlocking: boolean, isBlockingOrUnBlocking: boolean,
doCommentModUnBlock: (string) => void, doCommentModUnBlock: (string, boolean) => void,
doCommentModBlock: (string) => void, doCommentModBlock: (string, boolean) => void,
}; };
function ChannelBlockButton(props: Props) { function ChannelBlockButton(props: Props) {
@ -15,9 +15,9 @@ function ChannelBlockButton(props: Props) {
function handleClick() { function handleClick() {
if (isBlocked) { if (isBlocked) {
doCommentModUnBlock(uri); doCommentModUnBlock(uri, false);
} else { } else {
doCommentModBlock(uri); doCommentModBlock(uri, false);
} }
} }

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doToggleMuteChannel } from 'redux/actions/blocked'; import { doChannelMute, doChannelUnmute } from 'redux/actions/blocked';
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked'; import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import ChannelMuteButton from './view'; import ChannelMuteButton from './view';
@ -8,5 +8,6 @@ const select = (state, props) => ({
}); });
export default connect(select, { export default connect(select, {
doToggleMuteChannel, doChannelMute,
doChannelUnmute,
})(ChannelMuteButton); })(ChannelMuteButton);

View file

@ -6,17 +6,26 @@ type Props = {
uri: string, uri: string,
isMuted: boolean, isMuted: boolean,
channelClaim: ?ChannelClaim, channelClaim: ?ChannelClaim,
doToggleMuteChannel: (string) => void, doChannelMute: (string, boolean) => void,
doChannelUnmute: (string, boolean) => void,
}; };
function ChannelBlockButton(props: Props) { function ChannelBlockButton(props: Props) {
const { uri, doToggleMuteChannel, isMuted } = props; const { uri, doChannelMute, doChannelUnmute, isMuted } = props;
function handleClick() {
if (isMuted) {
doChannelUnmute(uri, false);
} else {
doChannelMute(uri, false);
}
}
return ( return (
<Button <Button
button={isMuted ? 'alt' : 'secondary'} button={isMuted ? 'alt' : 'secondary'}
label={isMuted ? __('Unmute') : __('Mute')} label={isMuted ? __('Unmute') : __('Mute')}
onClick={() => doToggleMuteChannel(uri)} onClick={handleClick}
/> />
); );
} }

View file

@ -44,7 +44,6 @@ type Props = {
liveLivestreamsFirst?: boolean, liveLivestreamsFirst?: boolean,
livestreamMap?: { [string]: any }, livestreamMap?: { [string]: any },
searchOptions?: any, searchOptions?: any,
channelIsMine: boolean,
collectionId?: string, collectionId?: string,
}; };
@ -76,7 +75,6 @@ export default function ClaimList(props: Props) {
liveLivestreamsFirst, liveLivestreamsFirst,
livestreamMap, livestreamMap,
searchOptions, searchOptions,
channelIsMine,
collectionId, collectionId,
} = props; } = props;
@ -136,7 +134,6 @@ export default function ClaimList(props: Props) {
showHiddenByUser={showHiddenByUser} showHiddenByUser={showHiddenByUser}
properties={renderProperties} properties={renderProperties}
live={resolveLive(index)} live={resolveLive(index)}
channelIsMine={channelIsMine}
collectionId={collectionId} collectionId={collectionId}
/> />
))} ))}

View file

@ -73,7 +73,6 @@ type Props = {
livestreamMap?: { [string]: any }, livestreamMap?: { [string]: any },
hasSource?: boolean, hasSource?: boolean,
isChannel?: boolean, isChannel?: boolean,
channelIsMine?: boolean,
empty?: string, empty?: string,
}; };
@ -130,7 +129,6 @@ function ClaimListDiscover(props: Props) {
livestreamMap, livestreamMap,
hasSource, hasSource,
isChannel = false, isChannel = false,
channelIsMine = false,
empty, empty,
} = props; } = props;
const didNavigateForward = history.action === 'PUSH'; const didNavigateForward = history.action === 'PUSH';
@ -514,7 +512,6 @@ function ClaimListDiscover(props: Props) {
liveLivestreamsFirst={liveLivestreamsFirst} liveLivestreamsFirst={liveLivestreamsFirst}
livestreamMap={livestreamMap} livestreamMap={livestreamMap}
searchOptions={options} searchOptions={options}
channelIsMine={channelIsMine}
/> />
{loading && ( {loading && (
<div className="claim-grid"> <div className="claim-grid">

View file

@ -2,35 +2,38 @@ import { connect } from 'react-redux';
import { import {
doCollectionEdit, doCollectionEdit,
makeSelectClaimForUri, makeSelectClaimForUri,
makeSelectClaimIsMine, makeSelectFileInfoForUri,
doPrepareEdit,
makeSelectCollectionForIdHasClaimUrl, makeSelectCollectionForIdHasClaimUrl,
makeSelectNameForCollectionId, makeSelectNameForCollectionId,
makeSelectCollectionIsMine, makeSelectCollectionIsMine,
COLLECTIONS_CONSTS, COLLECTIONS_CONSTS,
} from 'lbry-redux'; } from 'lbry-redux';
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked'; import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import { doToggleMuteChannel } from 'redux/actions/blocked'; import { doChannelMute, doChannelUnmute } from 'redux/actions/blocked';
import { doSetActiveChannel, doSetIncognito, doOpenModal } from 'redux/actions/app';
import { doCommentModBlock, doCommentModUnBlock } from 'redux/actions/comments'; import { doCommentModBlock, doCommentModUnBlock } from 'redux/actions/comments';
import { makeSelectChannelIsBlocked } from 'redux/selectors/comments'; import { makeSelectChannelIsBlocked } from 'redux/selectors/comments';
import { doOpenModal } from 'redux/actions/app';
import { doToast } from 'redux/actions/notifications'; import { doToast } from 'redux/actions/notifications';
import { makeSelectUserPropForProp } from 'redux/selectors/user'; import { makeSelectUserPropForProp } from 'redux/selectors/user';
import ClaimPreview from './view';
import * as USER from 'constants/user'; import * as USER from 'constants/user';
import { makeSelectSigningIsMine } from 'redux/selectors/content';
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
import ClaimPreview from './view';
import fs from 'fs';
const select = (state, props) => { const select = (state, props) => {
const claim = makeSelectClaimForUri(props.uri)(state); const claim = makeSelectClaimForUri(props.uri)(state);
const permanentUri = claim && claim.permanent_url; const permanentUri = claim && claim.permanent_url;
return { return {
claim, claim,
claimIsMine: props.channelIsMine claimIsMine: makeSelectSigningIsMine(props.uri)(state),
? props.isRepost
? makeSelectClaimIsMine(props.uri)(state)
: true
: makeSelectClaimIsMine(props.uri)(state),
hasClaimInWatchLater: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.WATCH_LATER_ID, permanentUri)(state), hasClaimInWatchLater: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.WATCH_LATER_ID, permanentUri)(state),
channelIsMuted: makeSelectChannelIsMuted(props.uri)(state), channelIsMuted: makeSelectChannelIsMuted(props.uri)(state),
channelIsBlocked: makeSelectChannelIsBlocked(props.uri)(state), channelIsBlocked: makeSelectChannelIsBlocked(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
isSubscribed: makeSelectIsSubscribed(props.channelUri, true)(state),
claimInCollection: makeSelectCollectionForIdHasClaimUrl(props.collectionId, permanentUri)(state), claimInCollection: makeSelectCollectionForIdHasClaimUrl(props.collectionId, permanentUri)(state),
collectionName: makeSelectNameForCollectionId(props.collectionId)(state), collectionName: makeSelectNameForCollectionId(props.collectionId)(state),
isMyCollection: makeSelectCollectionIsMine(props.collectionId)(state), isMyCollection: makeSelectCollectionIsMine(props.collectionId)(state),
@ -38,11 +41,26 @@ const select = (state, props) => {
}; };
}; };
export default connect(select, { const perform = (dispatch) => ({
doToggleMuteChannel, prepareEdit: (publishData, uri, fileInfo) => {
doCommentModBlock, if (publishData.signing_channel) {
doCommentModUnBlock, dispatch(doSetIncognito(false));
doCollectionEdit, dispatch(doSetActiveChannel(publishData.signing_channel.claim_id));
doOpenModal, } else {
doToast, dispatch(doSetIncognito(true));
})(ClaimPreview); }
dispatch(doPrepareEdit(publishData, uri, fileInfo, fs));
},
doToast: (props) => dispatch(doToast(props)),
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
doChannelMute: (channelUri) => dispatch(doChannelMute(channelUri)),
doChannelUnmute: (channelUri) => dispatch(doChannelUnmute(channelUri)),
doCommentModBlock: (channelUri) => dispatch(doCommentModBlock(channelUri)),
doCommentModUnBlock: (channelUri) => dispatch(doCommentModUnBlock(channelUri)),
doChannelSubscribe: (subscription) => dispatch(doChannelSubscribe(subscription)),
doChannelUnsubscribe: (subscription) => dispatch(doChannelUnsubscribe(subscription)),
doCollectionEdit: (collection, props) => dispatch(doCollectionEdit(collection, props)),
});
export default connect(select, perform)(ClaimPreview);

View file

@ -9,63 +9,84 @@ import { Menu, MenuButton, MenuList, MenuItem } from '@reach/menu-button';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import { generateShareUrl } from 'util/url'; import { generateShareUrl } from 'util/url';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { COLLECTIONS_CONSTS } from 'lbry-redux'; import { buildURI, parseURI, COLLECTIONS_CONSTS } from 'lbry-redux';
const SHARE_DOMAIN = SHARE_DOMAIN_URL || URL; const SHARE_DOMAIN = SHARE_DOMAIN_URL || URL;
const PAGE_VIEW_QUERY = `view`;
const EDIT_PAGE = 'edit';
type SubscriptionArgs = {
channelName: string,
uri: string,
notificationsDisabled?: boolean,
};
type Props = { type Props = {
uri: string, uri: string,
channelUri: string,
claim: ?Claim, claim: ?Claim,
openModal: (id: string, {}) => void,
inline?: boolean, inline?: boolean,
claimIsMine: boolean,
channelIsMuted: boolean, channelIsMuted: boolean,
channelIsBlocked: boolean, channelIsBlocked: boolean,
doToggleMuteChannel: (string) => void, doChannelMute: (string) => void,
doChannelUnmute: (string) => void,
doCommentModBlock: (string) => void, doCommentModBlock: (string) => void,
doCommentModUnBlock: (string) => void, doCommentModUnBlock: (string) => void,
channelIsMine: boolean,
isRepost: boolean, isRepost: boolean,
doCollectionEdit: (string, any) => void, doCollectionEdit: (string, any) => void,
hasClaimInWatchLater: boolean, hasClaimInWatchLater: boolean,
doOpenModal: (string, {}) => void,
claimInCollection: boolean, claimInCollection: boolean,
collectionName?: string, collectionName?: string,
collectionId: string, collectionId: string,
isMyCollection: boolean, isMyCollection: boolean,
doToast: ({ message: string }) => void, doToast: ({ message: string }) => void,
hasExperimentalUi: boolean, hasExperimentalUi: boolean,
claimIsMine: boolean,
fileInfo: FileListItem,
prepareEdit: ({}, string, {}) => void,
isSubscribed: boolean,
doChannelSubscribe: (SubscriptionArgs) => void,
doChannelUnsubscribe: (SubscriptionArgs) => void,
}; };
function ClaimMenuList(props: Props) { function ClaimMenuList(props: Props) {
const { const {
uri, uri,
channelUri,
claim, claim,
openModal,
inline = false, inline = false,
claimIsMine, doChannelMute,
doToggleMuteChannel, doChannelUnmute,
channelIsMuted, channelIsMuted,
channelIsBlocked, channelIsBlocked,
doCommentModBlock, doCommentModBlock,
doCommentModUnBlock, doCommentModUnBlock,
doCollectionEdit, doCollectionEdit,
hasClaimInWatchLater, hasClaimInWatchLater,
doOpenModal,
collectionId, collectionId,
collectionName, collectionName,
isMyCollection, isMyCollection,
doToast, doToast,
hasExperimentalUi, hasExperimentalUi,
claimIsMine,
fileInfo,
prepareEdit,
isSubscribed,
doChannelSubscribe,
doChannelUnsubscribe,
} = props; } = props;
const incognito = channelUri && !(channelUri.includes('@'));
const signingChannel = claim && (claim.signing_channel || claim);
const isChannel = !incognito && signingChannel === claim;
const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0));
const subscriptionLabel = isSubscribed ? __('Unfollow') : __('Follow');
const { push } = useHistory(); const { push } = useHistory();
if (!claim) { if (!claim) {
return null; return null;
} }
const channelUri = claim
? claim.value_type === 'channel'
? claim.permanent_url
: (claim.signing_channel && claim.signing_channel.permanent_url) || ''
: '';
const shareUrl: string = generateShareUrl(SHARE_DOMAIN, uri); const shareUrl: string = generateShareUrl(SHARE_DOMAIN, uri);
const isCollectionClaim = claim && claim.value_type === 'collection'; const isCollectionClaim = claim && claim.value_type === 'collection';
@ -78,8 +99,28 @@ function ClaimMenuList(props: Props) {
// $FlowFixMe // $FlowFixMe
(claim.value.stream_type === 'audio' || claim.value.stream_type === 'video'); (claim.value.stream_type === 'audio' || claim.value.stream_type === 'video');
function handleFollow() {
const permanentUrl = signingChannel && signingChannel.permanent_url;
const { channelName } = parseURI(permanentUrl);
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
subscriptionHandler({
channelName: '@' + channelName,
uri: permanentUrl,
notificationsDisabled: true,
});
}
function handleAnalytics() {
push(`/$/${PAGES.CREATOR_DASHBOARD}?channel=${encodeURIComponent(signingChannel.canonical_url)}`);
}
function handleToggleMute() { function handleToggleMute() {
doToggleMuteChannel(channelUri); if (channelIsMuted) {
doChannelUnmute(channelUri);
} else {
doChannelMute(channelUri);
}
} }
function handleToggleBlock() { function handleToggleBlock() {
@ -90,6 +131,40 @@ function ClaimMenuList(props: Props) {
} }
} }
function handleEdit() {
if (!isChannel) {
const signingChannelName = signingChannel && signingChannel.name;
let editUri;
const uriObject: { streamName: string, streamClaimId: string, channelName?: string } = {
streamName: claim.name,
streamClaimId: claim.claim_id,
};
if (signingChannelName) {
uriObject.channelName = signingChannelName;
}
editUri = buildURI(uriObject);
push(`/$/${PAGES.UPLOAD}`);
prepareEdit(claim, editUri, fileInfo);
} else {
const channelUrl = claim.name + ':' + claim.claim_id;
push(`/${channelUrl}?${PAGE_VIEW_QUERY}=${EDIT_PAGE}`);
}
}
function handleDelete() {
if (!isChannel) {
openModal(MODALS.CONFIRM_FILE_REMOVE, { uri });
} else {
openModal(MODALS.CONFIRM_CLAIM_REVOKE, { claim: claim, cb: () => replace(`/$/${PAGES.CHANNELS}`) });
}
}
function handleSupport() {
openModal(MODALS.SEND_TIP, { uri, isSupport: true });
}
function handleCopyLink() { function handleCopyLink() {
navigator.clipboard.writeText(shareUrl); navigator.clipboard.writeText(shareUrl);
} }
@ -110,6 +185,29 @@ function ClaimMenuList(props: Props) {
<Icon size={20} icon={ICONS.MORE_VERTICAL} /> <Icon size={20} icon={ICONS.MORE_VERTICAL} />
</MenuButton> </MenuButton>
<MenuList className="menu__list"> <MenuList className="menu__list">
{!incognito && (!claimIsMine ? (
<MenuItem className="comment__menu-option" onSelect={handleFollow}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.SUBSCRIBE} />
{subscriptionLabel}
</div>
</MenuItem>
) : (
<MenuItem className="comment__menu-option" onSelect={handleAnalytics}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.ANALYTICS} />
{__('Channel Analytics')}
</div>
</MenuItem>
))}
<MenuItem className="comment__menu-option" onSelect={handleSupport}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.LBC} />
{__('Support')}
</div>
</MenuItem>
{hasExperimentalUi && ( {hasExperimentalUi && (
<> <>
{/* WATCH LATER */} {/* WATCH LATER */}
@ -148,7 +246,7 @@ function ClaimMenuList(props: Props) {
</MenuItem> </MenuItem>
<MenuItem <MenuItem
className="comment__menu-option" className="comment__menu-option"
onSelect={() => doOpenModal(MODALS.COLLECTION_DELETE, { collectionId })} onSelect={() => openModal(MODALS.COLLECTION_DELETE, { collectionId })}
> >
<div className="menu__link"> <div className="menu__link">
<Icon aria-hidden icon={ICONS.DELETE} /> <Icon aria-hidden icon={ICONS.DELETE} />
@ -161,7 +259,7 @@ function ClaimMenuList(props: Props) {
{isPlayable && ( {isPlayable && (
<MenuItem <MenuItem
className="comment__menu-option" className="comment__menu-option"
onSelect={() => doOpenModal(MODALS.COLLECTION_ADD, { uri, type: 'playlist' })} onSelect={() => openModal(MODALS.COLLECTION_ADD, { uri, type: 'playlist' })}
> >
<div className="menu__link"> <div className="menu__link">
<Icon aria-hidden icon={ICONS.STACK} /> <Icon aria-hidden icon={ICONS.STACK} />
@ -169,10 +267,11 @@ function ClaimMenuList(props: Props) {
</div> </div>
</MenuItem> </MenuItem>
)} )}
<hr className="menu__separator" />
</> </>
)} )}
<hr className="menu__separator" />
{channelUri && !claimIsMine && !isMyCollection && ( {channelUri && !claimIsMine && !isMyCollection ? !incognito && (
<> <>
<MenuItem className="comment__menu-option" onSelect={handleToggleBlock}> <MenuItem className="comment__menu-option" onSelect={handleToggleBlock}>
<div className="menu__link"> <div className="menu__link">
@ -187,10 +286,27 @@ function ClaimMenuList(props: Props) {
{channelIsMuted ? __('Unmute Channel') : __('Mute Channel')} {channelIsMuted ? __('Unmute Channel') : __('Mute Channel')}
</div> </div>
</MenuItem> </MenuItem>
</>
) : (
<>
<MenuItem className="comment__menu-option" onSelect={handleEdit}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.EDIT} />
{__('Edit')}
</div>
</MenuItem>
<hr className="menu__separator" /> {showDelete && (
<MenuItem className="comment__menu-option" onSelect={handleDelete}>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.DELETE} />
{__('Delete')}
</div>
</MenuItem>
)}
</> </>
)} )}
<hr className="menu__separator" />
<MenuItem className="comment__menu-option" onSelect={handleCopyLink}> <MenuItem className="comment__menu-option" onSelect={handleCopyLink}>
<div className="menu__link"> <div className="menu__link">

View file

@ -161,6 +161,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
collectionParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, collectionId); collectionParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, collectionId);
navigateUrl = navigateUrl + `?` + collectionParams.toString(); navigateUrl = navigateUrl + `?` + collectionParams.toString();
} }
const channelUri = claim && (signingChannel ? signingChannel.permanent_url : claim.permanent_url);
const navLinkProps = { const navLinkProps = {
to: navigateUrl, to: navigateUrl,
onClick: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(),
@ -421,7 +422,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
)} )}
</div> </div>
</div> </div>
{!hideMenu && <ClaimMenuList uri={uri} collectionId={collectionId} />} {!hideMenu && <ClaimMenuList uri={uri} collectionId={collectionId} channelUri={channelUri} />}
</> </>
</WrapperElement> </WrapperElement>
); );

View file

@ -43,7 +43,6 @@ type Props = {
showHiddenByUser?: boolean, showHiddenByUser?: boolean,
properties?: (Claim) => void, properties?: (Claim) => void,
live?: boolean, live?: boolean,
channelIsMine?: boolean,
collectionId?: string, collectionId?: string,
}; };
@ -67,7 +66,6 @@ function ClaimPreviewTile(props: Props) {
showHiddenByUser, showHiddenByUser,
properties, properties,
live, live,
channelIsMine,
collectionId, collectionId,
} = props; } = props;
const isRepost = claim && claim.repost_channel_url; const isRepost = claim && claim.repost_channel_url;
@ -100,11 +98,8 @@ function ClaimPreviewTile(props: Props) {
} }
} }
let channelUri;
const signingChannel = claim && claim.signing_channel; const signingChannel = claim && claim.signing_channel;
if (signingChannel) { const channelUri = signingChannel && signingChannel.permanent_url;
channelUri = signingChannel.permanent_url;
}
function handleClick(e) { function handleClick(e) {
if (navigateUrl) { if (navigateUrl) {
@ -219,7 +214,7 @@ function ClaimPreviewTile(props: Props) {
</div> </div>
)} )}
{/* CHECK CLAIM MENU LIST PARAMS (IS REPOST?) */} {/* CHECK CLAIM MENU LIST PARAMS (IS REPOST?) */}
<ClaimMenuList uri={uri} collectionId={listId} channelIsMine={channelIsMine} isRepost={isRepost} /> <ClaimMenuList uri={uri} collectionId={listId} channelUri={channelUri} isRepost={isRepost} />
</h2> </h2>
</NavLink> </NavLink>
<div> <div>

View file

@ -1,7 +1,7 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeSelectChannelPermUrlForClaimUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux'; import { makeSelectChannelPermUrlForClaimUri, makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
import { doCommentAbandon, doCommentPin, doCommentList, doCommentModBlock } from 'redux/actions/comments'; import { doCommentAbandon, doCommentPin, doCommentList, doCommentModBlock } from 'redux/actions/comments';
import { doToggleMuteChannel } from 'redux/actions/blocked'; import { doChannelMute } from 'redux/actions/blocked';
// import { doSetActiveChannel } from 'redux/actions/app'; // import { doSetActiveChannel } from 'redux/actions/app';
import { doSetPlayingUri } from 'redux/actions/content'; import { doSetPlayingUri } from 'redux/actions/content';
import { selectActiveChannelClaim } from 'redux/selectors/app'; import { selectActiveChannelClaim } from 'redux/selectors/app';
@ -19,7 +19,7 @@ const select = (state, props) => ({
const perform = (dispatch) => ({ const perform = (dispatch) => ({
clearPlayingUri: () => dispatch(doSetPlayingUri({ uri: null })), clearPlayingUri: () => dispatch(doSetPlayingUri({ uri: null })),
deleteComment: (commentId, creatorChannelUrl) => dispatch(doCommentAbandon(commentId, creatorChannelUrl)), deleteComment: (commentId, creatorChannelUrl) => dispatch(doCommentAbandon(commentId, creatorChannelUrl)),
blockChannel: (channelUri) => dispatch(doToggleMuteChannel(channelUri)), muteChannel: (channelUri) => dispatch(doChannelMute(channelUri)),
pinComment: (commentId, remove) => dispatch(doCommentPin(commentId, remove)), pinComment: (commentId, remove) => dispatch(doCommentPin(commentId, remove)),
fetchComments: (uri) => dispatch(doCommentList(uri)), fetchComments: (uri) => dispatch(doCommentList(uri)),
// setActiveChannel: channelId => dispatch(doSetActiveChannel(channelId)), // setActiveChannel: channelId => dispatch(doSetActiveChannel(channelId)),

View file

@ -15,7 +15,7 @@ type Props = {
linkedComment?: any, linkedComment?: any,
isPinned: boolean, isPinned: boolean,
pinComment: (string, boolean) => Promise<any>, pinComment: (string, boolean) => Promise<any>,
blockChannel: (string) => void, muteChannel: (string) => void,
fetchComments: (string) => void, fetchComments: (string) => void,
handleEditComment: () => void, handleEditComment: () => void,
contentChannelPermanentUrl: any, contentChannelPermanentUrl: any,
@ -34,7 +34,7 @@ function CommentMenuList(props: Props) {
commentIsMine, commentIsMine,
commentId, commentId,
deleteComment, deleteComment,
blockChannel, muteChannel,
pinComment, pinComment,
clearPlayingUri, clearPlayingUri,
activeChannelClaim, activeChannelClaim,
@ -66,7 +66,7 @@ function CommentMenuList(props: Props) {
} }
function handleCommentMute() { function handleCommentMute() {
blockChannel(authorUri); muteChannel(authorUri);
} }
return ( return (

View file

@ -191,8 +191,7 @@ function ChannelPage(props: Props) {
{!channelIsBlackListed && <ShareButton uri={uri} />} {!channelIsBlackListed && <ShareButton uri={uri} />}
{!(isBlocked || isMuted) && <ClaimSupportButton uri={uri} />} {!(isBlocked || isMuted) && <ClaimSupportButton uri={uri} />}
{!(isBlocked || isMuted) && (!channelIsBlackListed || isSubscribed) && <SubscribeButton uri={permanentUrl} />} {!(isBlocked || isMuted) && (!channelIsBlackListed || isSubscribed) && <SubscribeButton uri={permanentUrl} />}
{/* TODO: add channel collections <ClaimCollectionAddButton uri={uri} fileAction /> */} <ClaimMenuList uri={claim.permanent_url} channelUri={claim.permanent_url} inline />
<ClaimMenuList uri={claim.permanent_url} inline />
</div> </div>
{cover && <img className={classnames('channel-cover__custom')} src={cover} />} {cover && <img className={classnames('channel-cover__custom')} src={cover} />}
<div className="channel__primary-info"> <div className="channel__primary-info">

View file

@ -2,8 +2,10 @@
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import { selectPrefsReady } from 'redux/selectors/sync'; import { selectPrefsReady } from 'redux/selectors/sync';
import { doAlertWaitingForSync } from 'redux/actions/app'; import { doAlertWaitingForSync } from 'redux/actions/app';
import { doToast } from 'redux/actions/notifications';
export const doToggleMuteChannel = (uri: string) => (dispatch: Dispatch, getState: GetState) => { export function doToggleMuteChannel(uri: string, showLink: boolean, unmute: boolean = false) {
return async (dispatch: Dispatch, getState: GetState) => {
const state = getState(); const state = getState();
const ready = selectPrefsReady(state); const ready = selectPrefsReady(state);
@ -17,4 +19,23 @@ export const doToggleMuteChannel = (uri: string) => (dispatch: Dispatch, getStat
uri, uri,
}, },
}); });
};
dispatch(doToast({
message: __(!unmute ? 'Channel muted. You will not see them again.' : 'Channel unmuted!'),
linkText: __(showLink ? 'See All' : ''),
linkTarget: '/settings/block_and_mute',
}));
};
}
export function doChannelMute(uri: string, showLink: boolean = true) {
return (dispatch: Dispatch) => {
return dispatch(doToggleMuteChannel(uri, showLink));
};
}
export function doChannelUnmute(uri: string, showLink: boolean = true) {
return (dispatch: Dispatch) => {
return dispatch(doToggleMuteChannel(uri, showLink, true));
};
}

View file

@ -506,7 +506,7 @@ async function channelSignName(channelClaimId: string, channelName: string) {
} }
// Hides a users comments from all creator's claims and prevent them from commenting in the future // Hides a users comments from all creator's claims and prevent them from commenting in the future
export function doCommentModToggleBlock(channelUri: string, unblock: boolean = false) { export function doCommentModToggleBlock(channelUri: string, showLink: boolean, unblock: boolean = false) {
return async (dispatch: Dispatch, getState: GetState) => { return async (dispatch: Dispatch, getState: GetState) => {
const state = getState(); const state = getState();
const myChannels = selectMyChannelClaims(state); const myChannels = selectMyChannelClaims(state);
@ -564,9 +564,11 @@ export function doCommentModToggleBlock(channelUri: string, unblock: boolean = f
data: { channelUri }, data: { channelUri },
}); });
if (!unblock) { dispatch(doToast({
dispatch(doToast({ message: __('Channel blocked. You will not see them again.') })); message: __(!unblock ? 'Channel blocked. They will not interact with you again.' : 'Channel unblocked!'),
} linkText: __(showLink ? 'See All' : ''),
linkTarget: '/settings/block_and_mute',
}));
}) })
.catch(() => { .catch(() => {
dispatch({ dispatch({
@ -582,15 +584,15 @@ export function doCommentModToggleBlock(channelUri: string, unblock: boolean = f
}; };
} }
export function doCommentModBlock(commentAuthor: string) { export function doCommentModBlock(commentAuthor: string, showLink: boolean = true) {
return (dispatch: Dispatch) => { return (dispatch: Dispatch) => {
return dispatch(doCommentModToggleBlock(commentAuthor)); return dispatch(doCommentModToggleBlock(commentAuthor, showLink));
}; };
} }
export function doCommentModUnBlock(commentAuthor: string) { export function doCommentModUnBlock(commentAuthor: string, showLink: boolean = true) {
return (dispatch: Dispatch) => { return (dispatch: Dispatch) => {
return dispatch(doCommentModToggleBlock(commentAuthor, true)); return dispatch(doCommentModToggleBlock(commentAuthor, showLink, true));
}; };
} }

View file

@ -1,11 +1,20 @@
// @flow
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import REWARDS from 'rewards'; import REWARDS from 'rewards';
import { Lbryio } from 'lbryinc'; import { Lbryio } from 'lbryinc';
import { doClaimRewardType } from 'redux/actions/rewards'; import { doClaimRewardType } from 'redux/actions/rewards';
import { parseURI } from 'lbry-redux'; import { parseURI } from 'lbry-redux';
import { doAlertWaitingForSync } from 'redux/actions/app'; import { doAlertWaitingForSync } from 'redux/actions/app';
import { doToast } from 'redux/actions/notifications';
export const doChannelSubscribe = subscription => (dispatch, getState) => { type SubscriptionArgs = {
channelName: string,
uri: string,
notificationsDisabled?: boolean,
};
export function doToggleSubscription(subscription: SubscriptionArgs, isSubscribed: boolean = false) {
return async (dispatch: Dispatch, getState: GetState) => {
const { const {
settings: { daemonSettings }, settings: { daemonSettings },
sync: { prefsReady: ready }, sync: { prefsReady: ready },
@ -18,19 +27,23 @@ export const doChannelSubscribe = subscription => (dispatch, getState) => {
const { share_usage_data: shareSetting } = daemonSettings; const { share_usage_data: shareSetting } = daemonSettings;
const isSharingData = shareSetting || IS_WEB; const isSharingData = shareSetting || IS_WEB;
if (!isSubscribed) {
const subscriptionUri = subscription.uri; const subscriptionUri = subscription.uri;
if (!subscriptionUri.startsWith('lbry://')) { if (!subscriptionUri.startsWith('lbry://')) {
throw Error(`Subscription uris must include the "lbry://" prefix.\nTried to subscribe to ${subscriptionUri}`); throw Error(`Subscription uris must include the "lbry://" prefix.\nTried to subscribe to ${subscriptionUri}`);
} }
}
dispatch({ dispatch({
type: ACTIONS.CHANNEL_SUBSCRIBE, type: !isSubscribed ? ACTIONS.CHANNEL_SUBSCRIBE : ACTIONS.CHANNEL_UNSUBSCRIBE,
data: subscription, data: subscription,
}); });
// if the user isn't sharing data, keep the subscriptions entirely in the app // if the user isn't sharing data, keep the subscriptions entirely in the app
if (isSharingData || IS_WEB) { if (isSharingData || IS_WEB) {
const { channelClaimId } = parseURI(subscription.uri); const { channelClaimId } = parseURI(subscription.uri);
if (!isSubscribed) {
// They are sharing data, we can store their subscriptions in our internal database // They are sharing data, we can store their subscriptions in our internal database
Lbryio.call('subscription', 'new', { Lbryio.call('subscription', 'new', {
channel_name: subscription.channelName, channel_name: subscription.channelName,
@ -39,31 +52,27 @@ export const doChannelSubscribe = subscription => (dispatch, getState) => {
}); });
dispatch(doClaimRewardType(REWARDS.TYPE_SUBSCRIPTION, { failSilently: true })); dispatch(doClaimRewardType(REWARDS.TYPE_SUBSCRIPTION, { failSilently: true }));
} } else {
};
export const doChannelUnsubscribe = subscription => (dispatch, getState) => {
const {
settings: { daemonSettings },
sync: { prefsReady: ready },
} = getState();
if (!ready) {
return dispatch(doAlertWaitingForSync());
}
const { share_usage_data: shareSetting } = daemonSettings;
const isSharingData = shareSetting || IS_WEB;
dispatch({
type: ACTIONS.CHANNEL_UNSUBSCRIBE,
data: subscription,
});
if (isSharingData) {
const { channelClaimId } = parseURI(subscription.uri);
Lbryio.call('subscription', 'delete', { Lbryio.call('subscription', 'delete', {
claim_id: channelClaimId, claim_id: channelClaimId,
}); });
} }
}; }
dispatch(doToast({
message: __(!isSubscribed ? 'You followed %CHANNEL_NAME%!' : 'Unfollowed %CHANNEL_NAME%.', { CHANNEL_NAME: subscription.channelName }),
}));
};
}
export function doChannelSubscribe(subscription: SubscriptionArgs) {
return (dispatch: Dispatch) => {
return dispatch(doToggleSubscription(subscription));
};
}
export function doChannelUnsubscribe(subscription: SubscriptionArgs) {
return (dispatch: Dispatch) => {
return dispatch(doToggleSubscription(subscription, true));
};
}

View file

@ -11,6 +11,8 @@ import {
parseURI, parseURI,
makeSelectContentTypeForUri, makeSelectContentTypeForUri,
makeSelectFileNameForUri, makeSelectFileNameForUri,
normalizeURI,
selectMyActiveClaims,
} from 'lbry-redux'; } from 'lbry-redux';
import { makeSelectRecommendedContentForUri } from 'redux/selectors/search'; import { makeSelectRecommendedContentForUri } from 'redux/selectors/search';
import { selectMutedChannels } from 'redux/selectors/blocked'; import { selectMutedChannels } from 'redux/selectors/blocked';
@ -243,3 +245,22 @@ export const makeSelectInsufficientCreditsForUri = (uri: string) =>
return !isMine && costInfo && costInfo.cost > 0 && costInfo.cost > balance; return !isMine && costInfo && costInfo.cost > 0 && costInfo.cost > balance;
} }
); );
export const makeSelectSigningIsMine = (rawUri: string) => {
let uri;
try {
uri = normalizeURI(rawUri);
} catch (e) { }
return createSelector(selectClaimsByUri, selectMyActiveClaims, (claims, myClaims) => {
try {
parseURI(uri);
} catch (e) {
return false;
}
const repostedChannel = claims && claims[uri] && claims[uri].reposted_claim && (claims[uri].reposted_claim.signing_channel || claims[uri].reposted_claim);
const signingChannel = claims && claims[uri] && (repostedChannel || claims[uri].signing_channel || claims[uri]);
return signingChannel && myClaims.has(signingChannel.claim_id);
});
};