refactor 'active' channel usage across the app
This commit is contained in:
parent
bce86ae8a3
commit
db87125dc8
53 changed files with 726 additions and 944 deletions
|
@ -740,7 +740,6 @@
|
||||||
"Repost": "Repost",
|
"Repost": "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 (
|
||||||
|
<div className="channel__selector">
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton className="">
|
<MenuButton>
|
||||||
<ChannelListItem uri={selectedChannelUrl} isSelected />
|
{(incognito && !hideAnon) || !activeChannelUrl ? (
|
||||||
|
<IncognitoSelector isSelected />
|
||||||
|
) : (
|
||||||
|
<ChannelListItem uri={activeChannelUrl} isSelected />
|
||||||
|
)}
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList className="menu__list channel__list">
|
<MenuList className="menu__list channel__list">
|
||||||
{channels &&
|
{channels &&
|
||||||
channels.map(channel => (
|
channels.map(channel => (
|
||||||
<MenuItem
|
<MenuItem key={channel.permanent_url} onSelect={() => handleChannelSelect(channel)}>
|
||||||
key={channel.canonical_url}
|
<ChannelListItem uri={channel.permanent_url} />
|
||||||
onSelect={() => {
|
|
||||||
if (selectedChannelUrl !== channel.canonical_url) {
|
|
||||||
onChannelSelect(channel.canonical_url);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ChannelListItem uri={channel.canonical_url} />
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
|
{!hideAnon && (
|
||||||
|
<MenuItem onSelect={() => doSetIncognito(true)}>
|
||||||
|
<IncognitoSelector />
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem onSelect={() => push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`)}>
|
||||||
|
<div className="channel__list-item">
|
||||||
|
<Icon sectionIcon icon={ICONS.CHANNEL} />
|
||||||
|
<h2 className="channel__list-text">Create a new channel</h2>
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</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);
|
|
||||||
|
|
||||||
return {
|
|
||||||
activeChannel: channel,
|
|
||||||
pending: props.authorUri && makeSelectClaimIsPending(props.authorUri)(state),
|
|
||||||
channel: props.authorUri && makeSelectClaimForUri(props.authorUri)(state),
|
|
||||||
isResolvingUri: props.authorUri && makeSelectIsUriResolving(props.authorUri)(state),
|
|
||||||
thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state),
|
thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state),
|
||||||
channelIsBlocked: props.authorUri && selectChannelIsBlocked(props.authorUri)(state),
|
channelIsBlocked: props.authorUri && selectChannelIsBlocked(props.authorUri)(state),
|
||||||
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
|
||||||
isFetchingComments: selectIsFetchingComments(state),
|
isFetchingComments: selectIsFetchingComments(state),
|
||||||
myChannels: selectMyChannelClaims(state),
|
myChannels: selectMyChannelClaims(state),
|
||||||
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
|
||||||
commentIdentityChannel: makeSelectMyChannelPermUrlForName(channel)(state),
|
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
|
||||||
contentChannel: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
|
activeChannelClaim: selectActiveChannelClaim(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,46 +60,22 @@ 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={
|
|
||||||
<React.Fragment>
|
|
||||||
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
|
||||||
<fieldset-section>
|
<fieldset-section>
|
||||||
<label>{__('Name')}</label>
|
<label>{__('Name')}</label>
|
||||||
<div className="form-field__prefix">{`lbry://${
|
<div className="form-field__prefix">{prefix}</div>
|
||||||
!channel || channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : `${channel}/`
|
|
||||||
}`}</div>
|
|
||||||
</fieldset-section>
|
</fieldset-section>
|
||||||
<FormField
|
<FormField
|
||||||
type="text"
|
type="text"
|
||||||
name="content_name"
|
name="content_name"
|
||||||
value={name}
|
value={name}
|
||||||
disabled={disabled}
|
|
||||||
error={nameError}
|
error={nameError}
|
||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
|
onBlur={() => setBlurred(true)}
|
||||||
/>
|
/>
|
||||||
</fieldset-group>
|
</fieldset-group>
|
||||||
<div className="form-field__help">
|
<div className="form-field__help">
|
||||||
|
@ -128,33 +86,7 @@ function PublishName(props: Props) {
|
||||||
onEditMyClaim={editExistingClaim}
|
onEditMyClaim={editExistingClaim}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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={
|
||||||
|
<>
|
||||||
|
{__('Winning amount: %amount%', {
|
||||||
amount: Number(takeoverAmount).toFixed(2),
|
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,115 +1,51 @@
|
||||||
// @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() {
|
|
||||||
const { channel, channels, fetchChannelListMine, fetchingChannels, emailVerified, onChannelChange } = this.props;
|
|
||||||
if (IS_WEB && !emailVerified) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((!channels || !channels.length) && !fetchingChannels) {
|
|
||||||
fetchChannelListMine();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channels && channels.length && !channels.find(chan => chan.name === channel)) {
|
|
||||||
const elem = document.getElementById(ID_FF_SELECT_CHANNEL);
|
|
||||||
// $FlowFixMe
|
|
||||||
if (elem && elem.value && elem.value !== channel) {
|
|
||||||
setTimeout(() => {
|
|
||||||
// $FlowFixMe
|
|
||||||
onChannelChange(elem.value);
|
|
||||||
}, 250);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
const { channels, fetchingChannels, hideAnon } = this.props;
|
|
||||||
if (!fetchingChannels && !channels && hideAnon) {
|
|
||||||
this.setState({ addingChannel: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChannelChange(event: SyntheticInputEvent<*>) {
|
|
||||||
const { onChannelChange } = this.props;
|
|
||||||
const channel = event.target.value;
|
|
||||||
|
|
||||||
if (channel === CHANNEL_NEW) {
|
|
||||||
this.setState({ addingChannel: true });
|
|
||||||
onChannelChange(channel);
|
|
||||||
} else {
|
|
||||||
this.setState({ addingChannel: false });
|
|
||||||
onChannelChange(channel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChangeToNewChannel(props: Object) {
|
|
||||||
const { onChannelChange } = this.props;
|
|
||||||
const { newChannelName } = props;
|
|
||||||
|
|
||||||
this.setState({ addingChannel: false });
|
|
||||||
|
|
||||||
const channelName = `@${newChannelName.trim()}`;
|
|
||||||
onChannelChange(channelName);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const channel = this.state.addingChannel ? CHANNEL_NEW : this.props.channel;
|
|
||||||
const { fetchingChannels, channels = [], hideAnon, hideNew, label, injected = [], tiny } = this.props;
|
|
||||||
const { addingChannel } = this.state;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
id={ID_FF_SELECT_CHANNEL}
|
|
||||||
name="channel"
|
name="channel"
|
||||||
label={!tiny && (label || __('Channel'))}
|
label={!tiny && (label || __('Channel'))}
|
||||||
labelOnLeft={tiny}
|
labelOnLeft={tiny}
|
||||||
type={tiny ? 'select-tiny' : 'select'}
|
type={tiny ? 'select-tiny' : 'select'}
|
||||||
onChange={this.handleChannelChange}
|
onChange={handleChannelChange}
|
||||||
value={channel}
|
value={activeChannelClaim && activeChannelClaim.claim_id}
|
||||||
|
disabled={fetchingChannels}
|
||||||
>
|
>
|
||||||
{!hideAnon && <option value={CHANNEL_ANONYMOUS}>{__('Anonymous')}</option>}
|
{fetchingChannels ? (
|
||||||
{channels &&
|
<option>{__('Loading your channels...')}</option>
|
||||||
channels.map(({ name, claim_id: claimId }) => (
|
) : (
|
||||||
<option key={claimId} value={name}>
|
<>
|
||||||
|
{myChannelClaims &&
|
||||||
|
myChannelClaims.map(({ name, claim_id: claimId }) => (
|
||||||
|
<option key={claimId} value={claimId}>
|
||||||
{name}
|
{name}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
|
@ -119,13 +55,11 @@ class ChannelSelection extends React.PureComponent<Props, State> {
|
||||||
{item}
|
{item}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
{!fetchingChannels && !hideNew && <option value={CHANNEL_NEW}>{__('New channel...')}</option>}
|
</>
|
||||||
|
)}
|
||||||
</FormField>
|
</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…
Reference in a new issue