fix story validation and content loading on web
This commit is contained in:
parent
c7ea2a14ad
commit
c47c6f6034
7 changed files with 81 additions and 55 deletions
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
38
ui/effects/use-fetch-streaming-url.js
Normal file
38
ui/effects/use-fetch-streaming-url.js
Normal 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;
|
||||||
|
}
|
Loading…
Reference in a new issue