fix story validation and content loading on web

This commit is contained in:
btzr-io 2020-07-28 21:56:07 -05:00 committed by Sean Yesmunt
parent c7ea2a14ad
commit c47c6f6034
7 changed files with 81 additions and 55 deletions

View file

@ -18,6 +18,7 @@ type Props = {
name: ?string, name: ?string,
title: ?string, title: ?string,
filePath: string | WebFile, filePath: string | WebFile,
fileMimeType: ?string,
isStillEditing: boolean, isStillEditing: boolean,
balance: number, balance: number,
updatePublishForm: ({}) => void, updatePublishForm: ({}) => void,
@ -43,6 +44,7 @@ function PublishFile(props: Props) {
title, title,
balance, balance,
filePath, filePath,
fileMimeType,
isStillEditing, isStillEditing,
updatePublishForm, updatePublishForm,
disabled, disabled,
@ -343,6 +345,7 @@ function PublishFile(props: Props) {
label={__('Story content')} label={__('Story content')}
uri={uri} uri={uri}
disabled={disabled} disabled={disabled}
fileMimeType={fileMimeType}
setPrevFileText={setPrevFileText} setPrevFileText={setPrevFileText}
setCurrentFileType={setCurrentFileType} setCurrentFileType={setCurrentFileType}
/> />

View file

@ -120,9 +120,12 @@ function PublishForm(props: Props) {
const TAGS_LIMIT = 5; const TAGS_LIMIT = 5;
const fileFormDisabled = mode === PUBLISH_MODES.FILE && !filePath; const fileFormDisabled = mode === PUBLISH_MODES.FILE && !filePath;
const storyFormDisabled = mode === PUBLISH_MODES.STORY && (!fileText || fileText === ''); const emptyStoryError = mode === PUBLISH_MODES.STORY && (!fileText || fileText.trim() === '');
const formDisabled = ((fileFormDisabled || storyFormDisabled) && !editingURI) || publishing; const formDisabled = (fileFormDisabled && !editingURI) || emptyStoryError || publishing;
const isInProgress = filePath || editingURI || name || title; const isInProgress = filePath || editingURI || name || title;
// Editing content info
const uri = myClaimForUri ? myClaimForUri.permanent_url : undefined;
const fileMimeType = myClaimForUri ? myClaimForUri.value.source.media_type : undefined;
// If they are editing, they don't need a new file chosen // If they are editing, they don't need a new file chosen
const formValidLessFile = const formValidLessFile =
@ -131,6 +134,7 @@ function PublishForm(props: Props) {
title && title &&
bid && bid &&
!bidError && !bidError &&
!emptyStoryError &&
!(uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS); !(uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS);
const isOverwritingExistingClaim = !editingURI && myClaimForUri; const isOverwritingExistingClaim = !editingURI && myClaimForUri;
@ -280,19 +284,16 @@ function PublishForm(props: Props) {
// Update mode on editing // Update mode on editing
useEffect(() => { useEffect(() => {
if (autoSwitchMode && editingURI && myClaimForUri) { if (autoSwitchMode && editingURI && myClaimForUri) {
const { media_type: mediaType } = myClaimForUri.value.source;
// Change publish mode to "story" if editing content type is markdown // Change publish mode to "story" if editing content type is markdown
if (mediaType === 'text/markdown' && mode !== PUBLISH_MODES.STORY) { if (fileMimeType === 'text/markdown' && mode !== PUBLISH_MODES.STORY) {
setMode(PUBLISH_MODES.STORY); setMode(PUBLISH_MODES.STORY);
// Prevent forced mode // Prevent forced mode
setAutoSwitchMode(false); setAutoSwitchMode(false);
} }
} }
}, [autoSwitchMode, editingURI, myClaimForUri, mode, setMode, setAutoSwitchMode]); }, [autoSwitchMode, editingURI, fileMimeType, myClaimForUri, mode, setMode, setAutoSwitchMode]);
// Editing claim uri // Editing claim uri
const uri = myClaimForUri ? myClaimForUri.permanent_url : undefined;
return ( return (
<div className="card-stack"> <div className="card-stack">
<div className="button-tab-group"> <div className="button-tab-group">
@ -312,6 +313,7 @@ function PublishForm(props: Props) {
<PublishFile <PublishFile
uri={uri} uri={uri}
mode={mode} mode={mode}
fileMimeType={fileMimeType}
disabled={disabled || publishing} disabled={disabled || publishing}
inProgress={isInProgress} inProgress={isInProgress}
setPublishMode={setMode} setPublishMode={setMode}

View file

@ -6,7 +6,6 @@ const select = state => ({
bid: makeSelectPublishFormValue('bid')(state), bid: makeSelectPublishFormValue('bid')(state),
name: makeSelectPublishFormValue('name')(state), name: makeSelectPublishFormValue('name')(state),
title: makeSelectPublishFormValue('title')(state), title: makeSelectPublishFormValue('title')(state),
fileText: makeSelectPublishFormValue('fileText')(state),
bidError: makeSelectPublishFormValue('bidError')(state), bidError: makeSelectPublishFormValue('bidError')(state),
editingUri: makeSelectPublishFormValue('editingUri')(state), editingUri: makeSelectPublishFormValue('editingUri')(state),
uploadThumbnailStatus: makeSelectPublishFormValue('uploadThumbnailStatus')(state), uploadThumbnailStatus: makeSelectPublishFormValue('uploadThumbnailStatus')(state),

View file

@ -2,35 +2,20 @@
import React from 'react'; import React from 'react';
import { THUMBNAIL_STATUSES, isNameValid } from 'lbry-redux'; import { THUMBNAIL_STATUSES, isNameValid } from 'lbry-redux';
import { INVALID_NAME_ERROR } from 'constants/claim'; import { INVALID_NAME_ERROR } from 'constants/claim';
import * as PUBLISH_MODES from 'constants/publish_types';
type Props = { type Props = {
mode: ?string,
title: ?string, title: ?string,
name: ?string, name: ?string,
bid: ?string, bid: ?string,
bidError: ?string, bidError: ?string,
editingURI: ?string, editingURI: ?string,
filePath: ?string, filePath: ?string,
fileText: ?string,
isStillEditing: boolean, isStillEditing: boolean,
uploadThumbnailStatus: string, uploadThumbnailStatus: string,
}; };
function PublishFormErrors(props: Props) { function PublishFormErrors(props: Props) {
const { const { name, title, bid, bidError, editingURI, filePath, isStillEditing, uploadThumbnailStatus } = props;
name,
mode,
title,
bid,
bidError,
editingURI,
filePath,
fileText,
isStillEditing,
uploadThumbnailStatus,
} = props;
const emptyStoryError = mode === PUBLISH_MODES.STORY && (!fileText || fileText.trim() === '');
// These are extra help // These are extra help
// If there is an error it will be presented as an inline error as well // If there is an error it will be presented as an inline error as well
return ( return (
@ -40,7 +25,6 @@ function PublishFormErrors(props: Props) {
{!isNameValid(name, false) && INVALID_NAME_ERROR} {!isNameValid(name, false) && INVALID_NAME_ERROR}
{!bid && <div>{__('A deposit amount is required')}</div>} {!bid && <div>{__('A deposit amount is required')}</div>}
{bidError && <div>{__('Please check your deposit amount.')}</div>} {bidError && <div>{__('Please check your deposit amount.')}</div>}
{emptyStoryError && <div>{__("Can't publish an empty story")}</div>}
{uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS && ( {uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS && (
<div>{__('Please wait for thumbnail to finish uploading')}</div> <div>{__('Please wait for thumbnail to finish uploading')}</div>
)} )}

View file

@ -1,18 +1,10 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { selectIsStillEditing, makeSelectPublishFormValue, doUpdatePublishForm } from 'lbry-redux';
selectIsStillEditing,
makeSelectPublishFormValue,
doUpdatePublishForm,
makeSelectFileInfoForUri,
makeSelectStreamingUrlForUri,
} from 'lbry-redux';
import StoryEditor from './view'; import StoryEditor from './view';
const select = (state, props) => ({ const select = (state, props) => ({
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
filePath: makeSelectPublishFormValue('filePath')(state), filePath: makeSelectPublishFormValue('filePath')(state),
fileText: makeSelectPublishFormValue('fileText')(state), fileText: makeSelectPublishFormValue('fileText')(state),
streamingUrl: makeSelectStreamingUrlForUri(props.uri)(state),
isStillEditing: selectIsStillEditing(state), isStillEditing: selectIsStillEditing(state),
}); });

View file

@ -4,15 +4,15 @@ import { SIMPLE_SITE } from 'config';
import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field'; import { FF_MAX_CHARS_IN_DESCRIPTION } from 'constants/form-field';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
import useFetchStreamingUrl from 'effects/use-fetch-streaming-url';
type Props = { type Props = {
uri: ?string, uri: ?string,
label: ?string, label: ?string,
disabled: ?boolean, disabled: ?boolean,
fileInfo: FileListItem,
filePath: string | WebFile, filePath: string | WebFile,
fileText: ?string, fileText: ?string,
streamingUrl: ?string, fileMimeType: ?string,
isStillEditing: boolean, isStillEditing: boolean,
setPrevFileText: string => void, setPrevFileText: string => void,
updatePublishForm: ({}) => void, updatePublishForm: ({}) => void,
@ -24,32 +24,43 @@ function StoryEditor(props: Props) {
uri, uri,
label, label,
disabled, disabled,
fileInfo,
filePath, filePath,
fileText, fileText,
streamingUrl, fileMimeType,
isStillEditing, isStillEditing,
setPrevFileText, setPrevFileText,
updatePublishForm, updatePublishForm,
setCurrentFileType, setCurrentFileType,
} = props; } = props;
const [isLoadign, setIsLoadign] = React.useState(false); const editing = isStillEditing && uri;
const [ready, setReady] = React.useState(!editing);
const [loading, setLoading] = React.useState(false);
const [advancedEditor, setAdvancedEditor] = usePersistedState('publish-form-story-mode', false); const [advancedEditor, setAdvancedEditor] = usePersistedState('publish-form-story-mode', false);
const { streamingUrl } = useFetchStreamingUrl(uri);
function toggleMarkdown() { function toggleMarkdown() {
setAdvancedEditor(!advancedEditor); setAdvancedEditor(!advancedEditor);
} }
// Ready to edit content
useEffect(() => { useEffect(() => {
if (fileText && isLoadign) { if (!ready && !loading && fileText && streamingUrl) {
setIsLoadign(false); setReady(true);
} }
}, [fileText, isLoadign, setIsLoadign]); }, [ready, loading, fileText, streamingUrl]);
useEffect(() => {
if (fileText && loading) {
setLoading(false);
} else if (!fileText && loading) {
setLoading(true);
}
}, [fileText, loading, setLoading]);
useEffect(() => { useEffect(() => {
function readFileStream(url) { function readFileStream(url) {
setIsLoadign(true);
return fetch(url).then(res => res.text()); return fetch(url).then(res => res.text());
} }
@ -67,24 +78,21 @@ function StoryEditor(props: Props) {
} }
} }
const isEditingFile = isStillEditing && uri && fileInfo; if (editing) {
if (isEditingFile) {
const { mime_type: mimeType } = fileInfo;
// Editing same file (previously published) // Editing same file (previously published)
// User can use a different file to replace the content // User can use a different file to replace the content
if (!filePath && !fileText && streamingUrl && mimeType === 'text/markdown') { if (!ready && !filePath && !fileText && streamingUrl && fileMimeType === 'text/markdown') {
setCurrentFileType(mimeType); setCurrentFileType(fileMimeType);
updateEditorText(streamingUrl); updateEditorText(streamingUrl);
} }
} }
}, [ }, [
uri, ready,
editing,
fileText, fileText,
filePath, filePath,
fileInfo, fileMimeType,
streamingUrl, streamingUrl,
isStillEditing,
setPrevFileText, setPrevFileText,
updatePublishForm, updatePublishForm,
setCurrentFileType, setCurrentFileType,
@ -95,9 +103,9 @@ function StoryEditor(props: Props) {
type={!SIMPLE_SITE && advancedEditor ? 'markdown' : 'textarea'} type={!SIMPLE_SITE && advancedEditor ? 'markdown' : 'textarea'}
name="content_story" name="content_story"
label={label} label={label}
placeholder={isLoadign ? __('Loadign...') : __('My content for this story...')} placeholder={__('My content for this story...')}
value={fileText} value={ready ? fileText : __('Loading...')}
disabled={isLoadign || disabled} disabled={!ready || disabled}
onChange={value => updatePublishForm({ fileText: advancedEditor ? value : value.target.value })} onChange={value => updatePublishForm({ fileText: advancedEditor ? value : value.target.value })}
quickActionLabel={!SIMPLE_SITE && (advancedEditor ? __('Simple Editor') : __('Advanced Editor'))} quickActionLabel={!SIMPLE_SITE && (advancedEditor ? __('Simple Editor') : __('Advanced Editor'))}
quickActionHandler={toggleMarkdown} quickActionHandler={toggleMarkdown}

View file

@ -0,0 +1,38 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import useIsMounted from 'effects/use-is-mounted';
// Fetch streaming url
export default function useFetchStreamingUrl(uri) {
const isMounted = useIsMounted();
const [state, setState] = React.useState({
error: false,
fetching: false,
streamingUrl: null,
});
React.useEffect(() => {
async function fetchClaim(claimUri) {
try {
setState({ fetching: true });
const data = await Lbry.get({ uri: claimUri });
if (data && isMounted.current) {
const { streaming_url: streamingUrl } = data;
setState({ fetching: false, streamingUrl });
}
} catch (error) {
if (isMounted.current) {
setState({ error, fetching: false });
}
}
}
if (uri && !state.error && !state.fetching && !state.streamingUrl) {
fetchClaim(uri);
}
}, [uri, state]);
return state;
}