support channel pending state

This commit is contained in:
jessop 2020-06-21 12:51:06 -04:00
parent 6ff1cfc91e
commit e065c81f88
11 changed files with 164 additions and 126 deletions

View file

@ -1236,5 +1236,8 @@
"Loading your channels...": "Loading your channels...", "Loading your channels...": "Loading your channels...",
"Try Out the App!": "Try Out the App!", "Try Out the App!": "Try Out the App!",
"Download the app to track files you've viewed and downloaded.": "Download the app to track files you've viewed and downloaded.", "Download the app to track files you've viewed and downloaded.": "Download the app to track files you've viewed and downloaded.",
"Create a New Channel": "Create a New Channel" "Create a New Channel": "Create a New Channel",
"Thumbnail source": "Thumbnail source",
"Cover source": "Cover source",
"Your changes will be live in a few minutes": "Your changes will be live in a few minutes"
} }

View file

@ -31,6 +31,7 @@ function ChannelAbout(props: Props) {
<div className="card"> <div className="card">
<section className="section card--section"> <section className="section card--section">
<Fragment> <Fragment>
<label>{__('Description')}</label>
{description && ( {description && (
<div className="media__info-text media__info-text--constrained"> <div className="media__info-text media__info-text--constrained">
<MarkdownPreview content={description} /> <MarkdownPreview content={description} />

View file

@ -8,6 +8,7 @@ import {
doUpdateChannel, doUpdateChannel,
makeSelectAmountForUri, makeSelectAmountForUri,
makeSelectClaimForUri, makeSelectClaimForUri,
selectUpdateChannelError,
} from 'lbry-redux'; } from 'lbry-redux';
import ChannelPage from './view'; import ChannelPage from './view';
@ -24,6 +25,7 @@ const select = (state, props) => ({
languages: makeSelectMetadataItemForUri(props.uri, 'languages')(state), languages: makeSelectMetadataItemForUri(props.uri, 'languages')(state),
amount: makeSelectAmountForUri(props.uri)(state), amount: makeSelectAmountForUri(props.uri)(state),
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),
updateError: selectUpdateChannelError(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -3,10 +3,10 @@ import React, { useState } from 'react';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import SelectAsset from 'component/selectAsset'; import SelectAsset from 'component/selectAsset';
import * as PAGES from 'constants/pages';
import { MINIMUM_PUBLISH_BID } from 'constants/claim'; import { MINIMUM_PUBLISH_BID } from 'constants/claim';
import TagsSearch from 'component/tagsSearch'; import TagsSearch from 'component/tagsSearch';
import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field'; import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field';
import ErrorText from 'component/common/error-text';
type Props = { type Props = {
claim: ChannelClaim, claim: ChannelClaim,
@ -22,10 +22,11 @@ type Props = {
tags: Array<string>, tags: Array<string>,
locations: Array<string>, locations: Array<string>,
languages: Array<string>, languages: Array<string>,
updateChannel: any => void, updateChannel: any => Promise<any>,
updateThumb: string => void, updateThumb: string => void,
updateCover: string => void, updateCover: string => void,
setEditing: boolean => void, doneEditing: () => void,
updateError: string,
}; };
function ChannelForm(props: Props) { function ChannelForm(props: Props) {
@ -41,10 +42,11 @@ function ChannelForm(props: Props) {
locations, locations,
languages, languages,
amount, amount,
setEditing, doneEditing,
updateChannel, updateChannel,
updateThumb, updateThumb,
updateCover, updateCover,
updateError,
} = props; } = props;
const { claim_id: claimId } = claim; const { claim_id: claimId } = claim;
@ -101,110 +103,119 @@ function ChannelForm(props: Props) {
}; };
const handleSubmit = () => { const handleSubmit = () => {
updateChannel(params); updateChannel(params).then(success => {
setEditing(false); if (success) {
doneEditing();
}
});
}; };
// TODO clear and bail after submit // TODO clear and bail after submit
return ( return (
<section className={'card--section'}> <div className="card">
<SelectAsset <section className={'section card--section'}>
onUpdate={v => handleThumbnailChange(v)} <SelectAsset
currentValue={params.thumbnailUrl} onUpdate={v => handleThumbnailChange(v)}
assetName={'Thumbnail'} currentValue={params.thumbnailUrl}
recommended={__('Recommended ratio is 1:1')} assetName={'Thumbnail'}
/> recommended={__('Recommended ratio is 1:1')}
/>
<SelectAsset <SelectAsset
onUpdate={v => handleCoverChange(v)} onUpdate={v => handleCoverChange(v)}
currentValue={params.coverUrl} currentValue={params.coverUrl}
assetName={'Cover'} assetName={'Cover'}
recommended={__('Recommended ratio is 6.25:1')} recommended={__('Recommended ratio is 6.25:1')}
/> />
<FormField <FormField
type="text" type="text"
name="channel_title2" name="channel_title2"
label={__('Title')} label={__('Title')}
placeholder={__('Titular Title')} placeholder={__('Titular Title')}
disabled={false} disabled={false}
value={params.title} value={params.title}
onChange={e => setParams({ ...params, title: e.target.value })} onChange={e => setParams({ ...params, title: e.target.value })}
/> />
<FormField <FormField
className="form-field--price-amount" className="form-field--price-amount"
type="number" type="number"
name="content_bid2" name="content_bid2"
step="any" step="any"
label={__('Deposit (LBC)')} label={__('Deposit (LBC)')}
postfix="LBC" postfix="LBC"
value={params.amount} value={params.amount}
error={bidError} error={bidError}
min="0.0" min="0.0"
disabled={false} disabled={false}
onChange={event => handleBidChange(parseFloat(event.target.value))} onChange={event => handleBidChange(parseFloat(event.target.value))}
placeholder={0.1} placeholder={0.1}
/> />
<FormField <FormField
type="text" type="text"
name="channel_website2" name="channel_website2"
label={__('Website')} label={__('Website')}
placeholder={__('aprettygoodsite.com')} placeholder={__('aprettygoodsite.com')}
disabled={false} disabled={false}
value={params.website} value={params.website}
onChange={e => setParams({ ...params, website: e.target.value })} onChange={e => setParams({ ...params, website: e.target.value })}
/> />
<FormField <FormField
type="text" type="text"
name="content_email2" name="content_email2"
label={__('Email')} label={__('Email')}
placeholder={__('yourstruly@example.com')} placeholder={__('yourstruly@example.com')}
disabled={false} disabled={false}
value={params.email} value={params.email}
onChange={e => setParams({ ...params, email: e.target.value })} onChange={e => setParams({ ...params, email: e.target.value })}
/> />
<FormField <FormField
type="markdown" type="markdown"
name="content_description2" name="content_description2"
label={__('Description')} label={__('Description')}
placeholder={__('Description of your content')} placeholder={__('Description of your content')}
value={params.description} value={params.description}
disabled={false} disabled={false}
onChange={text => setParams({ ...params, description: text })} onChange={text => setParams({ ...params, description: text })}
textAreaMaxLength={FF_MAX_CHARS_IN_DESCRIPTION} textAreaMaxLength={FF_MAX_CHARS_IN_DESCRIPTION}
/> />
<TagsSearch <TagsSearch
suggestMature suggestMature
disableAutoFocus disableAutoFocus
tagsPassedIn={params.tags || []} tagsPassedIn={params.tags || []}
label={__('Tags Selected')} label={__('Tags Selected')}
onRemove={clickedTag => { onRemove={clickedTag => {
const newTags = params.tags.slice().filter(tag => tag.name !== clickedTag.name); const newTags = params.tags.slice().filter(tag => tag.name !== clickedTag.name);
setParams({ ...params, tags: newTags }); setParams({ ...params, tags: newTags });
}} }}
onSelect={newTags => { onSelect={newTags => {
newTags.forEach(newTag => { newTags.forEach(newTag => {
if (!params.tags.map(savedTag => savedTag.name).includes(newTag.name)) { if (!params.tags.map(savedTag => savedTag.name).includes(newTag.name)) {
setParams({ ...params, tags: [...params.tags, newTag] }); setParams({ ...params, tags: [...params.tags, newTag] });
} else { } else {
// If it already exists and the user types it in, remove it // If it already exists and the user types it in, remove it
setParams({ ...params, tags: params.tags.filter(tag => tag.name !== newTag.name) }); setParams({ ...params, tags: params.tags.filter(tag => tag.name !== newTag.name) });
} }
}); });
}} }}
/> />
<div className={'section__actions'}> <div className={'section__actions'}>
<Button button="primary" label={__('Submit')} onClick={handleSubmit} /> <Button button="primary" label={__('Submit')} onClick={handleSubmit} />
<Button button="link" label={__('Cancel')} navigate={`$/${PAGES.CHANNELS}`} /> <Button button="link" label={__('Cancel')} onClick={doneEditing} />
</div> </div>
<p className="help"> {updateError && updateError.length ? (
{__('After submitting, you will not see the changes immediately. Please check back in a few minutes.')} <ErrorText>{updateError}</ErrorText>
</p> ) : (
</section> <p className="help">
{__('After submitting, you will not see the changes immediately. Please check back in a few minutes.')}
</p>
)}
</section>
</div>
); );
} }

View file

@ -8,14 +8,15 @@ type Props = {
doOpenModal: (string, {}) => void, doOpenModal: (string, {}) => void,
claim: StreamClaim, claim: StreamClaim,
abandonActionCallback: any => void, abandonActionCallback: any => void,
iconSize: number,
}; };
export default function ClaimAbandonButton(props: Props) { export default function ClaimAbandonButton(props: Props) {
const { doOpenModal, claim, abandonActionCallback } = props; const { doOpenModal, claim, abandonActionCallback, iconSize } = props;
function abandonClaim() { function abandonClaim() {
doOpenModal(MODALS.CONFIRM_CLAIM_REVOKE, { claim: claim, cb: abandonActionCallback }); doOpenModal(MODALS.CONFIRM_CLAIM_REVOKE, { claim: claim, cb: abandonActionCallback });
} }
return <Button button="secondary" icon={ICONS.DELETE} onClick={abandonClaim} />; return <Button disabled={!claim} button="alt" iconSize={iconSize} icon={ICONS.DELETE} onClick={abandonClaim} />;
} }

View file

@ -61,7 +61,7 @@ function ClaimPreviewTile(props: Props) {
const isRepost = claim && claim.repost_channel_url; const isRepost = claim && claim.repost_channel_url;
const shouldFetch = claim === undefined; const shouldFetch = claim === undefined;
const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail; const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail;
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0; const claimsInChannel = (claim && claim.meta && claim.meta.claims_in_channel) || 0;
const canonicalUrl = claim && claim.canonical_url; const canonicalUrl = claim && claim.canonical_url;
const navigateUrl = formatLbryUrlForWeb(canonicalUrl || uri || '/'); const navigateUrl = formatLbryUrlForWeb(canonicalUrl || uri || '/');

View file

@ -7,6 +7,7 @@ import {
selectCurrentChannelPage, selectCurrentChannelPage,
makeSelectClaimForUri, makeSelectClaimForUri,
selectChannelIsBlocked, selectChannelIsBlocked,
makeSelectClaimIsPending,
} from 'lbry-redux'; } from 'lbry-redux';
import { selectBlackListedOutpoints, doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc'; import { selectBlackListedOutpoints, doFetchSubCount, makeSelectSubCountForUri } from 'lbryinc';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions'; import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
@ -23,6 +24,7 @@ const select = (state, props) => ({
channelIsBlocked: selectChannelIsBlocked(props.uri)(state), channelIsBlocked: selectChannelIsBlocked(props.uri)(state),
blackListedOutpoints: selectBlackListedOutpoints(state), blackListedOutpoints: selectBlackListedOutpoints(state),
subCount: makeSelectSubCountForUri(props.uri)(state), subCount: makeSelectSubCountForUri(props.uri)(state),
pending: makeSelectClaimIsPending(props.uri)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -46,6 +46,7 @@ type Props = {
}>, }>,
fetchSubCount: string => void, fetchSubCount: string => void,
subCount: number, subCount: number,
pending: boolean,
}; };
function ChannelPage(props: Props) { function ChannelPage(props: Props) {
@ -64,6 +65,7 @@ function ChannelPage(props: Props) {
blackListedOutpoints, blackListedOutpoints,
fetchSubCount, fetchSubCount,
subCount, subCount,
pending,
} = props; } = props;
const { channelName } = parseURI(uri); const { channelName } = parseURI(uri);
@ -98,6 +100,12 @@ function ChannelPage(props: Props) {
history.push(`${url}${search}`); history.push(`${url}${search}`);
} }
function doneEditing() {
setEditing(false);
setThumbPreview(thumbnail);
setCoverPreview(cover);
}
useEffect(() => { useEffect(() => {
Lbryio.call('yt', 'get_youtuber', { channel_claim_id: claimId }).then(response => { Lbryio.call('yt', 'get_youtuber', { channel_claim_id: claimId }).then(response => {
if (response.is_verified_youtuber) { if (response.is_verified_youtuber) {
@ -176,11 +184,27 @@ function ChannelPage(props: Props) {
<HelpLink href="https://lbry.com/faq/views" /> <HelpLink href="https://lbry.com/faq/views" />
</span> </span>
{channelIsMine && !editing && ( {channelIsMine && !editing && (
<>
{pending ? (
<span>{__('Your changes will be live in a few minutes')}</span>
) : (
<Button
button="alt"
title={__('Edit')}
onClick={() => setEditing(!editing)}
icon={ICONS.EDIT}
iconSize={18}
disabled={pending}
/>
)}
</>
)}
{channelIsMine && editing && (
<Button <Button
button="alt" button="alt"
title={__('Edit')} title={__('Cancel')}
onClick={() => setEditing(!editing)} onClick={() => doneEditing()}
icon={ICONS.EDIT} icon={ICONS.REMOVE}
iconSize={18} iconSize={18}
/> />
)} )}
@ -203,7 +227,7 @@ function ChannelPage(props: Props) {
{editing ? ( {editing ? (
<ChannelEdit <ChannelEdit
uri={uri} uri={uri}
setEditing={setEditing} doneEditing={doneEditing}
updateThumb={v => setThumbPreview(v)} updateThumb={v => setThumbPreview(v)}
updateCover={v => setCoverPreview(v)} updateCover={v => setCoverPreview(v)}
/> />

View file

@ -1,10 +1,16 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectMyChannelClaims, doFetchChannelListMine, selectFetchingMyChannels } from 'lbry-redux'; import {
selectMyChannelClaims,
selectMyChannelUrls,
doFetchChannelListMine,
selectFetchingMyChannels,
} from 'lbry-redux';
import { selectYoutubeChannels } from 'redux/selectors/user'; import { selectYoutubeChannels } from 'redux/selectors/user';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
import ChannelsPage from './view'; import ChannelsPage from './view';
const select = state => ({ const select = state => ({
channelUrls: selectMyChannelUrls(state),
channels: selectMyChannelClaims(state), channels: selectMyChannelClaims(state),
fetchingChannels: selectFetchingMyChannels(state), fetchingChannels: selectFetchingMyChannels(state),
youtubeChannels: selectYoutubeChannels(state), youtubeChannels: selectYoutubeChannels(state),

View file

@ -11,6 +11,7 @@ import Card from 'component/common/card';
type Props = { type Props = {
channels: Array<ChannelClaim>, channels: Array<ChannelClaim>,
channelUrls: Array<string>,
fetchChannelListMine: () => void, fetchChannelListMine: () => void,
fetchingChannels: boolean, fetchingChannels: boolean,
youtubeChannels: ?Array<any>, youtubeChannels: ?Array<any>,
@ -18,30 +19,19 @@ type Props = {
}; };
export default function ChannelsPage(props: Props) { export default function ChannelsPage(props: Props) {
const { channels, fetchChannelListMine, fetchingChannels, youtubeChannels, openModal } = props; const { channels, channelUrls, fetchChannelListMine, fetchingChannels, youtubeChannels, openModal } = props;
const hasYoutubeChannels = youtubeChannels && Boolean(youtubeChannels.length); const hasYoutubeChannels = youtubeChannels && Boolean(youtubeChannels.length);
const hasPendingChannels = channels && channels.some(channel => channel.confirmations < 0); const hasPendingChannels = channels && channels.some(channel => channel.confirmations < 0);
useEffect(() => { useEffect(() => {
fetchChannelListMine(); fetchChannelListMine();
let interval;
if (hasPendingChannels) {
interval = setInterval(() => {
fetchChannelListMine();
}, 5000);
}
return () => {
clearInterval(interval);
};
}, [fetchChannelListMine, hasPendingChannels]); }, [fetchChannelListMine, hasPendingChannels]);
return ( return (
<Page> <Page>
{hasYoutubeChannels && <YoutubeTransferStatus hideChannelLink />} {hasYoutubeChannels && <YoutubeTransferStatus hideChannelLink />}
{channels && Boolean(channels.length) && ( {channelUrls && Boolean(channelUrls.length) && (
<Card <Card
title={__('Your Channels')} title={__('Your Channels')}
titleActions={ titleActions={
@ -53,12 +43,10 @@ export default function ChannelsPage(props: Props) {
/> />
} }
isBodyList isBodyList
body={ body={<ClaimList isCardBody loading={fetchingChannels} uris={channelUrls} />}
<ClaimList isCardBody loading={fetchingChannels} uris={channels.map(channel => channel.permanent_url)} />
}
/> />
)} )}
{!(channels && channels.length) && ( {!(channelUrls && channelUrls.length) && (
<React.Fragment> <React.Fragment>
{!fetchingChannels ? ( {!fetchingChannels ? (
<section className="main--empty"> <section className="main--empty">

View file

@ -6,7 +6,7 @@ import {
batchActions, batchActions,
selectMyClaims, selectMyClaims,
doPublish, doPublish,
doCheckPendingPublishes, doCheckPendingClaims,
doCheckReflectingFiles, doCheckReflectingFiles,
ACTIONS as LBRY_REDUX_ACTIONS, ACTIONS as LBRY_REDUX_ACTIONS,
} from 'lbry-redux'; } from 'lbry-redux';
@ -101,5 +101,5 @@ export const doCheckPendingPublishesApp = () => (dispatch: Dispatch, getState: G
}; };
} }
}; };
return dispatch(doCheckPendingPublishes(onConfirmed)); return dispatch(doCheckPendingClaims(onConfirmed));
}; };