refactor 'active' channel usage across the app
This commit is contained in:
parent
bce86ae8a3
commit
db87125dc8
53 changed files with 726 additions and 944 deletions
|
@ -740,7 +740,6 @@
|
|||
"Repost": "Repost",
|
||||
"Repost your favorite claims to help more people discover them!": "Repost your favorite claims to help more people discover them!",
|
||||
"Repost %title%": "Repost %title%",
|
||||
"Channel to repost on": "Channel to repost on",
|
||||
"Advanced": "Advanced",
|
||||
"community name": "community name",
|
||||
"Change this to repost to a different %lbry_naming_link%.": "Change this to repost to a different %lbry_naming_link%.",
|
||||
|
|
|
@ -5,17 +5,28 @@ import { selectGetSyncErrorMessage, selectSyncFatalError } from 'redux/selectors
|
|||
import { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user';
|
||||
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
||||
import { doFetchChannelListMine, SETTINGS } from 'lbry-redux';
|
||||
import { doFetchChannelListMine, selectMyChannelUrls, SETTINGS } from 'lbry-redux';
|
||||
import {
|
||||
makeSelectClientSetting,
|
||||
selectLanguage,
|
||||
selectLoadedLanguages,
|
||||
selectThemePath,
|
||||
} from 'redux/selectors/settings';
|
||||
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded, selectModal } from 'redux/selectors/app';
|
||||
import {
|
||||
selectIsUpgradeAvailable,
|
||||
selectAutoUpdateDownloaded,
|
||||
selectModal,
|
||||
selectActiveChannelId,
|
||||
} from 'redux/selectors/app';
|
||||
import { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings';
|
||||
import { doSyncLoop } from 'redux/actions/sync';
|
||||
import { doDownloadUpgradeRequested, doSignIn, doGetAndPopulatePreferences } from 'redux/actions/app';
|
||||
import {
|
||||
doDownloadUpgradeRequested,
|
||||
doSignIn,
|
||||
doGetAndPopulatePreferences,
|
||||
doSetActiveChannel,
|
||||
doSetIncognito,
|
||||
} from 'redux/actions/app';
|
||||
import App from './view';
|
||||
|
||||
const select = state => ({
|
||||
|
@ -33,6 +44,8 @@ const select = state => ({
|
|||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
currentModal: selectModal(state),
|
||||
syncFatalError: selectSyncFatalError(state),
|
||||
activeChannelId: selectActiveChannelId(state),
|
||||
myChannelUrls: selectMyChannelUrls(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
@ -45,6 +58,8 @@ const perform = dispatch => ({
|
|||
getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()),
|
||||
syncLoop: noInterval => dispatch(doSyncLoop(noInterval)),
|
||||
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
|
||||
setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()),
|
||||
setIncognito: () => dispatch(doSetIncognito()),
|
||||
});
|
||||
|
||||
export default hot(connect(select, perform)(App));
|
||||
|
|
|
@ -80,6 +80,10 @@ type Props = {
|
|||
syncEnabled: boolean,
|
||||
currentModal: any,
|
||||
syncFatalError: boolean,
|
||||
activeChannelId: ?string,
|
||||
myChannelUrls: ?Array<string>,
|
||||
setActiveChannelIfNotSet: (?string) => void,
|
||||
setIncognito: boolean => void,
|
||||
};
|
||||
|
||||
function App(props: Props) {
|
||||
|
@ -106,6 +110,10 @@ function App(props: Props) {
|
|||
syncLoop,
|
||||
currentModal,
|
||||
syncFatalError,
|
||||
activeChannelId,
|
||||
myChannelUrls,
|
||||
setActiveChannelIfNotSet,
|
||||
setIncognito,
|
||||
} = props;
|
||||
|
||||
const appRef = useRef();
|
||||
|
@ -133,7 +141,8 @@ function App(props: Props) {
|
|||
const shouldHideNag = pathname.startsWith(`/$/${PAGES.EMBED}`) || pathname.startsWith(`/$/${PAGES.AUTH_VERIFY}`);
|
||||
const userId = user && user.id;
|
||||
const useCustomScrollbar = !IS_MAC;
|
||||
|
||||
const hasMyChannels = myChannelUrls && myChannelUrls.length > 0;
|
||||
const hasNoChannels = myChannelUrls && myChannelUrls.length === 0;
|
||||
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
|
||||
|
||||
let uri;
|
||||
|
@ -228,6 +237,14 @@ function App(props: Props) {
|
|||
document.documentElement.setAttribute('theme', theme);
|
||||
}, [theme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasMyChannels && !activeChannelId) {
|
||||
setActiveChannelIfNotSet();
|
||||
} else if (hasNoChannels) {
|
||||
setIncognito(true);
|
||||
}
|
||||
}, [hasMyChannels, activeChannelId, setActiveChannelIfNotSet]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!languages.includes(language)) {
|
||||
setLanguage(language);
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
import { connect } from 'react-redux';
|
||||
import SelectChannel from './view';
|
||||
import { selectMyChannelClaims } from 'lbry-redux';
|
||||
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
|
||||
import { doSetActiveChannel, doSetIncognito } from 'redux/actions/app';
|
||||
import SelectChannel from './view';
|
||||
|
||||
const select = state => ({
|
||||
channels: selectMyChannelClaims(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
incognito: selectIncognito(state),
|
||||
});
|
||||
|
||||
export default connect(select)(SelectChannel);
|
||||
export default connect(select, {
|
||||
doSetActiveChannel,
|
||||
doSetIncognito,
|
||||
})(SelectChannel);
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
||||
import ChannelTitle from 'component/channelTitle';
|
||||
import Icon from 'component/common/icon';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
type Props = {
|
||||
selectedChannelUrl: string, // currently selected channel
|
||||
channels: ?Array<ChannelClaim>,
|
||||
onChannelSelect: (url: string) => void,
|
||||
hideAnon?: boolean,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
doSetActiveChannel: string => void,
|
||||
incognito: boolean,
|
||||
doSetIncognito: boolean => void,
|
||||
};
|
||||
|
||||
type ListItemProps = {
|
||||
|
@ -30,34 +37,64 @@ function ChannelListItem(props: ListItemProps) {
|
|||
);
|
||||
}
|
||||
|
||||
function ChannelSelector(props: Props) {
|
||||
const { channels, selectedChannelUrl, onChannelSelect } = props;
|
||||
type IncognitoSelectorProps = {
|
||||
isSelected?: boolean,
|
||||
};
|
||||
|
||||
if (!channels || !selectedChannelUrl) {
|
||||
return null;
|
||||
function IncognitoSelector(props: IncognitoSelectorProps) {
|
||||
return (
|
||||
<div className={classnames('channel__list-item', { 'channel__list-item--selected': props.isSelected })}>
|
||||
<Icon sectionIcon icon={ICONS.ANONYMOUS} />
|
||||
<h2 className="channel__list-text">{__('Anonymous')}</h2>
|
||||
{props.isSelected && <Icon icon={ICONS.DOWN} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ChannelSelector(props: Props) {
|
||||
const { channels, activeChannelClaim, doSetActiveChannel, hideAnon = false, incognito, doSetIncognito } = props;
|
||||
const {
|
||||
push,
|
||||
location: { pathname },
|
||||
} = useHistory();
|
||||
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
|
||||
|
||||
function handleChannelSelect(channelClaim) {
|
||||
doSetIncognito(false);
|
||||
doSetActiveChannel(channelClaim.claim_id);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="channel__selector">
|
||||
<Menu>
|
||||
<MenuButton className="">
|
||||
<ChannelListItem uri={selectedChannelUrl} isSelected />
|
||||
<MenuButton>
|
||||
{(incognito && !hideAnon) || !activeChannelUrl ? (
|
||||
<IncognitoSelector isSelected />
|
||||
) : (
|
||||
<ChannelListItem uri={activeChannelUrl} isSelected />
|
||||
)}
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list channel__list">
|
||||
{channels &&
|
||||
channels.map(channel => (
|
||||
<MenuItem
|
||||
key={channel.canonical_url}
|
||||
onSelect={() => {
|
||||
if (selectedChannelUrl !== channel.canonical_url) {
|
||||
onChannelSelect(channel.canonical_url);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ChannelListItem uri={channel.canonical_url} />
|
||||
<MenuItem key={channel.permanent_url} onSelect={() => handleChannelSelect(channel)}>
|
||||
<ChannelListItem uri={channel.permanent_url} />
|
||||
</MenuItem>
|
||||
))}
|
||||
{!hideAnon && (
|
||||
<MenuItem onSelect={() => doSetIncognito(true)}>
|
||||
<IncognitoSelector />
|
||||
</MenuItem>
|
||||
)}
|
||||
<MenuItem onSelect={() => push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`)}>
|
||||
<div className="channel__list-item">
|
||||
<Icon sectionIcon icon={ICONS.CHANNEL} />
|
||||
<h2 className="channel__list-text">Create a new channel</h2>
|
||||
</div>
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,49 +1,28 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doResolveUri,
|
||||
makeSelectClaimIsPending,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectThumbnailForUri,
|
||||
makeSelectIsUriResolving,
|
||||
selectMyChannelClaims,
|
||||
makeSelectMyChannelPermUrlForName,
|
||||
makeSelectChannelPermUrlForClaimUri,
|
||||
} from 'lbry-redux';
|
||||
import { makeSelectThumbnailForUri, selectMyChannelClaims, makeSelectChannelPermUrlForClaimUri } from 'lbry-redux';
|
||||
import { doCommentAbandon, doCommentUpdate, doCommentPin, doCommentList } from 'redux/actions/comments';
|
||||
import { doToggleBlockChannel } from 'redux/actions/blocked';
|
||||
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import {
|
||||
selectIsFetchingComments,
|
||||
makeSelectOthersReactionsForComment,
|
||||
selectCommentChannel,
|
||||
} from 'redux/selectors/comments';
|
||||
import { selectIsFetchingComments, makeSelectOthersReactionsForComment } from 'redux/selectors/comments';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import Comment from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
const channel = selectCommentChannel(state);
|
||||
|
||||
return {
|
||||
activeChannel: channel,
|
||||
pending: props.authorUri && makeSelectClaimIsPending(props.authorUri)(state),
|
||||
channel: props.authorUri && makeSelectClaimForUri(props.authorUri)(state),
|
||||
isResolvingUri: props.authorUri && makeSelectIsUriResolving(props.authorUri)(state),
|
||||
const select = (state, props) => ({
|
||||
thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state),
|
||||
channelIsBlocked: props.authorUri && selectChannelIsBlocked(props.authorUri)(state),
|
||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||
isFetchingComments: selectIsFetchingComments(state),
|
||||
myChannels: selectMyChannelClaims(state),
|
||||
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
||||
commentIdentityChannel: makeSelectMyChannelPermUrlForName(channel)(state),
|
||||
contentChannel: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
|
||||
};
|
||||
};
|
||||
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
closeInlinePlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
updateComment: (commentId, comment) => dispatch(doCommentUpdate(commentId, comment)),
|
||||
deleteComment: commentId => dispatch(doCommentAbandon(commentId)),
|
||||
blockChannel: channelUri => dispatch(doToggleBlockChannel(channelUri)),
|
||||
|
|
|
@ -5,7 +5,6 @@ import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
|
|||
import { SITE_NAME, SIMPLE_SITE, ENABLE_COMMENT_REACTIONS } from 'config';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { parseURI } from 'lbry-redux';
|
||||
import { isEmpty } from 'util/object';
|
||||
import DateTime from 'component/dateTime';
|
||||
import Button from 'component/button';
|
||||
import Expandable from 'component/expandable';
|
||||
|
@ -29,10 +28,6 @@ type Props = {
|
|||
commentId: string, // sha256 digest identifying the comment
|
||||
message: string, // comment body
|
||||
timePosted: number, // Comment timestamp
|
||||
channel: ?Claim, // Channel Claim, retrieved to obtain thumbnail
|
||||
pending?: boolean,
|
||||
resolveUri: string => void, // resolves the URI
|
||||
isResolvingUri: boolean, // if the URI is currently being resolved
|
||||
channelIsBlocked: boolean, // if the channel is blacklisted in the app
|
||||
claimIsMine: boolean, // if you control the claim which this comment was posted on
|
||||
commentIsMine: boolean, // if this comment was signed by an owned channel
|
||||
|
@ -53,7 +48,8 @@ type Props = {
|
|||
pinComment: (string, boolean) => Promise<any>,
|
||||
fetchComments: string => void,
|
||||
commentIdentityChannel: any,
|
||||
contentChannel: any,
|
||||
contentChannelPermanentUrl: any,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
};
|
||||
|
||||
const LENGTH_TO_COLLAPSE = 300;
|
||||
|
@ -67,10 +63,6 @@ function Comment(props: Props) {
|
|||
authorUri,
|
||||
timePosted,
|
||||
message,
|
||||
pending,
|
||||
channel,
|
||||
isResolvingUri,
|
||||
resolveUri,
|
||||
channelIsBlocked,
|
||||
commentIsMine,
|
||||
commentId,
|
||||
|
@ -87,8 +79,8 @@ function Comment(props: Props) {
|
|||
pinComment,
|
||||
fetchComments,
|
||||
othersReacts,
|
||||
commentIdentityChannel,
|
||||
contentChannel,
|
||||
contentChannelPermanentUrl,
|
||||
activeChannelClaim,
|
||||
} = props;
|
||||
const {
|
||||
push,
|
||||
|
@ -108,10 +100,7 @@ function Comment(props: Props) {
|
|||
const dislikesCount = (othersReacts && othersReacts.dislike) || 0;
|
||||
const totalLikesAndDislikes = likesCount + dislikesCount;
|
||||
const slimedToDeath = totalLikesAndDislikes >= 5 && dislikesCount / totalLikesAndDislikes > 0.8;
|
||||
// to debounce subsequent requests
|
||||
const shouldFetch =
|
||||
channel === undefined ||
|
||||
(channel !== null && channel.value_type === 'channel' && isEmpty(channel.meta) && !pending);
|
||||
|
||||
let channelOwnerOfContent;
|
||||
try {
|
||||
const { channelName } = parseURI(uri);
|
||||
|
@ -121,11 +110,6 @@ function Comment(props: Props) {
|
|||
} catch (e) {}
|
||||
|
||||
useEffect(() => {
|
||||
// If author was extracted from the URI, then it must be valid.
|
||||
if (authorUri && author && !isResolvingUri && shouldFetch) {
|
||||
resolveUri(authorUri);
|
||||
}
|
||||
|
||||
if (isEditing) {
|
||||
setCharCount(editedMessage.length);
|
||||
|
||||
|
@ -143,7 +127,7 @@ function Comment(props: Props) {
|
|||
window.removeEventListener('keydown', handleEscape);
|
||||
};
|
||||
}
|
||||
}, [isResolvingUri, shouldFetch, author, authorUri, resolveUri, editedMessage, isEditing, setEditing]);
|
||||
}, [author, authorUri, editedMessage, isEditing, setEditing]);
|
||||
|
||||
function handleEditMessageChanged(event) {
|
||||
setCommentValue(!SIMPLE_SITE && advancedEditor ? event : event.target.value);
|
||||
|
@ -262,7 +246,7 @@ function Comment(props: Props) {
|
|||
{__('Block Channel')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{commentIdentityChannel === contentChannel && isTopLevel && (
|
||||
{activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl && isTopLevel && (
|
||||
<MenuItem
|
||||
className="comment__menu-option menu__link"
|
||||
onSelect={
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux';
|
||||
import { selectIsPostingComment, selectCommentChannel } from 'redux/selectors/comments';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { doCommentCreate, doSetCommentChannel } from 'redux/actions/comments';
|
||||
import { selectIsPostingComment } from 'redux/selectors/comments';
|
||||
import { doOpenModal, doSetActiveChannel } from 'redux/actions/app';
|
||||
import { doCommentCreate } from 'redux/actions/comments';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import { CommentCreate } from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -12,14 +13,13 @@ const select = (state, props) => ({
|
|||
channels: selectMyChannelClaims(state),
|
||||
isFetchingChannels: selectFetchingMyChannels(state),
|
||||
isPostingComment: selectIsPostingComment(state),
|
||||
activeChannel: selectCommentChannel(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
});
|
||||
|
||||
const perform = (dispatch, ownProps) => ({
|
||||
createComment: (comment, claimId, channel, parentId) =>
|
||||
dispatch(doCommentCreate(comment, claimId, channel, parentId, ownProps.uri)),
|
||||
createComment: (comment, claimId, parentId) => dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri)),
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
setCommentChannel: name => dispatch(doSetCommentChannel(name)),
|
||||
setActiveChannel: claimId => dispatch(doSetActiveChannel(claimId)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(CommentCreate);
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
// @flow
|
||||
import { SIMPLE_SITE } from 'config';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import { CHANNEL_NEW } from 'constants/claim';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { FormField, Form } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import ChannelSelection from 'component/selectChannel';
|
||||
import SelectChannel from 'component/selectChannel';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
|
||||
import { useHistory } from 'react-router';
|
||||
|
@ -25,7 +24,7 @@ type Props = {
|
|||
isReply: boolean,
|
||||
isPostingComment: boolean,
|
||||
activeChannel: string,
|
||||
setCommentChannel: string => void,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
};
|
||||
|
||||
export function CommentCreate(props: Props) {
|
||||
|
@ -40,8 +39,7 @@ export function CommentCreate(props: Props) {
|
|||
isReply,
|
||||
parentId,
|
||||
isPostingComment,
|
||||
activeChannel,
|
||||
setCommentChannel,
|
||||
activeChannelClaim,
|
||||
} = props;
|
||||
const buttonref: ElementRef<any> = React.useRef();
|
||||
const { push } = useHistory();
|
||||
|
@ -50,21 +48,7 @@ export function CommentCreate(props: Props) {
|
|||
const [charCount, setCharCount] = useState(commentValue.length);
|
||||
const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false);
|
||||
const hasChannels = channels && channels.length;
|
||||
const disabled = isPostingComment || activeChannel === CHANNEL_NEW || !commentValue.length;
|
||||
const topChannel =
|
||||
channels &&
|
||||
channels.reduce((top, channel) => {
|
||||
const topClaimCount = (top && top.meta && top.meta.claims_in_channel) || 0;
|
||||
const currentClaimCount = (activeChannel && channel.meta && channel.meta.claims_in_channel) || 0;
|
||||
return topClaimCount >= currentClaimCount ? top : channel;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// set default channel
|
||||
if ((activeChannel === '' || activeChannel === 'anonymous') && topChannel) {
|
||||
setCommentChannel(topChannel.name);
|
||||
}
|
||||
}, [activeChannel, topChannel, setCommentChannel]);
|
||||
const disabled = isPostingComment || !activeChannelClaim || !commentValue.length;
|
||||
|
||||
function handleCommentChange(event) {
|
||||
let commentValue;
|
||||
|
@ -94,8 +78,8 @@ export function CommentCreate(props: Props) {
|
|||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (activeChannel !== CHANNEL_NEW && commentValue.length) {
|
||||
createComment(commentValue, claimId, activeChannel, parentId).then(res => {
|
||||
if (activeChannelClaim && commentValue.length) {
|
||||
createComment(commentValue, claimId, parentId).then(res => {
|
||||
if (res && res.signature) {
|
||||
setCommentValue('');
|
||||
|
||||
|
@ -138,13 +122,13 @@ export function CommentCreate(props: Props) {
|
|||
})}
|
||||
>
|
||||
<FormField
|
||||
disabled={activeChannel === CHANNEL_NEW}
|
||||
disabled={!activeChannelClaim}
|
||||
type={SIMPLE_SITE ? 'textarea' : advancedEditor && !isReply ? 'markdown' : 'textarea'}
|
||||
name={isReply ? 'content_reply' : 'content_description'}
|
||||
label={
|
||||
<span className="comment-new__label-wrapper">
|
||||
<div className="comment-new__label">{isReply ? __('Replying as') + ' ' : __('Comment as') + ' '}</div>
|
||||
<ChannelSelection channel={activeChannel} hideAnon tiny hideNew onChannelChange={setCommentChannel} />
|
||||
<SelectChannel tiny />
|
||||
</span>
|
||||
}
|
||||
quickActionLabel={
|
||||
|
|
|
@ -2,19 +2,16 @@ import { connect } from 'react-redux';
|
|||
import Comment from './view';
|
||||
import { makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import {
|
||||
makeSelectMyReactionsForComment,
|
||||
makeSelectOthersReactionsForComment,
|
||||
selectCommentChannel,
|
||||
} from 'redux/selectors/comments';
|
||||
import { makeSelectMyReactionsForComment, makeSelectOthersReactionsForComment } from 'redux/selectors/comments';
|
||||
import { doCommentReact } from 'redux/actions/comments';
|
||||
import { selectActiveChannelId } from 'redux/selectors/app';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
myReacts: makeSelectMyReactionsForComment(props.commentId)(state),
|
||||
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
||||
activeChannel: selectCommentChannel(state),
|
||||
activeChannelId: selectActiveChannelId(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
|
|
@ -16,13 +16,13 @@ type Props = {
|
|||
commentId: string,
|
||||
pendingCommentReacts: Array<string>,
|
||||
claimIsMine: boolean,
|
||||
activeChannel: string,
|
||||
activeChannelId: ?string,
|
||||
claim: ?ChannelClaim,
|
||||
doToast: ({ message: string }) => void,
|
||||
};
|
||||
|
||||
export default function CommentReactions(props: Props) {
|
||||
const { myReacts, othersReacts, commentId, react, claimIsMine, claim, activeChannel, doToast } = props;
|
||||
const { myReacts, othersReacts, commentId, react, claimIsMine, claim, activeChannelId, doToast } = props;
|
||||
const {
|
||||
push,
|
||||
location: { pathname },
|
||||
|
@ -31,8 +31,8 @@ export default function CommentReactions(props: Props) {
|
|||
claim &&
|
||||
claimIsMine &&
|
||||
(claim.value_type === 'channel'
|
||||
? claim.name === activeChannel
|
||||
: claim.signing_channel && claim.signing_channel.name === activeChannel);
|
||||
? claim.claim_id === activeChannelId
|
||||
: claim.signing_channel && claim.signing_channel.claim_id === activeChannelId);
|
||||
const authorUri =
|
||||
claim && claim.value_type === 'channel'
|
||||
? claim.canonical_url
|
||||
|
@ -52,7 +52,7 @@ export default function CommentReactions(props: Props) {
|
|||
const creatorLiked = getCountForReact(REACTION_TYPES.CREATOR_LIKE) > 0;
|
||||
|
||||
function handleCommentLike() {
|
||||
if (activeChannel) {
|
||||
if (activeChannelId) {
|
||||
react(commentId, REACTION_TYPES.LIKE);
|
||||
} else {
|
||||
promptForChannel();
|
||||
|
@ -60,7 +60,7 @@ export default function CommentReactions(props: Props) {
|
|||
}
|
||||
|
||||
function handleCommentDislike() {
|
||||
if (activeChannel) {
|
||||
if (activeChannelId) {
|
||||
react(commentId, REACTION_TYPES.DISLIKE);
|
||||
} else {
|
||||
promptForChannel();
|
||||
|
|
|
@ -5,10 +5,10 @@ import {
|
|||
selectIsFetchingComments,
|
||||
makeSelectTotalCommentsCountForUri,
|
||||
selectOthersReactsById,
|
||||
selectCommentChannel,
|
||||
} from 'redux/selectors/comments';
|
||||
import { doCommentList, doCommentReactList } from 'redux/actions/comments';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectActiveChannelId } from 'redux/selectors/app';
|
||||
import CommentsList from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -20,7 +20,7 @@ const select = (state, props) => ({
|
|||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||
fetchingChannels: selectFetchingMyChannels(state),
|
||||
reactionsById: selectOthersReactsById(state),
|
||||
activeChannel: selectCommentChannel(state),
|
||||
activeChannelId: selectActiveChannelId(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
|
|
@ -26,7 +26,7 @@ type Props = {
|
|||
totalComments: number,
|
||||
fetchingChannels: boolean,
|
||||
reactionsById: ?{ [string]: { [REACTION_TYPES.LIKE | REACTION_TYPES.DISLIKE]: number } },
|
||||
activeChannel: string,
|
||||
activeChannelId: ?string,
|
||||
};
|
||||
|
||||
function CommentList(props: Props) {
|
||||
|
@ -42,7 +42,7 @@ function CommentList(props: Props) {
|
|||
totalComments,
|
||||
fetchingChannels,
|
||||
reactionsById,
|
||||
activeChannel,
|
||||
activeChannelId,
|
||||
} = props;
|
||||
const commentRef = React.useRef();
|
||||
const spinnerRef = React.useRef();
|
||||
|
@ -90,7 +90,7 @@ function CommentList(props: Props) {
|
|||
})
|
||||
.catch(() => setReadyToDisplayComments(true));
|
||||
}
|
||||
}, [fetchReacts, uri, totalComments, activeChannel, fetchingChannels]);
|
||||
}, [fetchReacts, uri, totalComments, activeChannelId, fetchingChannels]);
|
||||
|
||||
useEffect(() => {
|
||||
if (readyToDisplayComments && linkedCommentId && commentRef && commentRef.current) {
|
||||
|
|
|
@ -1015,4 +1015,9 @@ export const icons = {
|
|||
<path d="m 18.089706,2.6673324 c -0.458672,0 -0.914415,0.081053 -1.342833,0.2381801 -0.726068,-1.5175206 -2.625165,-2.67785413 -4.515474,-2.67785413 -1.902023,0 -3.8128297,1.16033353 -4.5408481,2.67785413 C 7.2621303,2.7483855 6.8063878,2.6673324 6.348691,2.6673324 c -2.1528256,0 -3.9045598,1.7507491 -3.9045598,3.9035835 0,2.0230385 1.4648199,3.6410591 3.4146614,3.8752841 v 8.262918 h 2.9276892 v -3.415632 c 0.00968,-0.26944 0.2273915,-0.487951 0.4977084,-0.487951 0.2693563,0 0.4879486,0.218539 0.4879486,0.487951 v 3.415632 h 1.9420352 v -4.391535 c 0,-0.269439 0.217626,-0.487951 0.487948,-0.487951 0.269357,0 0.487946,0.218539 0.487946,0.487951 v 4.391535 h 1.951795 v -3.415632 c 0.01964,-0.26944 0.238125,-0.487951 0.507465,-0.487951 0.270325,0 0.487949,0.218539 0.468432,0.487951 v 3.415632 h 2.927689 V 10.4462 c 1.980095,-0.234307 3.445891,-1.8522456 3.445891,-3.8752841 0,-2.1528344 -1.750758,-3.9035835 -3.901634,-3.9035835" />
|
||||
</svg>
|
||||
),
|
||||
[ICONS.ANONYMOUS]: buildIcon(
|
||||
<g>
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
||||
</g>
|
||||
),
|
||||
};
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, doPrepareEdit } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
import CreatorAnalytics from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
prepareEdit: channelName => dispatch(doPrepareEdit({ signing_channel: { name: channelName } })),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(CreatorAnalytics);
|
||||
export default connect(select)(CreatorAnalytics);
|
||||
|
|
|
@ -26,7 +26,7 @@ export default function CreatorAnalytics(props: Props) {
|
|||
const history = useHistory();
|
||||
const [stats, setStats] = React.useState();
|
||||
const [error, setError] = React.useState();
|
||||
const [fetchingStats, setFetchingStats] = React.useState(true);
|
||||
const [fetchingStats, setFetchingStats] = React.useState(false);
|
||||
const claimId = claim && claim.claim_id;
|
||||
const channelHasClaims = claim && claim.meta && claim.meta.claims_in_channel && claim.meta.claims_in_channel > 0;
|
||||
|
||||
|
@ -81,7 +81,24 @@ export default function CreatorAnalytics(props: Props) {
|
|||
/>
|
||||
)}
|
||||
|
||||
{!error && (
|
||||
{!error && !channelHasClaims ? (
|
||||
<Yrbl
|
||||
type="sad"
|
||||
title={__("You haven't uploaded anything")}
|
||||
subtitle={__('Upload something to start tracking your stats!')}
|
||||
actions={
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
button="primary"
|
||||
label={__('Upload Something')}
|
||||
onClick={() => {
|
||||
history.push(`/$/${PAGES.UPLOAD}`);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Yrbl
|
||||
title={
|
||||
channelHasClaims
|
||||
|
@ -93,12 +110,7 @@ export default function CreatorAnalytics(props: Props) {
|
|||
<Button
|
||||
button="primary"
|
||||
label={__('Upload Something')}
|
||||
onClick={() => {
|
||||
if (claim) {
|
||||
prepareEdit(claim.name);
|
||||
history.push(`/$/${PAGES.UPLOAD}`);
|
||||
}
|
||||
}}
|
||||
onClick={() => history.push(`/$/${PAGES.UPLOAD}`)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import * as MODALS from 'constants/modal_types';
|
||||
import { connect } from 'react-redux';
|
||||
import { selectTotalBalance, selectBalance, formatCredits, selectMyChannelClaims, SETTINGS } from 'lbry-redux';
|
||||
import { selectTotalBalance, selectBalance, formatCredits, SETTINGS } from 'lbry-redux';
|
||||
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
|
||||
import { selectUserVerifiedEmail, selectUserEmail, selectEmailToVerify, selectUser } from 'redux/selectors/user';
|
||||
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
|
||||
import { doSetClientSetting } from 'redux/actions/settings';
|
||||
import { doSignOut, doOpenModal } from 'redux/actions/app';
|
||||
import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
||||
import { selectCommentChannel } from 'redux/selectors/comments';
|
||||
import { selectHasNavigated, selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import Header from './view';
|
||||
import { selectHasNavigated } from 'redux/selectors/app';
|
||||
|
||||
const select = state => ({
|
||||
language: selectLanguage(state),
|
||||
|
@ -25,8 +24,7 @@ const select = state => ({
|
|||
emailToVerify: selectEmailToVerify(state),
|
||||
hasNavigated: selectHasNavigated(state),
|
||||
user: selectUser(state),
|
||||
myChannels: selectMyChannelClaims(state),
|
||||
commentChannel: selectCommentChannel(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
|
|
@ -61,8 +61,7 @@ type Props = {
|
|||
setSidebarOpen: boolean => void,
|
||||
isAbsoluteSideNavHidden: boolean,
|
||||
hideCancel: boolean,
|
||||
myChannels: ?Array<ChannelClaim>,
|
||||
commentChannel: string,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
};
|
||||
|
||||
const Header = (props: Props) => {
|
||||
|
@ -90,8 +89,7 @@ const Header = (props: Props) => {
|
|||
isAbsoluteSideNavHidden,
|
||||
user,
|
||||
hideCancel,
|
||||
myChannels,
|
||||
commentChannel,
|
||||
activeChannelClaim,
|
||||
} = props;
|
||||
const isMobile = useIsMobile();
|
||||
// on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
|
||||
|
@ -102,18 +100,8 @@ const Header = (props: Props) => {
|
|||
const hasBackout = Boolean(backout);
|
||||
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
||||
const notificationsEnabled = (user && user.experimental_ui) || false;
|
||||
let channelUrl;
|
||||
let identityChannel;
|
||||
if (myChannels && myChannels.length >= 1) {
|
||||
if (myChannels.length === 1) {
|
||||
identityChannel = myChannels[0];
|
||||
} else if (commentChannel) {
|
||||
identityChannel = myChannels.find(chan => chan.name === commentChannel);
|
||||
} else {
|
||||
identityChannel = myChannels[0];
|
||||
}
|
||||
channelUrl = identityChannel && (identityChannel.permanent_url || identityChannel.canonical_url);
|
||||
}
|
||||
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
|
||||
|
||||
// Sign out if they click the "x" when they are on the password prompt
|
||||
const authHeaderAction = syncError ? { onClick: signOut } : { navigate: '/' };
|
||||
const homeButtonNavigationProps = isVerifyPage ? {} : authHeader ? authHeaderAction : { navigate: '/' };
|
||||
|
@ -288,7 +276,7 @@ const Header = (props: Props) => {
|
|||
history={history}
|
||||
handleThemeToggle={handleThemeToggle}
|
||||
currentTheme={currentTheme}
|
||||
channelUrl={channelUrl}
|
||||
activeChannelUrl={activeChannelUrl}
|
||||
openSignOutModal={openSignOutModal}
|
||||
email={email}
|
||||
signOut={signOut}
|
||||
|
@ -341,7 +329,7 @@ type HeaderMenuButtonProps = {
|
|||
history: { push: string => void },
|
||||
handleThemeToggle: string => void,
|
||||
currentTheme: string,
|
||||
channelUrl: ?string,
|
||||
activeChannelUrl: ?string,
|
||||
openSignOutModal: () => void,
|
||||
email: ?string,
|
||||
signOut: () => void,
|
||||
|
@ -354,7 +342,7 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
|||
history,
|
||||
handleThemeToggle,
|
||||
currentTheme,
|
||||
channelUrl,
|
||||
activeChannelUrl,
|
||||
openSignOutModal,
|
||||
email,
|
||||
signOut,
|
||||
|
@ -427,8 +415,8 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
|||
aria-label={__('Your account')}
|
||||
title={__('Your account')}
|
||||
className={classnames('header__navigation-item mobile-hidden', {
|
||||
'menu__title header__navigation-item--icon': !channelUrl,
|
||||
'header__navigation-item--profile-pic': channelUrl,
|
||||
'menu__title header__navigation-item--icon': !activeChannelUrl,
|
||||
'header__navigation-item--profile-pic': activeChannelUrl,
|
||||
})}
|
||||
// @if TARGET='app'
|
||||
onDoubleClick={e => {
|
||||
|
@ -436,7 +424,11 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
|||
}}
|
||||
// @endif
|
||||
>
|
||||
{channelUrl ? <ChannelThumbnail uri={channelUrl} /> : <Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />}
|
||||
{activeChannelUrl ? (
|
||||
<ChannelThumbnail uri={activeChannelUrl} />
|
||||
) : (
|
||||
<Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />
|
||||
)}
|
||||
</MenuButton>
|
||||
<MenuList className="menu__list--header">
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.UPLOADS}`)}>
|
||||
|
|
|
@ -5,7 +5,6 @@ import Button from 'component/button';
|
|||
import { Form, FormField } from 'component/common/form';
|
||||
import CopyableText from 'component/copyableText';
|
||||
import Card from 'component/common/card';
|
||||
import SelectChannel from 'component/selectChannel';
|
||||
import analytics from 'analytics';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import LbcSymbol from 'component/common/lbc-symbol';
|
||||
|
@ -21,7 +20,6 @@ type Props = {
|
|||
|
||||
function InviteNew(props: Props) {
|
||||
const { inviteNew, errorMessage, isPending, referralCode = '', channels } = props;
|
||||
const noChannels = !channels || !(channels.length > 0);
|
||||
|
||||
// Email
|
||||
const [email, setEmail] = useState('');
|
||||
|
@ -87,14 +85,20 @@ function InviteNew(props: Props) {
|
|||
actions={
|
||||
<React.Fragment>
|
||||
<CopyableText label={__('Your invite link')} copyable={referral} />
|
||||
{!noChannels && (
|
||||
<SelectChannel
|
||||
channel={referralSource}
|
||||
onChannelChange={channel => handleReferralChange(channel)}
|
||||
{channels && channels.length > 0 && (
|
||||
<FormField
|
||||
type="select"
|
||||
label={__('Customize link')}
|
||||
hideAnon
|
||||
injected={[referralCode]}
|
||||
/>
|
||||
value={referralSource}
|
||||
onChange={e => handleReferralChange(e.target.value)}
|
||||
>
|
||||
{channels.map(channel => (
|
||||
<option key={channel.claim_id} value={channel.name}>
|
||||
{channel.name}
|
||||
</option>
|
||||
))}
|
||||
<option value={referralCode}>{referralCode}</option>
|
||||
</FormField>
|
||||
)}
|
||||
</React.Fragment>
|
||||
}
|
||||
|
|
38
ui/component/publishBid/bid-help-text.jsx
Normal file
38
ui/component/publishBid/bid-help-text.jsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
// @flow
|
||||
type Props = {
|
||||
uri: ?string,
|
||||
isResolvingUri: boolean,
|
||||
amountNeededForTakeover: number,
|
||||
};
|
||||
|
||||
function BidHelpText(props: Props) {
|
||||
const { uri, isResolvingUri, amountNeededForTakeover } = props;
|
||||
|
||||
let bidHelpText;
|
||||
|
||||
if (uri) {
|
||||
if (isResolvingUri) {
|
||||
bidHelpText = __('Checking the winning claim amount...');
|
||||
} else if (amountNeededForTakeover === 0) {
|
||||
bidHelpText = __('You currently have the highest bid for this name.');
|
||||
} else if (!amountNeededForTakeover) {
|
||||
bidHelpText = __(
|
||||
'Any amount will give you the highest bid, but larger amounts help your content be trusted and discovered.'
|
||||
);
|
||||
} else {
|
||||
bidHelpText = __(
|
||||
'If you bid more than %amount% LBRY Credits, when someone navigates to %uri%, it will load your published content. However, you can get a longer version of this URL for any bid.',
|
||||
{
|
||||
amount: amountNeededForTakeover,
|
||||
uri: uri,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
bidHelpText = __('These LBRY Credits remain yours and the deposit can be undone at any time.');
|
||||
}
|
||||
|
||||
return bidHelpText;
|
||||
}
|
||||
|
||||
export default BidHelpText;
|
26
ui/component/publishBid/index.js
Normal file
26
ui/component/publishBid/index.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
makeSelectPublishFormValue,
|
||||
selectMyClaimForUri,
|
||||
selectIsResolvingPublishUris,
|
||||
doUpdatePublishForm,
|
||||
doPrepareEdit,
|
||||
selectBalance,
|
||||
} from 'lbry-redux';
|
||||
import PublishPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
name: makeSelectPublishFormValue('name')(state),
|
||||
bid: makeSelectPublishFormValue('bid')(state),
|
||||
uri: makeSelectPublishFormValue('uri')(state),
|
||||
isResolvingUri: selectIsResolvingPublishUris(state),
|
||||
balance: selectBalance(state),
|
||||
myClaimForUri: selectMyClaimForUri(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(PublishPage);
|
77
ui/component/publishBid/view.jsx
Normal file
77
ui/component/publishBid/view.jsx
Normal file
|
@ -0,0 +1,77 @@
|
|||
// @flow
|
||||
import { MINIMUM_PUBLISH_BID } from 'constants/claim';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { FormField } from 'component/common/form';
|
||||
import BidHelpText from './bid-help-text';
|
||||
import Card from 'component/common/card';
|
||||
import LbcSymbol from 'component/common/lbc-symbol';
|
||||
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
|
||||
|
||||
type Props = {
|
||||
name: string,
|
||||
bid: number,
|
||||
balance: number,
|
||||
myClaimForUri: ?StreamClaim,
|
||||
isResolvingUri: boolean,
|
||||
amountNeededForTakeover: number,
|
||||
updatePublishForm: ({}) => void,
|
||||
};
|
||||
|
||||
function PublishName(props: Props) {
|
||||
const { name, myClaimForUri, bid, isResolvingUri, amountNeededForTakeover, updatePublishForm, balance } = props;
|
||||
const [bidError, setBidError] = useState(undefined);
|
||||
const previousBidAmount = myClaimForUri && Number(myClaimForUri.amount);
|
||||
|
||||
useEffect(() => {
|
||||
const totalAvailableBidAmount = previousBidAmount ? previousBidAmount + balance : balance;
|
||||
|
||||
let bidError;
|
||||
if (bid === 0) {
|
||||
bidError = __('Deposit cannot be 0');
|
||||
} else if (bid < MINIMUM_PUBLISH_BID) {
|
||||
bidError = __('Your deposit must be higher');
|
||||
} else if (totalAvailableBidAmount < bid) {
|
||||
bidError = __('Deposit cannot be higher than your available balance: %balance%', {
|
||||
balance: totalAvailableBidAmount,
|
||||
});
|
||||
} else if (totalAvailableBidAmount <= bid + 0.05) {
|
||||
bidError = __('Please decrease your deposit to account for transaction fees or acquire more LBRY Credits.');
|
||||
}
|
||||
|
||||
setBidError(bidError);
|
||||
updatePublishForm({ bidError: bidError });
|
||||
}, [bid, previousBidAmount, balance, updatePublishForm]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
actions={
|
||||
<FormField
|
||||
type="number"
|
||||
name="content_bid"
|
||||
min="0"
|
||||
step="any"
|
||||
placeholder="0.123"
|
||||
className="form-field--price-amount"
|
||||
label={<LbcSymbol postfix={__('Deposit')} size={12} />}
|
||||
value={bid}
|
||||
error={bidError}
|
||||
disabled={!name}
|
||||
onChange={event => updatePublishForm({ bid: parseFloat(event.target.value) })}
|
||||
onWheel={e => e.stopPropagation()}
|
||||
helper={
|
||||
<>
|
||||
<BidHelpText
|
||||
uri={'lbry://' + name}
|
||||
amountNeededForTakeover={amountNeededForTakeover}
|
||||
isResolvingUri={isResolvingUri}
|
||||
/>
|
||||
<WalletSpendableBalanceHelp inline />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default PublishName;
|
|
@ -13,6 +13,7 @@ import Spinner from 'component/spinner';
|
|||
import I18nMessage from 'component/i18nMessage';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import * as PUBLISH_MODES from 'constants/publish_types';
|
||||
import PublishName from 'component/publishName';
|
||||
|
||||
type Props = {
|
||||
uri: ?string,
|
||||
|
@ -34,10 +35,8 @@ type Props = {
|
|||
size: number,
|
||||
duration: number,
|
||||
isVid: boolean,
|
||||
autoPopulateName: boolean,
|
||||
setPublishMode: string => void,
|
||||
setPrevFileText: string => void,
|
||||
setAutoPopulateName: boolean => void,
|
||||
header: Node,
|
||||
};
|
||||
|
||||
|
@ -63,8 +62,6 @@ function PublishFile(props: Props) {
|
|||
isVid,
|
||||
setPublishMode,
|
||||
setPrevFileText,
|
||||
autoPopulateName,
|
||||
setAutoPopulateName,
|
||||
header,
|
||||
} = props;
|
||||
|
||||
|
@ -231,10 +228,6 @@ function PublishFile(props: Props) {
|
|||
const title = event.target.value;
|
||||
// Update title
|
||||
updatePublishForm({ title });
|
||||
// Auto populate name from title
|
||||
if (autoPopulateName) {
|
||||
updatePublishForm({ name: parseName(title) });
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileReaderLoaded(event: ProgressEvent) {
|
||||
|
@ -327,11 +320,6 @@ function PublishFile(props: Props) {
|
|||
publishFormParams.name = parseName(fileName);
|
||||
}
|
||||
|
||||
// Prevent name autopopulation from title
|
||||
if (autoPopulateName) {
|
||||
setAutoPopulateName(false);
|
||||
}
|
||||
|
||||
// File path is not supported on web for security reasons so we use the name instead.
|
||||
setCurrentFile(file.path || file.name);
|
||||
updatePublishForm(publishFormParams);
|
||||
|
@ -357,6 +345,7 @@ function PublishFile(props: Props) {
|
|||
subtitle={isStillEditing && __('You are currently editing your upload.')}
|
||||
actions={
|
||||
<React.Fragment>
|
||||
<PublishName />
|
||||
<FormField
|
||||
type="text"
|
||||
name="content_title"
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from 'lbry-redux';
|
||||
import { doPublishDesktop } from 'redux/actions/publish';
|
||||
import { selectUnclaimedRewardValue } from 'redux/selectors/rewards';
|
||||
import { selectModal } from 'redux/selectors/app';
|
||||
import { selectModal, selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import PublishPage from './view';
|
||||
|
||||
|
@ -32,6 +32,8 @@ const select = state => ({
|
|||
totalRewardValue: selectUnclaimedRewardValue(state),
|
||||
modal: selectModal(state),
|
||||
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
incognito: selectIncognito(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
|
|
@ -9,17 +9,16 @@
|
|||
*/
|
||||
|
||||
import { SITE_NAME } from 'config';
|
||||
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { buildURI, isURIValid, isNameValid, THUMBNAIL_STATUSES } from 'lbry-redux';
|
||||
import Button from 'component/button';
|
||||
import SelectChannel from 'component/selectChannel';
|
||||
import ChannelSelect from 'component/channelSelector';
|
||||
import classnames from 'classnames';
|
||||
import TagsSelect from 'component/tagsSelect';
|
||||
import PublishDescription from 'component/publishDescription';
|
||||
import PublishPrice from 'component/publishPrice';
|
||||
import PublishFile from 'component/publishFile';
|
||||
import PublishName from 'component/publishName';
|
||||
import PublishBid from 'component/publishBid';
|
||||
import PublishAdditionalOptions from 'component/publishAdditionalOptions';
|
||||
import PublishFormErrors from 'component/publishFormErrors';
|
||||
import SelectThumbnail from 'component/selectThumbnail';
|
||||
|
@ -60,7 +59,6 @@ type Props = {
|
|||
amount: string,
|
||||
currency: string,
|
||||
},
|
||||
channel: string,
|
||||
name: ?string,
|
||||
nameError: ?string,
|
||||
isResolvingUri: boolean,
|
||||
|
@ -85,6 +83,8 @@ type Props = {
|
|||
ytSignupPending: boolean,
|
||||
modal: { id: string, modalProps: {} },
|
||||
enablePublishPreview: boolean,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
incognito: boolean,
|
||||
};
|
||||
|
||||
function PublishForm(props: Props) {
|
||||
|
@ -101,7 +101,6 @@ function PublishForm(props: Props) {
|
|||
const {
|
||||
thumbnail,
|
||||
name,
|
||||
channel,
|
||||
editingURI,
|
||||
myClaimForUri,
|
||||
resolveUri,
|
||||
|
@ -123,17 +122,16 @@ function PublishForm(props: Props) {
|
|||
ytSignupPending,
|
||||
modal,
|
||||
enablePublishPreview,
|
||||
activeChannelClaim,
|
||||
incognito,
|
||||
} = props;
|
||||
|
||||
// Used to check if name should be auto-populated from title
|
||||
const [autoPopulateNameFromTitle, setAutoPopulateNameFromTitle] = useState(!isStillEditing);
|
||||
|
||||
const TAGS_LIMIT = 5;
|
||||
const fileFormDisabled = mode === PUBLISH_MODES.FILE && !filePath;
|
||||
const emptyPostError = mode === PUBLISH_MODES.POST && (!fileText || fileText.trim() === '');
|
||||
const formDisabled = (fileFormDisabled && !editingURI) || emptyPostError || publishing;
|
||||
const isInProgress = filePath || editingURI || name || title;
|
||||
|
||||
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
|
||||
// Editing content info
|
||||
const uri = myClaimForUri ? myClaimForUri.permanent_url : undefined;
|
||||
const fileMimeType =
|
||||
|
@ -204,16 +202,13 @@ function PublishForm(props: Props) {
|
|||
|
||||
// Every time the channel or name changes, resolve the uris to find winning bid amounts
|
||||
useEffect(() => {
|
||||
// If they are midway through a channel creation, treat it as anonymous until it completes
|
||||
const channelName = channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : channel;
|
||||
|
||||
// We are only going to store the full uri, but we need to resolve the uri with and without the channel name
|
||||
let uri;
|
||||
try {
|
||||
uri = name && buildURI({ streamName: name, channelName });
|
||||
uri = name && buildURI({ streamName: name, activeChannelName });
|
||||
} catch (e) {}
|
||||
|
||||
if (channelName && name) {
|
||||
if (activeChannelName && name) {
|
||||
// resolve without the channel name so we know the winning bid for it
|
||||
try {
|
||||
const uriLessChannel = buildURI({ streamName: name });
|
||||
|
@ -227,15 +222,17 @@ function PublishForm(props: Props) {
|
|||
checkAvailability(name);
|
||||
updatePublishForm({ uri });
|
||||
}
|
||||
}, [name, channel, resolveUri, updatePublishForm, checkAvailability]);
|
||||
}, [name, activeChannelName, resolveUri, updatePublishForm, checkAvailability]);
|
||||
|
||||
useEffect(() => {
|
||||
updatePublishForm({ isMarkdownPost: mode === PUBLISH_MODES.POST });
|
||||
}, [mode, updatePublishForm]);
|
||||
|
||||
function handleChannelNameChange(channel) {
|
||||
updatePublishForm({ channel });
|
||||
useEffect(() => {
|
||||
if (activeChannelName) {
|
||||
updatePublishForm({ channel: undefined });
|
||||
}
|
||||
}, [activeChannelName, incognito, updatePublishForm]);
|
||||
|
||||
// @if TARGET='web'
|
||||
function createWebFile() {
|
||||
|
@ -331,18 +328,7 @@ function PublishForm(props: Props) {
|
|||
// Editing claim uri
|
||||
return (
|
||||
<div className="card-stack">
|
||||
<Card
|
||||
className={disabled ? 'card--disabled' : undefined}
|
||||
actions={
|
||||
<React.Fragment>
|
||||
<SelectChannel channel={channel} onChannelChange={handleChannelNameChange} />
|
||||
<p className="help">
|
||||
{__('This is a username or handle that your content can be found under.')}{' '}
|
||||
{__('Ex. @Marvel, @TheBeatles, @BooksByJoe')}
|
||||
</p>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
<ChannelSelect disabled={disabled} />
|
||||
|
||||
<PublishFile
|
||||
uri={uri}
|
||||
|
@ -352,8 +338,6 @@ function PublishForm(props: Props) {
|
|||
inProgress={isInProgress}
|
||||
setPublishMode={setMode}
|
||||
setPrevFileText={setPrevFileText}
|
||||
autoPopulateName={autoPopulateNameFromTitle}
|
||||
setAutoPopulateName={setAutoPopulateNameFromTitle}
|
||||
header={
|
||||
<>
|
||||
{MODES.map((modeName, index) => (
|
||||
|
@ -371,6 +355,7 @@ function PublishForm(props: Props) {
|
|||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
{!publishing && (
|
||||
<div className={classnames({ 'card--disabled': formDisabled })}>
|
||||
{mode === PUBLISH_MODES.FILE && <PublishDescription disabled={formDisabled} />}
|
||||
|
@ -402,11 +387,7 @@ function PublishForm(props: Props) {
|
|||
tagsChosen={tags}
|
||||
/>
|
||||
|
||||
<PublishName
|
||||
disabled={isStillEditing || formDisabled}
|
||||
autoPopulateName={autoPopulateNameFromTitle}
|
||||
setAutoPopulateName={setAutoPopulateNameFromTitle}
|
||||
/>
|
||||
<PublishBid disabled={isStillEditing || formDisabled} />
|
||||
<PublishPrice disabled={formDisabled} />
|
||||
<PublishAdditionalOptions disabled={formDisabled} />
|
||||
</div>
|
||||
|
|
|
@ -3,33 +3,25 @@ import {
|
|||
makeSelectPublishFormValue,
|
||||
selectIsStillEditing,
|
||||
selectMyClaimForUri,
|
||||
selectIsResolvingPublishUris,
|
||||
selectTakeOverAmount,
|
||||
doUpdatePublishForm,
|
||||
doPrepareEdit,
|
||||
selectBalance,
|
||||
} from 'lbry-redux';
|
||||
|
||||
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
|
||||
import { doSetActiveChannel } from 'redux/actions/app';
|
||||
import PublishPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
name: makeSelectPublishFormValue('name')(state),
|
||||
channel: makeSelectPublishFormValue('channel')(state),
|
||||
bid: makeSelectPublishFormValue('bid')(state),
|
||||
uri: makeSelectPublishFormValue('uri')(state),
|
||||
isStillEditing: selectIsStillEditing(state),
|
||||
isResolvingUri: selectIsResolvingPublishUris(state),
|
||||
amountNeededForTakeover: selectTakeOverAmount(state),
|
||||
balance: selectBalance(state),
|
||||
myClaimForUri: selectMyClaimForUri(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
incognito: selectIncognito(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
||||
setActiveChannel: claimId => dispatch(doSetActiveChannel(claimId)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(PublishPage);
|
||||
export default connect(select, perform)(PublishPage);
|
||||
|
|
|
@ -1,51 +1,41 @@
|
|||
// @flow
|
||||
import { CHANNEL_NEW, CHANNEL_ANONYMOUS, MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
|
||||
import { DOMAIN } from 'config';
|
||||
import { INVALID_NAME_ERROR } from 'constants/claim';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { isNameValid } from 'lbry-redux';
|
||||
import { FormField } from 'component/common/form';
|
||||
import NameHelpText from './name-help-text';
|
||||
import BidHelpText from './bid-help-text';
|
||||
import Card from 'component/common/card';
|
||||
import LbcSymbol from 'component/common/lbc-symbol';
|
||||
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
|
||||
|
||||
type Props = {
|
||||
name: string,
|
||||
channel: string,
|
||||
uri: string,
|
||||
bid: number,
|
||||
balance: number,
|
||||
disabled: boolean,
|
||||
isStillEditing: boolean,
|
||||
myClaimForUri: ?StreamClaim,
|
||||
isResolvingUri: boolean,
|
||||
amountNeededForTakeover: number,
|
||||
prepareEdit: ({}, string) => void,
|
||||
updatePublishForm: ({}) => void,
|
||||
autoPopulateName: boolean,
|
||||
setAutoPopulateName: boolean => void,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
incognito: boolean,
|
||||
};
|
||||
|
||||
function PublishName(props: Props) {
|
||||
const {
|
||||
name,
|
||||
channel,
|
||||
uri,
|
||||
disabled,
|
||||
isStillEditing,
|
||||
myClaimForUri,
|
||||
bid,
|
||||
isResolvingUri,
|
||||
amountNeededForTakeover,
|
||||
prepareEdit,
|
||||
updatePublishForm,
|
||||
balance,
|
||||
autoPopulateName,
|
||||
setAutoPopulateName,
|
||||
activeChannelClaim,
|
||||
incognito,
|
||||
} = props;
|
||||
const [nameError, setNameError] = useState(undefined);
|
||||
const [bidError, setBidError] = useState(undefined);
|
||||
const previousBidAmount = myClaimForUri && Number(myClaimForUri.amount);
|
||||
const [blurred, setBlurred] = React.useState(false);
|
||||
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
|
||||
let prefix = IS_WEB ? `${DOMAIN}/` : 'lbry://';
|
||||
if (activeChannelName && !incognito) {
|
||||
prefix += `${activeChannelName}/`;
|
||||
}
|
||||
|
||||
function editExistingClaim() {
|
||||
if (myClaimForUri) {
|
||||
|
@ -55,21 +45,13 @@ function PublishName(props: Props) {
|
|||
|
||||
function handleNameChange(event) {
|
||||
updatePublishForm({ name: event.target.value });
|
||||
|
||||
if (autoPopulateName) {
|
||||
setAutoPopulateName(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const hasName = name && name.trim() !== '';
|
||||
// Enable name autopopulation from title
|
||||
if (!hasName && !autoPopulateName) {
|
||||
setAutoPopulateName(true);
|
||||
if (!blurred && !name) {
|
||||
return;
|
||||
}
|
||||
}, [name, autoPopulateName, setAutoPopulateName]);
|
||||
|
||||
useEffect(() => {
|
||||
let nameError;
|
||||
if (!name) {
|
||||
nameError = __('A name is required');
|
||||
|
@ -78,46 +60,22 @@ function PublishName(props: Props) {
|
|||
}
|
||||
|
||||
setNameError(nameError);
|
||||
}, [name]);
|
||||
|
||||
useEffect(() => {
|
||||
const totalAvailableBidAmount = previousBidAmount ? previousBidAmount + balance : balance;
|
||||
|
||||
let bidError;
|
||||
if (bid === 0) {
|
||||
bidError = __('Deposit cannot be 0');
|
||||
} else if (bid < MINIMUM_PUBLISH_BID) {
|
||||
bidError = __('Your deposit must be higher');
|
||||
} else if (totalAvailableBidAmount < bid) {
|
||||
bidError = __('Deposit cannot be higher than your available balance: %balance%', {
|
||||
balance: totalAvailableBidAmount,
|
||||
});
|
||||
} else if (totalAvailableBidAmount <= bid + 0.05) {
|
||||
bidError = __('Please decrease your deposit to account for transaction fees or acquire more LBRY Credits.');
|
||||
}
|
||||
|
||||
setBidError(bidError);
|
||||
updatePublishForm({ bidError: bidError });
|
||||
}, [bid, previousBidAmount, balance, updatePublishForm]);
|
||||
}, [name, blurred]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
actions={
|
||||
<React.Fragment>
|
||||
<>
|
||||
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
||||
<fieldset-section>
|
||||
<label>{__('Name')}</label>
|
||||
<div className="form-field__prefix">{`lbry://${
|
||||
!channel || channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : `${channel}/`
|
||||
}`}</div>
|
||||
<div className="form-field__prefix">{prefix}</div>
|
||||
</fieldset-section>
|
||||
<FormField
|
||||
type="text"
|
||||
name="content_name"
|
||||
value={name}
|
||||
disabled={disabled}
|
||||
error={nameError}
|
||||
onChange={handleNameChange}
|
||||
onBlur={() => setBlurred(true)}
|
||||
/>
|
||||
</fieldset-group>
|
||||
<div className="form-field__help">
|
||||
|
@ -128,33 +86,7 @@ function PublishName(props: Props) {
|
|||
onEditMyClaim={editExistingClaim}
|
||||
/>
|
||||
</div>
|
||||
<FormField
|
||||
type="number"
|
||||
name="content_bid"
|
||||
min="0"
|
||||
step="any"
|
||||
placeholder="0.123"
|
||||
className="form-field--price-amount"
|
||||
label={<LbcSymbol postfix={__('Deposit')} size={12} />}
|
||||
value={bid}
|
||||
error={bidError}
|
||||
disabled={!name}
|
||||
onChange={event => updatePublishForm({ bid: parseFloat(event.target.value) })}
|
||||
onWheel={e => e.stopPropagation()}
|
||||
helper={
|
||||
<>
|
||||
<BidHelpText
|
||||
uri={'lbry://' + name}
|
||||
amountNeededForTakeover={amountNeededForTakeover}
|
||||
isResolvingUri={isResolvingUri}
|
||||
/>
|
||||
<WalletSpendableBalanceHelp inline />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,10 @@ import {
|
|||
doCheckPendingClaims,
|
||||
makeSelectEffectiveAmountForUri,
|
||||
makeSelectIsUriResolving,
|
||||
selectFetchingMyChannels,
|
||||
} from 'lbry-redux';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import RepostCreate from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -33,6 +35,8 @@ const select = (state, props) => ({
|
|||
myClaims: selectMyClaimsWithoutChannels(state),
|
||||
isResolvingPassedRepost: props.name && makeSelectIsUriResolving(`lbry://${props.name}`)(state),
|
||||
isResolvingEnteredRepost: props.repostUri && makeSelectIsUriResolving(`lbry://${props.repostUri}`)(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
fetchingMyChannels: selectFetchingMyChannels(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
// @flow
|
||||
|
||||
import * as ICONS from 'constants/icons';
|
||||
import { CHANNEL_NEW, MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
|
||||
import { MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import Card from 'component/common/card';
|
||||
import Button from 'component/button';
|
||||
import SelectChannel from 'component/selectChannel';
|
||||
import ChannelSelector from 'component/channelSelector';
|
||||
import { FormField } from 'component/common/form';
|
||||
import { parseURI, isNameValid, creditsToString, isURIValid, normalizeURI } from 'lbry-redux';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import analytics from 'analytics';
|
||||
import LbcSymbol from 'component/common/lbc-symbol';
|
||||
import ClaimPreview from 'component/claimPreview';
|
||||
import { URL as SITE_URL, URL_LOCAL, URL_DEV } from 'config';
|
||||
import HelpLink from 'component/common/help-link';
|
||||
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
|
||||
import Spinner from 'component/spinner';
|
||||
|
||||
type Props = {
|
||||
doToast: ({ message: string }) => void,
|
||||
|
@ -39,6 +40,8 @@ type Props = {
|
|||
enteredRepostAmount: number,
|
||||
isResolvingPassedRepost: boolean,
|
||||
isResolvingEnteredRepost: boolean,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
fetchingMyChannels: boolean,
|
||||
};
|
||||
|
||||
function RepostCreate(props: Props) {
|
||||
|
@ -50,7 +53,6 @@ function RepostCreate(props: Props) {
|
|||
claim,
|
||||
enteredContentClaim,
|
||||
balance,
|
||||
channels,
|
||||
reposting,
|
||||
doCheckPublishNameAvailability,
|
||||
uri, // ?from
|
||||
|
@ -63,12 +65,13 @@ function RepostCreate(props: Props) {
|
|||
passedRepostAmount,
|
||||
isResolvingPassedRepost,
|
||||
isResolvingEnteredRepost,
|
||||
activeChannelClaim,
|
||||
fetchingMyChannels,
|
||||
} = props;
|
||||
|
||||
const defaultName = name || (claim && claim.name) || '';
|
||||
const contentClaimId = claim && claim.claim_id;
|
||||
const enteredClaimId = enteredContentClaim && enteredContentClaim.claim_id;
|
||||
const [repostChannel, setRepostChannel] = usePersistedState('repost-channel', 'anonymous');
|
||||
|
||||
const [repostBid, setRepostBid] = React.useState(0.01);
|
||||
const [repostBidError, setRepostBidError] = React.useState(undefined);
|
||||
|
@ -80,9 +83,7 @@ function RepostCreate(props: Props) {
|
|||
|
||||
const { replace, goBack } = useHistory();
|
||||
const resolvingRepost = isResolvingEnteredRepost || isResolvingPassedRepost;
|
||||
const repostUrlName = `lbry://${
|
||||
!repostChannel || repostChannel === CHANNEL_NEW || repostChannel === 'anonymous' ? '' : `${repostChannel}/`
|
||||
}`;
|
||||
const repostUrlName = `lbry://${!activeChannelClaim ? '' : `${activeChannelClaim.name}/`}`;
|
||||
|
||||
const contentFirstRender = React.useRef(true);
|
||||
const setAutoRepostBid = amount => {
|
||||
|
@ -173,17 +174,6 @@ function RepostCreate(props: Props) {
|
|||
contentNameError = __('A name is required');
|
||||
}
|
||||
|
||||
// repostChannel
|
||||
const channelStrings = channels && channels.map(channel => channel.permanent_url).join(',');
|
||||
React.useEffect(() => {
|
||||
if (!repostChannel && channelStrings) {
|
||||
const channels = channelStrings.split(',');
|
||||
const newChannelUrl = channels[0];
|
||||
const { claimName } = parseURI(newChannelUrl);
|
||||
setRepostChannel(claimName);
|
||||
}
|
||||
}, [channelStrings]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (enteredRepostName && isNameValid(enteredRepostName, false)) {
|
||||
doCheckPublishNameAvailability(enteredRepostName).then(r => setAvailable(r));
|
||||
|
@ -287,12 +277,11 @@ function RepostCreate(props: Props) {
|
|||
};
|
||||
|
||||
function handleSubmit() {
|
||||
const channelToRepostTo = channels && channels.find(channel => channel.name === repostChannel);
|
||||
if (enteredRepostName && repostBid && repostClaimId) {
|
||||
doRepost({
|
||||
name: enteredRepostName,
|
||||
bid: creditsToString(repostBid),
|
||||
channel_id: channelToRepostTo ? channelToRepostTo.claim_id : undefined,
|
||||
channel_id: activeChannelClaim ? activeChannelClaim.claim_id : undefined,
|
||||
claim_id: repostClaimId,
|
||||
}).then((repostClaim: StreamClaim) => {
|
||||
doCheckPendingClaims();
|
||||
|
@ -308,8 +297,18 @@ function RepostCreate(props: Props) {
|
|||
goBack();
|
||||
}
|
||||
|
||||
if (fetchingMyChannels) {
|
||||
return (
|
||||
<div className="main--empty">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ChannelSelector />
|
||||
|
||||
<Card
|
||||
actions={
|
||||
<div>
|
||||
|
@ -331,6 +330,7 @@ function RepostCreate(props: Props) {
|
|||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!uri && (
|
||||
<fieldset-section>
|
||||
<ClaimPreview key={contentUri} uri={contentUri} actions={''} type={'large'} showNullPlaceholder />
|
||||
|
@ -362,12 +362,6 @@ function RepostCreate(props: Props) {
|
|||
/>
|
||||
</fieldset-group>
|
||||
</fieldset-section>
|
||||
<SelectChannel
|
||||
label={__('Channel to repost on')}
|
||||
hideNew
|
||||
channel={repostChannel}
|
||||
onChannelChange={newChannel => setRepostChannel(newChannel)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
type="number"
|
||||
|
@ -379,9 +373,14 @@ function RepostCreate(props: Props) {
|
|||
label={<LbcSymbol postfix={__('Support --[button to support a claim]--')} size={14} />}
|
||||
value={repostBid}
|
||||
error={repostBidError}
|
||||
helper={__('Winning amount: %amount%', {
|
||||
helper={
|
||||
<>
|
||||
{__('Winning amount: %amount%', {
|
||||
amount: Number(takeoverAmount).toFixed(2),
|
||||
})}
|
||||
<WalletSpendableBalanceHelp inline />
|
||||
</>
|
||||
}
|
||||
disabled={!enteredRepostName || resolvingRepost}
|
||||
onChange={event => setRepostBid(event.target.value)}
|
||||
onWheel={e => e.stopPropagation()}
|
||||
|
|
|
@ -8,17 +8,21 @@ import {
|
|||
doCreateChannel,
|
||||
} from 'lbry-redux';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import { doSetActiveChannel } from 'redux/actions/app';
|
||||
|
||||
const select = state => ({
|
||||
channels: selectMyChannelClaims(state),
|
||||
myChannelClaims: selectMyChannelClaims(state),
|
||||
fetchingChannels: selectFetchingMyChannels(state),
|
||||
balance: selectBalance(state),
|
||||
emailVerified: selectUserVerifiedEmail(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
|
||||
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
||||
setActiveChannel: claimId => dispatch(doSetActiveChannel(claimId)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(SelectChannel);
|
||||
|
|
|
@ -1,115 +1,51 @@
|
|||
// @flow
|
||||
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import { FormField } from 'component/common/form';
|
||||
import ChannelCreate from 'component/channelCreate';
|
||||
|
||||
type Props = {
|
||||
channel: string, // currently selected channel
|
||||
channels: ?Array<ChannelClaim>,
|
||||
balance: number,
|
||||
onChannelChange: string => void,
|
||||
createChannel: (string, number) => Promise<any>,
|
||||
fetchChannelListMine: () => void,
|
||||
tiny?: boolean,
|
||||
label: string,
|
||||
myChannelClaims: ?Array<ChannelClaim>,
|
||||
injected: ?Array<string>,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
setActiveChannel: string => void,
|
||||
fetchingChannels: boolean,
|
||||
hideAnon: boolean,
|
||||
hideNew: boolean,
|
||||
label?: string,
|
||||
injected?: Array<string>,
|
||||
emailVerified: boolean,
|
||||
tiny: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
addingChannel: boolean,
|
||||
};
|
||||
function SelectChannel(props: Props) {
|
||||
const {
|
||||
fetchingChannels,
|
||||
myChannelClaims = [],
|
||||
label,
|
||||
injected = [],
|
||||
tiny,
|
||||
activeChannelClaim,
|
||||
setActiveChannel,
|
||||
} = props;
|
||||
|
||||
const ID_FF_SELECT_CHANNEL = 'ID_FF_SELECT_CHANNEL';
|
||||
|
||||
class ChannelSelection extends React.PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
addingChannel: props.channel === CHANNEL_NEW,
|
||||
};
|
||||
|
||||
(this: any).handleChannelChange = this.handleChannelChange.bind(this);
|
||||
(this: any).handleChangeToNewChannel = this.handleChangeToNewChannel.bind(this);
|
||||
function handleChannelChange(event: SyntheticInputEvent<*>) {
|
||||
const channelClaimId = event.target.value;
|
||||
setActiveChannel(channelClaimId);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { channel, channels, fetchChannelListMine, fetchingChannels, emailVerified, onChannelChange } = this.props;
|
||||
if (IS_WEB && !emailVerified) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((!channels || !channels.length) && !fetchingChannels) {
|
||||
fetchChannelListMine();
|
||||
}
|
||||
|
||||
if (channels && channels.length && !channels.find(chan => chan.name === channel)) {
|
||||
const elem = document.getElementById(ID_FF_SELECT_CHANNEL);
|
||||
// $FlowFixMe
|
||||
if (elem && elem.value && elem.value !== channel) {
|
||||
setTimeout(() => {
|
||||
// $FlowFixMe
|
||||
onChannelChange(elem.value);
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { channels, fetchingChannels, hideAnon } = this.props;
|
||||
if (!fetchingChannels && !channels && hideAnon) {
|
||||
this.setState({ addingChannel: true });
|
||||
}
|
||||
}
|
||||
|
||||
handleChannelChange(event: SyntheticInputEvent<*>) {
|
||||
const { onChannelChange } = this.props;
|
||||
const channel = event.target.value;
|
||||
|
||||
if (channel === CHANNEL_NEW) {
|
||||
this.setState({ addingChannel: true });
|
||||
onChannelChange(channel);
|
||||
} else {
|
||||
this.setState({ addingChannel: false });
|
||||
onChannelChange(channel);
|
||||
}
|
||||
}
|
||||
|
||||
handleChangeToNewChannel(props: Object) {
|
||||
const { onChannelChange } = this.props;
|
||||
const { newChannelName } = props;
|
||||
|
||||
this.setState({ addingChannel: false });
|
||||
|
||||
const channelName = `@${newChannelName.trim()}`;
|
||||
onChannelChange(channelName);
|
||||
}
|
||||
|
||||
render() {
|
||||
const channel = this.state.addingChannel ? CHANNEL_NEW : this.props.channel;
|
||||
const { fetchingChannels, channels = [], hideAnon, hideNew, label, injected = [], tiny } = this.props;
|
||||
const { addingChannel } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
<FormField
|
||||
id={ID_FF_SELECT_CHANNEL}
|
||||
name="channel"
|
||||
label={!tiny && (label || __('Channel'))}
|
||||
labelOnLeft={tiny}
|
||||
type={tiny ? 'select-tiny' : 'select'}
|
||||
onChange={this.handleChannelChange}
|
||||
value={channel}
|
||||
onChange={handleChannelChange}
|
||||
value={activeChannelClaim && activeChannelClaim.claim_id}
|
||||
disabled={fetchingChannels}
|
||||
>
|
||||
{!hideAnon && <option value={CHANNEL_ANONYMOUS}>{__('Anonymous')}</option>}
|
||||
{channels &&
|
||||
channels.map(({ name, claim_id: claimId }) => (
|
||||
<option key={claimId} value={name}>
|
||||
{fetchingChannels ? (
|
||||
<option>{__('Loading your channels...')}</option>
|
||||
) : (
|
||||
<>
|
||||
{myChannelClaims &&
|
||||
myChannelClaims.map(({ name, claim_id: claimId }) => (
|
||||
<option key={claimId} value={claimId}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
|
@ -119,13 +55,11 @@ class ChannelSelection extends React.PureComponent<Props, State> {
|
|||
{item}
|
||||
</option>
|
||||
))}
|
||||
{!fetchingChannels && !hideNew && <option value={CHANNEL_NEW}>{__('New channel...')}</option>}
|
||||
</>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
{addingChannel && <ChannelCreate onSuccess={this.handleChangeToNewChannel} />}
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChannelSelection;
|
||||
export default SelectChannel;
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
selectIsSendingSupport,
|
||||
selectBalance,
|
||||
SETTINGS,
|
||||
selectMyChannelClaims,
|
||||
makeSelectClaimIsMine,
|
||||
selectFetchingMyChannels,
|
||||
} from 'lbry-redux';
|
||||
|
@ -14,6 +13,7 @@ import WalletSendTip from './view';
|
|||
import { doOpenModal, doHideModal } from 'redux/actions/app';
|
||||
import { withRouter } from 'react-router';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
|
||||
|
||||
const select = (state, props) => ({
|
||||
isPending: selectIsSendingSupport(state),
|
||||
|
@ -22,9 +22,10 @@ const select = (state, props) => ({
|
|||
balance: selectBalance(state),
|
||||
instantTipEnabled: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_ENABLED)(state),
|
||||
instantTipMax: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_MAX)(state),
|
||||
channels: selectMyChannelClaims(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
fetchingChannels: selectFetchingMyChannels(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
incognito: selectIncognito(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
|
|
|
@ -5,13 +5,13 @@ import * as PAGES from 'constants/pages';
|
|||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import { FormField, Form } from 'component/common/form';
|
||||
import { MINIMUM_PUBLISH_BID, CHANNEL_ANONYMOUS, CHANNEL_NEW } from 'constants/claim';
|
||||
import { MINIMUM_PUBLISH_BID } from 'constants/claim';
|
||||
import CreditAmount from 'component/common/credit-amount';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import Card from 'component/common/card';
|
||||
import classnames from 'classnames';
|
||||
import SelectChannel from 'component/selectChannel';
|
||||
import ChannelSelector from 'component/channelSelector';
|
||||
import LbcSymbol from 'component/common/lbc-symbol';
|
||||
import { parseURI } from 'lbry-redux';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
|
@ -34,7 +34,8 @@ type Props = {
|
|||
fetchingChannels: boolean,
|
||||
instantTipEnabled: boolean,
|
||||
instantTipMax: { amount: number, currency: string },
|
||||
channels: ?Array<ChannelClaim>,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
incognito: boolean,
|
||||
};
|
||||
|
||||
function WalletSendTip(props: Props) {
|
||||
|
@ -49,8 +50,9 @@ function WalletSendTip(props: Props) {
|
|||
instantTipMax,
|
||||
sendSupport,
|
||||
closeModal,
|
||||
channels,
|
||||
fetchingChannels,
|
||||
incognito,
|
||||
activeChannelClaim,
|
||||
} = props;
|
||||
const [presetTipAmount, setPresetTipAmount] = usePersistedState('comment-support:presetTip', DEFAULT_TIP_AMOUNTS[0]);
|
||||
const [customTipAmount, setCustomTipAmount] = usePersistedState('comment-support:customTip', 1.0);
|
||||
|
@ -58,21 +60,9 @@ function WalletSendTip(props: Props) {
|
|||
const [tipError, setTipError] = React.useState();
|
||||
const [sendAsTip, setSendAsTip] = usePersistedState('comment-support:sendAsTip', true);
|
||||
const [isConfirming, setIsConfirming] = React.useState(false);
|
||||
const [selectedChannel, setSelectedChannel] = usePersistedState('comment-support:channel');
|
||||
const { claim_id: claimId } = claim;
|
||||
const { channelName } = parseURI(uri);
|
||||
const noBalance = balance === 0;
|
||||
|
||||
const channelStrings = channels && channels.map(channel => channel.permanent_url).join(',');
|
||||
React.useEffect(() => {
|
||||
if (!selectedChannel && channelStrings) {
|
||||
const channels = channelStrings.split(',');
|
||||
const newChannelUrl = channels[0];
|
||||
const { claimName } = parseURI(newChannelUrl);
|
||||
setSelectedChannel(claimName);
|
||||
}
|
||||
}, [channelStrings, selectedChannel, setSelectedChannel]);
|
||||
|
||||
const tipAmount = useCustomTip ? customTipAmount : presetTipAmount;
|
||||
const isSupport = claimIsMine ? true : SIMPLE_SITE ? false : !sendAsTip;
|
||||
|
||||
|
@ -99,12 +89,8 @@ function WalletSendTip(props: Props) {
|
|||
|
||||
function sendSupportOrConfirm(instantTipMaxAmount = null) {
|
||||
let selectedChannelId;
|
||||
if (selectedChannel !== CHANNEL_ANONYMOUS) {
|
||||
const selectedChannelClaim = channels && channels.find(channelClaim => channelClaim.name === selectedChannel);
|
||||
|
||||
if (selectedChannelClaim) {
|
||||
selectedChannelId = selectedChannelClaim.claim_id;
|
||||
}
|
||||
if (!incognito && activeChannelClaim) {
|
||||
selectedChannelId = activeChannelClaim.claim_id;
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -124,12 +110,6 @@ function WalletSendTip(props: Props) {
|
|||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (selectedChannel === CHANNEL_NEW) {
|
||||
// This is the submission to create a new channel, and would
|
||||
// be handled by <ChannelSelection>, so do nothing here.
|
||||
return;
|
||||
}
|
||||
|
||||
if (tipAmount && claimId) {
|
||||
if (instantTipEnabled) {
|
||||
if (instantTipMax.currency === 'LBC') {
|
||||
|
@ -196,7 +176,9 @@ function WalletSendTip(props: Props) {
|
|||
<div className="confirm__label">{__('To --[the tip recipient]--')}</div>
|
||||
<div className="confirm__value">{channelName || title}</div>
|
||||
<div className="confirm__label">{__('From --[the tip sender]--')}</div>
|
||||
<div className="confirm__value">{selectedChannel}</div>
|
||||
<div className="confirm__value">
|
||||
{activeChannelClaim && !incognito ? activeChannelClaim.name : __('Anonymous')}
|
||||
</div>
|
||||
<div className="confirm__label">{__(isSupport ? 'Supporting' : 'Tipping')}</div>
|
||||
<div className="confirm__value">
|
||||
<LbcSymbol postfix={tipAmount} size={22} />
|
||||
|
@ -217,11 +199,7 @@ function WalletSendTip(props: Props) {
|
|||
) : (
|
||||
<>
|
||||
<div className="section">
|
||||
<SelectChannel
|
||||
label={__('Channel to show support as')}
|
||||
channel={selectedChannel}
|
||||
onChannelChange={newChannel => setSelectedChannel(newChannel)}
|
||||
/>
|
||||
<ChannelSelector />
|
||||
</div>
|
||||
|
||||
<div className="section">
|
||||
|
|
|
@ -30,6 +30,8 @@ export const SET_HAS_NAVIGATED = 'SET_HAS_NAVIGATED';
|
|||
export const SET_SYNC_LOCK = 'SET_SYNC_LOCK';
|
||||
export const TOGGLE_YOUTUBE_SYNC_INTEREST = 'TOGGLE_YOUTUBE_SYNC_INTEREST';
|
||||
export const TOGGLE_SPLASH_ANIMATION = 'TOGGLE_SPLASH_ANIMATION';
|
||||
export const SET_ACTIVE_CHANNEL = 'SET_ACTIVE_CHANNEL';
|
||||
export const SET_INCOGNITO = 'SET_INCOGNITO';
|
||||
|
||||
// Navigation
|
||||
export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH';
|
||||
|
@ -266,7 +268,6 @@ export const COMMENT_REACT_FAILED = 'COMMENT_REACT_FAILED';
|
|||
export const COMMENT_PIN_STARTED = 'COMMENT_PIN_STARTED';
|
||||
export const COMMENT_PIN_COMPLETED = 'COMMENT_PIN_COMPLETED';
|
||||
export const COMMENT_PIN_FAILED = 'COMMENT_PIN_FAILED';
|
||||
export const COMMENT_SET_CHANNEL = 'COMMENT_SET_CHANNEL';
|
||||
|
||||
// Blocked channels
|
||||
export const TOGGLE_BLOCK_CHANNEL = 'TOGGLE_BLOCK_CHANNEL';
|
||||
|
|
|
@ -130,3 +130,4 @@ export const PIN = 'Pin';
|
|||
export const BEST = 'Best';
|
||||
export const CREATOR_LIKE = 'CreatorLike';
|
||||
export const CHEF = 'Chef';
|
||||
export const ANONYMOUS = 'Anonymous';
|
||||
|
|
|
@ -36,7 +36,6 @@ export const WALLET_RECEIVE = 'wallet_receive';
|
|||
export const CREATE_CHANNEL = 'create_channel';
|
||||
export const YOUTUBE_WELCOME = 'youtube_welcome';
|
||||
export const SET_REFERRER = 'set_referrer';
|
||||
export const REPOST = 'repost';
|
||||
export const SIGN_OUT = 'sign_out';
|
||||
export const LIQUIDATE_SUPPORTS = 'liquidate_supports';
|
||||
export const MASS_TIP_UNLOCK = 'mass_tip_unlock';
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doHideModal } from 'redux/actions/app';
|
||||
import {
|
||||
makeSelectClaimForUri,
|
||||
makeSelectTitleForUri,
|
||||
selectBalance,
|
||||
selectMyChannelClaims,
|
||||
doRepost,
|
||||
selectRepostError,
|
||||
selectRepostLoading,
|
||||
doClearRepostError,
|
||||
selectMyClaimsWithoutChannels,
|
||||
doCheckPublishNameAvailability,
|
||||
} from 'lbry-redux';
|
||||
import { doToast } from 'redux/actions/notifications';
|
||||
import ModalRepost from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
channels: selectMyChannelClaims(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
balance: selectBalance(state),
|
||||
error: selectRepostError(state),
|
||||
reposting: selectRepostLoading(state),
|
||||
myClaims: selectMyClaimsWithoutChannels(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doHideModal,
|
||||
doRepost,
|
||||
doClearRepostError,
|
||||
doToast,
|
||||
doCheckPublishNameAvailability,
|
||||
})(ModalRepost);
|
|
@ -1,212 +0,0 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import { CHANNEL_NEW, MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
|
||||
import React from 'react';
|
||||
import { Modal } from 'modal/modal';
|
||||
import Card from 'component/common/card';
|
||||
import Button from 'component/button';
|
||||
import SelectChannel from 'component/selectChannel';
|
||||
import ErrorText from 'component/common/error-text';
|
||||
import { FormField } from 'component/common/form';
|
||||
import { parseURI, isNameValid, creditsToString } from 'lbry-redux';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import analytics from 'analytics';
|
||||
import LbcSymbol from 'component/common/lbc-symbol';
|
||||
|
||||
type Props = {
|
||||
doHideModal: () => void,
|
||||
doToast: ({ message: string }) => void,
|
||||
doClearRepostError: () => void,
|
||||
doRepost: StreamRepostOptions => Promise<*>,
|
||||
title: string,
|
||||
claim: ?StreamClaim,
|
||||
balance: number,
|
||||
channels: ?Array<ChannelClaim>,
|
||||
doCheckPublishNameAvailability: string => Promise<*>,
|
||||
error: ?string,
|
||||
reposting: boolean,
|
||||
};
|
||||
|
||||
function ModalRepost(props: Props) {
|
||||
const {
|
||||
doHideModal,
|
||||
doToast,
|
||||
doClearRepostError,
|
||||
doRepost,
|
||||
title,
|
||||
claim,
|
||||
balance,
|
||||
channels,
|
||||
error,
|
||||
reposting,
|
||||
doCheckPublishNameAvailability,
|
||||
} = props;
|
||||
const defaultName = claim && claim.name;
|
||||
const contentClaimId = claim && claim.claim_id;
|
||||
const [repostChannel, setRepostChannel] = usePersistedState('repost-channel');
|
||||
const [repostBid, setRepostBid] = React.useState(0.01);
|
||||
const [showAdvanced, setShowAdvanced] = React.useState();
|
||||
const [repostName, setRepostName] = React.useState(defaultName);
|
||||
const [available, setAvailable] = React.useState(true);
|
||||
|
||||
let repostBidError;
|
||||
if (repostBid === 0) {
|
||||
repostBidError = __('Deposit cannot be 0');
|
||||
} else if (balance === repostBid) {
|
||||
repostBidError = __('Please decrease your deposit to account for transaction fees');
|
||||
} else if (balance < repostBid) {
|
||||
repostBidError = __('Deposit cannot be higher than your available balance');
|
||||
} else if (repostBid < MINIMUM_PUBLISH_BID) {
|
||||
repostBidError = __('Your deposit must be higher');
|
||||
}
|
||||
|
||||
let repostNameError;
|
||||
if (!repostName) {
|
||||
repostNameError = __('A name is required');
|
||||
} else if (!isNameValid(repostName, false)) {
|
||||
repostNameError = INVALID_NAME_ERROR;
|
||||
} else if (!available) {
|
||||
repostNameError = __('You already have a claim with this name.');
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
if ((repostNameError || repostNameError) && !showAdvanced) {
|
||||
setShowAdvanced(true);
|
||||
}
|
||||
}, [repostBidError, repostNameError, showAdvanced, setShowAdvanced]);
|
||||
|
||||
const channelStrings = channels && channels.map(channel => channel.permanent_url).join(',');
|
||||
React.useEffect(() => {
|
||||
if (!repostChannel && channelStrings) {
|
||||
const channels = channelStrings.split(',');
|
||||
const newChannelUrl = channels[0];
|
||||
const { claimName } = parseURI(newChannelUrl);
|
||||
setRepostChannel(claimName);
|
||||
}
|
||||
}, [channelStrings]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (repostName && isNameValid(repostName, false)) {
|
||||
doCheckPublishNameAvailability(repostName).then(r => setAvailable(r));
|
||||
}
|
||||
}, [repostName, doCheckPublishNameAvailability]);
|
||||
|
||||
function handleSubmit() {
|
||||
const channelToRepostTo = channels && channels.find(channel => channel.name === repostChannel);
|
||||
if (channelToRepostTo && repostName && repostBid && repostChannel && contentClaimId) {
|
||||
doRepost({
|
||||
name: repostName,
|
||||
bid: creditsToString(repostBid),
|
||||
channel_id: channelToRepostTo.claim_id,
|
||||
claim_id: contentClaimId,
|
||||
}).then((repostClaim: StreamClaim) => {
|
||||
analytics.apiLogPublish(repostClaim);
|
||||
doHideModal();
|
||||
doToast({ message: __('Woohoo! Successfully reposted this claim.') });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleCloseModal() {
|
||||
doClearRepostError();
|
||||
doHideModal();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen type="card" onAborted={handleCloseModal} onConfirmed={handleCloseModal}>
|
||||
<Card
|
||||
title={
|
||||
<span>
|
||||
<I18nMessage tokens={{ title: <em>{title}</em> }}>Repost %title%</I18nMessage>
|
||||
</span>
|
||||
}
|
||||
subtitle={
|
||||
error ? (
|
||||
<ErrorText>{__('There was an error reposting this claim. Please try again later.')}</ErrorText>
|
||||
) : (
|
||||
<span>{__('Repost your favorite claims to help more people discover them!')}</span>
|
||||
)
|
||||
}
|
||||
actions={
|
||||
<div>
|
||||
<SelectChannel
|
||||
label={__('Channel to repost on')}
|
||||
hideAnon
|
||||
hideNew
|
||||
channel={repostChannel}
|
||||
onChannelChange={newChannel => setRepostChannel(newChannel)}
|
||||
/>
|
||||
{!showAdvanced && (
|
||||
<div className="section__actions">
|
||||
<Button button="link" label={__('Advanced')} onClick={() => setShowAdvanced(true)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showAdvanced && (
|
||||
<React.Fragment>
|
||||
<fieldset-section>
|
||||
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
||||
<fieldset-section>
|
||||
<label>{__('Name')}</label>
|
||||
<div className="form-field__prefix">{`lbry://${
|
||||
!repostChannel || repostChannel === CHANNEL_NEW ? '' : `${repostChannel}/`
|
||||
}`}</div>
|
||||
</fieldset-section>
|
||||
<FormField
|
||||
type="text"
|
||||
name="repost_name"
|
||||
value={repostName}
|
||||
error={repostNameError}
|
||||
onChange={event => setRepostName(event.target.value)}
|
||||
/>
|
||||
</fieldset-group>
|
||||
</fieldset-section>
|
||||
|
||||
<div className="form-field__help">
|
||||
<I18nMessage
|
||||
tokens={{
|
||||
lbry_naming_link: (
|
||||
<Button button="link" label={__('community name')} href="https://lbry.com/faq/naming" />
|
||||
),
|
||||
}}
|
||||
>
|
||||
Change this to repost to a different %lbry_naming_link%.
|
||||
</I18nMessage>
|
||||
</div>
|
||||
|
||||
<FormField
|
||||
type="number"
|
||||
name="repost_bid"
|
||||
min="0"
|
||||
step="any"
|
||||
placeholder="0.123"
|
||||
className="form-field--price-amount"
|
||||
label={<LbcSymbol postfix={__('Deposit')} size={14} />}
|
||||
value={repostBid}
|
||||
error={repostBidError}
|
||||
disabled={!repostName}
|
||||
onChange={event => setRepostBid(parseFloat(event.target.value))}
|
||||
onWheel={e => e.stopPropagation()}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
<div className="section__actions">
|
||||
<Button
|
||||
icon={ICONS.REPOST}
|
||||
disabled={reposting || repostBidError || repostNameError}
|
||||
button="primary"
|
||||
label={reposting ? __('Reposting') : __('Repost')}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<Button button="link" label={__('Cancel')} onClick={handleCloseModal} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModalRepost;
|
|
@ -34,7 +34,6 @@ import ModalWalletReceive from 'modal/modalWalletReceive';
|
|||
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
|
||||
import ModalCreateChannel from 'modal/modalChannelCreate';
|
||||
import ModalSetReferrer from 'modal/modalSetReferrer';
|
||||
import ModalRepost from 'modal/modalRepost';
|
||||
import ModalSignOut from 'modal/modalSignOut';
|
||||
import ModalSupportsLiquidate from 'modal/modalSupportsLiquidate';
|
||||
import ModalConfirmAge from 'modal/modalConfirmAge';
|
||||
|
@ -135,8 +134,6 @@ function ModalRouter(props: Props) {
|
|||
return <ModalCreateChannel {...modalProps} />;
|
||||
case MODALS.SET_REFERRER:
|
||||
return <ModalSetReferrer {...modalProps} />;
|
||||
case MODALS.REPOST:
|
||||
return <ModalRepost {...modalProps} />;
|
||||
case MODALS.SIGN_OUT:
|
||||
return <ModalSignOut {...modalProps} />;
|
||||
case MODALS.CONFIRM_AGE:
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
import { doSetActiveChannel } from 'redux/actions/app';
|
||||
import CreatorDashboardPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
channels: selectMyChannelClaims(state),
|
||||
fetchingChannels: selectFetchingMyChannels(state),
|
||||
activeChannelClaim: selectActiveChannelClaim(state),
|
||||
});
|
||||
|
||||
export default connect(select)(CreatorDashboardPage);
|
||||
export default connect(select, { doSetActiveChannel })(CreatorDashboardPage);
|
||||
|
|
|
@ -6,54 +6,17 @@ import Spinner from 'component/spinner';
|
|||
import Button from 'component/button';
|
||||
import CreatorAnalytics from 'component/creatorAnalytics';
|
||||
import ChannelSelector from 'component/channelSelector';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import Yrbl from 'component/yrbl';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
type Props = {
|
||||
channels: Array<ChannelClaim>,
|
||||
fetchingChannels: boolean,
|
||||
activeChannelClaim: ?ChannelClaim,
|
||||
};
|
||||
|
||||
const SELECTED_CHANNEL_QUERY_PARAM = 'channel';
|
||||
|
||||
export default function CreatorDashboardPage(props: Props) {
|
||||
const { channels, fetchingChannels } = props;
|
||||
const {
|
||||
push,
|
||||
location: { search, pathname },
|
||||
} = useHistory();
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const channelFromUrl = urlParams.get(SELECTED_CHANNEL_QUERY_PARAM);
|
||||
const [selectedChannelUrl, setSelectedChannelUrl] = usePersistedState('analytics-selected-channel');
|
||||
const { channels, fetchingChannels, activeChannelClaim } = props;
|
||||
const hasChannels = channels && channels.length > 0;
|
||||
const firstChannel = hasChannels && channels[0];
|
||||
const firstChannelUrl = firstChannel && (firstChannel.canonical_url || firstChannel.permanent_url); // permanent_url is needed for pending publishes
|
||||
const channelFoundForSelectedChannelUrl =
|
||||
channels &&
|
||||
channels.find(channel => {
|
||||
return selectedChannelUrl === channel.canonical_url || selectedChannelUrl === channel.permanent_url;
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
// set default channel
|
||||
if ((!selectedChannelUrl || !channelFoundForSelectedChannelUrl) && firstChannelUrl) {
|
||||
setSelectedChannelUrl(firstChannelUrl);
|
||||
}
|
||||
}, [setSelectedChannelUrl, selectedChannelUrl, firstChannelUrl, channelFoundForSelectedChannelUrl]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (channelFromUrl) {
|
||||
const decodedChannel = decodeURIComponent(channelFromUrl);
|
||||
setSelectedChannelUrl(decodedChannel);
|
||||
}
|
||||
}, [channelFromUrl, setSelectedChannelUrl]);
|
||||
|
||||
function updateUrl(channelUrl) {
|
||||
const newUrlParams = new URLSearchParams();
|
||||
newUrlParams.append(SELECTED_CHANNEL_QUERY_PARAM, encodeURIComponent(channelUrl));
|
||||
push(`${pathname}?${newUrlParams.toString()}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
|
@ -63,7 +26,7 @@ export default function CreatorDashboardPage(props: Props) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{!fetchingChannels && (!channels || !channels.length) && (
|
||||
{!fetchingChannels && !hasChannels && (
|
||||
<Yrbl
|
||||
type="happy"
|
||||
title={__("You haven't created a channel yet, let's fix that!")}
|
||||
|
@ -75,18 +38,10 @@ export default function CreatorDashboardPage(props: Props) {
|
|||
/>
|
||||
)}
|
||||
|
||||
{!fetchingChannels && channels && channels.length && (
|
||||
{!fetchingChannels && activeChannelClaim && (
|
||||
<React.Fragment>
|
||||
<div className="section">
|
||||
<ChannelSelector
|
||||
selectedChannelUrl={selectedChannelUrl}
|
||||
onChannelSelect={newChannelUrl => {
|
||||
updateUrl(newChannelUrl);
|
||||
setSelectedChannelUrl(newChannelUrl);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<CreatorAnalytics uri={selectedChannelUrl} />
|
||||
<ChannelSelector hideAnon />
|
||||
<CreatorAnalytics uri={activeChannelClaim.canonical_url} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Page>
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
SHARED_PREFERENCES,
|
||||
DAEMON_SETTINGS,
|
||||
SETTINGS,
|
||||
selectMyChannelClaims,
|
||||
} from 'lbry-redux';
|
||||
import { Lbryio } from 'lbryinc';
|
||||
import { selectFollowedTagsList } from 'redux/selectors/tags';
|
||||
|
@ -693,3 +694,54 @@ export function doToggleSplashAnimation() {
|
|||
type: ACTIONS.TOGGLE_SPLASH_ANIMATION,
|
||||
};
|
||||
}
|
||||
|
||||
export function doSetActiveChannel(claimId) {
|
||||
return (dispatch, getState) => {
|
||||
if (claimId) {
|
||||
return dispatch({
|
||||
type: ACTIONS.SET_ACTIVE_CHANNEL,
|
||||
data: {
|
||||
claimId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// If no claimId is passed, set the active channel to the one with the highest effective_amount
|
||||
const state = getState();
|
||||
const myChannelClaims = selectMyChannelClaims(state);
|
||||
|
||||
if (!myChannelClaims || !myChannelClaims.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const myChannelClaimsByEffectiveAmount = myChannelClaims.slice().sort((a, b) => {
|
||||
const effectiveAmountA = (a.meta && Number(a.meta.effective_amount)) || 0;
|
||||
const effectiveAmountB = (b.meta && Number(b.meta.effective_amount)) || 0;
|
||||
if (effectiveAmountA === effectiveAmountB) {
|
||||
return 0;
|
||||
} else if (effectiveAmountA > effectiveAmountB) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
const newActiveChannelClaim = myChannelClaimsByEffectiveAmount[0];
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.SET_ACTIVE_CHANNEL,
|
||||
data: {
|
||||
claimId: newActiveChannelClaim.claim_id,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doSetIncognito(incognitoEnabled) {
|
||||
return {
|
||||
type: ACTIONS.SET_INCOGNITO,
|
||||
data: {
|
||||
enabled: incognitoEnabled,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
// @flow
|
||||
import * as ACTIONS from 'constants/action_types';
|
||||
import * as REACTION_TYPES from 'constants/reactions';
|
||||
import { Lbry, selectClaimsByUri, selectMyChannelClaims } from 'lbry-redux';
|
||||
import { Lbry, selectClaimsByUri } from 'lbry-redux';
|
||||
import { doToast, doSeeNotifications } from 'redux/actions/notifications';
|
||||
import {
|
||||
makeSelectCommentIdsForUri,
|
||||
makeSelectMyReactionsForComment,
|
||||
makeSelectOthersReactionsForComment,
|
||||
selectPendingCommentReacts,
|
||||
selectCommentChannel,
|
||||
} from 'redux/selectors/comments';
|
||||
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
|
||||
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||
|
||||
export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
|
@ -49,34 +49,23 @@ export function doCommentList(uri: string, page: number = 1, pageSize: number =
|
|||
};
|
||||
}
|
||||
|
||||
export function doSetCommentChannel(channelName: string) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_SET_CHANNEL,
|
||||
data: channelName,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doCommentReactList(uri: string | null, commentId?: string) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const channel = selectCommentChannel(state);
|
||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||
const commentIds = uri ? makeSelectCommentIdsForUri(uri)(state) : [commentId];
|
||||
const myChannels = selectMyChannelClaims(state);
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_REACTION_LIST_STARTED,
|
||||
});
|
||||
|
||||
const params: { comment_ids: string, channel_name?: string, channel_id?: string } = {
|
||||
comment_ids: commentIds.join(','),
|
||||
};
|
||||
|
||||
if (channel && myChannels) {
|
||||
const claimForChannelName = myChannels && myChannels.find(chan => chan.name === channel);
|
||||
const channelId = claimForChannelName && claimForChannelName.claim_id;
|
||||
params['channel_name'] = channel;
|
||||
params['channel_id'] = channelId;
|
||||
if (activeChannelClaim) {
|
||||
params['channel_name'] = activeChannelClaim.name;
|
||||
params['channel_id'] = activeChannelClaim.claim_id;
|
||||
}
|
||||
|
||||
return Lbry.comment_react_list(params)
|
||||
|
@ -102,39 +91,38 @@ export function doCommentReactList(uri: string | null, commentId?: string) {
|
|||
export function doCommentReact(commentId: string, type: string) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const channel = selectCommentChannel(state);
|
||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||
const pendingReacts = selectPendingCommentReacts(state);
|
||||
const myChannels = selectMyChannelClaims(state);
|
||||
const notification = makeSelectNotificationForCommentId(commentId)(state);
|
||||
|
||||
if (!activeChannelClaim) {
|
||||
console.error('Unable to react to comment. No activeChannel is set.'); // eslint-disable-line
|
||||
return;
|
||||
}
|
||||
|
||||
if (notification && !notification.is_seen) {
|
||||
dispatch(doSeeNotifications([notification.id]));
|
||||
}
|
||||
|
||||
const exclusiveTypes = {
|
||||
[REACTION_TYPES.LIKE]: REACTION_TYPES.DISLIKE,
|
||||
[REACTION_TYPES.DISLIKE]: REACTION_TYPES.LIKE,
|
||||
};
|
||||
if (!channel || !myChannels) {
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_REACTION_LIST_FAILED,
|
||||
data: 'No active channel found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingReacts.includes(commentId + exclusiveTypes[type]) || pendingReacts.includes(commentId + type)) {
|
||||
// ignore dislikes during likes, for example
|
||||
return;
|
||||
}
|
||||
|
||||
let myReacts = makeSelectMyReactionsForComment(commentId)(state);
|
||||
const othersReacts = makeSelectOthersReactionsForComment(commentId)(state);
|
||||
const claimForChannelName = myChannels.find(chan => chan.name === channel);
|
||||
const channelId = claimForChannelName && claimForChannelName.claim_id;
|
||||
|
||||
const params: CommentReactParams = {
|
||||
comment_ids: commentId,
|
||||
channel_name: channel,
|
||||
channel_id: channelId,
|
||||
channel_name: activeChannelClaim.name,
|
||||
channel_id: activeChannelClaim.claim_id,
|
||||
react_type: type,
|
||||
};
|
||||
|
||||
if (myReacts.includes(type)) {
|
||||
params['remove'] = true;
|
||||
myReacts.splice(myReacts.indexOf(type), 1);
|
||||
|
@ -197,15 +185,16 @@ export function doCommentReact(commentId: string, type: string) {
|
|||
};
|
||||
}
|
||||
|
||||
export function doCommentCreate(
|
||||
comment: string = '',
|
||||
claim_id: string = '',
|
||||
channel: string,
|
||||
parent_id?: string,
|
||||
uri: string
|
||||
) {
|
||||
export function doCommentCreate(comment: string = '', claim_id: string = '', parent_id?: string, uri: string) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||
|
||||
if (!activeChannelClaim) {
|
||||
console.error('Unable to create comment. No activeChannel is set.'); // eslint-disable-line
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_CREATE_STARTED,
|
||||
});
|
||||
|
@ -217,28 +206,10 @@ export function doCommentCreate(
|
|||
}
|
||||
}
|
||||
|
||||
const myChannels = selectMyChannelClaims(state);
|
||||
const namedChannelClaim = myChannels && myChannels.find(myChannel => myChannel.name === channel);
|
||||
const channel_id = namedChannelClaim.claim_id;
|
||||
|
||||
if (channel_id == null) {
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_CREATE_FAILED,
|
||||
data: {},
|
||||
});
|
||||
dispatch(
|
||||
doToast({
|
||||
message: 'Channel cannot be anonymous, please select a channel and try again.',
|
||||
isError: true,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
return Lbry.comment_create({
|
||||
comment: comment,
|
||||
claim_id: claim_id,
|
||||
channel_id: channel_id,
|
||||
channel_id: activeChannelClaim.claim_id,
|
||||
parent_id: parent_id,
|
||||
})
|
||||
.then((result: CommentCreateResponse) => {
|
||||
|
@ -299,32 +270,23 @@ export function doCommentHide(comment_id: string) {
|
|||
export function doCommentPin(commentId: string, remove: boolean) {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
// const channel = localStorage.getItem('comment-channel');
|
||||
const channel = selectCommentChannel(state);
|
||||
const myChannels = selectMyChannelClaims(state);
|
||||
const claimForChannelName = myChannels && myChannels.find(chan => chan.name === channel);
|
||||
const channelId = claimForChannelName && claimForChannelName.claim_id;
|
||||
const activeChannel = selectActiveChannelClaim(state);
|
||||
|
||||
if (!activeChannel) {
|
||||
console.error('Unable to pin comment. No activeChannel is set.'); // eslint-disable-line
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_PIN_STARTED,
|
||||
});
|
||||
if (!channelId || !channel || !commentId) {
|
||||
return dispatch({
|
||||
type: ACTIONS.COMMENT_PIN_FAILED,
|
||||
data: { message: 'missing params - unable to pin' },
|
||||
});
|
||||
}
|
||||
const params: { comment_id: string, channel_name: string, channel_id: string, remove?: boolean } = {
|
||||
|
||||
return Lbry.comment_pin({
|
||||
comment_id: commentId,
|
||||
channel_name: channel,
|
||||
channel_id: channelId,
|
||||
};
|
||||
|
||||
if (remove) {
|
||||
params['remove'] = true;
|
||||
}
|
||||
|
||||
return Lbry.comment_pin(params)
|
||||
channel_name: activeChannel.name,
|
||||
channel_id: activeChannel.claim_id,
|
||||
...(remove ? { remove: true } : {}),
|
||||
})
|
||||
.then((result: CommentPinResponse) => {
|
||||
dispatch({
|
||||
type: ACTIONS.COMMENT_PIN_COMPLETED,
|
||||
|
|
|
@ -44,6 +44,8 @@ export type AppState = {
|
|||
allowAnalytics: boolean,
|
||||
hasNavigated: boolean,
|
||||
interestedInYoutubeSync: boolean,
|
||||
activeChannel: ?string,
|
||||
incognito: boolean,
|
||||
};
|
||||
|
||||
const defaultState: AppState = {
|
||||
|
@ -80,6 +82,8 @@ const defaultState: AppState = {
|
|||
allowAnalytics: false,
|
||||
hasNavigated: false,
|
||||
interestedInYoutubeSync: false,
|
||||
activeChannel: undefined,
|
||||
incognito: false,
|
||||
};
|
||||
|
||||
// @@router comes from react-router
|
||||
|
@ -300,6 +304,19 @@ reducers[ACTIONS.TOGGLE_SPLASH_ANIMATION] = (state, action) => {
|
|||
};
|
||||
};
|
||||
|
||||
reducers[ACTIONS.SET_ACTIVE_CHANNEL] = (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
activeChannel: action.data.claimId,
|
||||
};
|
||||
};
|
||||
reducers[ACTIONS.SET_INCOGNITO] = (state, action) => {
|
||||
return {
|
||||
...state,
|
||||
incognito: action.data.enabled,
|
||||
};
|
||||
};
|
||||
|
||||
reducers[LBRY_REDUX_ACTIONS.USER_STATE_POPULATE] = (state, action) => {
|
||||
const { welcomeVersion, allowAnalytics } = action.data;
|
||||
return {
|
||||
|
|
|
@ -16,7 +16,6 @@ const defaultState: CommentsState = {
|
|||
typesReacting: [],
|
||||
myReactsByCommentId: undefined,
|
||||
othersReactsByCommentId: undefined,
|
||||
commentChannel: '',
|
||||
};
|
||||
|
||||
export default handleActions(
|
||||
|
@ -31,11 +30,6 @@ export default handleActions(
|
|||
isCommenting: false,
|
||||
}),
|
||||
|
||||
[ACTIONS.COMMENT_SET_CHANNEL]: (state: CommentsState, action: any) => ({
|
||||
...state,
|
||||
commentChannel: action.data,
|
||||
}),
|
||||
|
||||
[ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
|
||||
const { comment, claimId, uri }: { comment: Comment, claimId: string, uri: string } = action.data;
|
||||
const commentById = Object.assign({}, state.commentById);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import { selectClaimsById, selectMyChannelClaims } from 'lbry-redux';
|
||||
|
||||
export const selectState = state => state.app || {};
|
||||
|
||||
|
@ -84,3 +85,37 @@ export const selectIsPasswordSaved = createSelector(selectState, state => state.
|
|||
export const selectInterestedInYoutubeSync = createSelector(selectState, state => state.interestedInYoutubeSync);
|
||||
|
||||
export const selectSplashAnimationEnabled = createSelector(selectState, state => state.splashAnimationEnabled);
|
||||
|
||||
export const selectActiveChannelId = createSelector(selectState, state => state.activeChannel);
|
||||
|
||||
export const selectActiveChannelClaim = createSelector(
|
||||
selectActiveChannelId,
|
||||
selectClaimsById,
|
||||
selectMyChannelClaims,
|
||||
(activeChannelClaimId, claimsById, myChannelClaims) => {
|
||||
if (!activeChannelClaimId || !claimsById || !myChannelClaims || !myChannelClaims.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const activeChannelClaim = claimsById[activeChannelClaimId];
|
||||
if (activeChannelClaim) {
|
||||
return activeChannelClaim;
|
||||
}
|
||||
|
||||
const myChannelClaimsByEffectiveAmount = myChannelClaims.slice().sort((a, b) => {
|
||||
const effectiveAmountA = (a.meta && Number(a.meta.effective_amount)) || 0;
|
||||
const effectiveAmountB = (b.meta && Number(b.meta.effective_amount)) || 0;
|
||||
if (effectiveAmountA === effectiveAmountB) {
|
||||
return 0;
|
||||
} else if (effectiveAmountA > effectiveAmountB) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
return myChannelClaimsByEffectiveAmount[0];
|
||||
}
|
||||
);
|
||||
|
||||
export const selectIncognito = createSelector(selectState, state => state.incognito);
|
||||
|
|
|
@ -16,8 +16,6 @@ export const selectIsPostingComment = createSelector(selectState, state => state
|
|||
|
||||
export const selectIsFetchingReacts = createSelector(selectState, state => state.isFetchingReacts);
|
||||
|
||||
export const selectCommentChannel = createSelector(selectState, state => state.commentChannel);
|
||||
|
||||
export const selectOthersReactsById = createSelector(selectState, state => state.othersReactsByCommentId);
|
||||
|
||||
export const selectCommentsByClaimId = createSelector(selectState, selectCommentsById, (state, byId) => {
|
||||
|
|
|
@ -264,10 +264,12 @@ $metadata-z-index: 1;
|
|||
}
|
||||
|
||||
.menu__list.channel__list {
|
||||
margin-top: var(--spacing-s);
|
||||
margin-top: var(--spacing-xs);
|
||||
margin-left: 0;
|
||||
border-radius: var(--border-radius);
|
||||
background: transparent;
|
||||
max-height: 15rem;
|
||||
overflow-y: scroll;
|
||||
|
||||
[role='menuitem'] {
|
||||
&[data-selected] {
|
||||
|
@ -314,8 +316,14 @@ $metadata-z-index: 1;
|
|||
|
||||
.icon {
|
||||
color: var(--color-menu-icon);
|
||||
margin-left: var(--spacing-l);
|
||||
margin-right: var(--spacing-s);
|
||||
}
|
||||
|
||||
.icon__wrapper {
|
||||
padding: 0;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
margin-right: var(--spacing-m);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -326,5 +334,20 @@ $metadata-z-index: 1;
|
|||
.channel__list-item--selected {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-card-background-highlighted);
|
||||
|
||||
.icon--ChevronDown {
|
||||
margin-left: var(--spacing-l);
|
||||
}
|
||||
}
|
||||
|
||||
.channel__list-text {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.channel__selector {
|
||||
margin-bottom: var(--spacing-m);
|
||||
|
||||
@media (min-width: $breakpoint-small) {
|
||||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ label {
|
|||
font-size: var(--font-small);
|
||||
color: var(--color-input-label);
|
||||
display: inline-block;
|
||||
margin-bottom: var(--spacing-xxs);
|
||||
margin-bottom: 0.1rem;
|
||||
|
||||
.icon__lbc {
|
||||
margin-bottom: 4px;
|
||||
|
@ -361,11 +361,9 @@ fieldset-group {
|
|||
border: 1px solid;
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
border-right: 0;
|
||||
border-color: var(--color-input-border);
|
||||
color: var(--color-text-help);
|
||||
background-color: var(--color-input-bg);
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.icon__wrapper--Anonymous {
|
||||
background-color: var(--color-gray-1);
|
||||
|
||||
.icon {
|
||||
stroke: var(--color-black);
|
||||
}
|
||||
}
|
||||
|
||||
.icon--help {
|
||||
color: var(--color-subtitle);
|
||||
margin-left: var(--spacing-xs);
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
[data-reach-menu] {
|
||||
z-index: 10000;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-overlay {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
}
|
||||
|
||||
[data-reach-menu] {
|
||||
font-family: sans-serif;
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
|
|
|
@ -58,10 +58,10 @@ const appFilter = createFilter('app', [
|
|||
'welcomeVersion',
|
||||
'interestedInYoutubeSync',
|
||||
'splashAnimationEnabled',
|
||||
'activeChannel',
|
||||
]);
|
||||
// We only need to persist the receiveAddress for the wallet
|
||||
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
||||
const commentsFilter = createFilter('comments', ['commentChannel']);
|
||||
const searchFilter = createFilter('search', ['options']);
|
||||
const tagsFilter = createFilter('tags', ['followedTags']);
|
||||
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
||||
|
@ -82,7 +82,6 @@ const whiteListedReducers = [
|
|||
];
|
||||
|
||||
const transforms = [
|
||||
commentsFilter,
|
||||
fileInfoFilter,
|
||||
walletFilter,
|
||||
blockedFilter,
|
||||
|
|
Loading…
Reference in a new issue