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
static
ui
component
app
channelSelector
comment
commentCreate
commentReactions
commentsList
common
creatorAnalytics
header
inviteNew
publishBid
publishFile
publishForm
publishName
repostCreate
selectChannel
walletSendTip
constants
modal
page/creatorDashboard
redux
scss/component
store.js
|
@ -740,7 +740,6 @@
|
||||||
"Repost": "Repost",
|
"Repost": "Repost",
|
||||||
"Repost your favorite claims to help more people discover them!": "Repost your favorite claims to help more people discover them!",
|
"Repost your favorite claims to help more people discover them!": "Repost your favorite claims to help more people discover them!",
|
||||||
"Repost %title%": "Repost %title%",
|
"Repost %title%": "Repost %title%",
|
||||||
"Channel to repost on": "Channel to repost on",
|
|
||||||
"Advanced": "Advanced",
|
"Advanced": "Advanced",
|
||||||
"community name": "community name",
|
"community name": "community name",
|
||||||
"Change this to repost to a different %lbry_naming_link%.": "Change this to repost to a different %lbry_naming_link%.",
|
"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 { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user';
|
||||||
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
|
||||||
import { doFetchChannelListMine, SETTINGS } from 'lbry-redux';
|
import { doFetchChannelListMine, selectMyChannelUrls, SETTINGS } from 'lbry-redux';
|
||||||
import {
|
import {
|
||||||
makeSelectClientSetting,
|
makeSelectClientSetting,
|
||||||
selectLanguage,
|
selectLanguage,
|
||||||
selectLoadedLanguages,
|
selectLoadedLanguages,
|
||||||
selectThemePath,
|
selectThemePath,
|
||||||
} from 'redux/selectors/settings';
|
} 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 { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings';
|
||||||
import { doSyncLoop } from 'redux/actions/sync';
|
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';
|
import App from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
@ -33,6 +44,8 @@ const select = state => ({
|
||||||
isAuthenticated: selectUserVerifiedEmail(state),
|
isAuthenticated: selectUserVerifiedEmail(state),
|
||||||
currentModal: selectModal(state),
|
currentModal: selectModal(state),
|
||||||
syncFatalError: selectSyncFatalError(state),
|
syncFatalError: selectSyncFatalError(state),
|
||||||
|
activeChannelId: selectActiveChannelId(state),
|
||||||
|
myChannelUrls: selectMyChannelUrls(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
@ -45,6 +58,8 @@ const perform = dispatch => ({
|
||||||
getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()),
|
getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()),
|
||||||
syncLoop: noInterval => dispatch(doSyncLoop(noInterval)),
|
syncLoop: noInterval => dispatch(doSyncLoop(noInterval)),
|
||||||
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
|
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
|
||||||
|
setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()),
|
||||||
|
setIncognito: () => dispatch(doSetIncognito()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default hot(connect(select, perform)(App));
|
export default hot(connect(select, perform)(App));
|
||||||
|
|
|
@ -80,6 +80,10 @@ type Props = {
|
||||||
syncEnabled: boolean,
|
syncEnabled: boolean,
|
||||||
currentModal: any,
|
currentModal: any,
|
||||||
syncFatalError: boolean,
|
syncFatalError: boolean,
|
||||||
|
activeChannelId: ?string,
|
||||||
|
myChannelUrls: ?Array<string>,
|
||||||
|
setActiveChannelIfNotSet: (?string) => void,
|
||||||
|
setIncognito: boolean => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
function App(props: Props) {
|
function App(props: Props) {
|
||||||
|
@ -106,6 +110,10 @@ function App(props: Props) {
|
||||||
syncLoop,
|
syncLoop,
|
||||||
currentModal,
|
currentModal,
|
||||||
syncFatalError,
|
syncFatalError,
|
||||||
|
activeChannelId,
|
||||||
|
myChannelUrls,
|
||||||
|
setActiveChannelIfNotSet,
|
||||||
|
setIncognito,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const appRef = useRef();
|
const appRef = useRef();
|
||||||
|
@ -133,7 +141,8 @@ function App(props: Props) {
|
||||||
const shouldHideNag = pathname.startsWith(`/$/${PAGES.EMBED}`) || pathname.startsWith(`/$/${PAGES.AUTH_VERIFY}`);
|
const shouldHideNag = pathname.startsWith(`/$/${PAGES.EMBED}`) || pathname.startsWith(`/$/${PAGES.AUTH_VERIFY}`);
|
||||||
const userId = user && user.id;
|
const userId = user && user.id;
|
||||||
const useCustomScrollbar = !IS_MAC;
|
const useCustomScrollbar = !IS_MAC;
|
||||||
|
const hasMyChannels = myChannelUrls && myChannelUrls.length > 0;
|
||||||
|
const hasNoChannels = myChannelUrls && myChannelUrls.length === 0;
|
||||||
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
|
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
|
||||||
|
|
||||||
let uri;
|
let uri;
|
||||||
|
@ -228,6 +237,14 @@ function App(props: Props) {
|
||||||
document.documentElement.setAttribute('theme', theme);
|
document.documentElement.setAttribute('theme', theme);
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasMyChannels && !activeChannelId) {
|
||||||
|
setActiveChannelIfNotSet();
|
||||||
|
} else if (hasNoChannels) {
|
||||||
|
setIncognito(true);
|
||||||
|
}
|
||||||
|
}, [hasMyChannels, activeChannelId, setActiveChannelIfNotSet]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!languages.includes(language)) {
|
if (!languages.includes(language)) {
|
||||||
setLanguage(language);
|
setLanguage(language);
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import SelectChannel from './view';
|
|
||||||
import { selectMyChannelClaims } from 'lbry-redux';
|
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 => ({
|
const select = state => ({
|
||||||
channels: selectMyChannelClaims(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
|
// @flow
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ChannelThumbnail from 'component/channelThumbnail';
|
import ChannelThumbnail from 'component/channelThumbnail';
|
||||||
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
|
||||||
import ChannelTitle from 'component/channelTitle';
|
import ChannelTitle from 'component/channelTitle';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
selectedChannelUrl: string, // currently selected channel
|
selectedChannelUrl: string, // currently selected channel
|
||||||
channels: ?Array<ChannelClaim>,
|
channels: ?Array<ChannelClaim>,
|
||||||
onChannelSelect: (url: string) => void,
|
onChannelSelect: (url: string) => void,
|
||||||
|
hideAnon?: boolean,
|
||||||
|
activeChannelClaim: ?ChannelClaim,
|
||||||
|
doSetActiveChannel: string => void,
|
||||||
|
incognito: boolean,
|
||||||
|
doSetIncognito: boolean => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
type ListItemProps = {
|
type ListItemProps = {
|
||||||
|
@ -30,34 +37,64 @@ function ChannelListItem(props: ListItemProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChannelSelector(props: Props) {
|
type IncognitoSelectorProps = {
|
||||||
const { channels, selectedChannelUrl, onChannelSelect } = props;
|
isSelected?: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
if (!channels || !selectedChannelUrl) {
|
function IncognitoSelector(props: IncognitoSelectorProps) {
|
||||||
return null;
|
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 (
|
return (
|
||||||
<Menu>
|
<div className="channel__selector">
|
||||||
<MenuButton className="">
|
<Menu>
|
||||||
<ChannelListItem uri={selectedChannelUrl} isSelected />
|
<MenuButton>
|
||||||
</MenuButton>
|
{(incognito && !hideAnon) || !activeChannelUrl ? (
|
||||||
<MenuList className="menu__list channel__list">
|
<IncognitoSelector isSelected />
|
||||||
{channels &&
|
) : (
|
||||||
channels.map(channel => (
|
<ChannelListItem uri={activeChannelUrl} isSelected />
|
||||||
<MenuItem
|
)}
|
||||||
key={channel.canonical_url}
|
</MenuButton>
|
||||||
onSelect={() => {
|
<MenuList className="menu__list channel__list">
|
||||||
if (selectedChannelUrl !== channel.canonical_url) {
|
{channels &&
|
||||||
onChannelSelect(channel.canonical_url);
|
channels.map(channel => (
|
||||||
}
|
<MenuItem key={channel.permanent_url} onSelect={() => handleChannelSelect(channel)}>
|
||||||
}}
|
<ChannelListItem uri={channel.permanent_url} />
|
||||||
>
|
</MenuItem>
|
||||||
<ChannelListItem uri={channel.canonical_url} />
|
))}
|
||||||
|
{!hideAnon && (
|
||||||
|
<MenuItem onSelect={() => doSetIncognito(true)}>
|
||||||
|
<IncognitoSelector />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
)}
|
||||||
</MenuList>
|
<MenuItem onSelect={() => push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`)}>
|
||||||
</Menu>
|
<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 { connect } from 'react-redux';
|
||||||
import {
|
import { makeSelectThumbnailForUri, selectMyChannelClaims, makeSelectChannelPermUrlForClaimUri } from 'lbry-redux';
|
||||||
doResolveUri,
|
|
||||||
makeSelectClaimIsPending,
|
|
||||||
makeSelectClaimForUri,
|
|
||||||
makeSelectThumbnailForUri,
|
|
||||||
makeSelectIsUriResolving,
|
|
||||||
selectMyChannelClaims,
|
|
||||||
makeSelectMyChannelPermUrlForName,
|
|
||||||
makeSelectChannelPermUrlForClaimUri,
|
|
||||||
} from 'lbry-redux';
|
|
||||||
import { doCommentAbandon, doCommentUpdate, doCommentPin, doCommentList } from 'redux/actions/comments';
|
import { doCommentAbandon, doCommentUpdate, doCommentPin, doCommentList } from 'redux/actions/comments';
|
||||||
import { doToggleBlockChannel } from 'redux/actions/blocked';
|
import { doToggleBlockChannel } from 'redux/actions/blocked';
|
||||||
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import { doSetPlayingUri } from 'redux/actions/content';
|
import { doSetPlayingUri } from 'redux/actions/content';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
import {
|
import { selectIsFetchingComments, makeSelectOthersReactionsForComment } from 'redux/selectors/comments';
|
||||||
selectIsFetchingComments,
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
makeSelectOthersReactionsForComment,
|
|
||||||
selectCommentChannel,
|
|
||||||
} from 'redux/selectors/comments';
|
|
||||||
import Comment from './view';
|
import Comment from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => ({
|
||||||
const channel = selectCommentChannel(state);
|
thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state),
|
||||||
|
channelIsBlocked: props.authorUri && selectChannelIsBlocked(props.authorUri)(state),
|
||||||
return {
|
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||||
activeChannel: channel,
|
isFetchingComments: selectIsFetchingComments(state),
|
||||||
pending: props.authorUri && makeSelectClaimIsPending(props.authorUri)(state),
|
myChannels: selectMyChannelClaims(state),
|
||||||
channel: props.authorUri && makeSelectClaimForUri(props.authorUri)(state),
|
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
||||||
isResolvingUri: props.authorUri && makeSelectIsUriResolving(props.authorUri)(state),
|
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
|
||||||
thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state),
|
activeChannelClaim: selectActiveChannelClaim(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),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
closeInlinePlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
closeInlinePlayer: () => dispatch(doSetPlayingUri({ uri: null })),
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
|
||||||
updateComment: (commentId, comment) => dispatch(doCommentUpdate(commentId, comment)),
|
updateComment: (commentId, comment) => dispatch(doCommentUpdate(commentId, comment)),
|
||||||
deleteComment: commentId => dispatch(doCommentAbandon(commentId)),
|
deleteComment: commentId => dispatch(doCommentAbandon(commentId)),
|
||||||
blockChannel: channelUri => dispatch(doToggleBlockChannel(channelUri)),
|
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 { SITE_NAME, SIMPLE_SITE, ENABLE_COMMENT_REACTIONS } from 'config';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { parseURI } from 'lbry-redux';
|
import { parseURI } from 'lbry-redux';
|
||||||
import { isEmpty } from 'util/object';
|
|
||||||
import DateTime from 'component/dateTime';
|
import DateTime from 'component/dateTime';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Expandable from 'component/expandable';
|
import Expandable from 'component/expandable';
|
||||||
|
@ -29,10 +28,6 @@ type Props = {
|
||||||
commentId: string, // sha256 digest identifying the comment
|
commentId: string, // sha256 digest identifying the comment
|
||||||
message: string, // comment body
|
message: string, // comment body
|
||||||
timePosted: number, // Comment timestamp
|
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
|
channelIsBlocked: boolean, // if the channel is blacklisted in the app
|
||||||
claimIsMine: boolean, // if you control the claim which this comment was posted on
|
claimIsMine: boolean, // if you control the claim which this comment was posted on
|
||||||
commentIsMine: boolean, // if this comment was signed by an owned channel
|
commentIsMine: boolean, // if this comment was signed by an owned channel
|
||||||
|
@ -53,7 +48,8 @@ type Props = {
|
||||||
pinComment: (string, boolean) => Promise<any>,
|
pinComment: (string, boolean) => Promise<any>,
|
||||||
fetchComments: string => void,
|
fetchComments: string => void,
|
||||||
commentIdentityChannel: any,
|
commentIdentityChannel: any,
|
||||||
contentChannel: any,
|
contentChannelPermanentUrl: any,
|
||||||
|
activeChannelClaim: ?ChannelClaim,
|
||||||
};
|
};
|
||||||
|
|
||||||
const LENGTH_TO_COLLAPSE = 300;
|
const LENGTH_TO_COLLAPSE = 300;
|
||||||
|
@ -67,10 +63,6 @@ function Comment(props: Props) {
|
||||||
authorUri,
|
authorUri,
|
||||||
timePosted,
|
timePosted,
|
||||||
message,
|
message,
|
||||||
pending,
|
|
||||||
channel,
|
|
||||||
isResolvingUri,
|
|
||||||
resolveUri,
|
|
||||||
channelIsBlocked,
|
channelIsBlocked,
|
||||||
commentIsMine,
|
commentIsMine,
|
||||||
commentId,
|
commentId,
|
||||||
|
@ -87,8 +79,8 @@ function Comment(props: Props) {
|
||||||
pinComment,
|
pinComment,
|
||||||
fetchComments,
|
fetchComments,
|
||||||
othersReacts,
|
othersReacts,
|
||||||
commentIdentityChannel,
|
contentChannelPermanentUrl,
|
||||||
contentChannel,
|
activeChannelClaim,
|
||||||
} = props;
|
} = props;
|
||||||
const {
|
const {
|
||||||
push,
|
push,
|
||||||
|
@ -108,10 +100,7 @@ function Comment(props: Props) {
|
||||||
const dislikesCount = (othersReacts && othersReacts.dislike) || 0;
|
const dislikesCount = (othersReacts && othersReacts.dislike) || 0;
|
||||||
const totalLikesAndDislikes = likesCount + dislikesCount;
|
const totalLikesAndDislikes = likesCount + dislikesCount;
|
||||||
const slimedToDeath = totalLikesAndDislikes >= 5 && dislikesCount / totalLikesAndDislikes > 0.8;
|
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;
|
let channelOwnerOfContent;
|
||||||
try {
|
try {
|
||||||
const { channelName } = parseURI(uri);
|
const { channelName } = parseURI(uri);
|
||||||
|
@ -121,11 +110,6 @@ function Comment(props: Props) {
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If author was extracted from the URI, then it must be valid.
|
|
||||||
if (authorUri && author && !isResolvingUri && shouldFetch) {
|
|
||||||
resolveUri(authorUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
setCharCount(editedMessage.length);
|
setCharCount(editedMessage.length);
|
||||||
|
|
||||||
|
@ -143,7 +127,7 @@ function Comment(props: Props) {
|
||||||
window.removeEventListener('keydown', handleEscape);
|
window.removeEventListener('keydown', handleEscape);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, [isResolvingUri, shouldFetch, author, authorUri, resolveUri, editedMessage, isEditing, setEditing]);
|
}, [author, authorUri, editedMessage, isEditing, setEditing]);
|
||||||
|
|
||||||
function handleEditMessageChanged(event) {
|
function handleEditMessageChanged(event) {
|
||||||
setCommentValue(!SIMPLE_SITE && advancedEditor ? event : event.target.value);
|
setCommentValue(!SIMPLE_SITE && advancedEditor ? event : event.target.value);
|
||||||
|
@ -262,7 +246,7 @@ function Comment(props: Props) {
|
||||||
{__('Block Channel')}
|
{__('Block Channel')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{commentIdentityChannel === contentChannel && isTopLevel && (
|
{activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl && isTopLevel && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className="comment__menu-option menu__link"
|
className="comment__menu-option menu__link"
|
||||||
onSelect={
|
onSelect={
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { makeSelectClaimForUri, selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux';
|
import { makeSelectClaimForUri, selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux';
|
||||||
import { selectIsPostingComment, selectCommentChannel } from 'redux/selectors/comments';
|
import { selectIsPostingComment } from 'redux/selectors/comments';
|
||||||
import { doOpenModal } from 'redux/actions/app';
|
import { doOpenModal, doSetActiveChannel } from 'redux/actions/app';
|
||||||
import { doCommentCreate, doSetCommentChannel } from 'redux/actions/comments';
|
import { doCommentCreate } from 'redux/actions/comments';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
import { CommentCreate } from './view';
|
import { CommentCreate } from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -12,14 +13,13 @@ const select = (state, props) => ({
|
||||||
channels: selectMyChannelClaims(state),
|
channels: selectMyChannelClaims(state),
|
||||||
isFetchingChannels: selectFetchingMyChannels(state),
|
isFetchingChannels: selectFetchingMyChannels(state),
|
||||||
isPostingComment: selectIsPostingComment(state),
|
isPostingComment: selectIsPostingComment(state),
|
||||||
activeChannel: selectCommentChannel(state),
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = (dispatch, ownProps) => ({
|
const perform = (dispatch, ownProps) => ({
|
||||||
createComment: (comment, claimId, channel, parentId) =>
|
createComment: (comment, claimId, parentId) => dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri)),
|
||||||
dispatch(doCommentCreate(comment, claimId, channel, parentId, ownProps.uri)),
|
|
||||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||||
setCommentChannel: name => dispatch(doSetCommentChannel(name)),
|
setActiveChannel: claimId => dispatch(doSetActiveChannel(claimId)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(CommentCreate);
|
export default connect(select, perform)(CommentCreate);
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { SIMPLE_SITE } from 'config';
|
import { SIMPLE_SITE } from 'config';
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import { CHANNEL_NEW } from 'constants/claim';
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { FormField, Form } from 'component/common/form';
|
import { FormField, Form } from 'component/common/form';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import ChannelSelection from 'component/selectChannel';
|
import SelectChannel from 'component/selectChannel';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
|
import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
|
@ -25,7 +24,7 @@ type Props = {
|
||||||
isReply: boolean,
|
isReply: boolean,
|
||||||
isPostingComment: boolean,
|
isPostingComment: boolean,
|
||||||
activeChannel: string,
|
activeChannel: string,
|
||||||
setCommentChannel: string => void,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CommentCreate(props: Props) {
|
export function CommentCreate(props: Props) {
|
||||||
|
@ -40,8 +39,7 @@ export function CommentCreate(props: Props) {
|
||||||
isReply,
|
isReply,
|
||||||
parentId,
|
parentId,
|
||||||
isPostingComment,
|
isPostingComment,
|
||||||
activeChannel,
|
activeChannelClaim,
|
||||||
setCommentChannel,
|
|
||||||
} = props;
|
} = props;
|
||||||
const buttonref: ElementRef<any> = React.useRef();
|
const buttonref: ElementRef<any> = React.useRef();
|
||||||
const { push } = useHistory();
|
const { push } = useHistory();
|
||||||
|
@ -50,21 +48,7 @@ export function CommentCreate(props: Props) {
|
||||||
const [charCount, setCharCount] = useState(commentValue.length);
|
const [charCount, setCharCount] = useState(commentValue.length);
|
||||||
const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false);
|
const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false);
|
||||||
const hasChannels = channels && channels.length;
|
const hasChannels = channels && channels.length;
|
||||||
const disabled = isPostingComment || activeChannel === CHANNEL_NEW || !commentValue.length;
|
const disabled = isPostingComment || !activeChannelClaim || !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]);
|
|
||||||
|
|
||||||
function handleCommentChange(event) {
|
function handleCommentChange(event) {
|
||||||
let commentValue;
|
let commentValue;
|
||||||
|
@ -94,8 +78,8 @@ export function CommentCreate(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
if (activeChannel !== CHANNEL_NEW && commentValue.length) {
|
if (activeChannelClaim && commentValue.length) {
|
||||||
createComment(commentValue, claimId, activeChannel, parentId).then(res => {
|
createComment(commentValue, claimId, parentId).then(res => {
|
||||||
if (res && res.signature) {
|
if (res && res.signature) {
|
||||||
setCommentValue('');
|
setCommentValue('');
|
||||||
|
|
||||||
|
@ -138,13 +122,13 @@ export function CommentCreate(props: Props) {
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
disabled={activeChannel === CHANNEL_NEW}
|
disabled={!activeChannelClaim}
|
||||||
type={SIMPLE_SITE ? 'textarea' : advancedEditor && !isReply ? 'markdown' : 'textarea'}
|
type={SIMPLE_SITE ? 'textarea' : advancedEditor && !isReply ? 'markdown' : 'textarea'}
|
||||||
name={isReply ? 'content_reply' : 'content_description'}
|
name={isReply ? 'content_reply' : 'content_description'}
|
||||||
label={
|
label={
|
||||||
<span className="comment-new__label-wrapper">
|
<span className="comment-new__label-wrapper">
|
||||||
<div className="comment-new__label">{isReply ? __('Replying as') + ' ' : __('Comment as') + ' '}</div>
|
<div className="comment-new__label">{isReply ? __('Replying as') + ' ' : __('Comment as') + ' '}</div>
|
||||||
<ChannelSelection channel={activeChannel} hideAnon tiny hideNew onChannelChange={setCommentChannel} />
|
<SelectChannel tiny />
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
quickActionLabel={
|
quickActionLabel={
|
||||||
|
|
|
@ -2,19 +2,16 @@ import { connect } from 'react-redux';
|
||||||
import Comment from './view';
|
import Comment from './view';
|
||||||
import { makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
import { makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
import {
|
import { makeSelectMyReactionsForComment, makeSelectOthersReactionsForComment } from 'redux/selectors/comments';
|
||||||
makeSelectMyReactionsForComment,
|
|
||||||
makeSelectOthersReactionsForComment,
|
|
||||||
selectCommentChannel,
|
|
||||||
} from 'redux/selectors/comments';
|
|
||||||
import { doCommentReact } from 'redux/actions/comments';
|
import { doCommentReact } from 'redux/actions/comments';
|
||||||
|
import { selectActiveChannelId } from 'redux/selectors/app';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
myReacts: makeSelectMyReactionsForComment(props.commentId)(state),
|
myReacts: makeSelectMyReactionsForComment(props.commentId)(state),
|
||||||
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
||||||
activeChannel: selectCommentChannel(state),
|
activeChannelId: selectActiveChannelId(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -16,13 +16,13 @@ type Props = {
|
||||||
commentId: string,
|
commentId: string,
|
||||||
pendingCommentReacts: Array<string>,
|
pendingCommentReacts: Array<string>,
|
||||||
claimIsMine: boolean,
|
claimIsMine: boolean,
|
||||||
activeChannel: string,
|
activeChannelId: ?string,
|
||||||
claim: ?ChannelClaim,
|
claim: ?ChannelClaim,
|
||||||
doToast: ({ message: string }) => void,
|
doToast: ({ message: string }) => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CommentReactions(props: Props) {
|
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 {
|
const {
|
||||||
push,
|
push,
|
||||||
location: { pathname },
|
location: { pathname },
|
||||||
|
@ -31,8 +31,8 @@ export default function CommentReactions(props: Props) {
|
||||||
claim &&
|
claim &&
|
||||||
claimIsMine &&
|
claimIsMine &&
|
||||||
(claim.value_type === 'channel'
|
(claim.value_type === 'channel'
|
||||||
? claim.name === activeChannel
|
? claim.claim_id === activeChannelId
|
||||||
: claim.signing_channel && claim.signing_channel.name === activeChannel);
|
: claim.signing_channel && claim.signing_channel.claim_id === activeChannelId);
|
||||||
const authorUri =
|
const authorUri =
|
||||||
claim && claim.value_type === 'channel'
|
claim && claim.value_type === 'channel'
|
||||||
? claim.canonical_url
|
? claim.canonical_url
|
||||||
|
@ -52,7 +52,7 @@ export default function CommentReactions(props: Props) {
|
||||||
const creatorLiked = getCountForReact(REACTION_TYPES.CREATOR_LIKE) > 0;
|
const creatorLiked = getCountForReact(REACTION_TYPES.CREATOR_LIKE) > 0;
|
||||||
|
|
||||||
function handleCommentLike() {
|
function handleCommentLike() {
|
||||||
if (activeChannel) {
|
if (activeChannelId) {
|
||||||
react(commentId, REACTION_TYPES.LIKE);
|
react(commentId, REACTION_TYPES.LIKE);
|
||||||
} else {
|
} else {
|
||||||
promptForChannel();
|
promptForChannel();
|
||||||
|
@ -60,7 +60,7 @@ export default function CommentReactions(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCommentDislike() {
|
function handleCommentDislike() {
|
||||||
if (activeChannel) {
|
if (activeChannelId) {
|
||||||
react(commentId, REACTION_TYPES.DISLIKE);
|
react(commentId, REACTION_TYPES.DISLIKE);
|
||||||
} else {
|
} else {
|
||||||
promptForChannel();
|
promptForChannel();
|
||||||
|
|
|
@ -5,10 +5,10 @@ import {
|
||||||
selectIsFetchingComments,
|
selectIsFetchingComments,
|
||||||
makeSelectTotalCommentsCountForUri,
|
makeSelectTotalCommentsCountForUri,
|
||||||
selectOthersReactsById,
|
selectOthersReactsById,
|
||||||
selectCommentChannel,
|
|
||||||
} from 'redux/selectors/comments';
|
} from 'redux/selectors/comments';
|
||||||
import { doCommentList, doCommentReactList } from 'redux/actions/comments';
|
import { doCommentList, doCommentReactList } from 'redux/actions/comments';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
import { selectActiveChannelId } from 'redux/selectors/app';
|
||||||
import CommentsList from './view';
|
import CommentsList from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -20,7 +20,7 @@ const select = (state, props) => ({
|
||||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||||
fetchingChannels: selectFetchingMyChannels(state),
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
reactionsById: selectOthersReactsById(state),
|
reactionsById: selectOthersReactsById(state),
|
||||||
activeChannel: selectCommentChannel(state),
|
activeChannelId: selectActiveChannelId(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -26,7 +26,7 @@ type Props = {
|
||||||
totalComments: number,
|
totalComments: number,
|
||||||
fetchingChannels: boolean,
|
fetchingChannels: boolean,
|
||||||
reactionsById: ?{ [string]: { [REACTION_TYPES.LIKE | REACTION_TYPES.DISLIKE]: number } },
|
reactionsById: ?{ [string]: { [REACTION_TYPES.LIKE | REACTION_TYPES.DISLIKE]: number } },
|
||||||
activeChannel: string,
|
activeChannelId: ?string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function CommentList(props: Props) {
|
function CommentList(props: Props) {
|
||||||
|
@ -42,7 +42,7 @@ function CommentList(props: Props) {
|
||||||
totalComments,
|
totalComments,
|
||||||
fetchingChannels,
|
fetchingChannels,
|
||||||
reactionsById,
|
reactionsById,
|
||||||
activeChannel,
|
activeChannelId,
|
||||||
} = props;
|
} = props;
|
||||||
const commentRef = React.useRef();
|
const commentRef = React.useRef();
|
||||||
const spinnerRef = React.useRef();
|
const spinnerRef = React.useRef();
|
||||||
|
@ -90,7 +90,7 @@ function CommentList(props: Props) {
|
||||||
})
|
})
|
||||||
.catch(() => setReadyToDisplayComments(true));
|
.catch(() => setReadyToDisplayComments(true));
|
||||||
}
|
}
|
||||||
}, [fetchReacts, uri, totalComments, activeChannel, fetchingChannels]);
|
}, [fetchReacts, uri, totalComments, activeChannelId, fetchingChannels]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (readyToDisplayComments && linkedCommentId && commentRef && commentRef.current) {
|
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" />
|
<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>
|
</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 { connect } from 'react-redux';
|
||||||
import { makeSelectClaimForUri, doPrepareEdit } from 'lbry-redux';
|
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||||
import CreatorAnalytics from './view';
|
import CreatorAnalytics from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
claim: makeSelectClaimForUri(props.uri)(state),
|
claim: makeSelectClaimForUri(props.uri)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
export default connect(select)(CreatorAnalytics);
|
||||||
prepareEdit: channelName => dispatch(doPrepareEdit({ signing_channel: { name: channelName } })),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(select, perform)(CreatorAnalytics);
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default function CreatorAnalytics(props: Props) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [stats, setStats] = React.useState();
|
const [stats, setStats] = React.useState();
|
||||||
const [error, setError] = 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 claimId = claim && claim.claim_id;
|
||||||
const channelHasClaims = claim && claim.meta && claim.meta.claims_in_channel && claim.meta.claims_in_channel > 0;
|
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
|
<Yrbl
|
||||||
title={
|
title={
|
||||||
channelHasClaims
|
channelHasClaims
|
||||||
|
@ -93,12 +110,7 @@ export default function CreatorAnalytics(props: Props) {
|
||||||
<Button
|
<Button
|
||||||
button="primary"
|
button="primary"
|
||||||
label={__('Upload Something')}
|
label={__('Upload Something')}
|
||||||
onClick={() => {
|
onClick={() => history.push(`/$/${PAGES.UPLOAD}`)}
|
||||||
if (claim) {
|
|
||||||
prepareEdit(claim.name);
|
|
||||||
history.push(`/$/${PAGES.UPLOAD}`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
import { connect } from 'react-redux';
|
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 { selectGetSyncErrorMessage } from 'redux/selectors/sync';
|
||||||
import { selectUserVerifiedEmail, selectUserEmail, selectEmailToVerify, selectUser } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail, selectUserEmail, selectEmailToVerify, selectUser } from 'redux/selectors/user';
|
||||||
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
|
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
|
||||||
import { doSetClientSetting } from 'redux/actions/settings';
|
import { doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { doSignOut, doOpenModal } from 'redux/actions/app';
|
import { doSignOut, doOpenModal } from 'redux/actions/app';
|
||||||
import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/settings';
|
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 Header from './view';
|
||||||
import { selectHasNavigated } from 'redux/selectors/app';
|
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
language: selectLanguage(state),
|
language: selectLanguage(state),
|
||||||
|
@ -25,8 +24,7 @@ const select = state => ({
|
||||||
emailToVerify: selectEmailToVerify(state),
|
emailToVerify: selectEmailToVerify(state),
|
||||||
hasNavigated: selectHasNavigated(state),
|
hasNavigated: selectHasNavigated(state),
|
||||||
user: selectUser(state),
|
user: selectUser(state),
|
||||||
myChannels: selectMyChannelClaims(state),
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
commentChannel: selectCommentChannel(state),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -61,8 +61,7 @@ type Props = {
|
||||||
setSidebarOpen: boolean => void,
|
setSidebarOpen: boolean => void,
|
||||||
isAbsoluteSideNavHidden: boolean,
|
isAbsoluteSideNavHidden: boolean,
|
||||||
hideCancel: boolean,
|
hideCancel: boolean,
|
||||||
myChannels: ?Array<ChannelClaim>,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
commentChannel: string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header = (props: Props) => {
|
const Header = (props: Props) => {
|
||||||
|
@ -90,8 +89,7 @@ const Header = (props: Props) => {
|
||||||
isAbsoluteSideNavHidden,
|
isAbsoluteSideNavHidden,
|
||||||
user,
|
user,
|
||||||
hideCancel,
|
hideCancel,
|
||||||
myChannels,
|
activeChannelClaim,
|
||||||
commentChannel,
|
|
||||||
} = props;
|
} = props;
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
// on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
|
// 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 hasBackout = Boolean(backout);
|
||||||
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
|
||||||
const notificationsEnabled = (user && user.experimental_ui) || false;
|
const notificationsEnabled = (user && user.experimental_ui) || false;
|
||||||
let channelUrl;
|
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
|
||||||
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);
|
|
||||||
}
|
|
||||||
// Sign out if they click the "x" when they are on the password prompt
|
// Sign out if they click the "x" when they are on the password prompt
|
||||||
const authHeaderAction = syncError ? { onClick: signOut } : { navigate: '/' };
|
const authHeaderAction = syncError ? { onClick: signOut } : { navigate: '/' };
|
||||||
const homeButtonNavigationProps = isVerifyPage ? {} : authHeader ? authHeaderAction : { navigate: '/' };
|
const homeButtonNavigationProps = isVerifyPage ? {} : authHeader ? authHeaderAction : { navigate: '/' };
|
||||||
|
@ -288,7 +276,7 @@ const Header = (props: Props) => {
|
||||||
history={history}
|
history={history}
|
||||||
handleThemeToggle={handleThemeToggle}
|
handleThemeToggle={handleThemeToggle}
|
||||||
currentTheme={currentTheme}
|
currentTheme={currentTheme}
|
||||||
channelUrl={channelUrl}
|
activeChannelUrl={activeChannelUrl}
|
||||||
openSignOutModal={openSignOutModal}
|
openSignOutModal={openSignOutModal}
|
||||||
email={email}
|
email={email}
|
||||||
signOut={signOut}
|
signOut={signOut}
|
||||||
|
@ -341,7 +329,7 @@ type HeaderMenuButtonProps = {
|
||||||
history: { push: string => void },
|
history: { push: string => void },
|
||||||
handleThemeToggle: string => void,
|
handleThemeToggle: string => void,
|
||||||
currentTheme: string,
|
currentTheme: string,
|
||||||
channelUrl: ?string,
|
activeChannelUrl: ?string,
|
||||||
openSignOutModal: () => void,
|
openSignOutModal: () => void,
|
||||||
email: ?string,
|
email: ?string,
|
||||||
signOut: () => void,
|
signOut: () => void,
|
||||||
|
@ -354,7 +342,7 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||||
history,
|
history,
|
||||||
handleThemeToggle,
|
handleThemeToggle,
|
||||||
currentTheme,
|
currentTheme,
|
||||||
channelUrl,
|
activeChannelUrl,
|
||||||
openSignOutModal,
|
openSignOutModal,
|
||||||
email,
|
email,
|
||||||
signOut,
|
signOut,
|
||||||
|
@ -427,8 +415,8 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||||
aria-label={__('Your account')}
|
aria-label={__('Your account')}
|
||||||
title={__('Your account')}
|
title={__('Your account')}
|
||||||
className={classnames('header__navigation-item mobile-hidden', {
|
className={classnames('header__navigation-item mobile-hidden', {
|
||||||
'menu__title header__navigation-item--icon': !channelUrl,
|
'menu__title header__navigation-item--icon': !activeChannelUrl,
|
||||||
'header__navigation-item--profile-pic': channelUrl,
|
'header__navigation-item--profile-pic': activeChannelUrl,
|
||||||
})}
|
})}
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
onDoubleClick={e => {
|
onDoubleClick={e => {
|
||||||
|
@ -436,7 +424,11 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
|
||||||
}}
|
}}
|
||||||
// @endif
|
// @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>
|
</MenuButton>
|
||||||
<MenuList className="menu__list--header">
|
<MenuList className="menu__list--header">
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.UPLOADS}`)}>
|
<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 { Form, FormField } from 'component/common/form';
|
||||||
import CopyableText from 'component/copyableText';
|
import CopyableText from 'component/copyableText';
|
||||||
import Card from 'component/common/card';
|
import Card from 'component/common/card';
|
||||||
import SelectChannel from 'component/selectChannel';
|
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
import LbcSymbol from 'component/common/lbc-symbol';
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
|
@ -21,7 +20,6 @@ type Props = {
|
||||||
|
|
||||||
function InviteNew(props: Props) {
|
function InviteNew(props: Props) {
|
||||||
const { inviteNew, errorMessage, isPending, referralCode = '', channels } = props;
|
const { inviteNew, errorMessage, isPending, referralCode = '', channels } = props;
|
||||||
const noChannels = !channels || !(channels.length > 0);
|
|
||||||
|
|
||||||
// Email
|
// Email
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
|
@ -87,14 +85,20 @@ function InviteNew(props: Props) {
|
||||||
actions={
|
actions={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<CopyableText label={__('Your invite link')} copyable={referral} />
|
<CopyableText label={__('Your invite link')} copyable={referral} />
|
||||||
{!noChannels && (
|
{channels && channels.length > 0 && (
|
||||||
<SelectChannel
|
<FormField
|
||||||
channel={referralSource}
|
type="select"
|
||||||
onChannelChange={channel => handleReferralChange(channel)}
|
|
||||||
label={__('Customize link')}
|
label={__('Customize link')}
|
||||||
hideAnon
|
value={referralSource}
|
||||||
injected={[referralCode]}
|
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>
|
</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 I18nMessage from 'component/i18nMessage';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
import * as PUBLISH_MODES from 'constants/publish_types';
|
import * as PUBLISH_MODES from 'constants/publish_types';
|
||||||
|
import PublishName from 'component/publishName';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
uri: ?string,
|
uri: ?string,
|
||||||
|
@ -34,10 +35,8 @@ type Props = {
|
||||||
size: number,
|
size: number,
|
||||||
duration: number,
|
duration: number,
|
||||||
isVid: boolean,
|
isVid: boolean,
|
||||||
autoPopulateName: boolean,
|
|
||||||
setPublishMode: string => void,
|
setPublishMode: string => void,
|
||||||
setPrevFileText: string => void,
|
setPrevFileText: string => void,
|
||||||
setAutoPopulateName: boolean => void,
|
|
||||||
header: Node,
|
header: Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -63,8 +62,6 @@ function PublishFile(props: Props) {
|
||||||
isVid,
|
isVid,
|
||||||
setPublishMode,
|
setPublishMode,
|
||||||
setPrevFileText,
|
setPrevFileText,
|
||||||
autoPopulateName,
|
|
||||||
setAutoPopulateName,
|
|
||||||
header,
|
header,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
@ -231,10 +228,6 @@ function PublishFile(props: Props) {
|
||||||
const title = event.target.value;
|
const title = event.target.value;
|
||||||
// Update title
|
// Update title
|
||||||
updatePublishForm({ title });
|
updatePublishForm({ title });
|
||||||
// Auto populate name from title
|
|
||||||
if (autoPopulateName) {
|
|
||||||
updatePublishForm({ name: parseName(title) });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleFileReaderLoaded(event: ProgressEvent) {
|
function handleFileReaderLoaded(event: ProgressEvent) {
|
||||||
|
@ -327,11 +320,6 @@ function PublishFile(props: Props) {
|
||||||
publishFormParams.name = parseName(fileName);
|
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.
|
// File path is not supported on web for security reasons so we use the name instead.
|
||||||
setCurrentFile(file.path || file.name);
|
setCurrentFile(file.path || file.name);
|
||||||
updatePublishForm(publishFormParams);
|
updatePublishForm(publishFormParams);
|
||||||
|
@ -357,6 +345,7 @@ function PublishFile(props: Props) {
|
||||||
subtitle={isStillEditing && __('You are currently editing your upload.')}
|
subtitle={isStillEditing && __('You are currently editing your upload.')}
|
||||||
actions={
|
actions={
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
<PublishName />
|
||||||
<FormField
|
<FormField
|
||||||
type="text"
|
type="text"
|
||||||
name="content_title"
|
name="content_title"
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { doPublishDesktop } from 'redux/actions/publish';
|
import { doPublishDesktop } from 'redux/actions/publish';
|
||||||
import { selectUnclaimedRewardValue } from 'redux/selectors/rewards';
|
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 { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import PublishPage from './view';
|
import PublishPage from './view';
|
||||||
|
|
||||||
|
@ -32,6 +32,8 @@ const select = state => ({
|
||||||
totalRewardValue: selectUnclaimedRewardValue(state),
|
totalRewardValue: selectUnclaimedRewardValue(state),
|
||||||
modal: selectModal(state),
|
modal: selectModal(state),
|
||||||
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
|
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
|
||||||
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
|
incognito: selectIncognito(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -9,17 +9,16 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SITE_NAME } from 'config';
|
import { SITE_NAME } from 'config';
|
||||||
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
import React, { useEffect } from 'react';
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { buildURI, isURIValid, isNameValid, THUMBNAIL_STATUSES } from 'lbry-redux';
|
import { buildURI, isURIValid, isNameValid, THUMBNAIL_STATUSES } from 'lbry-redux';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import SelectChannel from 'component/selectChannel';
|
import ChannelSelect from 'component/channelSelector';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import TagsSelect from 'component/tagsSelect';
|
import TagsSelect from 'component/tagsSelect';
|
||||||
import PublishDescription from 'component/publishDescription';
|
import PublishDescription from 'component/publishDescription';
|
||||||
import PublishPrice from 'component/publishPrice';
|
import PublishPrice from 'component/publishPrice';
|
||||||
import PublishFile from 'component/publishFile';
|
import PublishFile from 'component/publishFile';
|
||||||
import PublishName from 'component/publishName';
|
import PublishBid from 'component/publishBid';
|
||||||
import PublishAdditionalOptions from 'component/publishAdditionalOptions';
|
import PublishAdditionalOptions from 'component/publishAdditionalOptions';
|
||||||
import PublishFormErrors from 'component/publishFormErrors';
|
import PublishFormErrors from 'component/publishFormErrors';
|
||||||
import SelectThumbnail from 'component/selectThumbnail';
|
import SelectThumbnail from 'component/selectThumbnail';
|
||||||
|
@ -60,7 +59,6 @@ type Props = {
|
||||||
amount: string,
|
amount: string,
|
||||||
currency: string,
|
currency: string,
|
||||||
},
|
},
|
||||||
channel: string,
|
|
||||||
name: ?string,
|
name: ?string,
|
||||||
nameError: ?string,
|
nameError: ?string,
|
||||||
isResolvingUri: boolean,
|
isResolvingUri: boolean,
|
||||||
|
@ -85,6 +83,8 @@ type Props = {
|
||||||
ytSignupPending: boolean,
|
ytSignupPending: boolean,
|
||||||
modal: { id: string, modalProps: {} },
|
modal: { id: string, modalProps: {} },
|
||||||
enablePublishPreview: boolean,
|
enablePublishPreview: boolean,
|
||||||
|
activeChannelClaim: ?ChannelClaim,
|
||||||
|
incognito: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function PublishForm(props: Props) {
|
function PublishForm(props: Props) {
|
||||||
|
@ -101,7 +101,6 @@ function PublishForm(props: Props) {
|
||||||
const {
|
const {
|
||||||
thumbnail,
|
thumbnail,
|
||||||
name,
|
name,
|
||||||
channel,
|
|
||||||
editingURI,
|
editingURI,
|
||||||
myClaimForUri,
|
myClaimForUri,
|
||||||
resolveUri,
|
resolveUri,
|
||||||
|
@ -123,17 +122,16 @@ function PublishForm(props: Props) {
|
||||||
ytSignupPending,
|
ytSignupPending,
|
||||||
modal,
|
modal,
|
||||||
enablePublishPreview,
|
enablePublishPreview,
|
||||||
|
activeChannelClaim,
|
||||||
|
incognito,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
// Used to check if name should be auto-populated from title
|
|
||||||
const [autoPopulateNameFromTitle, setAutoPopulateNameFromTitle] = useState(!isStillEditing);
|
|
||||||
|
|
||||||
const TAGS_LIMIT = 5;
|
const TAGS_LIMIT = 5;
|
||||||
const fileFormDisabled = mode === PUBLISH_MODES.FILE && !filePath;
|
const fileFormDisabled = mode === PUBLISH_MODES.FILE && !filePath;
|
||||||
const emptyPostError = mode === PUBLISH_MODES.POST && (!fileText || fileText.trim() === '');
|
const emptyPostError = mode === PUBLISH_MODES.POST && (!fileText || fileText.trim() === '');
|
||||||
const formDisabled = (fileFormDisabled && !editingURI) || emptyPostError || publishing;
|
const formDisabled = (fileFormDisabled && !editingURI) || emptyPostError || publishing;
|
||||||
const isInProgress = filePath || editingURI || name || title;
|
const isInProgress = filePath || editingURI || name || title;
|
||||||
|
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
|
||||||
// Editing content info
|
// Editing content info
|
||||||
const uri = myClaimForUri ? myClaimForUri.permanent_url : undefined;
|
const uri = myClaimForUri ? myClaimForUri.permanent_url : undefined;
|
||||||
const fileMimeType =
|
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
|
// Every time the channel or name changes, resolve the uris to find winning bid amounts
|
||||||
useEffect(() => {
|
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
|
// We are only going to store the full uri, but we need to resolve the uri with and without the channel name
|
||||||
let uri;
|
let uri;
|
||||||
try {
|
try {
|
||||||
uri = name && buildURI({ streamName: name, channelName });
|
uri = name && buildURI({ streamName: name, activeChannelName });
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
if (channelName && name) {
|
if (activeChannelName && name) {
|
||||||
// resolve without the channel name so we know the winning bid for it
|
// resolve without the channel name so we know the winning bid for it
|
||||||
try {
|
try {
|
||||||
const uriLessChannel = buildURI({ streamName: name });
|
const uriLessChannel = buildURI({ streamName: name });
|
||||||
|
@ -227,15 +222,17 @@ function PublishForm(props: Props) {
|
||||||
checkAvailability(name);
|
checkAvailability(name);
|
||||||
updatePublishForm({ uri });
|
updatePublishForm({ uri });
|
||||||
}
|
}
|
||||||
}, [name, channel, resolveUri, updatePublishForm, checkAvailability]);
|
}, [name, activeChannelName, resolveUri, updatePublishForm, checkAvailability]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updatePublishForm({ isMarkdownPost: mode === PUBLISH_MODES.POST });
|
updatePublishForm({ isMarkdownPost: mode === PUBLISH_MODES.POST });
|
||||||
}, [mode, updatePublishForm]);
|
}, [mode, updatePublishForm]);
|
||||||
|
|
||||||
function handleChannelNameChange(channel) {
|
useEffect(() => {
|
||||||
updatePublishForm({ channel });
|
if (activeChannelName) {
|
||||||
}
|
updatePublishForm({ channel: undefined });
|
||||||
|
}
|
||||||
|
}, [activeChannelName, incognito, updatePublishForm]);
|
||||||
|
|
||||||
// @if TARGET='web'
|
// @if TARGET='web'
|
||||||
function createWebFile() {
|
function createWebFile() {
|
||||||
|
@ -331,18 +328,7 @@ function PublishForm(props: Props) {
|
||||||
// Editing claim uri
|
// Editing claim uri
|
||||||
return (
|
return (
|
||||||
<div className="card-stack">
|
<div className="card-stack">
|
||||||
<Card
|
<ChannelSelect disabled={disabled} />
|
||||||
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>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PublishFile
|
<PublishFile
|
||||||
uri={uri}
|
uri={uri}
|
||||||
|
@ -352,8 +338,6 @@ function PublishForm(props: Props) {
|
||||||
inProgress={isInProgress}
|
inProgress={isInProgress}
|
||||||
setPublishMode={setMode}
|
setPublishMode={setMode}
|
||||||
setPrevFileText={setPrevFileText}
|
setPrevFileText={setPrevFileText}
|
||||||
autoPopulateName={autoPopulateNameFromTitle}
|
|
||||||
setAutoPopulateName={setAutoPopulateNameFromTitle}
|
|
||||||
header={
|
header={
|
||||||
<>
|
<>
|
||||||
{MODES.map((modeName, index) => (
|
{MODES.map((modeName, index) => (
|
||||||
|
@ -371,6 +355,7 @@ function PublishForm(props: Props) {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!publishing && (
|
{!publishing && (
|
||||||
<div className={classnames({ 'card--disabled': formDisabled })}>
|
<div className={classnames({ 'card--disabled': formDisabled })}>
|
||||||
{mode === PUBLISH_MODES.FILE && <PublishDescription disabled={formDisabled} />}
|
{mode === PUBLISH_MODES.FILE && <PublishDescription disabled={formDisabled} />}
|
||||||
|
@ -402,11 +387,7 @@ function PublishForm(props: Props) {
|
||||||
tagsChosen={tags}
|
tagsChosen={tags}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PublishName
|
<PublishBid disabled={isStillEditing || formDisabled} />
|
||||||
disabled={isStillEditing || formDisabled}
|
|
||||||
autoPopulateName={autoPopulateNameFromTitle}
|
|
||||||
setAutoPopulateName={setAutoPopulateNameFromTitle}
|
|
||||||
/>
|
|
||||||
<PublishPrice disabled={formDisabled} />
|
<PublishPrice disabled={formDisabled} />
|
||||||
<PublishAdditionalOptions disabled={formDisabled} />
|
<PublishAdditionalOptions disabled={formDisabled} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,33 +3,25 @@ import {
|
||||||
makeSelectPublishFormValue,
|
makeSelectPublishFormValue,
|
||||||
selectIsStillEditing,
|
selectIsStillEditing,
|
||||||
selectMyClaimForUri,
|
selectMyClaimForUri,
|
||||||
selectIsResolvingPublishUris,
|
|
||||||
selectTakeOverAmount,
|
|
||||||
doUpdatePublishForm,
|
doUpdatePublishForm,
|
||||||
doPrepareEdit,
|
doPrepareEdit,
|
||||||
selectBalance,
|
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
|
||||||
|
import { doSetActiveChannel } from 'redux/actions/app';
|
||||||
import PublishPage from './view';
|
import PublishPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
name: makeSelectPublishFormValue('name')(state),
|
name: makeSelectPublishFormValue('name')(state),
|
||||||
channel: makeSelectPublishFormValue('channel')(state),
|
|
||||||
bid: makeSelectPublishFormValue('bid')(state),
|
|
||||||
uri: makeSelectPublishFormValue('uri')(state),
|
|
||||||
isStillEditing: selectIsStillEditing(state),
|
isStillEditing: selectIsStillEditing(state),
|
||||||
isResolvingUri: selectIsResolvingPublishUris(state),
|
|
||||||
amountNeededForTakeover: selectTakeOverAmount(state),
|
|
||||||
balance: selectBalance(state),
|
|
||||||
myClaimForUri: selectMyClaimForUri(state),
|
myClaimForUri: selectMyClaimForUri(state),
|
||||||
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
|
incognito: selectIncognito(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
|
||||||
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
|
||||||
|
setActiveChannel: claimId => dispatch(doSetActiveChannel(claimId)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(select, perform)(PublishPage);
|
||||||
select,
|
|
||||||
perform
|
|
||||||
)(PublishPage);
|
|
||||||
|
|
|
@ -1,51 +1,41 @@
|
||||||
// @flow
|
// @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 React, { useState, useEffect } from 'react';
|
||||||
import { isNameValid } from 'lbry-redux';
|
import { isNameValid } from 'lbry-redux';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import NameHelpText from './name-help-text';
|
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 = {
|
type Props = {
|
||||||
name: string,
|
name: string,
|
||||||
channel: string,
|
|
||||||
uri: string,
|
uri: string,
|
||||||
bid: number,
|
|
||||||
balance: number,
|
|
||||||
disabled: boolean,
|
|
||||||
isStillEditing: boolean,
|
isStillEditing: boolean,
|
||||||
myClaimForUri: ?StreamClaim,
|
myClaimForUri: ?StreamClaim,
|
||||||
isResolvingUri: boolean,
|
|
||||||
amountNeededForTakeover: number,
|
amountNeededForTakeover: number,
|
||||||
prepareEdit: ({}, string) => void,
|
prepareEdit: ({}, string) => void,
|
||||||
updatePublishForm: ({}) => void,
|
updatePublishForm: ({}) => void,
|
||||||
autoPopulateName: boolean,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
setAutoPopulateName: boolean => void,
|
incognito: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function PublishName(props: Props) {
|
function PublishName(props: Props) {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
channel,
|
|
||||||
uri,
|
uri,
|
||||||
disabled,
|
|
||||||
isStillEditing,
|
isStillEditing,
|
||||||
myClaimForUri,
|
myClaimForUri,
|
||||||
bid,
|
|
||||||
isResolvingUri,
|
|
||||||
amountNeededForTakeover,
|
|
||||||
prepareEdit,
|
prepareEdit,
|
||||||
updatePublishForm,
|
updatePublishForm,
|
||||||
balance,
|
activeChannelClaim,
|
||||||
autoPopulateName,
|
incognito,
|
||||||
setAutoPopulateName,
|
|
||||||
} = props;
|
} = props;
|
||||||
const [nameError, setNameError] = useState(undefined);
|
const [nameError, setNameError] = useState(undefined);
|
||||||
const [bidError, setBidError] = useState(undefined);
|
const [blurred, setBlurred] = React.useState(false);
|
||||||
const previousBidAmount = myClaimForUri && Number(myClaimForUri.amount);
|
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
|
||||||
|
let prefix = IS_WEB ? `${DOMAIN}/` : 'lbry://';
|
||||||
|
if (activeChannelName && !incognito) {
|
||||||
|
prefix += `${activeChannelName}/`;
|
||||||
|
}
|
||||||
|
|
||||||
function editExistingClaim() {
|
function editExistingClaim() {
|
||||||
if (myClaimForUri) {
|
if (myClaimForUri) {
|
||||||
|
@ -55,21 +45,13 @@ function PublishName(props: Props) {
|
||||||
|
|
||||||
function handleNameChange(event) {
|
function handleNameChange(event) {
|
||||||
updatePublishForm({ name: event.target.value });
|
updatePublishForm({ name: event.target.value });
|
||||||
|
|
||||||
if (autoPopulateName) {
|
|
||||||
setAutoPopulateName(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hasName = name && name.trim() !== '';
|
if (!blurred && !name) {
|
||||||
// Enable name autopopulation from title
|
return;
|
||||||
if (!hasName && !autoPopulateName) {
|
|
||||||
setAutoPopulateName(true);
|
|
||||||
}
|
}
|
||||||
}, [name, autoPopulateName, setAutoPopulateName]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let nameError;
|
let nameError;
|
||||||
if (!name) {
|
if (!name) {
|
||||||
nameError = __('A name is required');
|
nameError = __('A name is required');
|
||||||
|
@ -78,83 +60,33 @@ function PublishName(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setNameError(nameError);
|
setNameError(nameError);
|
||||||
}, [name]);
|
}, [name, blurred]);
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<Card
|
<>
|
||||||
actions={
|
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
||||||
<React.Fragment>
|
<fieldset-section>
|
||||||
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
<label>{__('Name')}</label>
|
||||||
<fieldset-section>
|
<div className="form-field__prefix">{prefix}</div>
|
||||||
<label>{__('Name')}</label>
|
</fieldset-section>
|
||||||
<div className="form-field__prefix">{`lbry://${
|
<FormField
|
||||||
!channel || channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : `${channel}/`
|
type="text"
|
||||||
}`}</div>
|
name="content_name"
|
||||||
</fieldset-section>
|
value={name}
|
||||||
<FormField
|
error={nameError}
|
||||||
type="text"
|
onChange={handleNameChange}
|
||||||
name="content_name"
|
onBlur={() => setBlurred(true)}
|
||||||
value={name}
|
/>
|
||||||
disabled={disabled}
|
</fieldset-group>
|
||||||
error={nameError}
|
<div className="form-field__help">
|
||||||
onChange={handleNameChange}
|
<NameHelpText
|
||||||
/>
|
uri={uri}
|
||||||
</fieldset-group>
|
isStillEditing={isStillEditing}
|
||||||
<div className="form-field__help">
|
myClaimForUri={myClaimForUri}
|
||||||
<NameHelpText
|
onEditMyClaim={editExistingClaim}
|
||||||
uri={uri}
|
/>
|
||||||
isStillEditing={isStillEditing}
|
</div>
|
||||||
myClaimForUri={myClaimForUri}
|
</>
|
||||||
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,
|
doCheckPendingClaims,
|
||||||
makeSelectEffectiveAmountForUri,
|
makeSelectEffectiveAmountForUri,
|
||||||
makeSelectIsUriResolving,
|
makeSelectIsUriResolving,
|
||||||
|
selectFetchingMyChannels,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
import RepostCreate from './view';
|
import RepostCreate from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
@ -33,6 +35,8 @@ const select = (state, props) => ({
|
||||||
myClaims: selectMyClaimsWithoutChannels(state),
|
myClaims: selectMyClaimsWithoutChannels(state),
|
||||||
isResolvingPassedRepost: props.name && makeSelectIsUriResolving(`lbry://${props.name}`)(state),
|
isResolvingPassedRepost: props.name && makeSelectIsUriResolving(`lbry://${props.name}`)(state),
|
||||||
isResolvingEnteredRepost: props.repostUri && makeSelectIsUriResolving(`lbry://${props.repostUri}`)(state),
|
isResolvingEnteredRepost: props.repostUri && makeSelectIsUriResolving(`lbry://${props.repostUri}`)(state),
|
||||||
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
|
fetchingMyChannels: selectFetchingMyChannels(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, {
|
export default connect(select, {
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as ICONS from 'constants/icons';
|
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 React from 'react';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import Card from 'component/common/card';
|
import Card from 'component/common/card';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import SelectChannel from 'component/selectChannel';
|
import ChannelSelector from 'component/channelSelector';
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import { parseURI, isNameValid, creditsToString, isURIValid, normalizeURI } from 'lbry-redux';
|
import { parseURI, isNameValid, creditsToString, isURIValid, normalizeURI } from 'lbry-redux';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import LbcSymbol from 'component/common/lbc-symbol';
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
import ClaimPreview from 'component/claimPreview';
|
import ClaimPreview from 'component/claimPreview';
|
||||||
import { URL as SITE_URL, URL_LOCAL, URL_DEV } from 'config';
|
import { URL as SITE_URL, URL_LOCAL, URL_DEV } from 'config';
|
||||||
import HelpLink from 'component/common/help-link';
|
import HelpLink from 'component/common/help-link';
|
||||||
|
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
|
||||||
|
import Spinner from 'component/spinner';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
doToast: ({ message: string }) => void,
|
doToast: ({ message: string }) => void,
|
||||||
|
@ -39,6 +40,8 @@ type Props = {
|
||||||
enteredRepostAmount: number,
|
enteredRepostAmount: number,
|
||||||
isResolvingPassedRepost: boolean,
|
isResolvingPassedRepost: boolean,
|
||||||
isResolvingEnteredRepost: boolean,
|
isResolvingEnteredRepost: boolean,
|
||||||
|
activeChannelClaim: ?ChannelClaim,
|
||||||
|
fetchingMyChannels: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function RepostCreate(props: Props) {
|
function RepostCreate(props: Props) {
|
||||||
|
@ -50,7 +53,6 @@ function RepostCreate(props: Props) {
|
||||||
claim,
|
claim,
|
||||||
enteredContentClaim,
|
enteredContentClaim,
|
||||||
balance,
|
balance,
|
||||||
channels,
|
|
||||||
reposting,
|
reposting,
|
||||||
doCheckPublishNameAvailability,
|
doCheckPublishNameAvailability,
|
||||||
uri, // ?from
|
uri, // ?from
|
||||||
|
@ -63,12 +65,13 @@ function RepostCreate(props: Props) {
|
||||||
passedRepostAmount,
|
passedRepostAmount,
|
||||||
isResolvingPassedRepost,
|
isResolvingPassedRepost,
|
||||||
isResolvingEnteredRepost,
|
isResolvingEnteredRepost,
|
||||||
|
activeChannelClaim,
|
||||||
|
fetchingMyChannels,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const defaultName = name || (claim && claim.name) || '';
|
const defaultName = name || (claim && claim.name) || '';
|
||||||
const contentClaimId = claim && claim.claim_id;
|
const contentClaimId = claim && claim.claim_id;
|
||||||
const enteredClaimId = enteredContentClaim && enteredContentClaim.claim_id;
|
const enteredClaimId = enteredContentClaim && enteredContentClaim.claim_id;
|
||||||
const [repostChannel, setRepostChannel] = usePersistedState('repost-channel', 'anonymous');
|
|
||||||
|
|
||||||
const [repostBid, setRepostBid] = React.useState(0.01);
|
const [repostBid, setRepostBid] = React.useState(0.01);
|
||||||
const [repostBidError, setRepostBidError] = React.useState(undefined);
|
const [repostBidError, setRepostBidError] = React.useState(undefined);
|
||||||
|
@ -80,9 +83,7 @@ function RepostCreate(props: Props) {
|
||||||
|
|
||||||
const { replace, goBack } = useHistory();
|
const { replace, goBack } = useHistory();
|
||||||
const resolvingRepost = isResolvingEnteredRepost || isResolvingPassedRepost;
|
const resolvingRepost = isResolvingEnteredRepost || isResolvingPassedRepost;
|
||||||
const repostUrlName = `lbry://${
|
const repostUrlName = `lbry://${!activeChannelClaim ? '' : `${activeChannelClaim.name}/`}`;
|
||||||
!repostChannel || repostChannel === CHANNEL_NEW || repostChannel === 'anonymous' ? '' : `${repostChannel}/`
|
|
||||||
}`;
|
|
||||||
|
|
||||||
const contentFirstRender = React.useRef(true);
|
const contentFirstRender = React.useRef(true);
|
||||||
const setAutoRepostBid = amount => {
|
const setAutoRepostBid = amount => {
|
||||||
|
@ -173,17 +174,6 @@ function RepostCreate(props: Props) {
|
||||||
contentNameError = __('A name is required');
|
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(() => {
|
React.useEffect(() => {
|
||||||
if (enteredRepostName && isNameValid(enteredRepostName, false)) {
|
if (enteredRepostName && isNameValid(enteredRepostName, false)) {
|
||||||
doCheckPublishNameAvailability(enteredRepostName).then(r => setAvailable(r));
|
doCheckPublishNameAvailability(enteredRepostName).then(r => setAvailable(r));
|
||||||
|
@ -287,12 +277,11 @@ function RepostCreate(props: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
const channelToRepostTo = channels && channels.find(channel => channel.name === repostChannel);
|
|
||||||
if (enteredRepostName && repostBid && repostClaimId) {
|
if (enteredRepostName && repostBid && repostClaimId) {
|
||||||
doRepost({
|
doRepost({
|
||||||
name: enteredRepostName,
|
name: enteredRepostName,
|
||||||
bid: creditsToString(repostBid),
|
bid: creditsToString(repostBid),
|
||||||
channel_id: channelToRepostTo ? channelToRepostTo.claim_id : undefined,
|
channel_id: activeChannelClaim ? activeChannelClaim.claim_id : undefined,
|
||||||
claim_id: repostClaimId,
|
claim_id: repostClaimId,
|
||||||
}).then((repostClaim: StreamClaim) => {
|
}).then((repostClaim: StreamClaim) => {
|
||||||
doCheckPendingClaims();
|
doCheckPendingClaims();
|
||||||
|
@ -308,8 +297,18 @@ function RepostCreate(props: Props) {
|
||||||
goBack();
|
goBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fetchingMyChannels) {
|
||||||
|
return (
|
||||||
|
<div className="main--empty">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<ChannelSelector />
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
actions={
|
actions={
|
||||||
<div>
|
<div>
|
||||||
|
@ -331,6 +330,7 @@ function RepostCreate(props: Props) {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!uri && (
|
{!uri && (
|
||||||
<fieldset-section>
|
<fieldset-section>
|
||||||
<ClaimPreview key={contentUri} uri={contentUri} actions={''} type={'large'} showNullPlaceholder />
|
<ClaimPreview key={contentUri} uri={contentUri} actions={''} type={'large'} showNullPlaceholder />
|
||||||
|
@ -362,12 +362,6 @@ function RepostCreate(props: Props) {
|
||||||
/>
|
/>
|
||||||
</fieldset-group>
|
</fieldset-group>
|
||||||
</fieldset-section>
|
</fieldset-section>
|
||||||
<SelectChannel
|
|
||||||
label={__('Channel to repost on')}
|
|
||||||
hideNew
|
|
||||||
channel={repostChannel}
|
|
||||||
onChannelChange={newChannel => setRepostChannel(newChannel)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
type="number"
|
type="number"
|
||||||
|
@ -379,9 +373,14 @@ function RepostCreate(props: Props) {
|
||||||
label={<LbcSymbol postfix={__('Support --[button to support a claim]--')} size={14} />}
|
label={<LbcSymbol postfix={__('Support --[button to support a claim]--')} size={14} />}
|
||||||
value={repostBid}
|
value={repostBid}
|
||||||
error={repostBidError}
|
error={repostBidError}
|
||||||
helper={__('Winning amount: %amount%', {
|
helper={
|
||||||
amount: Number(takeoverAmount).toFixed(2),
|
<>
|
||||||
})}
|
{__('Winning amount: %amount%', {
|
||||||
|
amount: Number(takeoverAmount).toFixed(2),
|
||||||
|
})}
|
||||||
|
<WalletSpendableBalanceHelp inline />
|
||||||
|
</>
|
||||||
|
}
|
||||||
disabled={!enteredRepostName || resolvingRepost}
|
disabled={!enteredRepostName || resolvingRepost}
|
||||||
onChange={event => setRepostBid(event.target.value)}
|
onChange={event => setRepostBid(event.target.value)}
|
||||||
onWheel={e => e.stopPropagation()}
|
onWheel={e => e.stopPropagation()}
|
||||||
|
|
|
@ -8,17 +8,21 @@ import {
|
||||||
doCreateChannel,
|
doCreateChannel,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||||
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
|
import { doSetActiveChannel } from 'redux/actions/app';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
channels: selectMyChannelClaims(state),
|
myChannelClaims: selectMyChannelClaims(state),
|
||||||
fetchingChannels: selectFetchingMyChannels(state),
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
emailVerified: selectUserVerifiedEmail(state),
|
emailVerified: selectUserVerifiedEmail(state),
|
||||||
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
|
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
|
||||||
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
|
||||||
|
setActiveChannel: claimId => dispatch(doSetActiveChannel(claimId)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(SelectChannel);
|
export default connect(select, perform)(SelectChannel);
|
||||||
|
|
|
@ -1,131 +1,65 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
|
import React from 'react';
|
||||||
import React, { Fragment } from 'react';
|
|
||||||
import { FormField } from 'component/common/form';
|
import { FormField } from 'component/common/form';
|
||||||
import ChannelCreate from 'component/channelCreate';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
channel: string, // currently selected channel
|
tiny?: boolean,
|
||||||
channels: ?Array<ChannelClaim>,
|
label: string,
|
||||||
balance: number,
|
myChannelClaims: ?Array<ChannelClaim>,
|
||||||
onChannelChange: string => void,
|
injected: ?Array<string>,
|
||||||
createChannel: (string, number) => Promise<any>,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
fetchChannelListMine: () => void,
|
setActiveChannel: string => void,
|
||||||
fetchingChannels: boolean,
|
fetchingChannels: boolean,
|
||||||
hideAnon: boolean,
|
|
||||||
hideNew: boolean,
|
|
||||||
label?: string,
|
|
||||||
injected?: Array<string>,
|
|
||||||
emailVerified: boolean,
|
|
||||||
tiny: boolean,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type State = {
|
function SelectChannel(props: Props) {
|
||||||
addingChannel: boolean,
|
const {
|
||||||
};
|
fetchingChannels,
|
||||||
|
myChannelClaims = [],
|
||||||
|
label,
|
||||||
|
injected = [],
|
||||||
|
tiny,
|
||||||
|
activeChannelClaim,
|
||||||
|
setActiveChannel,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const ID_FF_SELECT_CHANNEL = 'ID_FF_SELECT_CHANNEL';
|
function handleChannelChange(event: SyntheticInputEvent<*>) {
|
||||||
|
const channelClaimId = event.target.value;
|
||||||
class ChannelSelection extends React.PureComponent<Props, State> {
|
setActiveChannel(channelClaimId);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
return (
|
||||||
const { channel, channels, fetchChannelListMine, fetchingChannels, emailVerified, onChannelChange } = this.props;
|
<>
|
||||||
if (IS_WEB && !emailVerified) {
|
<FormField
|
||||||
return;
|
name="channel"
|
||||||
}
|
label={!tiny && (label || __('Channel'))}
|
||||||
|
labelOnLeft={tiny}
|
||||||
if ((!channels || !channels.length) && !fetchingChannels) {
|
type={tiny ? 'select-tiny' : 'select'}
|
||||||
fetchChannelListMine();
|
onChange={handleChannelChange}
|
||||||
}
|
value={activeChannelClaim && activeChannelClaim.claim_id}
|
||||||
|
disabled={fetchingChannels}
|
||||||
if (channels && channels.length && !channels.find(chan => chan.name === channel)) {
|
>
|
||||||
const elem = document.getElementById(ID_FF_SELECT_CHANNEL);
|
{fetchingChannels ? (
|
||||||
// $FlowFixMe
|
<option>{__('Loading your channels...')}</option>
|
||||||
if (elem && elem.value && elem.value !== channel) {
|
) : (
|
||||||
setTimeout(() => {
|
<>
|
||||||
// $FlowFixMe
|
{myChannelClaims &&
|
||||||
onChannelChange(elem.value);
|
myChannelClaims.map(({ name, claim_id: claimId }) => (
|
||||||
}, 250);
|
<option key={claimId} value={claimId}>
|
||||||
}
|
{name}
|
||||||
}
|
</option>
|
||||||
}
|
))}
|
||||||
|
{injected &&
|
||||||
componentDidUpdate() {
|
injected.map(item => (
|
||||||
const { channels, fetchingChannels, hideAnon } = this.props;
|
<option key={item} value={item}>
|
||||||
if (!fetchingChannels && !channels && hideAnon) {
|
{item}
|
||||||
this.setState({ addingChannel: true });
|
</option>
|
||||||
}
|
))}
|
||||||
}
|
</>
|
||||||
|
)}
|
||||||
handleChannelChange(event: SyntheticInputEvent<*>) {
|
</FormField>
|
||||||
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}
|
|
||||||
>
|
|
||||||
{!hideAnon && <option value={CHANNEL_ANONYMOUS}>{__('Anonymous')}</option>}
|
|
||||||
{channels &&
|
|
||||||
channels.map(({ name, claim_id: claimId }) => (
|
|
||||||
<option key={claimId} value={name}>
|
|
||||||
{name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
{injected &&
|
|
||||||
injected.map(item => (
|
|
||||||
<option key={item} value={item}>
|
|
||||||
{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,
|
selectIsSendingSupport,
|
||||||
selectBalance,
|
selectBalance,
|
||||||
SETTINGS,
|
SETTINGS,
|
||||||
selectMyChannelClaims,
|
|
||||||
makeSelectClaimIsMine,
|
makeSelectClaimIsMine,
|
||||||
selectFetchingMyChannels,
|
selectFetchingMyChannels,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
@ -14,6 +13,7 @@ import WalletSendTip from './view';
|
||||||
import { doOpenModal, doHideModal } from 'redux/actions/app';
|
import { doOpenModal, doHideModal } from 'redux/actions/app';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
|
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
isPending: selectIsSendingSupport(state),
|
isPending: selectIsSendingSupport(state),
|
||||||
|
@ -22,9 +22,10 @@ const select = (state, props) => ({
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
instantTipEnabled: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_ENABLED)(state),
|
instantTipEnabled: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_ENABLED)(state),
|
||||||
instantTipMax: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_MAX)(state),
|
instantTipMax: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_MAX)(state),
|
||||||
channels: selectMyChannelClaims(state),
|
|
||||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||||
fetchingChannels: selectFetchingMyChannels(state),
|
fetchingChannels: selectFetchingMyChannels(state),
|
||||||
|
activeChannelClaim: selectActiveChannelClaim(state),
|
||||||
|
incognito: selectIncognito(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -5,13 +5,13 @@ import * as PAGES from 'constants/pages';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { FormField, Form } from 'component/common/form';
|
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 CreditAmount from 'component/common/credit-amount';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
import { Lbryio } from 'lbryinc';
|
import { Lbryio } from 'lbryinc';
|
||||||
import Card from 'component/common/card';
|
import Card from 'component/common/card';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import SelectChannel from 'component/selectChannel';
|
import ChannelSelector from 'component/channelSelector';
|
||||||
import LbcSymbol from 'component/common/lbc-symbol';
|
import LbcSymbol from 'component/common/lbc-symbol';
|
||||||
import { parseURI } from 'lbry-redux';
|
import { parseURI } from 'lbry-redux';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
import usePersistedState from 'effects/use-persisted-state';
|
||||||
|
@ -34,7 +34,8 @@ type Props = {
|
||||||
fetchingChannels: boolean,
|
fetchingChannels: boolean,
|
||||||
instantTipEnabled: boolean,
|
instantTipEnabled: boolean,
|
||||||
instantTipMax: { amount: number, currency: string },
|
instantTipMax: { amount: number, currency: string },
|
||||||
channels: ?Array<ChannelClaim>,
|
activeChannelClaim: ?ChannelClaim,
|
||||||
|
incognito: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
function WalletSendTip(props: Props) {
|
function WalletSendTip(props: Props) {
|
||||||
|
@ -49,8 +50,9 @@ function WalletSendTip(props: Props) {
|
||||||
instantTipMax,
|
instantTipMax,
|
||||||
sendSupport,
|
sendSupport,
|
||||||
closeModal,
|
closeModal,
|
||||||
channels,
|
|
||||||
fetchingChannels,
|
fetchingChannels,
|
||||||
|
incognito,
|
||||||
|
activeChannelClaim,
|
||||||
} = props;
|
} = props;
|
||||||
const [presetTipAmount, setPresetTipAmount] = usePersistedState('comment-support:presetTip', DEFAULT_TIP_AMOUNTS[0]);
|
const [presetTipAmount, setPresetTipAmount] = usePersistedState('comment-support:presetTip', DEFAULT_TIP_AMOUNTS[0]);
|
||||||
const [customTipAmount, setCustomTipAmount] = usePersistedState('comment-support:customTip', 1.0);
|
const [customTipAmount, setCustomTipAmount] = usePersistedState('comment-support:customTip', 1.0);
|
||||||
|
@ -58,21 +60,9 @@ function WalletSendTip(props: Props) {
|
||||||
const [tipError, setTipError] = React.useState();
|
const [tipError, setTipError] = React.useState();
|
||||||
const [sendAsTip, setSendAsTip] = usePersistedState('comment-support:sendAsTip', true);
|
const [sendAsTip, setSendAsTip] = usePersistedState('comment-support:sendAsTip', true);
|
||||||
const [isConfirming, setIsConfirming] = React.useState(false);
|
const [isConfirming, setIsConfirming] = React.useState(false);
|
||||||
const [selectedChannel, setSelectedChannel] = usePersistedState('comment-support:channel');
|
|
||||||
const { claim_id: claimId } = claim;
|
const { claim_id: claimId } = claim;
|
||||||
const { channelName } = parseURI(uri);
|
const { channelName } = parseURI(uri);
|
||||||
const noBalance = balance === 0;
|
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 tipAmount = useCustomTip ? customTipAmount : presetTipAmount;
|
||||||
const isSupport = claimIsMine ? true : SIMPLE_SITE ? false : !sendAsTip;
|
const isSupport = claimIsMine ? true : SIMPLE_SITE ? false : !sendAsTip;
|
||||||
|
|
||||||
|
@ -99,12 +89,8 @@ function WalletSendTip(props: Props) {
|
||||||
|
|
||||||
function sendSupportOrConfirm(instantTipMaxAmount = null) {
|
function sendSupportOrConfirm(instantTipMaxAmount = null) {
|
||||||
let selectedChannelId;
|
let selectedChannelId;
|
||||||
if (selectedChannel !== CHANNEL_ANONYMOUS) {
|
if (!incognito && activeChannelClaim) {
|
||||||
const selectedChannelClaim = channels && channels.find(channelClaim => channelClaim.name === selectedChannel);
|
selectedChannelId = activeChannelClaim.claim_id;
|
||||||
|
|
||||||
if (selectedChannelClaim) {
|
|
||||||
selectedChannelId = selectedChannelClaim.claim_id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -124,12 +110,6 @@ function WalletSendTip(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit() {
|
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 (tipAmount && claimId) {
|
||||||
if (instantTipEnabled) {
|
if (instantTipEnabled) {
|
||||||
if (instantTipMax.currency === 'LBC') {
|
if (instantTipMax.currency === 'LBC') {
|
||||||
|
@ -196,7 +176,9 @@ function WalletSendTip(props: Props) {
|
||||||
<div className="confirm__label">{__('To --[the tip recipient]--')}</div>
|
<div className="confirm__label">{__('To --[the tip recipient]--')}</div>
|
||||||
<div className="confirm__value">{channelName || title}</div>
|
<div className="confirm__value">{channelName || title}</div>
|
||||||
<div className="confirm__label">{__('From --[the tip sender]--')}</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__label">{__(isSupport ? 'Supporting' : 'Tipping')}</div>
|
||||||
<div className="confirm__value">
|
<div className="confirm__value">
|
||||||
<LbcSymbol postfix={tipAmount} size={22} />
|
<LbcSymbol postfix={tipAmount} size={22} />
|
||||||
|
@ -217,11 +199,7 @@ function WalletSendTip(props: Props) {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="section">
|
<div className="section">
|
||||||
<SelectChannel
|
<ChannelSelector />
|
||||||
label={__('Channel to show support as')}
|
|
||||||
channel={selectedChannel}
|
|
||||||
onChannelChange={newChannel => setSelectedChannel(newChannel)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="section">
|
<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 SET_SYNC_LOCK = 'SET_SYNC_LOCK';
|
||||||
export const TOGGLE_YOUTUBE_SYNC_INTEREST = 'TOGGLE_YOUTUBE_SYNC_INTEREST';
|
export const TOGGLE_YOUTUBE_SYNC_INTEREST = 'TOGGLE_YOUTUBE_SYNC_INTEREST';
|
||||||
export const TOGGLE_SPLASH_ANIMATION = 'TOGGLE_SPLASH_ANIMATION';
|
export const TOGGLE_SPLASH_ANIMATION = 'TOGGLE_SPLASH_ANIMATION';
|
||||||
|
export const SET_ACTIVE_CHANNEL = 'SET_ACTIVE_CHANNEL';
|
||||||
|
export const SET_INCOGNITO = 'SET_INCOGNITO';
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH';
|
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_STARTED = 'COMMENT_PIN_STARTED';
|
||||||
export const COMMENT_PIN_COMPLETED = 'COMMENT_PIN_COMPLETED';
|
export const COMMENT_PIN_COMPLETED = 'COMMENT_PIN_COMPLETED';
|
||||||
export const COMMENT_PIN_FAILED = 'COMMENT_PIN_FAILED';
|
export const COMMENT_PIN_FAILED = 'COMMENT_PIN_FAILED';
|
||||||
export const COMMENT_SET_CHANNEL = 'COMMENT_SET_CHANNEL';
|
|
||||||
|
|
||||||
// Blocked channels
|
// Blocked channels
|
||||||
export const TOGGLE_BLOCK_CHANNEL = 'TOGGLE_BLOCK_CHANNEL';
|
export const TOGGLE_BLOCK_CHANNEL = 'TOGGLE_BLOCK_CHANNEL';
|
||||||
|
|
|
@ -130,3 +130,4 @@ export const PIN = 'Pin';
|
||||||
export const BEST = 'Best';
|
export const BEST = 'Best';
|
||||||
export const CREATOR_LIKE = 'CreatorLike';
|
export const CREATOR_LIKE = 'CreatorLike';
|
||||||
export const CHEF = 'Chef';
|
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 CREATE_CHANNEL = 'create_channel';
|
||||||
export const YOUTUBE_WELCOME = 'youtube_welcome';
|
export const YOUTUBE_WELCOME = 'youtube_welcome';
|
||||||
export const SET_REFERRER = 'set_referrer';
|
export const SET_REFERRER = 'set_referrer';
|
||||||
export const REPOST = 'repost';
|
|
||||||
export const SIGN_OUT = 'sign_out';
|
export const SIGN_OUT = 'sign_out';
|
||||||
export const LIQUIDATE_SUPPORTS = 'liquidate_supports';
|
export const LIQUIDATE_SUPPORTS = 'liquidate_supports';
|
||||||
export const MASS_TIP_UNLOCK = 'mass_tip_unlock';
|
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 ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
|
||||||
import ModalCreateChannel from 'modal/modalChannelCreate';
|
import ModalCreateChannel from 'modal/modalChannelCreate';
|
||||||
import ModalSetReferrer from 'modal/modalSetReferrer';
|
import ModalSetReferrer from 'modal/modalSetReferrer';
|
||||||
import ModalRepost from 'modal/modalRepost';
|
|
||||||
import ModalSignOut from 'modal/modalSignOut';
|
import ModalSignOut from 'modal/modalSignOut';
|
||||||
import ModalSupportsLiquidate from 'modal/modalSupportsLiquidate';
|
import ModalSupportsLiquidate from 'modal/modalSupportsLiquidate';
|
||||||
import ModalConfirmAge from 'modal/modalConfirmAge';
|
import ModalConfirmAge from 'modal/modalConfirmAge';
|
||||||
|
@ -135,8 +134,6 @@ function ModalRouter(props: Props) {
|
||||||
return <ModalCreateChannel {...modalProps} />;
|
return <ModalCreateChannel {...modalProps} />;
|
||||||
case MODALS.SET_REFERRER:
|
case MODALS.SET_REFERRER:
|
||||||
return <ModalSetReferrer {...modalProps} />;
|
return <ModalSetReferrer {...modalProps} />;
|
||||||
case MODALS.REPOST:
|
|
||||||
return <ModalRepost {...modalProps} />;
|
|
||||||
case MODALS.SIGN_OUT:
|
case MODALS.SIGN_OUT:
|
||||||
return <ModalSignOut {...modalProps} />;
|
return <ModalSignOut {...modalProps} />;
|
||||||
case MODALS.CONFIRM_AGE:
|
case MODALS.CONFIRM_AGE:
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux';
|
import { selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux';
|
||||||
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
|
import { doSetActiveChannel } from 'redux/actions/app';
|
||||||
import CreatorDashboardPage from './view';
|
import CreatorDashboardPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
channels: selectMyChannelClaims(state),
|
channels: selectMyChannelClaims(state),
|
||||||
fetchingChannels: selectFetchingMyChannels(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 Button from 'component/button';
|
||||||
import CreatorAnalytics from 'component/creatorAnalytics';
|
import CreatorAnalytics from 'component/creatorAnalytics';
|
||||||
import ChannelSelector from 'component/channelSelector';
|
import ChannelSelector from 'component/channelSelector';
|
||||||
import usePersistedState from 'effects/use-persisted-state';
|
|
||||||
import Yrbl from 'component/yrbl';
|
import Yrbl from 'component/yrbl';
|
||||||
import { useHistory } from 'react-router';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
channels: Array<ChannelClaim>,
|
channels: Array<ChannelClaim>,
|
||||||
fetchingChannels: boolean,
|
fetchingChannels: boolean,
|
||||||
|
activeChannelClaim: ?ChannelClaim,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SELECTED_CHANNEL_QUERY_PARAM = 'channel';
|
|
||||||
|
|
||||||
export default function CreatorDashboardPage(props: Props) {
|
export default function CreatorDashboardPage(props: Props) {
|
||||||
const { channels, fetchingChannels } = props;
|
const { channels, fetchingChannels, activeChannelClaim } = 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 hasChannels = channels && channels.length > 0;
|
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 (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
|
@ -63,7 +26,7 @@ export default function CreatorDashboardPage(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!fetchingChannels && (!channels || !channels.length) && (
|
{!fetchingChannels && !hasChannels && (
|
||||||
<Yrbl
|
<Yrbl
|
||||||
type="happy"
|
type="happy"
|
||||||
title={__("You haven't created a channel yet, let's fix that!")}
|
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>
|
<React.Fragment>
|
||||||
<div className="section">
|
<ChannelSelector hideAnon />
|
||||||
<ChannelSelector
|
<CreatorAnalytics uri={activeChannelClaim.canonical_url} />
|
||||||
selectedChannelUrl={selectedChannelUrl}
|
|
||||||
onChannelSelect={newChannelUrl => {
|
|
||||||
updateUrl(newChannelUrl);
|
|
||||||
setSelectedChannelUrl(newChannelUrl);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<CreatorAnalytics uri={selectedChannelUrl} />
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
SHARED_PREFERENCES,
|
SHARED_PREFERENCES,
|
||||||
DAEMON_SETTINGS,
|
DAEMON_SETTINGS,
|
||||||
SETTINGS,
|
SETTINGS,
|
||||||
|
selectMyChannelClaims,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { Lbryio } from 'lbryinc';
|
import { Lbryio } from 'lbryinc';
|
||||||
import { selectFollowedTagsList } from 'redux/selectors/tags';
|
import { selectFollowedTagsList } from 'redux/selectors/tags';
|
||||||
|
@ -693,3 +694,54 @@ export function doToggleSplashAnimation() {
|
||||||
type: ACTIONS.TOGGLE_SPLASH_ANIMATION,
|
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
|
// @flow
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import * as REACTION_TYPES from 'constants/reactions';
|
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 { doToast, doSeeNotifications } from 'redux/actions/notifications';
|
||||||
import {
|
import {
|
||||||
makeSelectCommentIdsForUri,
|
makeSelectCommentIdsForUri,
|
||||||
makeSelectMyReactionsForComment,
|
makeSelectMyReactionsForComment,
|
||||||
makeSelectOthersReactionsForComment,
|
makeSelectOthersReactionsForComment,
|
||||||
selectPendingCommentReacts,
|
selectPendingCommentReacts,
|
||||||
selectCommentChannel,
|
|
||||||
} from 'redux/selectors/comments';
|
} from 'redux/selectors/comments';
|
||||||
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
|
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
|
||||||
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
|
|
||||||
export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) {
|
export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) {
|
||||||
return (dispatch: Dispatch, getState: GetState) => {
|
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) {
|
export function doCommentReactList(uri: string | null, commentId?: string) {
|
||||||
return (dispatch: Dispatch, getState: GetState) => {
|
return (dispatch: Dispatch, getState: GetState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const channel = selectCommentChannel(state);
|
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||||
const commentIds = uri ? makeSelectCommentIdsForUri(uri)(state) : [commentId];
|
const commentIds = uri ? makeSelectCommentIdsForUri(uri)(state) : [commentId];
|
||||||
const myChannels = selectMyChannelClaims(state);
|
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_REACTION_LIST_STARTED,
|
type: ACTIONS.COMMENT_REACTION_LIST_STARTED,
|
||||||
});
|
});
|
||||||
|
|
||||||
const params: { comment_ids: string, channel_name?: string, channel_id?: string } = {
|
const params: { comment_ids: string, channel_name?: string, channel_id?: string } = {
|
||||||
comment_ids: commentIds.join(','),
|
comment_ids: commentIds.join(','),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (channel && myChannels) {
|
if (activeChannelClaim) {
|
||||||
const claimForChannelName = myChannels && myChannels.find(chan => chan.name === channel);
|
params['channel_name'] = activeChannelClaim.name;
|
||||||
const channelId = claimForChannelName && claimForChannelName.claim_id;
|
params['channel_id'] = activeChannelClaim.claim_id;
|
||||||
params['channel_name'] = channel;
|
|
||||||
params['channel_id'] = channelId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Lbry.comment_react_list(params)
|
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) {
|
export function doCommentReact(commentId: string, type: string) {
|
||||||
return (dispatch: Dispatch, getState: GetState) => {
|
return (dispatch: Dispatch, getState: GetState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const channel = selectCommentChannel(state);
|
const activeChannelClaim = selectActiveChannelClaim(state);
|
||||||
const pendingReacts = selectPendingCommentReacts(state);
|
const pendingReacts = selectPendingCommentReacts(state);
|
||||||
const myChannels = selectMyChannelClaims(state);
|
|
||||||
const notification = makeSelectNotificationForCommentId(commentId)(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) {
|
if (notification && !notification.is_seen) {
|
||||||
dispatch(doSeeNotifications([notification.id]));
|
dispatch(doSeeNotifications([notification.id]));
|
||||||
}
|
}
|
||||||
|
|
||||||
const exclusiveTypes = {
|
const exclusiveTypes = {
|
||||||
[REACTION_TYPES.LIKE]: REACTION_TYPES.DISLIKE,
|
[REACTION_TYPES.LIKE]: REACTION_TYPES.DISLIKE,
|
||||||
[REACTION_TYPES.DISLIKE]: REACTION_TYPES.LIKE,
|
[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)) {
|
if (pendingReacts.includes(commentId + exclusiveTypes[type]) || pendingReacts.includes(commentId + type)) {
|
||||||
// ignore dislikes during likes, for example
|
// ignore dislikes during likes, for example
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let myReacts = makeSelectMyReactionsForComment(commentId)(state);
|
let myReacts = makeSelectMyReactionsForComment(commentId)(state);
|
||||||
const othersReacts = makeSelectOthersReactionsForComment(commentId)(state);
|
const othersReacts = makeSelectOthersReactionsForComment(commentId)(state);
|
||||||
const claimForChannelName = myChannels.find(chan => chan.name === channel);
|
|
||||||
const channelId = claimForChannelName && claimForChannelName.claim_id;
|
|
||||||
|
|
||||||
const params: CommentReactParams = {
|
const params: CommentReactParams = {
|
||||||
comment_ids: commentId,
|
comment_ids: commentId,
|
||||||
channel_name: channel,
|
channel_name: activeChannelClaim.name,
|
||||||
channel_id: channelId,
|
channel_id: activeChannelClaim.claim_id,
|
||||||
react_type: type,
|
react_type: type,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (myReacts.includes(type)) {
|
if (myReacts.includes(type)) {
|
||||||
params['remove'] = true;
|
params['remove'] = true;
|
||||||
myReacts.splice(myReacts.indexOf(type), 1);
|
myReacts.splice(myReacts.indexOf(type), 1);
|
||||||
|
@ -197,15 +185,16 @@ export function doCommentReact(commentId: string, type: string) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doCommentCreate(
|
export function doCommentCreate(comment: string = '', claim_id: string = '', parent_id?: string, uri: string) {
|
||||||
comment: string = '',
|
|
||||||
claim_id: string = '',
|
|
||||||
channel: string,
|
|
||||||
parent_id?: string,
|
|
||||||
uri: string
|
|
||||||
) {
|
|
||||||
return (dispatch: Dispatch, getState: GetState) => {
|
return (dispatch: Dispatch, getState: GetState) => {
|
||||||
const state = 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({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_CREATE_STARTED,
|
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({
|
return Lbry.comment_create({
|
||||||
comment: comment,
|
comment: comment,
|
||||||
claim_id: claim_id,
|
claim_id: claim_id,
|
||||||
channel_id: channel_id,
|
channel_id: activeChannelClaim.claim_id,
|
||||||
parent_id: parent_id,
|
parent_id: parent_id,
|
||||||
})
|
})
|
||||||
.then((result: CommentCreateResponse) => {
|
.then((result: CommentCreateResponse) => {
|
||||||
|
@ -299,32 +270,23 @@ export function doCommentHide(comment_id: string) {
|
||||||
export function doCommentPin(commentId: string, remove: boolean) {
|
export function doCommentPin(commentId: string, remove: boolean) {
|
||||||
return (dispatch: Dispatch, getState: GetState) => {
|
return (dispatch: Dispatch, getState: GetState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
// const channel = localStorage.getItem('comment-channel');
|
const activeChannel = selectActiveChannelClaim(state);
|
||||||
const channel = selectCommentChannel(state);
|
|
||||||
const myChannels = selectMyChannelClaims(state);
|
if (!activeChannel) {
|
||||||
const claimForChannelName = myChannels && myChannels.find(chan => chan.name === channel);
|
console.error('Unable to pin comment. No activeChannel is set.'); // eslint-disable-line
|
||||||
const channelId = claimForChannelName && claimForChannelName.claim_id;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_PIN_STARTED,
|
type: ACTIONS.COMMENT_PIN_STARTED,
|
||||||
});
|
});
|
||||||
if (!channelId || !channel || !commentId) {
|
|
||||||
return dispatch({
|
return Lbry.comment_pin({
|
||||||
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 } = {
|
|
||||||
comment_id: commentId,
|
comment_id: commentId,
|
||||||
channel_name: channel,
|
channel_name: activeChannel.name,
|
||||||
channel_id: channelId,
|
channel_id: activeChannel.claim_id,
|
||||||
};
|
...(remove ? { remove: true } : {}),
|
||||||
|
})
|
||||||
if (remove) {
|
|
||||||
params['remove'] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Lbry.comment_pin(params)
|
|
||||||
.then((result: CommentPinResponse) => {
|
.then((result: CommentPinResponse) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.COMMENT_PIN_COMPLETED,
|
type: ACTIONS.COMMENT_PIN_COMPLETED,
|
||||||
|
|
|
@ -44,6 +44,8 @@ export type AppState = {
|
||||||
allowAnalytics: boolean,
|
allowAnalytics: boolean,
|
||||||
hasNavigated: boolean,
|
hasNavigated: boolean,
|
||||||
interestedInYoutubeSync: boolean,
|
interestedInYoutubeSync: boolean,
|
||||||
|
activeChannel: ?string,
|
||||||
|
incognito: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultState: AppState = {
|
const defaultState: AppState = {
|
||||||
|
@ -80,6 +82,8 @@ const defaultState: AppState = {
|
||||||
allowAnalytics: false,
|
allowAnalytics: false,
|
||||||
hasNavigated: false,
|
hasNavigated: false,
|
||||||
interestedInYoutubeSync: false,
|
interestedInYoutubeSync: false,
|
||||||
|
activeChannel: undefined,
|
||||||
|
incognito: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// @@router comes from react-router
|
// @@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) => {
|
reducers[LBRY_REDUX_ACTIONS.USER_STATE_POPULATE] = (state, action) => {
|
||||||
const { welcomeVersion, allowAnalytics } = action.data;
|
const { welcomeVersion, allowAnalytics } = action.data;
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -16,7 +16,6 @@ const defaultState: CommentsState = {
|
||||||
typesReacting: [],
|
typesReacting: [],
|
||||||
myReactsByCommentId: undefined,
|
myReactsByCommentId: undefined,
|
||||||
othersReactsByCommentId: undefined,
|
othersReactsByCommentId: undefined,
|
||||||
commentChannel: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default handleActions(
|
export default handleActions(
|
||||||
|
@ -31,11 +30,6 @@ export default handleActions(
|
||||||
isCommenting: false,
|
isCommenting: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
[ACTIONS.COMMENT_SET_CHANNEL]: (state: CommentsState, action: any) => ({
|
|
||||||
...state,
|
|
||||||
commentChannel: action.data,
|
|
||||||
}),
|
|
||||||
|
|
||||||
[ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
|
[ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
|
||||||
const { comment, claimId, uri }: { comment: Comment, claimId: string, uri: string } = action.data;
|
const { comment, claimId, uri }: { comment: Comment, claimId: string, uri: string } = action.data;
|
||||||
const commentById = Object.assign({}, state.commentById);
|
const commentById = Object.assign({}, state.commentById);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import { selectClaimsById, selectMyChannelClaims } from 'lbry-redux';
|
||||||
|
|
||||||
export const selectState = state => state.app || {};
|
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 selectInterestedInYoutubeSync = createSelector(selectState, state => state.interestedInYoutubeSync);
|
||||||
|
|
||||||
export const selectSplashAnimationEnabled = createSelector(selectState, state => state.splashAnimationEnabled);
|
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 selectIsFetchingReacts = createSelector(selectState, state => state.isFetchingReacts);
|
||||||
|
|
||||||
export const selectCommentChannel = createSelector(selectState, state => state.commentChannel);
|
|
||||||
|
|
||||||
export const selectOthersReactsById = createSelector(selectState, state => state.othersReactsByCommentId);
|
export const selectOthersReactsById = createSelector(selectState, state => state.othersReactsByCommentId);
|
||||||
|
|
||||||
export const selectCommentsByClaimId = createSelector(selectState, selectCommentsById, (state, byId) => {
|
export const selectCommentsByClaimId = createSelector(selectState, selectCommentsById, (state, byId) => {
|
||||||
|
|
|
@ -264,10 +264,12 @@ $metadata-z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu__list.channel__list {
|
.menu__list.channel__list {
|
||||||
margin-top: var(--spacing-s);
|
margin-top: var(--spacing-xs);
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
max-height: 15rem;
|
||||||
|
overflow-y: scroll;
|
||||||
|
|
||||||
[role='menuitem'] {
|
[role='menuitem'] {
|
||||||
&[data-selected] {
|
&[data-selected] {
|
||||||
|
@ -314,8 +316,14 @@ $metadata-z-index: 1;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: var(--color-menu-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 {
|
&:hover {
|
||||||
|
@ -326,5 +334,20 @@ $metadata-z-index: 1;
|
||||||
.channel__list-item--selected {
|
.channel__list-item--selected {
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: var(--border-radius);
|
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);
|
font-size: var(--font-small);
|
||||||
color: var(--color-input-label);
|
color: var(--color-input-label);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: var(--spacing-xxs);
|
margin-bottom: 0.1rem;
|
||||||
|
|
||||||
.icon__lbc {
|
.icon__lbc {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
@ -361,11 +361,9 @@ fieldset-group {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-top-left-radius: var(--border-radius);
|
border-top-left-radius: var(--border-radius);
|
||||||
border-bottom-left-radius: var(--border-radius);
|
border-bottom-left-radius: var(--border-radius);
|
||||||
border-right: 0;
|
|
||||||
border-color: var(--color-input-border);
|
border-color: var(--color-input-border);
|
||||||
color: var(--color-text-help);
|
color: var(--color-text-help);
|
||||||
background-color: var(--color-input-bg);
|
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 {
|
.icon--help {
|
||||||
color: var(--color-subtitle);
|
color: var(--color-subtitle);
|
||||||
margin-left: var(--spacing-xs);
|
margin-left: var(--spacing-xs);
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-reach-menu] {
|
||||||
|
z-index: 10000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-reach-menu] {
|
[data-reach-menu] {
|
||||||
font-family: sans-serif;
|
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
|
@ -58,10 +58,10 @@ const appFilter = createFilter('app', [
|
||||||
'welcomeVersion',
|
'welcomeVersion',
|
||||||
'interestedInYoutubeSync',
|
'interestedInYoutubeSync',
|
||||||
'splashAnimationEnabled',
|
'splashAnimationEnabled',
|
||||||
|
'activeChannel',
|
||||||
]);
|
]);
|
||||||
// We only need to persist the receiveAddress for the wallet
|
// We only need to persist the receiveAddress for the wallet
|
||||||
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
||||||
const commentsFilter = createFilter('comments', ['commentChannel']);
|
|
||||||
const searchFilter = createFilter('search', ['options']);
|
const searchFilter = createFilter('search', ['options']);
|
||||||
const tagsFilter = createFilter('tags', ['followedTags']);
|
const tagsFilter = createFilter('tags', ['followedTags']);
|
||||||
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
||||||
|
@ -82,7 +82,6 @@ const whiteListedReducers = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const transforms = [
|
const transforms = [
|
||||||
commentsFilter,
|
|
||||||
fileInfoFilter,
|
fileInfoFilter,
|
||||||
walletFilter,
|
walletFilter,
|
||||||
blockedFilter,
|
blockedFilter,
|
||||||
|
|
Loading…
Add table
Reference in a new issue