Fix publish form when editing livestream (#565)

* Update publish form when editing livestream + update to radios for liveststream release time

* Fix bug where upload tools may remain visible upon switching upload type, even when no option to upload is available.

* move publish source state up, when editing livestream only show scheduling option when source is none.

* Reset any set release time if switching to live stream mode.

* Update date/time cmpnts to reset any chnages they made on umount. Update schedule date/time cmpnt to clear release time when selecting anytime option.

* Additional filtering of internal tags

* Default to replay view when editing a liveststream
This commit is contained in:
Dan Peterson 2021-12-31 11:20:54 -06:00 committed by GitHub
parent e94f66a5fe
commit da06c14e60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 155 additions and 113 deletions

View file

@ -29,7 +29,7 @@ type Props = {
needsYTAuth: boolean, needsYTAuth: boolean,
fetchAccessToken: () => void, fetchAccessToken: () => void,
accessToken: string, accessToken: string,
isLivestreamMode: boolean, showSchedulingOptions: boolean,
}; };
function PublishAdditionalOptions(props: Props) { function PublishAdditionalOptions(props: Props) {
@ -40,7 +40,7 @@ function PublishAdditionalOptions(props: Props) {
otherLicenseDescription, otherLicenseDescription,
licenseUrl, licenseUrl,
updatePublishForm, updatePublishForm,
isLivestreamMode, showSchedulingOptions,
// user, // user,
// useLBRYUploader, // useLBRYUploader,
// needsYTAuth, // needsYTAuth,
@ -156,7 +156,7 @@ function PublishAdditionalOptions(props: Props) {
)} */} )} */}
{/* @endif */} {/* @endif */}
<div className="section"> <div className="section">
{!isLivestreamMode && <PublishReleaseDate />} {!showSchedulingOptions && <PublishReleaseDate />}
<FormField <FormField
label={__('Language')} label={__('Language')}

View file

@ -19,6 +19,7 @@ import Empty from 'component/common/empty';
import moment from 'moment'; import moment from 'moment';
import classnames from 'classnames'; import classnames from 'classnames';
import ReactPaginate from 'react-paginate'; import ReactPaginate from 'react-paginate';
import { SOURCE_NONE, SOURCE_SELECT, SOURCE_UPLOAD } from 'constants/publish_sources';
type Props = { type Props = {
uri: ?string, uri: ?string,
@ -51,6 +52,8 @@ type Props = {
channelSignature: { signature?: string, signing_ts?: string }, channelSignature: { signature?: string, signing_ts?: string },
isCheckingLivestreams: boolean, isCheckingLivestreams: boolean,
setWaitForFile: (boolean) => void, setWaitForFile: (boolean) => void,
fileSelectSource: string,
changeFileSelectSource: (string) => void,
}; };
function PublishFile(props: Props) { function PublishFile(props: Props) {
@ -84,12 +87,10 @@ function PublishFile(props: Props) {
channelSignature, channelSignature,
isCheckingLivestreams, isCheckingLivestreams,
setWaitForFile, setWaitForFile,
fileSelectSource,
changeFileSelectSource,
} = props; } = props;
const SOURCE_NONE = 'none';
const SOURCE_SELECT = 'select';
const SOURCE_UPLOAD = 'upload';
const RECOMMENDED_BITRATE = 6000000; const RECOMMENDED_BITRATE = 6000000;
const TV_PUBLISH_SIZE_LIMIT_BYTES = WEB_PUBLISH_SIZE_LIMIT_GB * 1073741824; const TV_PUBLISH_SIZE_LIMIT_BYTES = WEB_PUBLISH_SIZE_LIMIT_GB * 1073741824;
const TV_PUBLISH_SIZE_LIMIT_GB_STR = String(WEB_PUBLISH_SIZE_LIMIT_GB); const TV_PUBLISH_SIZE_LIMIT_GB_STR = String(WEB_PUBLISH_SIZE_LIMIT_GB);
@ -114,16 +115,13 @@ function PublishFile(props: Props) {
const fileSelectorModes = [ const fileSelectorModes = [
{ label: __('Upload'), actionName: SOURCE_UPLOAD, icon: ICONS.PUBLISH }, { label: __('Upload'), actionName: SOURCE_UPLOAD, icon: ICONS.PUBLISH },
{ label: __('Choose Replay'), actionName: SOURCE_SELECT, icon: ICONS.MENU }, { label: __('Choose Replay'), actionName: SOURCE_SELECT, icon: ICONS.MENU },
{ label: __('None'), actionName: SOURCE_NONE }, { label: isLivestreamClaim ? __('Edit / Update') : __('None'), actionName: SOURCE_NONE },
]; ];
const livestreamDataStr = JSON.stringify(livestreamData); const livestreamDataStr = JSON.stringify(livestreamData);
const hasLivestreamData = livestreamData && Boolean(livestreamData.length); const hasLivestreamData = livestreamData && Boolean(livestreamData.length);
const showSourceSelector = isLivestreamClaim || (hasLivestreamData && mode === PUBLISH_MODES.FILE); const showSourceSelector = isLivestreamClaim || (hasLivestreamData && mode === PUBLISH_MODES.FILE);
const [fileSelectSource, setFileSelectSource] = useState(
IS_WEB && showSourceSelector && name ? SOURCE_SELECT : SOURCE_UPLOAD
);
// const [showFileUpdate, setShowFileUpdate] = useState(false); // const [showFileUpdate, setShowFileUpdate] = useState(false);
const [selectedFileIndex, setSelectedFileIndex] = useState(null); const [selectedFileIndex, setSelectedFileIndex] = useState(null);
const PAGE_SIZE = 4; const PAGE_SIZE = 4;
@ -142,15 +140,21 @@ function PublishFile(props: Props) {
} }
}, [currentFileType, mode, isStillEditing, updatePublishForm]); }, [currentFileType, mode, isStillEditing, updatePublishForm]);
// set default file source to select if necessary // Initialize default file source state.
useEffect(() => { useEffect(() => {
if (hasLivestreamData && isLivestreamClaim) { // Editing a livestream
setWaitForFile(true); if (isLivestreamClaim) {
setFileSelectSource(SOURCE_SELECT); changeFileSelectSource(SOURCE_SELECT);
} else if (isLivestreamClaim) {
setFileSelectSource(SOURCE_NONE);
} }
}, [hasLivestreamData, isLivestreamClaim, setFileSelectSource]); // Publishing a livestream
else if (mode === PUBLISH_MODES.LIVESTREAM) {
changeFileSelectSource(SOURCE_NONE);
} else if (showSourceSelector && name) {
changeFileSelectSource(SOURCE_SELECT);
} else {
changeFileSelectSource(SOURCE_UPLOAD);
}
}, [mode]); // eslint-disable-line react-hooks/exhaustive-deps
const normalizeUrlForProtocol = (url) => { const normalizeUrlForProtocol = (url) => {
if (url.startsWith('https://')) { if (url.startsWith('https://')) {
@ -328,7 +332,7 @@ function PublishFile(props: Props) {
updatePublishForm({ remoteFileUrl: livestreamData[selectedFileIndex].data.fileLocation }); updatePublishForm({ remoteFileUrl: livestreamData[selectedFileIndex].data.fileLocation });
} }
} }
setFileSelectSource(source); changeFileSelectSource(source);
setWaitForFile(source !== SOURCE_NONE); setWaitForFile(source !== SOURCE_NONE);
} }
@ -437,7 +441,7 @@ function PublishFile(props: Props) {
updatePublishForm(publishFormParams); updatePublishForm(publishFormParams);
} }
const showFileUpload = mode === PUBLISH_MODES.FILE; const showFileUpload = mode === PUBLISH_MODES.FILE || PUBLISH_MODES.LIVESTREAM;
const isPublishPost = mode === PUBLISH_MODES.POST; const isPublishPost = mode === PUBLISH_MODES.POST;
return ( return (
@ -514,7 +518,7 @@ function PublishFile(props: Props) {
</fieldset-section> </fieldset-section>
)} )}
{fileSelectSource === SOURCE_UPLOAD && showFileUpload && ( {showSourceSelector && fileSelectSource === SOURCE_UPLOAD && showFileUpload && (
<> <>
<FileSelector <FileSelector
label={__('File')} label={__('File')}
@ -528,86 +532,97 @@ function PublishFile(props: Props) {
{getUploadMessage()} {getUploadMessage()}
</> </>
)} )}
{fileSelectSource === SOURCE_SELECT && showFileUpload && hasLivestreamData && !isCheckingLivestreams && ( {showSourceSelector &&
<> fileSelectSource === SOURCE_SELECT &&
<fieldset-section> showFileUpload &&
<label>{__('Select Replay')}</label> hasLivestreamData &&
<div className="table__wrapper"> !isCheckingLivestreams && (
<table className="table table--livestream-data"> <>
<tbody>
{livestreamData.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE).map((item, i) => (
<tr
onClick={() => setSelectedFileIndex((currentPage - 1) * PAGE_SIZE + i)}
key={item.id}
className={classnames('livestream__data-row', {
'livestream__data-row--selected': selectedFileIndex === (currentPage - 1) * PAGE_SIZE + i,
})}
>
<td>
<FormField
type="radio"
checked={selectedFileIndex === (currentPage - 1) * PAGE_SIZE + i}
label={null}
onClick={() => setSelectedFileIndex((currentPage - 1) * PAGE_SIZE + i)}
className="livestream__data-row-radio"
/>
</td>
<td>
<div className="livestream_thumb_container">
{item.data.thumbnails.slice(0, 3).map((thumb) => (
<img key={thumb} className="livestream___thumb" src={thumb} />
))}
</div>
</td>
<td>
{`${Math.floor(item.data.fileDuration / 60)} ${
Math.floor(item.data.fileDuration / 60) > 1 ? __('minutes') : __('minute')
}`}
<div className="table__item-label">
{`${moment(item.data.uploadedAt).from(moment())}`}
</div>
</td>
<td>
<CopyableText
primaryButton
copyable={normalizeUrlForProtocol(item.data.fileLocation)}
snackMessage={__('Url copied.')}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
</fieldset-section>
<fieldset-group class="fieldset-group--smushed fieldgroup--paginate">
<fieldset-section> <fieldset-section>
<ReactPaginate <label>{__('Select Replay')}</label>
pageCount={totalPages} <div className="table__wrapper">
pageRangeDisplayed={2} <table className="table table--livestream-data">
previousLabel="" <tbody>
nextLabel="" {livestreamData
activeClassName="pagination__item--selected" .slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE)
pageClassName="pagination__item" .map((item, i) => (
previousClassName="pagination__item pagination__item--previous" <tr
nextClassName="pagination__item pagination__item--next" onClick={() => setSelectedFileIndex((currentPage - 1) * PAGE_SIZE + i)}
breakClassName="pagination__item pagination__item--break" key={item.id}
marginPagesDisplayed={2} className={classnames('livestream__data-row', {
onPageChange={(e) => handlePaginateReplays(e.selected + 1)} 'livestream__data-row--selected':
forcePage={currentPage - 1} selectedFileIndex === (currentPage - 1) * PAGE_SIZE + i,
initialPage={currentPage - 1} })}
containerClassName="pagination" >
/> <td>
<FormField
type="radio"
checked={selectedFileIndex === (currentPage - 1) * PAGE_SIZE + i}
label={null}
onClick={() => setSelectedFileIndex((currentPage - 1) * PAGE_SIZE + i)}
className="livestream__data-row-radio"
/>
</td>
<td>
<div className="livestream_thumb_container">
{item.data.thumbnails.slice(0, 3).map((thumb) => (
<img key={thumb} className="livestream___thumb" src={thumb} />
))}
</div>
</td>
<td>
{`${Math.floor(item.data.fileDuration / 60)} ${
Math.floor(item.data.fileDuration / 60) > 1 ? __('minutes') : __('minute')
}`}
<div className="table__item-label">
{`${moment(item.data.uploadedAt).from(moment())}`}
</div>
</td>
<td>
<CopyableText
primaryButton
copyable={normalizeUrlForProtocol(item.data.fileLocation)}
snackMessage={__('Url copied.')}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
</fieldset-section> </fieldset-section>
</fieldset-group> <fieldset-group class="fieldset-group--smushed fieldgroup--paginate">
</> <fieldset-section>
)} <ReactPaginate
{fileSelectSource === SOURCE_SELECT && showFileUpload && !hasLivestreamData && !isCheckingLivestreams && ( pageCount={totalPages}
<div className="main--empty empty"> pageRangeDisplayed={2}
<Empty text={__('No replays found.')} /> previousLabel=""
</div> nextLabel=""
)} activeClassName="pagination__item--selected"
{fileSelectSource === SOURCE_SELECT && showFileUpload && isCheckingLivestreams && ( pageClassName="pagination__item"
previousClassName="pagination__item pagination__item--previous"
nextClassName="pagination__item pagination__item--next"
breakClassName="pagination__item pagination__item--break"
marginPagesDisplayed={2}
onPageChange={(e) => handlePaginateReplays(e.selected + 1)}
forcePage={currentPage - 1}
initialPage={currentPage - 1}
containerClassName="pagination"
/>
</fieldset-section>
</fieldset-group>
</>
)}
{showSourceSelector &&
fileSelectSource === SOURCE_SELECT &&
showFileUpload &&
!hasLivestreamData &&
!isCheckingLivestreams && (
<div className="main--empty empty">
<Empty text={__('No replays found.')} />
</div>
)}
{showSourceSelector && fileSelectSource === SOURCE_SELECT && showFileUpload && isCheckingLivestreams && (
<div className="main--empty empty"> <div className="main--empty empty">
<Spinner small /> <Spinner small />
</div> </div>

View file

@ -32,6 +32,7 @@ import Spinner from 'component/spinner';
import { toHex } from 'util/hex'; import { toHex } from 'util/hex';
import { LIVESTREAM_REPLAY_API } from 'constants/livestream'; import { LIVESTREAM_REPLAY_API } from 'constants/livestream';
import PublishStreamReleaseDate from 'component/publishStreamReleaseDate'; import PublishStreamReleaseDate from 'component/publishStreamReleaseDate';
import { SOURCE_NONE } from 'constants/publish_sources';
// @if TARGET='app' // @if TARGET='app'
import fs from 'fs'; import fs from 'fs';
@ -173,7 +174,8 @@ function PublishForm(props: Props) {
[PUBLISH_MODES.LIVESTREAM]: 'Livestream --[noun, livestream tab button]--', [PUBLISH_MODES.LIVESTREAM]: 'Livestream --[noun, livestream tab button]--',
}; };
const [mode, setMode] = React.useState(_uploadType || PUBLISH_MODES.FILE); const defaultPublishMode = isLivestreamClaim ? PUBLISH_MODES.LIVESTREAM : PUBLISH_MODES.FILE;
const [mode, setMode] = React.useState(_uploadType || defaultPublishMode);
const [isCheckingLivestreams, setCheckingLivestreams] = React.useState(false); const [isCheckingLivestreams, setCheckingLivestreams] = React.useState(false);
let customSubtitle; let customSubtitle;
@ -422,9 +424,8 @@ function PublishForm(props: Props) {
// set mode based on urlParams 'type' // set mode based on urlParams 'type'
useEffect(() => { useEffect(() => {
// Default to standard file publish if none specified
if (!_uploadType) { if (!_uploadType) {
setMode(PUBLISH_MODES.FILE); setMode(defaultPublishMode);
return; return;
} }
@ -448,9 +449,8 @@ function PublishForm(props: Props) {
return; return;
} }
// Default to standard file publish setMode(defaultPublishMode);
setMode(PUBLISH_MODES.FILE); }, [_uploadType, enableLivestream, defaultPublishMode]);
}, [_uploadType, enableLivestream]);
// if we have a type urlparam, update it? necessary? // if we have a type urlparam, update it? necessary?
useEffect(() => { useEffect(() => {
@ -560,6 +560,15 @@ function PublishForm(props: Props) {
} }
}, [mode, updatePublishForm]); }, [mode, updatePublishForm]);
// Source Selector State.
const [fileSelectSource, setFileSelectSource] = useState();
const changeFileSelectSource = (state) => setFileSelectSource(state);
const [showSchedulingOptions, setShowSchedulingOptions] = useState(false);
useEffect(() => {
setShowSchedulingOptions(isLivestreamMode && fileSelectSource === SOURCE_NONE);
}, [isLivestreamMode, fileSelectSource]);
if (publishing) { if (publishing) {
return ( return (
<div className="main--empty"> <div className="main--empty">
@ -568,12 +577,15 @@ function PublishForm(props: Props) {
</div> </div>
); );
} }
// Editing claim uri // Editing claim uri
return ( return (
<div className="card-stack"> <div className="card-stack">
<ChannelSelect hideAnon={isLivestreamMode} disabled={disabled} /> <ChannelSelect hideAnon={isLivestreamMode} disabled={disabled} />
<PublishFile <PublishFile
fileSelectSource={fileSelectSource}
changeFileSelectSource={changeFileSelectSource}
uri={permanentUrl} uri={permanentUrl}
mode={mode} mode={mode}
fileMimeType={fileMimeType} fileMimeType={fileMimeType}
@ -610,7 +622,7 @@ function PublishForm(props: Props) {
{!publishing && ( {!publishing && (
<div className={classnames({ 'card--disabled': formDisabled })}> <div className={classnames({ 'card--disabled': formDisabled })}>
{isLivestreamMode && <Card className={'card--enable-overflow'} body={<PublishStreamReleaseDate />} />} {showSchedulingOptions && <Card className={'card--enable-overflow'} body={<PublishStreamReleaseDate />} />}
{mode !== PUBLISH_MODES.POST && <PublishDescription disabled={formDisabled} />} {mode !== PUBLISH_MODES.POST && <PublishDescription disabled={formDisabled} />}
@ -646,7 +658,7 @@ function PublishForm(props: Props) {
<PublishBid disabled={isStillEditing || formDisabled} /> <PublishBid disabled={isStillEditing || formDisabled} />
{!isLivestreamMode && <PublishPrice disabled={formDisabled} />} {!isLivestreamMode && <PublishPrice disabled={formDisabled} />}
<PublishAdditionalOptions disabled={formDisabled} isLivestreamMode={isLivestreamMode} /> <PublishAdditionalOptions disabled={formDisabled} showSchedulingOptions={showSchedulingOptions} />
</div> </div>
)} )}
<section> <section>

View file

@ -1,5 +1,5 @@
// @flow // @flow
import React from 'react'; import React, { useEffect } from 'react';
import Button from 'component/button'; import Button from 'component/button';
import DateTimePicker from 'react-datetime-picker'; import DateTimePicker from 'react-datetime-picker';
@ -86,6 +86,12 @@ const PublishReleaseDate = (props: Props) => {
} }
} }
useEffect(() => {
return () => {
updatePublishForm({ releaseTimeEdited: undefined });
};
}, []);
return ( return (
<div className="form-field-date-picker"> <div className="form-field-date-picker">
<label>{__('Release date')}</label> <label>{__('Release date')}</label>

View file

@ -59,8 +59,8 @@ const PublishStreamReleaseDate = (props: Props) => {
<div className={'w-full flex flex-col mt-s md:mt-0 md:h-12 md:items-center md:flex-row'}> <div className={'w-full flex flex-col mt-s md:mt-0 md:h-12 md:items-center md:flex-row'}>
<FormField <FormField
type="checkbox" type="radio"
name="rightNow" name="anytime"
disabled={false} disabled={false}
onChange={handleToggle} onChange={handleToggle}
checked={!publishLater} checked={!publishLater}
@ -69,8 +69,8 @@ const PublishStreamReleaseDate = (props: Props) => {
<div className={'md:ml-m mt-s md:mt-0'}> <div className={'md:ml-m mt-s md:mt-0'}>
<FormField <FormField
type="checkbox" type="radio"
name="rightNow" name="scheduled_time"
disabled={false} disabled={false}
onChange={handleToggle} onChange={handleToggle}
checked={publishLater} checked={publishLater}

View file

@ -0,0 +1,3 @@
export const SOURCE_NONE = 'none';
export const SOURCE_SELECT = 'select';
export const SOURCE_UPLOAD = 'upload';

View file

@ -13,6 +13,7 @@ import ChannelThumbnail from 'component/channelThumbnail';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import { NO_FILE } from 'redux/actions/publish'; import { NO_FILE } from 'redux/actions/publish';
import { INTERNAL_TAGS } from 'constants/tags';
type Props = { type Props = {
filePath: string | WebFile, filePath: string | WebFile,
@ -200,9 +201,11 @@ const ModalPublishPreview = (props: Props) => {
<p>{licenseType}</p> <p>{licenseType}</p>
); );
const visibleTags = tags.filter((tag) => !INTERNAL_TAGS.includes(tag.name));
const tagsValue = const tagsValue =
// Do nothing for onClick(). Setting to 'null' results in "View Tag" action -- we don't want to leave the modal. // Do nothing for onClick(). Setting to 'null' results in "View Tag" action -- we don't want to leave the modal.
tags.map((tag) => <Tag key={tag.name} title={tag.name} name={tag.name} type={'flow'} onClick={() => {}} />); visibleTags.map((tag) => <Tag key={tag.name} title={tag.name} name={tag.name} type={'flow'} onClick={() => {}} />);
const depositValue = bid ? <LbcSymbol postfix={`${bid}`} size={14} /> : <p>---</p>; const depositValue = bid ? <LbcSymbol postfix={`${bid}`} size={14} /> : <p>---</p>;

View file

@ -333,6 +333,9 @@ export const makeSelectMetadataForUri = (uri: string) =>
export const makeSelectMetadataItemForUri = (uri: string, key: string) => export const makeSelectMetadataItemForUri = (uri: string, key: string) =>
createSelector(makeSelectMetadataForUri(uri), (metadata: ChannelMetadata | StreamMetadata) => { createSelector(makeSelectMetadataForUri(uri), (metadata: ChannelMetadata | StreamMetadata) => {
if (key === 'tags') {
return metadata.tags ? metadata.tags.filter((tag) => !INTERNAL_TAGS.includes(tag)) : [];
}
return metadata ? metadata[key] : undefined; return metadata ? metadata[key] : undefined;
}); });