refactor 'active' channel usage across the app

This commit is contained in:
Sean Yesmunt 2021-02-09 11:05:56 -05:00
parent bce86ae8a3
commit db87125dc8
53 changed files with 726 additions and 944 deletions

View file

@ -740,7 +740,6 @@
"Repost": "Repost",
"Repost your favorite claims to help more people discover them!": "Repost your favorite claims to help more people discover them!",
"Repost %title%": "Repost %title%",
"Channel to repost on": "Channel to repost on",
"Advanced": "Advanced",
"community name": "community name",
"Change this to repost to a different %lbry_naming_link%.": "Change this to repost to a different %lbry_naming_link%.",

View file

@ -5,17 +5,28 @@ import { selectGetSyncErrorMessage, selectSyncFatalError } from 'redux/selectors
import { doFetchAccessToken, doUserSetReferrer } from 'redux/actions/user';
import { selectUser, selectAccessToken, selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectUnclaimedRewards } from 'redux/selectors/rewards';
import { doFetchChannelListMine, SETTINGS } from 'lbry-redux';
import { doFetchChannelListMine, selectMyChannelUrls, SETTINGS } from 'lbry-redux';
import {
makeSelectClientSetting,
selectLanguage,
selectLoadedLanguages,
selectThemePath,
} from 'redux/selectors/settings';
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded, selectModal } from 'redux/selectors/app';
import {
selectIsUpgradeAvailable,
selectAutoUpdateDownloaded,
selectModal,
selectActiveChannelId,
} from 'redux/selectors/app';
import { doGetWalletSyncPreference, doSetLanguage } from 'redux/actions/settings';
import { doSyncLoop } from 'redux/actions/sync';
import { doDownloadUpgradeRequested, doSignIn, doGetAndPopulatePreferences } from 'redux/actions/app';
import {
doDownloadUpgradeRequested,
doSignIn,
doGetAndPopulatePreferences,
doSetActiveChannel,
doSetIncognito,
} from 'redux/actions/app';
import App from './view';
const select = state => ({
@ -33,6 +44,8 @@ const select = state => ({
isAuthenticated: selectUserVerifiedEmail(state),
currentModal: selectModal(state),
syncFatalError: selectSyncFatalError(state),
activeChannelId: selectActiveChannelId(state),
myChannelUrls: selectMyChannelUrls(state),
});
const perform = dispatch => ({
@ -45,6 +58,8 @@ const perform = dispatch => ({
getWalletSyncPref: () => dispatch(doGetWalletSyncPreference()),
syncLoop: noInterval => dispatch(doSyncLoop(noInterval)),
setReferrer: (referrer, doClaim) => dispatch(doUserSetReferrer(referrer, doClaim)),
setActiveChannelIfNotSet: () => dispatch(doSetActiveChannel()),
setIncognito: () => dispatch(doSetIncognito()),
});
export default hot(connect(select, perform)(App));

View file

@ -80,6 +80,10 @@ type Props = {
syncEnabled: boolean,
currentModal: any,
syncFatalError: boolean,
activeChannelId: ?string,
myChannelUrls: ?Array<string>,
setActiveChannelIfNotSet: (?string) => void,
setIncognito: boolean => void,
};
function App(props: Props) {
@ -106,6 +110,10 @@ function App(props: Props) {
syncLoop,
currentModal,
syncFatalError,
activeChannelId,
myChannelUrls,
setActiveChannelIfNotSet,
setIncognito,
} = props;
const appRef = useRef();
@ -133,7 +141,8 @@ function App(props: Props) {
const shouldHideNag = pathname.startsWith(`/$/${PAGES.EMBED}`) || pathname.startsWith(`/$/${PAGES.AUTH_VERIFY}`);
const userId = user && user.id;
const useCustomScrollbar = !IS_MAC;
const hasMyChannels = myChannelUrls && myChannelUrls.length > 0;
const hasNoChannels = myChannelUrls && myChannelUrls.length === 0;
const shouldMigrateLanguage = LANGUAGE_MIGRATIONS[language];
let uri;
@ -228,6 +237,14 @@ function App(props: Props) {
document.documentElement.setAttribute('theme', theme);
}, [theme]);
useEffect(() => {
if (hasMyChannels && !activeChannelId) {
setActiveChannelIfNotSet();
} else if (hasNoChannels) {
setIncognito(true);
}
}, [hasMyChannels, activeChannelId, setActiveChannelIfNotSet]);
useEffect(() => {
if (!languages.includes(language)) {
setLanguage(language);

View file

@ -1,9 +1,16 @@
import { connect } from 'react-redux';
import SelectChannel from './view';
import { selectMyChannelClaims } from 'lbry-redux';
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
import { doSetActiveChannel, doSetIncognito } from 'redux/actions/app';
import SelectChannel from './view';
const select = state => ({
channels: selectMyChannelClaims(state),
activeChannelClaim: selectActiveChannelClaim(state),
incognito: selectIncognito(state),
});
export default connect(select)(SelectChannel);
export default connect(select, {
doSetActiveChannel,
doSetIncognito,
})(SelectChannel);

View file

@ -1,16 +1,23 @@
// @flow
import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import classnames from 'classnames';
import React from 'react';
import ChannelThumbnail from 'component/channelThumbnail';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
import ChannelTitle from 'component/channelTitle';
import Icon from 'component/common/icon';
import { useHistory } from 'react-router';
type Props = {
selectedChannelUrl: string, // currently selected channel
channels: ?Array<ChannelClaim>,
onChannelSelect: (url: string) => void,
hideAnon?: boolean,
activeChannelClaim: ?ChannelClaim,
doSetActiveChannel: string => void,
incognito: boolean,
doSetIncognito: boolean => void,
};
type ListItemProps = {
@ -30,34 +37,64 @@ function ChannelListItem(props: ListItemProps) {
);
}
function ChannelSelector(props: Props) {
const { channels, selectedChannelUrl, onChannelSelect } = props;
type IncognitoSelectorProps = {
isSelected?: boolean,
};
if (!channels || !selectedChannelUrl) {
return null;
function IncognitoSelector(props: IncognitoSelectorProps) {
return (
<div className={classnames('channel__list-item', { 'channel__list-item--selected': props.isSelected })}>
<Icon sectionIcon icon={ICONS.ANONYMOUS} />
<h2 className="channel__list-text">{__('Anonymous')}</h2>
{props.isSelected && <Icon icon={ICONS.DOWN} />}
</div>
);
}
function ChannelSelector(props: Props) {
const { channels, activeChannelClaim, doSetActiveChannel, hideAnon = false, incognito, doSetIncognito } = props;
const {
push,
location: { pathname },
} = useHistory();
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
function handleChannelSelect(channelClaim) {
doSetIncognito(false);
doSetActiveChannel(channelClaim.claim_id);
}
return (
<div className="channel__selector">
<Menu>
<MenuButton className="">
<ChannelListItem uri={selectedChannelUrl} isSelected />
<MenuButton>
{(incognito && !hideAnon) || !activeChannelUrl ? (
<IncognitoSelector isSelected />
) : (
<ChannelListItem uri={activeChannelUrl} isSelected />
)}
</MenuButton>
<MenuList className="menu__list channel__list">
{channels &&
channels.map(channel => (
<MenuItem
key={channel.canonical_url}
onSelect={() => {
if (selectedChannelUrl !== channel.canonical_url) {
onChannelSelect(channel.canonical_url);
}
}}
>
<ChannelListItem uri={channel.canonical_url} />
<MenuItem key={channel.permanent_url} onSelect={() => handleChannelSelect(channel)}>
<ChannelListItem uri={channel.permanent_url} />
</MenuItem>
))}
{!hideAnon && (
<MenuItem onSelect={() => doSetIncognito(true)}>
<IncognitoSelector />
</MenuItem>
)}
<MenuItem onSelect={() => push(`/$/${PAGES.CHANNEL_NEW}?redirect=${pathname}`)}>
<div className="channel__list-item">
<Icon sectionIcon icon={ICONS.CHANNEL} />
<h2 className="channel__list-text">Create a new channel</h2>
</div>
</MenuItem>
</MenuList>
</Menu>
</div>
);
}

View file

@ -1,49 +1,28 @@
import { connect } from 'react-redux';
import {
doResolveUri,
makeSelectClaimIsPending,
makeSelectClaimForUri,
makeSelectThumbnailForUri,
makeSelectIsUriResolving,
selectMyChannelClaims,
makeSelectMyChannelPermUrlForName,
makeSelectChannelPermUrlForClaimUri,
} from 'lbry-redux';
import { makeSelectThumbnailForUri, selectMyChannelClaims, makeSelectChannelPermUrlForClaimUri } from 'lbry-redux';
import { doCommentAbandon, doCommentUpdate, doCommentPin, doCommentList } from 'redux/actions/comments';
import { doToggleBlockChannel } from 'redux/actions/blocked';
import { selectChannelIsBlocked } from 'redux/selectors/blocked';
import { doToast } from 'redux/actions/notifications';
import { doSetPlayingUri } from 'redux/actions/content';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import {
selectIsFetchingComments,
makeSelectOthersReactionsForComment,
selectCommentChannel,
} from 'redux/selectors/comments';
import { selectIsFetchingComments, makeSelectOthersReactionsForComment } from 'redux/selectors/comments';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import Comment from './view';
const select = (state, props) => {
const channel = selectCommentChannel(state);
return {
activeChannel: channel,
pending: props.authorUri && makeSelectClaimIsPending(props.authorUri)(state),
channel: props.authorUri && makeSelectClaimForUri(props.authorUri)(state),
isResolvingUri: props.authorUri && makeSelectIsUriResolving(props.authorUri)(state),
const select = (state, props) => ({
thumbnail: props.authorUri && makeSelectThumbnailForUri(props.authorUri)(state),
channelIsBlocked: props.authorUri && selectChannelIsBlocked(props.authorUri)(state),
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
isFetchingComments: selectIsFetchingComments(state),
myChannels: selectMyChannelClaims(state),
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
commentIdentityChannel: makeSelectMyChannelPermUrlForName(channel)(state),
contentChannel: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
};
};
contentChannelPermanentUrl: makeSelectChannelPermUrlForClaimUri(props.uri)(state),
activeChannelClaim: selectActiveChannelClaim(state),
});
const perform = dispatch => ({
closeInlinePlayer: () => dispatch(doSetPlayingUri({ uri: null })),
resolveUri: uri => dispatch(doResolveUri(uri)),
updateComment: (commentId, comment) => dispatch(doCommentUpdate(commentId, comment)),
deleteComment: commentId => dispatch(doCommentAbandon(commentId)),
blockChannel: channelUri => dispatch(doToggleBlockChannel(channelUri)),

View file

@ -5,7 +5,6 @@ import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
import { SITE_NAME, SIMPLE_SITE, ENABLE_COMMENT_REACTIONS } from 'config';
import React, { useEffect, useState } from 'react';
import { parseURI } from 'lbry-redux';
import { isEmpty } from 'util/object';
import DateTime from 'component/dateTime';
import Button from 'component/button';
import Expandable from 'component/expandable';
@ -29,10 +28,6 @@ type Props = {
commentId: string, // sha256 digest identifying the comment
message: string, // comment body
timePosted: number, // Comment timestamp
channel: ?Claim, // Channel Claim, retrieved to obtain thumbnail
pending?: boolean,
resolveUri: string => void, // resolves the URI
isResolvingUri: boolean, // if the URI is currently being resolved
channelIsBlocked: boolean, // if the channel is blacklisted in the app
claimIsMine: boolean, // if you control the claim which this comment was posted on
commentIsMine: boolean, // if this comment was signed by an owned channel
@ -53,7 +48,8 @@ type Props = {
pinComment: (string, boolean) => Promise<any>,
fetchComments: string => void,
commentIdentityChannel: any,
contentChannel: any,
contentChannelPermanentUrl: any,
activeChannelClaim: ?ChannelClaim,
};
const LENGTH_TO_COLLAPSE = 300;
@ -67,10 +63,6 @@ function Comment(props: Props) {
authorUri,
timePosted,
message,
pending,
channel,
isResolvingUri,
resolveUri,
channelIsBlocked,
commentIsMine,
commentId,
@ -87,8 +79,8 @@ function Comment(props: Props) {
pinComment,
fetchComments,
othersReacts,
commentIdentityChannel,
contentChannel,
contentChannelPermanentUrl,
activeChannelClaim,
} = props;
const {
push,
@ -108,10 +100,7 @@ function Comment(props: Props) {
const dislikesCount = (othersReacts && othersReacts.dislike) || 0;
const totalLikesAndDislikes = likesCount + dislikesCount;
const slimedToDeath = totalLikesAndDislikes >= 5 && dislikesCount / totalLikesAndDislikes > 0.8;
// to debounce subsequent requests
const shouldFetch =
channel === undefined ||
(channel !== null && channel.value_type === 'channel' && isEmpty(channel.meta) && !pending);
let channelOwnerOfContent;
try {
const { channelName } = parseURI(uri);
@ -121,11 +110,6 @@ function Comment(props: Props) {
} catch (e) {}
useEffect(() => {
// If author was extracted from the URI, then it must be valid.
if (authorUri && author && !isResolvingUri && shouldFetch) {
resolveUri(authorUri);
}
if (isEditing) {
setCharCount(editedMessage.length);
@ -143,7 +127,7 @@ function Comment(props: Props) {
window.removeEventListener('keydown', handleEscape);
};
}
}, [isResolvingUri, shouldFetch, author, authorUri, resolveUri, editedMessage, isEditing, setEditing]);
}, [author, authorUri, editedMessage, isEditing, setEditing]);
function handleEditMessageChanged(event) {
setCommentValue(!SIMPLE_SITE && advancedEditor ? event : event.target.value);
@ -262,7 +246,7 @@ function Comment(props: Props) {
{__('Block Channel')}
</MenuItem>
)}
{commentIdentityChannel === contentChannel && isTopLevel && (
{activeChannelClaim && activeChannelClaim.permanent_url === contentChannelPermanentUrl && isTopLevel && (
<MenuItem
className="comment__menu-option menu__link"
onSelect={

View file

@ -1,9 +1,10 @@
import { connect } from 'react-redux';
import { makeSelectClaimForUri, selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux';
import { selectIsPostingComment, selectCommentChannel } from 'redux/selectors/comments';
import { doOpenModal } from 'redux/actions/app';
import { doCommentCreate, doSetCommentChannel } from 'redux/actions/comments';
import { selectIsPostingComment } from 'redux/selectors/comments';
import { doOpenModal, doSetActiveChannel } from 'redux/actions/app';
import { doCommentCreate } from 'redux/actions/comments';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import { CommentCreate } from './view';
const select = (state, props) => ({
@ -12,14 +13,13 @@ const select = (state, props) => ({
channels: selectMyChannelClaims(state),
isFetchingChannels: selectFetchingMyChannels(state),
isPostingComment: selectIsPostingComment(state),
activeChannel: selectCommentChannel(state),
activeChannelClaim: selectActiveChannelClaim(state),
});
const perform = (dispatch, ownProps) => ({
createComment: (comment, claimId, channel, parentId) =>
dispatch(doCommentCreate(comment, claimId, channel, parentId, ownProps.uri)),
createComment: (comment, claimId, parentId) => dispatch(doCommentCreate(comment, claimId, parentId, ownProps.uri)),
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
setCommentChannel: name => dispatch(doSetCommentChannel(name)),
setActiveChannel: claimId => dispatch(doSetActiveChannel(claimId)),
});
export default connect(select, perform)(CommentCreate);

View file

@ -1,12 +1,11 @@
// @flow
import { SIMPLE_SITE } from 'config';
import * as PAGES from 'constants/pages';
import { CHANNEL_NEW } from 'constants/claim';
import React, { useEffect, useState } from 'react';
import classnames from 'classnames';
import { FormField, Form } from 'component/common/form';
import Button from 'component/button';
import ChannelSelection from 'component/selectChannel';
import SelectChannel from 'component/selectChannel';
import usePersistedState from 'effects/use-persisted-state';
import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
import { useHistory } from 'react-router';
@ -25,7 +24,7 @@ type Props = {
isReply: boolean,
isPostingComment: boolean,
activeChannel: string,
setCommentChannel: string => void,
activeChannelClaim: ?ChannelClaim,
};
export function CommentCreate(props: Props) {
@ -40,8 +39,7 @@ export function CommentCreate(props: Props) {
isReply,
parentId,
isPostingComment,
activeChannel,
setCommentChannel,
activeChannelClaim,
} = props;
const buttonref: ElementRef<any> = React.useRef();
const { push } = useHistory();
@ -50,21 +48,7 @@ export function CommentCreate(props: Props) {
const [charCount, setCharCount] = useState(commentValue.length);
const [advancedEditor, setAdvancedEditor] = usePersistedState('comment-editor-mode', false);
const hasChannels = channels && channels.length;
const disabled = isPostingComment || activeChannel === CHANNEL_NEW || !commentValue.length;
const topChannel =
channels &&
channels.reduce((top, channel) => {
const topClaimCount = (top && top.meta && top.meta.claims_in_channel) || 0;
const currentClaimCount = (activeChannel && channel.meta && channel.meta.claims_in_channel) || 0;
return topClaimCount >= currentClaimCount ? top : channel;
});
useEffect(() => {
// set default channel
if ((activeChannel === '' || activeChannel === 'anonymous') && topChannel) {
setCommentChannel(topChannel.name);
}
}, [activeChannel, topChannel, setCommentChannel]);
const disabled = isPostingComment || !activeChannelClaim || !commentValue.length;
function handleCommentChange(event) {
let commentValue;
@ -94,8 +78,8 @@ export function CommentCreate(props: Props) {
}
function handleSubmit() {
if (activeChannel !== CHANNEL_NEW && commentValue.length) {
createComment(commentValue, claimId, activeChannel, parentId).then(res => {
if (activeChannelClaim && commentValue.length) {
createComment(commentValue, claimId, parentId).then(res => {
if (res && res.signature) {
setCommentValue('');
@ -138,13 +122,13 @@ export function CommentCreate(props: Props) {
})}
>
<FormField
disabled={activeChannel === CHANNEL_NEW}
disabled={!activeChannelClaim}
type={SIMPLE_SITE ? 'textarea' : advancedEditor && !isReply ? 'markdown' : 'textarea'}
name={isReply ? 'content_reply' : 'content_description'}
label={
<span className="comment-new__label-wrapper">
<div className="comment-new__label">{isReply ? __('Replying as') + ' ' : __('Comment as') + ' '}</div>
<ChannelSelection channel={activeChannel} hideAnon tiny hideNew onChannelChange={setCommentChannel} />
<SelectChannel tiny />
</span>
}
quickActionLabel={

View file

@ -2,19 +2,16 @@ import { connect } from 'react-redux';
import Comment from './view';
import { makeSelectClaimIsMine, makeSelectClaimForUri } from 'lbry-redux';
import { doToast } from 'redux/actions/notifications';
import {
makeSelectMyReactionsForComment,
makeSelectOthersReactionsForComment,
selectCommentChannel,
} from 'redux/selectors/comments';
import { makeSelectMyReactionsForComment, makeSelectOthersReactionsForComment } from 'redux/selectors/comments';
import { doCommentReact } from 'redux/actions/comments';
import { selectActiveChannelId } from 'redux/selectors/app';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
myReacts: makeSelectMyReactionsForComment(props.commentId)(state),
othersReacts: makeSelectOthersReactionsForComment(props.commentId)(state),
activeChannel: selectCommentChannel(state),
activeChannelId: selectActiveChannelId(state),
});
const perform = dispatch => ({

View file

@ -16,13 +16,13 @@ type Props = {
commentId: string,
pendingCommentReacts: Array<string>,
claimIsMine: boolean,
activeChannel: string,
activeChannelId: ?string,
claim: ?ChannelClaim,
doToast: ({ message: string }) => void,
};
export default function CommentReactions(props: Props) {
const { myReacts, othersReacts, commentId, react, claimIsMine, claim, activeChannel, doToast } = props;
const { myReacts, othersReacts, commentId, react, claimIsMine, claim, activeChannelId, doToast } = props;
const {
push,
location: { pathname },
@ -31,8 +31,8 @@ export default function CommentReactions(props: Props) {
claim &&
claimIsMine &&
(claim.value_type === 'channel'
? claim.name === activeChannel
: claim.signing_channel && claim.signing_channel.name === activeChannel);
? claim.claim_id === activeChannelId
: claim.signing_channel && claim.signing_channel.claim_id === activeChannelId);
const authorUri =
claim && claim.value_type === 'channel'
? claim.canonical_url
@ -52,7 +52,7 @@ export default function CommentReactions(props: Props) {
const creatorLiked = getCountForReact(REACTION_TYPES.CREATOR_LIKE) > 0;
function handleCommentLike() {
if (activeChannel) {
if (activeChannelId) {
react(commentId, REACTION_TYPES.LIKE);
} else {
promptForChannel();
@ -60,7 +60,7 @@ export default function CommentReactions(props: Props) {
}
function handleCommentDislike() {
if (activeChannel) {
if (activeChannelId) {
react(commentId, REACTION_TYPES.DISLIKE);
} else {
promptForChannel();

View file

@ -5,10 +5,10 @@ import {
selectIsFetchingComments,
makeSelectTotalCommentsCountForUri,
selectOthersReactsById,
selectCommentChannel,
} from 'redux/selectors/comments';
import { doCommentList, doCommentReactList } from 'redux/actions/comments';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectActiveChannelId } from 'redux/selectors/app';
import CommentsList from './view';
const select = (state, props) => ({
@ -20,7 +20,7 @@ const select = (state, props) => ({
commentingEnabled: IS_WEB ? Boolean(selectUserVerifiedEmail(state)) : true,
fetchingChannels: selectFetchingMyChannels(state),
reactionsById: selectOthersReactsById(state),
activeChannel: selectCommentChannel(state),
activeChannelId: selectActiveChannelId(state),
});
const perform = dispatch => ({

View file

@ -26,7 +26,7 @@ type Props = {
totalComments: number,
fetchingChannels: boolean,
reactionsById: ?{ [string]: { [REACTION_TYPES.LIKE | REACTION_TYPES.DISLIKE]: number } },
activeChannel: string,
activeChannelId: ?string,
};
function CommentList(props: Props) {
@ -42,7 +42,7 @@ function CommentList(props: Props) {
totalComments,
fetchingChannels,
reactionsById,
activeChannel,
activeChannelId,
} = props;
const commentRef = React.useRef();
const spinnerRef = React.useRef();
@ -90,7 +90,7 @@ function CommentList(props: Props) {
})
.catch(() => setReadyToDisplayComments(true));
}
}, [fetchReacts, uri, totalComments, activeChannel, fetchingChannels]);
}, [fetchReacts, uri, totalComments, activeChannelId, fetchingChannels]);
useEffect(() => {
if (readyToDisplayComments && linkedCommentId && commentRef && commentRef.current) {

View file

@ -1015,4 +1015,9 @@ export const icons = {
<path d="m 18.089706,2.6673324 c -0.458672,0 -0.914415,0.081053 -1.342833,0.2381801 -0.726068,-1.5175206 -2.625165,-2.67785413 -4.515474,-2.67785413 -1.902023,0 -3.8128297,1.16033353 -4.5408481,2.67785413 C 7.2621303,2.7483855 6.8063878,2.6673324 6.348691,2.6673324 c -2.1528256,0 -3.9045598,1.7507491 -3.9045598,3.9035835 0,2.0230385 1.4648199,3.6410591 3.4146614,3.8752841 v 8.262918 h 2.9276892 v -3.415632 c 0.00968,-0.26944 0.2273915,-0.487951 0.4977084,-0.487951 0.2693563,0 0.4879486,0.218539 0.4879486,0.487951 v 3.415632 h 1.9420352 v -4.391535 c 0,-0.269439 0.217626,-0.487951 0.487948,-0.487951 0.269357,0 0.487946,0.218539 0.487946,0.487951 v 4.391535 h 1.951795 v -3.415632 c 0.01964,-0.26944 0.238125,-0.487951 0.507465,-0.487951 0.270325,0 0.487949,0.218539 0.468432,0.487951 v 3.415632 h 2.927689 V 10.4462 c 1.980095,-0.234307 3.445891,-1.8522456 3.445891,-3.8752841 0,-2.1528344 -1.750758,-3.9035835 -3.901634,-3.9035835" />
</svg>
),
[ICONS.ANONYMOUS]: buildIcon(
<g>
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
</g>
),
};

View file

@ -1,13 +1,9 @@
import { connect } from 'react-redux';
import { makeSelectClaimForUri, doPrepareEdit } from 'lbry-redux';
import { makeSelectClaimForUri } from 'lbry-redux';
import CreatorAnalytics from './view';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
});
const perform = dispatch => ({
prepareEdit: channelName => dispatch(doPrepareEdit({ signing_channel: { name: channelName } })),
});
export default connect(select, perform)(CreatorAnalytics);
export default connect(select)(CreatorAnalytics);

View file

@ -26,7 +26,7 @@ export default function CreatorAnalytics(props: Props) {
const history = useHistory();
const [stats, setStats] = React.useState();
const [error, setError] = React.useState();
const [fetchingStats, setFetchingStats] = React.useState(true);
const [fetchingStats, setFetchingStats] = React.useState(false);
const claimId = claim && claim.claim_id;
const channelHasClaims = claim && claim.meta && claim.meta.claims_in_channel && claim.meta.claims_in_channel > 0;
@ -81,7 +81,24 @@ export default function CreatorAnalytics(props: Props) {
/>
)}
{!error && (
{!error && !channelHasClaims ? (
<Yrbl
type="sad"
title={__("You haven't uploaded anything")}
subtitle={__('Upload something to start tracking your stats!')}
actions={
<div className="section__actions">
<Button
button="primary"
label={__('Upload Something')}
onClick={() => {
history.push(`/$/${PAGES.UPLOAD}`);
}}
/>
</div>
}
/>
) : (
<Yrbl
title={
channelHasClaims
@ -93,12 +110,7 @@ export default function CreatorAnalytics(props: Props) {
<Button
button="primary"
label={__('Upload Something')}
onClick={() => {
if (claim) {
prepareEdit(claim.name);
history.push(`/$/${PAGES.UPLOAD}`);
}
}}
onClick={() => history.push(`/$/${PAGES.UPLOAD}`)}
/>
</div>
}

View file

@ -1,15 +1,14 @@
import * as MODALS from 'constants/modal_types';
import { connect } from 'react-redux';
import { selectTotalBalance, selectBalance, formatCredits, selectMyChannelClaims, SETTINGS } from 'lbry-redux';
import { selectTotalBalance, selectBalance, formatCredits, SETTINGS } from 'lbry-redux';
import { selectGetSyncErrorMessage } from 'redux/selectors/sync';
import { selectUserVerifiedEmail, selectUserEmail, selectEmailToVerify, selectUser } from 'redux/selectors/user';
import { doClearEmailEntry, doClearPasswordEntry } from 'redux/actions/user';
import { doSetClientSetting } from 'redux/actions/settings';
import { doSignOut, doOpenModal } from 'redux/actions/app';
import { makeSelectClientSetting, selectLanguage } from 'redux/selectors/settings';
import { selectCommentChannel } from 'redux/selectors/comments';
import { selectHasNavigated, selectActiveChannelClaim } from 'redux/selectors/app';
import Header from './view';
import { selectHasNavigated } from 'redux/selectors/app';
const select = state => ({
language: selectLanguage(state),
@ -25,8 +24,7 @@ const select = state => ({
emailToVerify: selectEmailToVerify(state),
hasNavigated: selectHasNavigated(state),
user: selectUser(state),
myChannels: selectMyChannelClaims(state),
commentChannel: selectCommentChannel(state),
activeChannelClaim: selectActiveChannelClaim(state),
});
const perform = dispatch => ({

View file

@ -61,8 +61,7 @@ type Props = {
setSidebarOpen: boolean => void,
isAbsoluteSideNavHidden: boolean,
hideCancel: boolean,
myChannels: ?Array<ChannelClaim>,
commentChannel: string,
activeChannelClaim: ?ChannelClaim,
};
const Header = (props: Props) => {
@ -90,8 +89,7 @@ const Header = (props: Props) => {
isAbsoluteSideNavHidden,
user,
hideCancel,
myChannels,
commentChannel,
activeChannelClaim,
} = props;
const isMobile = useIsMobile();
// on the verify page don't let anyone escape other than by closing the tab to keep session data consistent
@ -102,18 +100,8 @@ const Header = (props: Props) => {
const hasBackout = Boolean(backout);
const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {};
const notificationsEnabled = (user && user.experimental_ui) || false;
let channelUrl;
let identityChannel;
if (myChannels && myChannels.length >= 1) {
if (myChannels.length === 1) {
identityChannel = myChannels[0];
} else if (commentChannel) {
identityChannel = myChannels.find(chan => chan.name === commentChannel);
} else {
identityChannel = myChannels[0];
}
channelUrl = identityChannel && (identityChannel.permanent_url || identityChannel.canonical_url);
}
const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url;
// Sign out if they click the "x" when they are on the password prompt
const authHeaderAction = syncError ? { onClick: signOut } : { navigate: '/' };
const homeButtonNavigationProps = isVerifyPage ? {} : authHeader ? authHeaderAction : { navigate: '/' };
@ -288,7 +276,7 @@ const Header = (props: Props) => {
history={history}
handleThemeToggle={handleThemeToggle}
currentTheme={currentTheme}
channelUrl={channelUrl}
activeChannelUrl={activeChannelUrl}
openSignOutModal={openSignOutModal}
email={email}
signOut={signOut}
@ -341,7 +329,7 @@ type HeaderMenuButtonProps = {
history: { push: string => void },
handleThemeToggle: string => void,
currentTheme: string,
channelUrl: ?string,
activeChannelUrl: ?string,
openSignOutModal: () => void,
email: ?string,
signOut: () => void,
@ -354,7 +342,7 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
history,
handleThemeToggle,
currentTheme,
channelUrl,
activeChannelUrl,
openSignOutModal,
email,
signOut,
@ -427,8 +415,8 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
aria-label={__('Your account')}
title={__('Your account')}
className={classnames('header__navigation-item mobile-hidden', {
'menu__title header__navigation-item--icon': !channelUrl,
'header__navigation-item--profile-pic': channelUrl,
'menu__title header__navigation-item--icon': !activeChannelUrl,
'header__navigation-item--profile-pic': activeChannelUrl,
})}
// @if TARGET='app'
onDoubleClick={e => {
@ -436,7 +424,11 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) {
}}
// @endif
>
{channelUrl ? <ChannelThumbnail uri={channelUrl} /> : <Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />}
{activeChannelUrl ? (
<ChannelThumbnail uri={activeChannelUrl} />
) : (
<Icon size={18} icon={ICONS.ACCOUNT} aria-hidden />
)}
</MenuButton>
<MenuList className="menu__list--header">
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.UPLOADS}`)}>

View file

@ -5,7 +5,6 @@ import Button from 'component/button';
import { Form, FormField } from 'component/common/form';
import CopyableText from 'component/copyableText';
import Card from 'component/common/card';
import SelectChannel from 'component/selectChannel';
import analytics from 'analytics';
import I18nMessage from 'component/i18nMessage';
import LbcSymbol from 'component/common/lbc-symbol';
@ -21,7 +20,6 @@ type Props = {
function InviteNew(props: Props) {
const { inviteNew, errorMessage, isPending, referralCode = '', channels } = props;
const noChannels = !channels || !(channels.length > 0);
// Email
const [email, setEmail] = useState('');
@ -87,14 +85,20 @@ function InviteNew(props: Props) {
actions={
<React.Fragment>
<CopyableText label={__('Your invite link')} copyable={referral} />
{!noChannels && (
<SelectChannel
channel={referralSource}
onChannelChange={channel => handleReferralChange(channel)}
{channels && channels.length > 0 && (
<FormField
type="select"
label={__('Customize link')}
hideAnon
injected={[referralCode]}
/>
value={referralSource}
onChange={e => handleReferralChange(e.target.value)}
>
{channels.map(channel => (
<option key={channel.claim_id} value={channel.name}>
{channel.name}
</option>
))}
<option value={referralCode}>{referralCode}</option>
</FormField>
)}
</React.Fragment>
}

View 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;

View 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);

View 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;

View file

@ -13,6 +13,7 @@ import Spinner from 'component/spinner';
import I18nMessage from 'component/i18nMessage';
import usePersistedState from 'effects/use-persisted-state';
import * as PUBLISH_MODES from 'constants/publish_types';
import PublishName from 'component/publishName';
type Props = {
uri: ?string,
@ -34,10 +35,8 @@ type Props = {
size: number,
duration: number,
isVid: boolean,
autoPopulateName: boolean,
setPublishMode: string => void,
setPrevFileText: string => void,
setAutoPopulateName: boolean => void,
header: Node,
};
@ -63,8 +62,6 @@ function PublishFile(props: Props) {
isVid,
setPublishMode,
setPrevFileText,
autoPopulateName,
setAutoPopulateName,
header,
} = props;
@ -231,10 +228,6 @@ function PublishFile(props: Props) {
const title = event.target.value;
// Update title
updatePublishForm({ title });
// Auto populate name from title
if (autoPopulateName) {
updatePublishForm({ name: parseName(title) });
}
}
function handleFileReaderLoaded(event: ProgressEvent) {
@ -327,11 +320,6 @@ function PublishFile(props: Props) {
publishFormParams.name = parseName(fileName);
}
// Prevent name autopopulation from title
if (autoPopulateName) {
setAutoPopulateName(false);
}
// File path is not supported on web for security reasons so we use the name instead.
setCurrentFile(file.path || file.name);
updatePublishForm(publishFormParams);
@ -357,6 +345,7 @@ function PublishFile(props: Props) {
subtitle={isStillEditing && __('You are currently editing your upload.')}
actions={
<React.Fragment>
<PublishName />
<FormField
type="text"
name="content_title"

View file

@ -15,7 +15,7 @@ import {
} from 'lbry-redux';
import { doPublishDesktop } from 'redux/actions/publish';
import { selectUnclaimedRewardValue } from 'redux/selectors/rewards';
import { selectModal } from 'redux/selectors/app';
import { selectModal, selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import PublishPage from './view';
@ -32,6 +32,8 @@ const select = state => ({
totalRewardValue: selectUnclaimedRewardValue(state),
modal: selectModal(state),
enablePublishPreview: makeSelectClientSetting(SETTINGS.ENABLE_PUBLISH_PREVIEW)(state),
activeChannelClaim: selectActiveChannelClaim(state),
incognito: selectIncognito(state),
});
const perform = dispatch => ({

View file

@ -9,17 +9,16 @@
*/
import { SITE_NAME } from 'config';
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { buildURI, isURIValid, isNameValid, THUMBNAIL_STATUSES } from 'lbry-redux';
import Button from 'component/button';
import SelectChannel from 'component/selectChannel';
import ChannelSelect from 'component/channelSelector';
import classnames from 'classnames';
import TagsSelect from 'component/tagsSelect';
import PublishDescription from 'component/publishDescription';
import PublishPrice from 'component/publishPrice';
import PublishFile from 'component/publishFile';
import PublishName from 'component/publishName';
import PublishBid from 'component/publishBid';
import PublishAdditionalOptions from 'component/publishAdditionalOptions';
import PublishFormErrors from 'component/publishFormErrors';
import SelectThumbnail from 'component/selectThumbnail';
@ -60,7 +59,6 @@ type Props = {
amount: string,
currency: string,
},
channel: string,
name: ?string,
nameError: ?string,
isResolvingUri: boolean,
@ -85,6 +83,8 @@ type Props = {
ytSignupPending: boolean,
modal: { id: string, modalProps: {} },
enablePublishPreview: boolean,
activeChannelClaim: ?ChannelClaim,
incognito: boolean,
};
function PublishForm(props: Props) {
@ -101,7 +101,6 @@ function PublishForm(props: Props) {
const {
thumbnail,
name,
channel,
editingURI,
myClaimForUri,
resolveUri,
@ -123,17 +122,16 @@ function PublishForm(props: Props) {
ytSignupPending,
modal,
enablePublishPreview,
activeChannelClaim,
incognito,
} = props;
// Used to check if name should be auto-populated from title
const [autoPopulateNameFromTitle, setAutoPopulateNameFromTitle] = useState(!isStillEditing);
const TAGS_LIMIT = 5;
const fileFormDisabled = mode === PUBLISH_MODES.FILE && !filePath;
const emptyPostError = mode === PUBLISH_MODES.POST && (!fileText || fileText.trim() === '');
const formDisabled = (fileFormDisabled && !editingURI) || emptyPostError || publishing;
const isInProgress = filePath || editingURI || name || title;
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
// Editing content info
const uri = myClaimForUri ? myClaimForUri.permanent_url : undefined;
const fileMimeType =
@ -204,16 +202,13 @@ function PublishForm(props: Props) {
// Every time the channel or name changes, resolve the uris to find winning bid amounts
useEffect(() => {
// If they are midway through a channel creation, treat it as anonymous until it completes
const channelName = channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : channel;
// We are only going to store the full uri, but we need to resolve the uri with and without the channel name
let uri;
try {
uri = name && buildURI({ streamName: name, channelName });
uri = name && buildURI({ streamName: name, activeChannelName });
} catch (e) {}
if (channelName && name) {
if (activeChannelName && name) {
// resolve without the channel name so we know the winning bid for it
try {
const uriLessChannel = buildURI({ streamName: name });
@ -227,15 +222,17 @@ function PublishForm(props: Props) {
checkAvailability(name);
updatePublishForm({ uri });
}
}, [name, channel, resolveUri, updatePublishForm, checkAvailability]);
}, [name, activeChannelName, resolveUri, updatePublishForm, checkAvailability]);
useEffect(() => {
updatePublishForm({ isMarkdownPost: mode === PUBLISH_MODES.POST });
}, [mode, updatePublishForm]);
function handleChannelNameChange(channel) {
updatePublishForm({ channel });
useEffect(() => {
if (activeChannelName) {
updatePublishForm({ channel: undefined });
}
}, [activeChannelName, incognito, updatePublishForm]);
// @if TARGET='web'
function createWebFile() {
@ -331,18 +328,7 @@ function PublishForm(props: Props) {
// Editing claim uri
return (
<div className="card-stack">
<Card
className={disabled ? 'card--disabled' : undefined}
actions={
<React.Fragment>
<SelectChannel channel={channel} onChannelChange={handleChannelNameChange} />
<p className="help">
{__('This is a username or handle that your content can be found under.')}{' '}
{__('Ex. @Marvel, @TheBeatles, @BooksByJoe')}
</p>
</React.Fragment>
}
/>
<ChannelSelect disabled={disabled} />
<PublishFile
uri={uri}
@ -352,8 +338,6 @@ function PublishForm(props: Props) {
inProgress={isInProgress}
setPublishMode={setMode}
setPrevFileText={setPrevFileText}
autoPopulateName={autoPopulateNameFromTitle}
setAutoPopulateName={setAutoPopulateNameFromTitle}
header={
<>
{MODES.map((modeName, index) => (
@ -371,6 +355,7 @@ function PublishForm(props: Props) {
</>
}
/>
{!publishing && (
<div className={classnames({ 'card--disabled': formDisabled })}>
{mode === PUBLISH_MODES.FILE && <PublishDescription disabled={formDisabled} />}
@ -402,11 +387,7 @@ function PublishForm(props: Props) {
tagsChosen={tags}
/>
<PublishName
disabled={isStillEditing || formDisabled}
autoPopulateName={autoPopulateNameFromTitle}
setAutoPopulateName={setAutoPopulateNameFromTitle}
/>
<PublishBid disabled={isStillEditing || formDisabled} />
<PublishPrice disabled={formDisabled} />
<PublishAdditionalOptions disabled={formDisabled} />
</div>

View file

@ -3,33 +3,25 @@ import {
makeSelectPublishFormValue,
selectIsStillEditing,
selectMyClaimForUri,
selectIsResolvingPublishUris,
selectTakeOverAmount,
doUpdatePublishForm,
doPrepareEdit,
selectBalance,
} from 'lbry-redux';
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
import { doSetActiveChannel } from 'redux/actions/app';
import PublishPage from './view';
const select = state => ({
name: makeSelectPublishFormValue('name')(state),
channel: makeSelectPublishFormValue('channel')(state),
bid: makeSelectPublishFormValue('bid')(state),
uri: makeSelectPublishFormValue('uri')(state),
isStillEditing: selectIsStillEditing(state),
isResolvingUri: selectIsResolvingPublishUris(state),
amountNeededForTakeover: selectTakeOverAmount(state),
balance: selectBalance(state),
myClaimForUri: selectMyClaimForUri(state),
activeChannelClaim: selectActiveChannelClaim(state),
incognito: selectIncognito(state),
});
const perform = dispatch => ({
updatePublishForm: value => dispatch(doUpdatePublishForm(value)),
prepareEdit: (claim, uri) => dispatch(doPrepareEdit(claim, uri)),
setActiveChannel: claimId => dispatch(doSetActiveChannel(claimId)),
});
export default connect(
select,
perform
)(PublishPage);
export default connect(select, perform)(PublishPage);

View file

@ -1,51 +1,41 @@
// @flow
import { CHANNEL_NEW, CHANNEL_ANONYMOUS, MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
import { DOMAIN } from 'config';
import { INVALID_NAME_ERROR } from 'constants/claim';
import React, { useState, useEffect } from 'react';
import { isNameValid } from 'lbry-redux';
import { FormField } from 'component/common/form';
import NameHelpText from './name-help-text';
import BidHelpText from './bid-help-text';
import Card from 'component/common/card';
import LbcSymbol from 'component/common/lbc-symbol';
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
type Props = {
name: string,
channel: string,
uri: string,
bid: number,
balance: number,
disabled: boolean,
isStillEditing: boolean,
myClaimForUri: ?StreamClaim,
isResolvingUri: boolean,
amountNeededForTakeover: number,
prepareEdit: ({}, string) => void,
updatePublishForm: ({}) => void,
autoPopulateName: boolean,
setAutoPopulateName: boolean => void,
activeChannelClaim: ?ChannelClaim,
incognito: boolean,
};
function PublishName(props: Props) {
const {
name,
channel,
uri,
disabled,
isStillEditing,
myClaimForUri,
bid,
isResolvingUri,
amountNeededForTakeover,
prepareEdit,
updatePublishForm,
balance,
autoPopulateName,
setAutoPopulateName,
activeChannelClaim,
incognito,
} = props;
const [nameError, setNameError] = useState(undefined);
const [bidError, setBidError] = useState(undefined);
const previousBidAmount = myClaimForUri && Number(myClaimForUri.amount);
const [blurred, setBlurred] = React.useState(false);
const activeChannelName = activeChannelClaim && activeChannelClaim.name;
let prefix = IS_WEB ? `${DOMAIN}/` : 'lbry://';
if (activeChannelName && !incognito) {
prefix += `${activeChannelName}/`;
}
function editExistingClaim() {
if (myClaimForUri) {
@ -55,21 +45,13 @@ function PublishName(props: Props) {
function handleNameChange(event) {
updatePublishForm({ name: event.target.value });
if (autoPopulateName) {
setAutoPopulateName(false);
}
}
useEffect(() => {
const hasName = name && name.trim() !== '';
// Enable name autopopulation from title
if (!hasName && !autoPopulateName) {
setAutoPopulateName(true);
if (!blurred && !name) {
return;
}
}, [name, autoPopulateName, setAutoPopulateName]);
useEffect(() => {
let nameError;
if (!name) {
nameError = __('A name is required');
@ -78,46 +60,22 @@ function PublishName(props: Props) {
}
setNameError(nameError);
}, [name]);
useEffect(() => {
const totalAvailableBidAmount = previousBidAmount ? previousBidAmount + balance : balance;
let bidError;
if (bid === 0) {
bidError = __('Deposit cannot be 0');
} else if (bid < MINIMUM_PUBLISH_BID) {
bidError = __('Your deposit must be higher');
} else if (totalAvailableBidAmount < bid) {
bidError = __('Deposit cannot be higher than your available balance: %balance%', {
balance: totalAvailableBidAmount,
});
} else if (totalAvailableBidAmount <= bid + 0.05) {
bidError = __('Please decrease your deposit to account for transaction fees or acquire more LBRY Credits.');
}
setBidError(bidError);
updatePublishForm({ bidError: bidError });
}, [bid, previousBidAmount, balance, updatePublishForm]);
}, [name, blurred]);
return (
<Card
actions={
<React.Fragment>
<>
<fieldset-group class="fieldset-group--smushed fieldset-group--disabled-prefix">
<fieldset-section>
<label>{__('Name')}</label>
<div className="form-field__prefix">{`lbry://${
!channel || channel === CHANNEL_ANONYMOUS || channel === CHANNEL_NEW ? '' : `${channel}/`
}`}</div>
<div className="form-field__prefix">{prefix}</div>
</fieldset-section>
<FormField
type="text"
name="content_name"
value={name}
disabled={disabled}
error={nameError}
onChange={handleNameChange}
onBlur={() => setBlurred(true)}
/>
</fieldset-group>
<div className="form-field__help">
@ -128,33 +86,7 @@ function PublishName(props: Props) {
onEditMyClaim={editExistingClaim}
/>
</div>
<FormField
type="number"
name="content_bid"
min="0"
step="any"
placeholder="0.123"
className="form-field--price-amount"
label={<LbcSymbol postfix={__('Deposit')} size={12} />}
value={bid}
error={bidError}
disabled={!name}
onChange={event => updatePublishForm({ bid: parseFloat(event.target.value) })}
onWheel={e => e.stopPropagation()}
helper={
<>
<BidHelpText
uri={'lbry://' + name}
amountNeededForTakeover={amountNeededForTakeover}
isResolvingUri={isResolvingUri}
/>
<WalletSpendableBalanceHelp inline />
</>
}
/>
</React.Fragment>
}
/>
);
}

View file

@ -14,8 +14,10 @@ import {
doCheckPendingClaims,
makeSelectEffectiveAmountForUri,
makeSelectIsUriResolving,
selectFetchingMyChannels,
} from 'lbry-redux';
import { doToast } from 'redux/actions/notifications';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import RepostCreate from './view';
const select = (state, props) => ({
@ -33,6 +35,8 @@ const select = (state, props) => ({
myClaims: selectMyClaimsWithoutChannels(state),
isResolvingPassedRepost: props.name && makeSelectIsUriResolving(`lbry://${props.name}`)(state),
isResolvingEnteredRepost: props.repostUri && makeSelectIsUriResolving(`lbry://${props.repostUri}`)(state),
activeChannelClaim: selectActiveChannelClaim(state),
fetchingMyChannels: selectFetchingMyChannels(state),
});
export default connect(select, {

View file

@ -1,20 +1,21 @@
// @flow
import * as ICONS from 'constants/icons';
import { CHANNEL_NEW, MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
import { MINIMUM_PUBLISH_BID, INVALID_NAME_ERROR } from 'constants/claim';
import React from 'react';
import { useHistory } from 'react-router';
import Card from 'component/common/card';
import Button from 'component/button';
import SelectChannel from 'component/selectChannel';
import ChannelSelector from 'component/channelSelector';
import { FormField } from 'component/common/form';
import { parseURI, isNameValid, creditsToString, isURIValid, normalizeURI } from 'lbry-redux';
import usePersistedState from 'effects/use-persisted-state';
import analytics from 'analytics';
import LbcSymbol from 'component/common/lbc-symbol';
import ClaimPreview from 'component/claimPreview';
import { URL as SITE_URL, URL_LOCAL, URL_DEV } from 'config';
import HelpLink from 'component/common/help-link';
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
import Spinner from 'component/spinner';
type Props = {
doToast: ({ message: string }) => void,
@ -39,6 +40,8 @@ type Props = {
enteredRepostAmount: number,
isResolvingPassedRepost: boolean,
isResolvingEnteredRepost: boolean,
activeChannelClaim: ?ChannelClaim,
fetchingMyChannels: boolean,
};
function RepostCreate(props: Props) {
@ -50,7 +53,6 @@ function RepostCreate(props: Props) {
claim,
enteredContentClaim,
balance,
channels,
reposting,
doCheckPublishNameAvailability,
uri, // ?from
@ -63,12 +65,13 @@ function RepostCreate(props: Props) {
passedRepostAmount,
isResolvingPassedRepost,
isResolvingEnteredRepost,
activeChannelClaim,
fetchingMyChannels,
} = props;
const defaultName = name || (claim && claim.name) || '';
const contentClaimId = claim && claim.claim_id;
const enteredClaimId = enteredContentClaim && enteredContentClaim.claim_id;
const [repostChannel, setRepostChannel] = usePersistedState('repost-channel', 'anonymous');
const [repostBid, setRepostBid] = React.useState(0.01);
const [repostBidError, setRepostBidError] = React.useState(undefined);
@ -80,9 +83,7 @@ function RepostCreate(props: Props) {
const { replace, goBack } = useHistory();
const resolvingRepost = isResolvingEnteredRepost || isResolvingPassedRepost;
const repostUrlName = `lbry://${
!repostChannel || repostChannel === CHANNEL_NEW || repostChannel === 'anonymous' ? '' : `${repostChannel}/`
}`;
const repostUrlName = `lbry://${!activeChannelClaim ? '' : `${activeChannelClaim.name}/`}`;
const contentFirstRender = React.useRef(true);
const setAutoRepostBid = amount => {
@ -173,17 +174,6 @@ function RepostCreate(props: Props) {
contentNameError = __('A name is required');
}
// repostChannel
const channelStrings = channels && channels.map(channel => channel.permanent_url).join(',');
React.useEffect(() => {
if (!repostChannel && channelStrings) {
const channels = channelStrings.split(',');
const newChannelUrl = channels[0];
const { claimName } = parseURI(newChannelUrl);
setRepostChannel(claimName);
}
}, [channelStrings]);
React.useEffect(() => {
if (enteredRepostName && isNameValid(enteredRepostName, false)) {
doCheckPublishNameAvailability(enteredRepostName).then(r => setAvailable(r));
@ -287,12 +277,11 @@ function RepostCreate(props: Props) {
};
function handleSubmit() {
const channelToRepostTo = channels && channels.find(channel => channel.name === repostChannel);
if (enteredRepostName && repostBid && repostClaimId) {
doRepost({
name: enteredRepostName,
bid: creditsToString(repostBid),
channel_id: channelToRepostTo ? channelToRepostTo.claim_id : undefined,
channel_id: activeChannelClaim ? activeChannelClaim.claim_id : undefined,
claim_id: repostClaimId,
}).then((repostClaim: StreamClaim) => {
doCheckPendingClaims();
@ -308,8 +297,18 @@ function RepostCreate(props: Props) {
goBack();
}
if (fetchingMyChannels) {
return (
<div className="main--empty">
<Spinner />
</div>
);
}
return (
<>
<ChannelSelector />
<Card
actions={
<div>
@ -331,6 +330,7 @@ function RepostCreate(props: Props) {
/>
</>
)}
{!uri && (
<fieldset-section>
<ClaimPreview key={contentUri} uri={contentUri} actions={''} type={'large'} showNullPlaceholder />
@ -362,12 +362,6 @@ function RepostCreate(props: Props) {
/>
</fieldset-group>
</fieldset-section>
<SelectChannel
label={__('Channel to repost on')}
hideNew
channel={repostChannel}
onChannelChange={newChannel => setRepostChannel(newChannel)}
/>
<FormField
type="number"
@ -379,9 +373,14 @@ function RepostCreate(props: Props) {
label={<LbcSymbol postfix={__('Support --[button to support a claim]--')} size={14} />}
value={repostBid}
error={repostBidError}
helper={__('Winning amount: %amount%', {
helper={
<>
{__('Winning amount: %amount%', {
amount: Number(takeoverAmount).toFixed(2),
})}
<WalletSpendableBalanceHelp inline />
</>
}
disabled={!enteredRepostName || resolvingRepost}
onChange={event => setRepostBid(event.target.value)}
onWheel={e => e.stopPropagation()}

View file

@ -8,17 +8,21 @@ import {
doCreateChannel,
} from 'lbry-redux';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import { doSetActiveChannel } from 'redux/actions/app';
const select = state => ({
channels: selectMyChannelClaims(state),
myChannelClaims: selectMyChannelClaims(state),
fetchingChannels: selectFetchingMyChannels(state),
balance: selectBalance(state),
emailVerified: selectUserVerifiedEmail(state),
activeChannelClaim: selectActiveChannelClaim(state),
});
const perform = dispatch => ({
createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)),
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
setActiveChannel: claimId => dispatch(doSetActiveChannel(claimId)),
});
export default connect(select, perform)(SelectChannel);

View file

@ -1,115 +1,51 @@
// @flow
import { CHANNEL_NEW, CHANNEL_ANONYMOUS } from 'constants/claim';
import React, { Fragment } from 'react';
import React from 'react';
import { FormField } from 'component/common/form';
import ChannelCreate from 'component/channelCreate';
type Props = {
channel: string, // currently selected channel
channels: ?Array<ChannelClaim>,
balance: number,
onChannelChange: string => void,
createChannel: (string, number) => Promise<any>,
fetchChannelListMine: () => void,
tiny?: boolean,
label: string,
myChannelClaims: ?Array<ChannelClaim>,
injected: ?Array<string>,
activeChannelClaim: ?ChannelClaim,
setActiveChannel: string => void,
fetchingChannels: boolean,
hideAnon: boolean,
hideNew: boolean,
label?: string,
injected?: Array<string>,
emailVerified: boolean,
tiny: boolean,
};
type State = {
addingChannel: boolean,
};
function SelectChannel(props: Props) {
const {
fetchingChannels,
myChannelClaims = [],
label,
injected = [],
tiny,
activeChannelClaim,
setActiveChannel,
} = props;
const ID_FF_SELECT_CHANNEL = 'ID_FF_SELECT_CHANNEL';
class ChannelSelection extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
addingChannel: props.channel === CHANNEL_NEW,
};
(this: any).handleChannelChange = this.handleChannelChange.bind(this);
(this: any).handleChangeToNewChannel = this.handleChangeToNewChannel.bind(this);
function handleChannelChange(event: SyntheticInputEvent<*>) {
const channelClaimId = event.target.value;
setActiveChannel(channelClaimId);
}
componentDidMount() {
const { channel, channels, fetchChannelListMine, fetchingChannels, emailVerified, onChannelChange } = this.props;
if (IS_WEB && !emailVerified) {
return;
}
if ((!channels || !channels.length) && !fetchingChannels) {
fetchChannelListMine();
}
if (channels && channels.length && !channels.find(chan => chan.name === channel)) {
const elem = document.getElementById(ID_FF_SELECT_CHANNEL);
// $FlowFixMe
if (elem && elem.value && elem.value !== channel) {
setTimeout(() => {
// $FlowFixMe
onChannelChange(elem.value);
}, 250);
}
}
}
componentDidUpdate() {
const { channels, fetchingChannels, hideAnon } = this.props;
if (!fetchingChannels && !channels && hideAnon) {
this.setState({ addingChannel: true });
}
}
handleChannelChange(event: SyntheticInputEvent<*>) {
const { onChannelChange } = this.props;
const channel = event.target.value;
if (channel === CHANNEL_NEW) {
this.setState({ addingChannel: true });
onChannelChange(channel);
} else {
this.setState({ addingChannel: false });
onChannelChange(channel);
}
}
handleChangeToNewChannel(props: Object) {
const { onChannelChange } = this.props;
const { newChannelName } = props;
this.setState({ addingChannel: false });
const channelName = `@${newChannelName.trim()}`;
onChannelChange(channelName);
}
render() {
const channel = this.state.addingChannel ? CHANNEL_NEW : this.props.channel;
const { fetchingChannels, channels = [], hideAnon, hideNew, label, injected = [], tiny } = this.props;
const { addingChannel } = this.state;
return (
<Fragment>
<>
<FormField
id={ID_FF_SELECT_CHANNEL}
name="channel"
label={!tiny && (label || __('Channel'))}
labelOnLeft={tiny}
type={tiny ? 'select-tiny' : 'select'}
onChange={this.handleChannelChange}
value={channel}
onChange={handleChannelChange}
value={activeChannelClaim && activeChannelClaim.claim_id}
disabled={fetchingChannels}
>
{!hideAnon && <option value={CHANNEL_ANONYMOUS}>{__('Anonymous')}</option>}
{channels &&
channels.map(({ name, claim_id: claimId }) => (
<option key={claimId} value={name}>
{fetchingChannels ? (
<option>{__('Loading your channels...')}</option>
) : (
<>
{myChannelClaims &&
myChannelClaims.map(({ name, claim_id: claimId }) => (
<option key={claimId} value={claimId}>
{name}
</option>
))}
@ -119,13 +55,11 @@ class ChannelSelection extends React.PureComponent<Props, State> {
{item}
</option>
))}
{!fetchingChannels && !hideNew && <option value={CHANNEL_NEW}>{__('New channel...')}</option>}
</>
)}
</FormField>
{addingChannel && <ChannelCreate onSuccess={this.handleChangeToNewChannel} />}
</Fragment>
</>
);
}
}
export default ChannelSelection;
export default SelectChannel;

View file

@ -6,7 +6,6 @@ import {
selectIsSendingSupport,
selectBalance,
SETTINGS,
selectMyChannelClaims,
makeSelectClaimIsMine,
selectFetchingMyChannels,
} from 'lbry-redux';
@ -14,6 +13,7 @@ import WalletSendTip from './view';
import { doOpenModal, doHideModal } from 'redux/actions/app';
import { withRouter } from 'react-router';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { selectActiveChannelClaim, selectIncognito } from 'redux/selectors/app';
const select = (state, props) => ({
isPending: selectIsSendingSupport(state),
@ -22,9 +22,10 @@ const select = (state, props) => ({
balance: selectBalance(state),
instantTipEnabled: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_ENABLED)(state),
instantTipMax: makeSelectClientSetting(SETTINGS.INSTANT_PURCHASE_MAX)(state),
channels: selectMyChannelClaims(state),
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
fetchingChannels: selectFetchingMyChannels(state),
activeChannelClaim: selectActiveChannelClaim(state),
incognito: selectIncognito(state),
});
const perform = dispatch => ({

View file

@ -5,13 +5,13 @@ import * as PAGES from 'constants/pages';
import React from 'react';
import Button from 'component/button';
import { FormField, Form } from 'component/common/form';
import { MINIMUM_PUBLISH_BID, CHANNEL_ANONYMOUS, CHANNEL_NEW } from 'constants/claim';
import { MINIMUM_PUBLISH_BID } from 'constants/claim';
import CreditAmount from 'component/common/credit-amount';
import I18nMessage from 'component/i18nMessage';
import { Lbryio } from 'lbryinc';
import Card from 'component/common/card';
import classnames from 'classnames';
import SelectChannel from 'component/selectChannel';
import ChannelSelector from 'component/channelSelector';
import LbcSymbol from 'component/common/lbc-symbol';
import { parseURI } from 'lbry-redux';
import usePersistedState from 'effects/use-persisted-state';
@ -34,7 +34,8 @@ type Props = {
fetchingChannels: boolean,
instantTipEnabled: boolean,
instantTipMax: { amount: number, currency: string },
channels: ?Array<ChannelClaim>,
activeChannelClaim: ?ChannelClaim,
incognito: boolean,
};
function WalletSendTip(props: Props) {
@ -49,8 +50,9 @@ function WalletSendTip(props: Props) {
instantTipMax,
sendSupport,
closeModal,
channels,
fetchingChannels,
incognito,
activeChannelClaim,
} = props;
const [presetTipAmount, setPresetTipAmount] = usePersistedState('comment-support:presetTip', DEFAULT_TIP_AMOUNTS[0]);
const [customTipAmount, setCustomTipAmount] = usePersistedState('comment-support:customTip', 1.0);
@ -58,21 +60,9 @@ function WalletSendTip(props: Props) {
const [tipError, setTipError] = React.useState();
const [sendAsTip, setSendAsTip] = usePersistedState('comment-support:sendAsTip', true);
const [isConfirming, setIsConfirming] = React.useState(false);
const [selectedChannel, setSelectedChannel] = usePersistedState('comment-support:channel');
const { claim_id: claimId } = claim;
const { channelName } = parseURI(uri);
const noBalance = balance === 0;
const channelStrings = channels && channels.map(channel => channel.permanent_url).join(',');
React.useEffect(() => {
if (!selectedChannel && channelStrings) {
const channels = channelStrings.split(',');
const newChannelUrl = channels[0];
const { claimName } = parseURI(newChannelUrl);
setSelectedChannel(claimName);
}
}, [channelStrings, selectedChannel, setSelectedChannel]);
const tipAmount = useCustomTip ? customTipAmount : presetTipAmount;
const isSupport = claimIsMine ? true : SIMPLE_SITE ? false : !sendAsTip;
@ -99,12 +89,8 @@ function WalletSendTip(props: Props) {
function sendSupportOrConfirm(instantTipMaxAmount = null) {
let selectedChannelId;
if (selectedChannel !== CHANNEL_ANONYMOUS) {
const selectedChannelClaim = channels && channels.find(channelClaim => channelClaim.name === selectedChannel);
if (selectedChannelClaim) {
selectedChannelId = selectedChannelClaim.claim_id;
}
if (!incognito && activeChannelClaim) {
selectedChannelId = activeChannelClaim.claim_id;
}
if (
@ -124,12 +110,6 @@ function WalletSendTip(props: Props) {
}
function handleSubmit() {
if (selectedChannel === CHANNEL_NEW) {
// This is the submission to create a new channel, and would
// be handled by <ChannelSelection>, so do nothing here.
return;
}
if (tipAmount && claimId) {
if (instantTipEnabled) {
if (instantTipMax.currency === 'LBC') {
@ -196,7 +176,9 @@ function WalletSendTip(props: Props) {
<div className="confirm__label">{__('To --[the tip recipient]--')}</div>
<div className="confirm__value">{channelName || title}</div>
<div className="confirm__label">{__('From --[the tip sender]--')}</div>
<div className="confirm__value">{selectedChannel}</div>
<div className="confirm__value">
{activeChannelClaim && !incognito ? activeChannelClaim.name : __('Anonymous')}
</div>
<div className="confirm__label">{__(isSupport ? 'Supporting' : 'Tipping')}</div>
<div className="confirm__value">
<LbcSymbol postfix={tipAmount} size={22} />
@ -217,11 +199,7 @@ function WalletSendTip(props: Props) {
) : (
<>
<div className="section">
<SelectChannel
label={__('Channel to show support as')}
channel={selectedChannel}
onChannelChange={newChannel => setSelectedChannel(newChannel)}
/>
<ChannelSelector />
</div>
<div className="section">

View file

@ -30,6 +30,8 @@ export const SET_HAS_NAVIGATED = 'SET_HAS_NAVIGATED';
export const SET_SYNC_LOCK = 'SET_SYNC_LOCK';
export const TOGGLE_YOUTUBE_SYNC_INTEREST = 'TOGGLE_YOUTUBE_SYNC_INTEREST';
export const TOGGLE_SPLASH_ANIMATION = 'TOGGLE_SPLASH_ANIMATION';
export const SET_ACTIVE_CHANNEL = 'SET_ACTIVE_CHANNEL';
export const SET_INCOGNITO = 'SET_INCOGNITO';
// Navigation
export const CHANGE_AFTER_AUTH_PATH = 'CHANGE_AFTER_AUTH_PATH';
@ -266,7 +268,6 @@ export const COMMENT_REACT_FAILED = 'COMMENT_REACT_FAILED';
export const COMMENT_PIN_STARTED = 'COMMENT_PIN_STARTED';
export const COMMENT_PIN_COMPLETED = 'COMMENT_PIN_COMPLETED';
export const COMMENT_PIN_FAILED = 'COMMENT_PIN_FAILED';
export const COMMENT_SET_CHANNEL = 'COMMENT_SET_CHANNEL';
// Blocked channels
export const TOGGLE_BLOCK_CHANNEL = 'TOGGLE_BLOCK_CHANNEL';

View file

@ -130,3 +130,4 @@ export const PIN = 'Pin';
export const BEST = 'Best';
export const CREATOR_LIKE = 'CreatorLike';
export const CHEF = 'Chef';
export const ANONYMOUS = 'Anonymous';

View file

@ -36,7 +36,6 @@ export const WALLET_RECEIVE = 'wallet_receive';
export const CREATE_CHANNEL = 'create_channel';
export const YOUTUBE_WELCOME = 'youtube_welcome';
export const SET_REFERRER = 'set_referrer';
export const REPOST = 'repost';
export const SIGN_OUT = 'sign_out';
export const LIQUIDATE_SUPPORTS = 'liquidate_supports';
export const MASS_TIP_UNLOCK = 'mass_tip_unlock';

View file

@ -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);

View file

@ -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;

View file

@ -34,7 +34,6 @@ import ModalWalletReceive from 'modal/modalWalletReceive';
import ModalYoutubeWelcome from 'modal/modalYoutubeWelcome';
import ModalCreateChannel from 'modal/modalChannelCreate';
import ModalSetReferrer from 'modal/modalSetReferrer';
import ModalRepost from 'modal/modalRepost';
import ModalSignOut from 'modal/modalSignOut';
import ModalSupportsLiquidate from 'modal/modalSupportsLiquidate';
import ModalConfirmAge from 'modal/modalConfirmAge';
@ -135,8 +134,6 @@ function ModalRouter(props: Props) {
return <ModalCreateChannel {...modalProps} />;
case MODALS.SET_REFERRER:
return <ModalSetReferrer {...modalProps} />;
case MODALS.REPOST:
return <ModalRepost {...modalProps} />;
case MODALS.SIGN_OUT:
return <ModalSignOut {...modalProps} />;
case MODALS.CONFIRM_AGE:

View file

@ -1,10 +1,13 @@
import { connect } from 'react-redux';
import { selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux';
import { selectActiveChannelClaim } from 'redux/selectors/app';
import { doSetActiveChannel } from 'redux/actions/app';
import CreatorDashboardPage from './view';
const select = state => ({
channels: selectMyChannelClaims(state),
fetchingChannels: selectFetchingMyChannels(state),
activeChannelClaim: selectActiveChannelClaim(state),
});
export default connect(select)(CreatorDashboardPage);
export default connect(select, { doSetActiveChannel })(CreatorDashboardPage);

View file

@ -6,54 +6,17 @@ import Spinner from 'component/spinner';
import Button from 'component/button';
import CreatorAnalytics from 'component/creatorAnalytics';
import ChannelSelector from 'component/channelSelector';
import usePersistedState from 'effects/use-persisted-state';
import Yrbl from 'component/yrbl';
import { useHistory } from 'react-router';
type Props = {
channels: Array<ChannelClaim>,
fetchingChannels: boolean,
activeChannelClaim: ?ChannelClaim,
};
const SELECTED_CHANNEL_QUERY_PARAM = 'channel';
export default function CreatorDashboardPage(props: Props) {
const { channels, fetchingChannels } = props;
const {
push,
location: { search, pathname },
} = useHistory();
const urlParams = new URLSearchParams(search);
const channelFromUrl = urlParams.get(SELECTED_CHANNEL_QUERY_PARAM);
const [selectedChannelUrl, setSelectedChannelUrl] = usePersistedState('analytics-selected-channel');
const { channels, fetchingChannels, activeChannelClaim } = props;
const hasChannels = channels && channels.length > 0;
const firstChannel = hasChannels && channels[0];
const firstChannelUrl = firstChannel && (firstChannel.canonical_url || firstChannel.permanent_url); // permanent_url is needed for pending publishes
const channelFoundForSelectedChannelUrl =
channels &&
channels.find(channel => {
return selectedChannelUrl === channel.canonical_url || selectedChannelUrl === channel.permanent_url;
});
React.useEffect(() => {
// set default channel
if ((!selectedChannelUrl || !channelFoundForSelectedChannelUrl) && firstChannelUrl) {
setSelectedChannelUrl(firstChannelUrl);
}
}, [setSelectedChannelUrl, selectedChannelUrl, firstChannelUrl, channelFoundForSelectedChannelUrl]);
React.useEffect(() => {
if (channelFromUrl) {
const decodedChannel = decodeURIComponent(channelFromUrl);
setSelectedChannelUrl(decodedChannel);
}
}, [channelFromUrl, setSelectedChannelUrl]);
function updateUrl(channelUrl) {
const newUrlParams = new URLSearchParams();
newUrlParams.append(SELECTED_CHANNEL_QUERY_PARAM, encodeURIComponent(channelUrl));
push(`${pathname}?${newUrlParams.toString()}`);
}
return (
<Page>
@ -63,7 +26,7 @@ export default function CreatorDashboardPage(props: Props) {
</div>
)}
{!fetchingChannels && (!channels || !channels.length) && (
{!fetchingChannels && !hasChannels && (
<Yrbl
type="happy"
title={__("You haven't created a channel yet, let's fix that!")}
@ -75,18 +38,10 @@ export default function CreatorDashboardPage(props: Props) {
/>
)}
{!fetchingChannels && channels && channels.length && (
{!fetchingChannels && activeChannelClaim && (
<React.Fragment>
<div className="section">
<ChannelSelector
selectedChannelUrl={selectedChannelUrl}
onChannelSelect={newChannelUrl => {
updateUrl(newChannelUrl);
setSelectedChannelUrl(newChannelUrl);
}}
/>
</div>
<CreatorAnalytics uri={selectedChannelUrl} />
<ChannelSelector hideAnon />
<CreatorAnalytics uri={activeChannelClaim.canonical_url} />
</React.Fragment>
)}
</Page>

View file

@ -21,6 +21,7 @@ import {
SHARED_PREFERENCES,
DAEMON_SETTINGS,
SETTINGS,
selectMyChannelClaims,
} from 'lbry-redux';
import { Lbryio } from 'lbryinc';
import { selectFollowedTagsList } from 'redux/selectors/tags';
@ -693,3 +694,54 @@ export function doToggleSplashAnimation() {
type: ACTIONS.TOGGLE_SPLASH_ANIMATION,
};
}
export function doSetActiveChannel(claimId) {
return (dispatch, getState) => {
if (claimId) {
return dispatch({
type: ACTIONS.SET_ACTIVE_CHANNEL,
data: {
claimId,
},
});
}
// If no claimId is passed, set the active channel to the one with the highest effective_amount
const state = getState();
const myChannelClaims = selectMyChannelClaims(state);
if (!myChannelClaims || !myChannelClaims.length) {
return;
}
const myChannelClaimsByEffectiveAmount = myChannelClaims.slice().sort((a, b) => {
const effectiveAmountA = (a.meta && Number(a.meta.effective_amount)) || 0;
const effectiveAmountB = (b.meta && Number(b.meta.effective_amount)) || 0;
if (effectiveAmountA === effectiveAmountB) {
return 0;
} else if (effectiveAmountA > effectiveAmountB) {
return -1;
} else {
return 1;
}
});
const newActiveChannelClaim = myChannelClaimsByEffectiveAmount[0];
dispatch({
type: ACTIONS.SET_ACTIVE_CHANNEL,
data: {
claimId: newActiveChannelClaim.claim_id,
},
});
};
}
export function doSetIncognito(incognitoEnabled) {
return {
type: ACTIONS.SET_INCOGNITO,
data: {
enabled: incognitoEnabled,
},
};
}

View file

@ -1,16 +1,16 @@
// @flow
import * as ACTIONS from 'constants/action_types';
import * as REACTION_TYPES from 'constants/reactions';
import { Lbry, selectClaimsByUri, selectMyChannelClaims } from 'lbry-redux';
import { Lbry, selectClaimsByUri } from 'lbry-redux';
import { doToast, doSeeNotifications } from 'redux/actions/notifications';
import {
makeSelectCommentIdsForUri,
makeSelectMyReactionsForComment,
makeSelectOthersReactionsForComment,
selectPendingCommentReacts,
selectCommentChannel,
} from 'redux/selectors/comments';
import { makeSelectNotificationForCommentId } from 'redux/selectors/notifications';
import { selectActiveChannelClaim } from 'redux/selectors/app';
export function doCommentList(uri: string, page: number = 1, pageSize: number = 99999) {
return (dispatch: Dispatch, getState: GetState) => {
@ -49,34 +49,23 @@ export function doCommentList(uri: string, page: number = 1, pageSize: number =
};
}
export function doSetCommentChannel(channelName: string) {
return (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.COMMENT_SET_CHANNEL,
data: channelName,
});
};
}
export function doCommentReactList(uri: string | null, commentId?: string) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const channel = selectCommentChannel(state);
const activeChannelClaim = selectActiveChannelClaim(state);
const commentIds = uri ? makeSelectCommentIdsForUri(uri)(state) : [commentId];
const myChannels = selectMyChannelClaims(state);
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_STARTED,
});
const params: { comment_ids: string, channel_name?: string, channel_id?: string } = {
comment_ids: commentIds.join(','),
};
if (channel && myChannels) {
const claimForChannelName = myChannels && myChannels.find(chan => chan.name === channel);
const channelId = claimForChannelName && claimForChannelName.claim_id;
params['channel_name'] = channel;
params['channel_id'] = channelId;
if (activeChannelClaim) {
params['channel_name'] = activeChannelClaim.name;
params['channel_id'] = activeChannelClaim.claim_id;
}
return Lbry.comment_react_list(params)
@ -102,39 +91,38 @@ export function doCommentReactList(uri: string | null, commentId?: string) {
export function doCommentReact(commentId: string, type: string) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const channel = selectCommentChannel(state);
const activeChannelClaim = selectActiveChannelClaim(state);
const pendingReacts = selectPendingCommentReacts(state);
const myChannels = selectMyChannelClaims(state);
const notification = makeSelectNotificationForCommentId(commentId)(state);
if (!activeChannelClaim) {
console.error('Unable to react to comment. No activeChannel is set.'); // eslint-disable-line
return;
}
if (notification && !notification.is_seen) {
dispatch(doSeeNotifications([notification.id]));
}
const exclusiveTypes = {
[REACTION_TYPES.LIKE]: REACTION_TYPES.DISLIKE,
[REACTION_TYPES.DISLIKE]: REACTION_TYPES.LIKE,
};
if (!channel || !myChannels) {
dispatch({
type: ACTIONS.COMMENT_REACTION_LIST_FAILED,
data: 'No active channel found',
});
return;
}
if (pendingReacts.includes(commentId + exclusiveTypes[type]) || pendingReacts.includes(commentId + type)) {
// ignore dislikes during likes, for example
return;
}
let myReacts = makeSelectMyReactionsForComment(commentId)(state);
const othersReacts = makeSelectOthersReactionsForComment(commentId)(state);
const claimForChannelName = myChannels.find(chan => chan.name === channel);
const channelId = claimForChannelName && claimForChannelName.claim_id;
const params: CommentReactParams = {
comment_ids: commentId,
channel_name: channel,
channel_id: channelId,
channel_name: activeChannelClaim.name,
channel_id: activeChannelClaim.claim_id,
react_type: type,
};
if (myReacts.includes(type)) {
params['remove'] = true;
myReacts.splice(myReacts.indexOf(type), 1);
@ -197,15 +185,16 @@ export function doCommentReact(commentId: string, type: string) {
};
}
export function doCommentCreate(
comment: string = '',
claim_id: string = '',
channel: string,
parent_id?: string,
uri: string
) {
export function doCommentCreate(comment: string = '', claim_id: string = '', parent_id?: string, uri: string) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const activeChannelClaim = selectActiveChannelClaim(state);
if (!activeChannelClaim) {
console.error('Unable to create comment. No activeChannel is set.'); // eslint-disable-line
return;
}
dispatch({
type: ACTIONS.COMMENT_CREATE_STARTED,
});
@ -217,28 +206,10 @@ export function doCommentCreate(
}
}
const myChannels = selectMyChannelClaims(state);
const namedChannelClaim = myChannels && myChannels.find(myChannel => myChannel.name === channel);
const channel_id = namedChannelClaim.claim_id;
if (channel_id == null) {
dispatch({
type: ACTIONS.COMMENT_CREATE_FAILED,
data: {},
});
dispatch(
doToast({
message: 'Channel cannot be anonymous, please select a channel and try again.',
isError: true,
})
);
return;
}
return Lbry.comment_create({
comment: comment,
claim_id: claim_id,
channel_id: channel_id,
channel_id: activeChannelClaim.claim_id,
parent_id: parent_id,
})
.then((result: CommentCreateResponse) => {
@ -299,32 +270,23 @@ export function doCommentHide(comment_id: string) {
export function doCommentPin(commentId: string, remove: boolean) {
return (dispatch: Dispatch, getState: GetState) => {
const state = getState();
// const channel = localStorage.getItem('comment-channel');
const channel = selectCommentChannel(state);
const myChannels = selectMyChannelClaims(state);
const claimForChannelName = myChannels && myChannels.find(chan => chan.name === channel);
const channelId = claimForChannelName && claimForChannelName.claim_id;
const activeChannel = selectActiveChannelClaim(state);
if (!activeChannel) {
console.error('Unable to pin comment. No activeChannel is set.'); // eslint-disable-line
return;
}
dispatch({
type: ACTIONS.COMMENT_PIN_STARTED,
});
if (!channelId || !channel || !commentId) {
return dispatch({
type: ACTIONS.COMMENT_PIN_FAILED,
data: { message: 'missing params - unable to pin' },
});
}
const params: { comment_id: string, channel_name: string, channel_id: string, remove?: boolean } = {
return Lbry.comment_pin({
comment_id: commentId,
channel_name: channel,
channel_id: channelId,
};
if (remove) {
params['remove'] = true;
}
return Lbry.comment_pin(params)
channel_name: activeChannel.name,
channel_id: activeChannel.claim_id,
...(remove ? { remove: true } : {}),
})
.then((result: CommentPinResponse) => {
dispatch({
type: ACTIONS.COMMENT_PIN_COMPLETED,

View file

@ -44,6 +44,8 @@ export type AppState = {
allowAnalytics: boolean,
hasNavigated: boolean,
interestedInYoutubeSync: boolean,
activeChannel: ?string,
incognito: boolean,
};
const defaultState: AppState = {
@ -80,6 +82,8 @@ const defaultState: AppState = {
allowAnalytics: false,
hasNavigated: false,
interestedInYoutubeSync: false,
activeChannel: undefined,
incognito: false,
};
// @@router comes from react-router
@ -300,6 +304,19 @@ reducers[ACTIONS.TOGGLE_SPLASH_ANIMATION] = (state, action) => {
};
};
reducers[ACTIONS.SET_ACTIVE_CHANNEL] = (state, action) => {
return {
...state,
activeChannel: action.data.claimId,
};
};
reducers[ACTIONS.SET_INCOGNITO] = (state, action) => {
return {
...state,
incognito: action.data.enabled,
};
};
reducers[LBRY_REDUX_ACTIONS.USER_STATE_POPULATE] = (state, action) => {
const { welcomeVersion, allowAnalytics } = action.data;
return {

View file

@ -16,7 +16,6 @@ const defaultState: CommentsState = {
typesReacting: [],
myReactsByCommentId: undefined,
othersReactsByCommentId: undefined,
commentChannel: '',
};
export default handleActions(
@ -31,11 +30,6 @@ export default handleActions(
isCommenting: false,
}),
[ACTIONS.COMMENT_SET_CHANNEL]: (state: CommentsState, action: any) => ({
...state,
commentChannel: action.data,
}),
[ACTIONS.COMMENT_CREATE_COMPLETED]: (state: CommentsState, action: any): CommentsState => {
const { comment, claimId, uri }: { comment: Comment, claimId: string, uri: string } = action.data;
const commentById = Object.assign({}, state.commentById);

View file

@ -1,4 +1,5 @@
import { createSelector } from 'reselect';
import { selectClaimsById, selectMyChannelClaims } from 'lbry-redux';
export const selectState = state => state.app || {};
@ -84,3 +85,37 @@ export const selectIsPasswordSaved = createSelector(selectState, state => state.
export const selectInterestedInYoutubeSync = createSelector(selectState, state => state.interestedInYoutubeSync);
export const selectSplashAnimationEnabled = createSelector(selectState, state => state.splashAnimationEnabled);
export const selectActiveChannelId = createSelector(selectState, state => state.activeChannel);
export const selectActiveChannelClaim = createSelector(
selectActiveChannelId,
selectClaimsById,
selectMyChannelClaims,
(activeChannelClaimId, claimsById, myChannelClaims) => {
if (!activeChannelClaimId || !claimsById || !myChannelClaims || !myChannelClaims.length) {
return undefined;
}
const activeChannelClaim = claimsById[activeChannelClaimId];
if (activeChannelClaim) {
return activeChannelClaim;
}
const myChannelClaimsByEffectiveAmount = myChannelClaims.slice().sort((a, b) => {
const effectiveAmountA = (a.meta && Number(a.meta.effective_amount)) || 0;
const effectiveAmountB = (b.meta && Number(b.meta.effective_amount)) || 0;
if (effectiveAmountA === effectiveAmountB) {
return 0;
} else if (effectiveAmountA > effectiveAmountB) {
return -1;
} else {
return 1;
}
});
return myChannelClaimsByEffectiveAmount[0];
}
);
export const selectIncognito = createSelector(selectState, state => state.incognito);

View file

@ -16,8 +16,6 @@ export const selectIsPostingComment = createSelector(selectState, state => state
export const selectIsFetchingReacts = createSelector(selectState, state => state.isFetchingReacts);
export const selectCommentChannel = createSelector(selectState, state => state.commentChannel);
export const selectOthersReactsById = createSelector(selectState, state => state.othersReactsByCommentId);
export const selectCommentsByClaimId = createSelector(selectState, selectCommentsById, (state, byId) => {

View file

@ -264,10 +264,12 @@ $metadata-z-index: 1;
}
.menu__list.channel__list {
margin-top: var(--spacing-s);
margin-top: var(--spacing-xs);
margin-left: 0;
border-radius: var(--border-radius);
background: transparent;
max-height: 15rem;
overflow-y: scroll;
[role='menuitem'] {
&[data-selected] {
@ -314,8 +316,14 @@ $metadata-z-index: 1;
.icon {
color: var(--color-menu-icon);
margin-left: var(--spacing-l);
margin-right: var(--spacing-s);
}
.icon__wrapper {
padding: 0;
height: 2rem;
width: 2rem;
margin-right: var(--spacing-m);
border-radius: var(--border-radius);
}
&:hover {
@ -326,5 +334,20 @@ $metadata-z-index: 1;
.channel__list-item--selected {
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
background-color: var(--color-card-background-highlighted);
.icon--ChevronDown {
margin-left: var(--spacing-l);
}
}
.channel__list-text {
font-weight: var(--font-weight-bold);
}
.channel__selector {
margin-bottom: var(--spacing-m);
@media (min-width: $breakpoint-small) {
margin-bottom: var(--spacing-l);
}
}

View file

@ -105,7 +105,7 @@ label {
font-size: var(--font-small);
color: var(--color-input-label);
display: inline-block;
margin-bottom: var(--spacing-xxs);
margin-bottom: 0.1rem;
.icon__lbc {
margin-bottom: 4px;
@ -361,11 +361,9 @@ fieldset-group {
border: 1px solid;
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
border-right: 0;
border-color: var(--color-input-border);
color: var(--color-text-help);
background-color: var(--color-input-bg);
border-right: 1px solid var(--border-color);
}
}

View file

@ -33,6 +33,14 @@
}
}
.icon__wrapper--Anonymous {
background-color: var(--color-gray-1);
.icon {
stroke: var(--color-black);
}
}
.icon--help {
color: var(--color-subtitle);
margin-left: var(--spacing-xs);

View file

@ -5,6 +5,10 @@
overflow-y: hidden;
}
}
[data-reach-menu] {
z-index: 10000;
}
}
.modal-overlay {

View file

@ -5,7 +5,6 @@
}
[data-reach-menu] {
font-family: sans-serif;
display: block;
position: absolute;
z-index: 2;

View file

@ -58,10 +58,10 @@ const appFilter = createFilter('app', [
'welcomeVersion',
'interestedInYoutubeSync',
'splashAnimationEnabled',
'activeChannel',
]);
// We only need to persist the receiveAddress for the wallet
const walletFilter = createFilter('wallet', ['receiveAddress']);
const commentsFilter = createFilter('comments', ['commentChannel']);
const searchFilter = createFilter('search', ['options']);
const tagsFilter = createFilter('tags', ['followedTags']);
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
@ -82,7 +82,6 @@ const whiteListedReducers = [
];
const transforms = [
commentsFilter,
fileInfoFilter,
walletFilter,
blockedFilter,