2020-08-13 21:06:43 +02:00
|
|
|
// @flow
|
|
|
|
import React from 'react';
|
2021-05-15 06:10:28 +02:00
|
|
|
import moment from 'moment';
|
2020-08-13 21:06:43 +02:00
|
|
|
import Button from 'component/button';
|
2020-08-11 11:54:09 +02:00
|
|
|
import { Form, FormField } from 'component/common/form';
|
2020-08-13 21:06:43 +02:00
|
|
|
import { Modal } from 'modal/modal';
|
|
|
|
import Card from 'component/common/card';
|
|
|
|
import Tag from 'component/tag';
|
|
|
|
import MarkdownPreview from 'component/common/markdown-preview';
|
2022-03-29 09:20:55 +02:00
|
|
|
import { getLanguageName } from 'constants/languages';
|
2020-08-13 21:06:43 +02:00
|
|
|
import { COPYRIGHT, OTHER } from 'constants/licenses';
|
2020-10-01 10:53:46 +02:00
|
|
|
import LbcSymbol from 'component/common/lbc-symbol';
|
2021-03-21 09:38:54 +01:00
|
|
|
import ChannelThumbnail from 'component/channelThumbnail';
|
|
|
|
import * as ICONS from 'constants/icons';
|
|
|
|
import Icon from 'component/common/icon';
|
2021-03-26 23:03:16 +01:00
|
|
|
import { NO_FILE } from 'redux/actions/publish';
|
2021-12-30 22:57:02 +01:00
|
|
|
import { INTERNAL_TAGS } from 'constants/tags';
|
2020-08-13 21:06:43 +02:00
|
|
|
|
|
|
|
type Props = {
|
|
|
|
filePath: string | WebFile,
|
2020-10-01 09:52:52 +02:00
|
|
|
isMarkdownPost: boolean,
|
2020-08-13 21:06:43 +02:00
|
|
|
optimize: boolean,
|
|
|
|
title: ?string,
|
|
|
|
description: ?string,
|
|
|
|
channel: ?string,
|
|
|
|
bid: ?number,
|
|
|
|
uri: ?string,
|
|
|
|
contentIsFree: boolean,
|
|
|
|
fee: {
|
|
|
|
amount: string,
|
|
|
|
currency: string,
|
|
|
|
},
|
|
|
|
language: string,
|
2021-05-15 06:10:28 +02:00
|
|
|
releaseTimeEdited: ?number,
|
2020-08-13 21:06:43 +02:00
|
|
|
licenseType: string,
|
|
|
|
otherLicenseDescription: ?string,
|
|
|
|
licenseUrl: ?string,
|
|
|
|
tags: Array<Tag>,
|
|
|
|
isVid: boolean,
|
|
|
|
ffmpegStatus: any,
|
|
|
|
previewResponse: PublishResponse,
|
|
|
|
publish: (?string, ?boolean) => void,
|
|
|
|
closeModal: () => void,
|
2020-08-11 11:54:09 +02:00
|
|
|
enablePublishPreview: boolean,
|
2021-03-09 15:04:49 +01:00
|
|
|
setEnablePublishPreview: (boolean) => void,
|
2020-08-12 03:24:41 +02:00
|
|
|
isStillEditing: boolean,
|
2021-03-21 09:38:54 +01:00
|
|
|
myChannels: ?Array<ChannelClaim>,
|
2021-03-30 01:05:18 +02:00
|
|
|
publishSuccess: boolean,
|
|
|
|
publishing: boolean,
|
2021-04-14 06:06:11 +02:00
|
|
|
isLivestreamClaim: boolean,
|
|
|
|
remoteFile: string,
|
2022-03-29 09:20:55 +02:00
|
|
|
appLanguage: string,
|
2022-07-11 16:12:37 +02:00
|
|
|
// isLivestreamPublish?: boolean,
|
2020-08-13 21:06:43 +02:00
|
|
|
};
|
|
|
|
|
2021-03-30 01:05:18 +02:00
|
|
|
// class ModalPublishPreview extends React.PureComponent<Props> {
|
|
|
|
const ModalPublishPreview = (props: Props) => {
|
|
|
|
const {
|
|
|
|
filePath,
|
|
|
|
isMarkdownPost,
|
|
|
|
optimize,
|
|
|
|
title,
|
|
|
|
description,
|
|
|
|
channel,
|
|
|
|
bid,
|
|
|
|
uri,
|
|
|
|
contentIsFree,
|
|
|
|
fee,
|
|
|
|
language,
|
2021-05-15 06:10:28 +02:00
|
|
|
releaseTimeEdited,
|
2021-03-30 01:05:18 +02:00
|
|
|
licenseType,
|
|
|
|
otherLicenseDescription,
|
|
|
|
licenseUrl,
|
|
|
|
tags,
|
|
|
|
isVid,
|
|
|
|
ffmpegStatus = {},
|
|
|
|
previewResponse,
|
|
|
|
enablePublishPreview,
|
|
|
|
setEnablePublishPreview,
|
|
|
|
isStillEditing,
|
|
|
|
myChannels,
|
|
|
|
publishSuccess,
|
|
|
|
publishing,
|
|
|
|
publish,
|
|
|
|
closeModal,
|
2021-04-14 06:06:11 +02:00
|
|
|
isLivestreamClaim,
|
|
|
|
remoteFile,
|
2022-03-29 09:20:55 +02:00
|
|
|
appLanguage,
|
2022-07-11 16:12:37 +02:00
|
|
|
// isLivestreamPublish,
|
2021-03-30 01:05:18 +02:00
|
|
|
} = props;
|
2021-04-14 06:06:11 +02:00
|
|
|
|
2021-08-26 16:53:32 +02:00
|
|
|
const maxCharsBeforeOverflow = 128;
|
|
|
|
|
|
|
|
const formattedTitle = React.useMemo(() => {
|
|
|
|
if (title && title.length > maxCharsBeforeOverflow) {
|
|
|
|
return title.slice(0, maxCharsBeforeOverflow).trim() + '...';
|
|
|
|
}
|
|
|
|
return title;
|
|
|
|
}, [title]);
|
|
|
|
|
|
|
|
const formattedUri = React.useMemo(() => {
|
|
|
|
if (uri && uri.length > maxCharsBeforeOverflow) {
|
|
|
|
return uri.slice(0, maxCharsBeforeOverflow).trim() + '...';
|
|
|
|
}
|
|
|
|
return uri;
|
|
|
|
}, [uri]);
|
|
|
|
|
2021-03-30 01:05:18 +02:00
|
|
|
const livestream =
|
2021-04-14 06:06:11 +02:00
|
|
|
(uri && isLivestreamClaim) ||
|
2021-03-30 01:05:18 +02:00
|
|
|
// $FlowFixMe
|
2021-04-14 06:06:11 +02:00
|
|
|
(previewResponse.outputs[0] && previewResponse.outputs[0].value && !previewResponse.outputs[0].value.source);
|
2021-03-30 01:05:18 +02:00
|
|
|
// leave the confirm modal up if we're not going straight to upload/reflecting
|
2021-03-30 04:04:14 +02:00
|
|
|
// @if TARGET='web'
|
2021-03-30 01:05:18 +02:00
|
|
|
React.useEffect(() => {
|
2021-03-30 04:04:14 +02:00
|
|
|
if (publishing && !livestream) {
|
2021-03-30 01:05:18 +02:00
|
|
|
closeModal();
|
|
|
|
} else if (publishSuccess) {
|
|
|
|
closeModal();
|
|
|
|
}
|
|
|
|
}, [publishSuccess, publishing, livestream]);
|
2021-03-30 04:04:14 +02:00
|
|
|
// @endif
|
2022-07-11 16:12:37 +02:00
|
|
|
|
2021-03-30 01:05:18 +02:00
|
|
|
function onConfirmed() {
|
2020-08-13 21:06:43 +02:00
|
|
|
// Publish for real:
|
2021-03-30 01:05:18 +02:00
|
|
|
publish(getFilePathName(filePath), false);
|
2021-03-30 04:04:14 +02:00
|
|
|
// @if TARGET='app'
|
|
|
|
closeModal();
|
|
|
|
// @endif
|
2020-08-13 21:06:43 +02:00
|
|
|
}
|
|
|
|
|
2021-03-30 01:05:18 +02:00
|
|
|
function getFilePathName(filePath: string | WebFile) {
|
2020-08-12 03:24:41 +02:00
|
|
|
if (!filePath) {
|
2021-03-26 23:03:16 +01:00
|
|
|
return NO_FILE;
|
2020-08-12 03:24:41 +02:00
|
|
|
}
|
|
|
|
|
2020-08-13 21:06:43 +02:00
|
|
|
if (typeof filePath === 'string') {
|
|
|
|
return filePath;
|
|
|
|
} else {
|
|
|
|
return filePath.name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-30 01:05:18 +02:00
|
|
|
function createRow(label: string, value: any) {
|
2020-08-13 21:06:43 +02:00
|
|
|
return (
|
|
|
|
<tr>
|
|
|
|
<td>{label}</td>
|
|
|
|
<td>{value}</td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-16 22:59:13 +01:00
|
|
|
const releasesInFuture = releaseTimeEdited && moment(releaseTimeEdited * 1000).isAfter();
|
|
|
|
|
2021-03-30 01:05:18 +02:00
|
|
|
const txFee = previewResponse ? previewResponse['total_fee'] : null;
|
|
|
|
// $FlowFixMe add outputs[0] etc to PublishResponse type
|
|
|
|
const isOptimizeAvail = filePath && filePath !== '' && isVid && ffmpegStatus.available;
|
2022-07-11 16:12:37 +02:00
|
|
|
|
|
|
|
var modalTitle = 'Upload';
|
|
|
|
var confirmBtnText = 'Save';
|
|
|
|
|
2021-03-30 01:05:18 +02:00
|
|
|
if (isStillEditing) {
|
2022-07-11 16:12:37 +02:00
|
|
|
if (livestream || isLivestreamClaim) {
|
2021-04-14 06:06:11 +02:00
|
|
|
modalTitle = __('Confirm Update');
|
|
|
|
} else {
|
|
|
|
modalTitle = __('Confirm Edit');
|
|
|
|
}
|
2022-07-11 16:12:37 +02:00
|
|
|
} else if (livestream || isLivestreamClaim || remoteFile) {
|
|
|
|
modalTitle = releasesInFuture
|
|
|
|
? __('Schedule Livestream')
|
|
|
|
: (!livestream || !isLivestreamClaim) && remoteFile
|
|
|
|
? __('Publish Replay')
|
|
|
|
: __('Create Livestream');
|
|
|
|
} else if (isMarkdownPost) {
|
|
|
|
modalTitle = __('Confirm Post');
|
2021-03-30 01:05:18 +02:00
|
|
|
} else {
|
|
|
|
modalTitle = __('Confirm Upload');
|
2020-08-11 11:54:09 +02:00
|
|
|
}
|
|
|
|
|
2021-03-30 01:05:18 +02:00
|
|
|
if (!publishing) {
|
2022-07-11 16:12:37 +02:00
|
|
|
if (isMarkdownPost) {
|
|
|
|
confirmBtnText = __('Post');
|
|
|
|
} else if (livestream || isLivestreamClaim) {
|
2021-03-24 06:22:02 +01:00
|
|
|
confirmBtnText = __('Create');
|
|
|
|
} else {
|
|
|
|
confirmBtnText = __('Upload');
|
|
|
|
}
|
2021-03-30 01:05:18 +02:00
|
|
|
} else {
|
2022-07-11 16:12:37 +02:00
|
|
|
if (isMarkdownPost) {
|
2021-03-30 01:05:18 +02:00
|
|
|
confirmBtnText = __('Saving');
|
2022-07-11 16:12:37 +02:00
|
|
|
} else if (livestream || isLivestreamClaim) {
|
2021-03-30 01:05:18 +02:00
|
|
|
confirmBtnText = __('Creating');
|
|
|
|
} else {
|
|
|
|
confirmBtnText = __('Uploading');
|
2020-10-01 10:53:46 +02:00
|
|
|
}
|
2021-03-30 01:05:18 +02:00
|
|
|
}
|
2020-10-01 10:53:46 +02:00
|
|
|
|
2021-12-16 22:59:13 +01:00
|
|
|
const releaseDateText = releasesInFuture ? __('Scheduled for') : __('Release date');
|
|
|
|
|
2021-03-30 01:05:18 +02:00
|
|
|
const descriptionValue = description ? (
|
|
|
|
<div className="media__info-text-preview">
|
|
|
|
<MarkdownPreview content={description} simpleLinks />
|
|
|
|
</div>
|
|
|
|
) : null;
|
|
|
|
|
|
|
|
const licenseValue =
|
|
|
|
licenseType === COPYRIGHT ? (
|
|
|
|
<p>© {otherLicenseDescription}</p>
|
|
|
|
) : licenseType === OTHER ? (
|
|
|
|
<p>
|
|
|
|
{otherLicenseDescription}
|
|
|
|
<br />
|
|
|
|
{licenseUrl}
|
|
|
|
</p>
|
|
|
|
) : (
|
2022-03-29 09:20:55 +02:00
|
|
|
<p>{__(licenseType)}</p>
|
2020-08-13 21:06:43 +02:00
|
|
|
);
|
2021-03-30 01:05:18 +02:00
|
|
|
|
2021-12-30 22:57:02 +01:00
|
|
|
const visibleTags = tags.filter((tag) => !INTERNAL_TAGS.includes(tag.name));
|
|
|
|
|
2021-03-30 01:05:18 +02:00
|
|
|
const tagsValue =
|
|
|
|
// Do nothing for onClick(). Setting to 'null' results in "View Tag" action -- we don't want to leave the modal.
|
2021-12-30 22:57:02 +01:00
|
|
|
visibleTags.map((tag) => <Tag key={tag.name} title={tag.name} name={tag.name} type={'flow'} onClick={() => {}} />);
|
2021-03-30 01:05:18 +02:00
|
|
|
|
|
|
|
const depositValue = bid ? <LbcSymbol postfix={`${bid}`} size={14} /> : <p>---</p>;
|
|
|
|
|
|
|
|
let priceValue = __('Free');
|
|
|
|
if (!contentIsFree) {
|
|
|
|
if (fee.currency === 'LBC') {
|
|
|
|
priceValue = <LbcSymbol postfix={fee.amount} />;
|
|
|
|
} else {
|
|
|
|
priceValue = `${fee.amount} ${fee.currency}`;
|
|
|
|
}
|
2020-08-13 21:06:43 +02:00
|
|
|
}
|
2021-03-30 01:05:18 +02:00
|
|
|
|
|
|
|
const channelValue = (channel) => {
|
|
|
|
const channelClaim = myChannels && myChannels.find((x) => x.name === channel);
|
|
|
|
return channel ? (
|
|
|
|
<div className="channel-value">
|
ChannelThumbnail improvements
- [x] (6332) The IntersectionObserver method of lazy-loading loads cached images visibly late on slower devices. Previously, it was also showing the "broken image" icon briefly, which we mended by placing a dummy transparent image as the initial src.
- Reverted that ugly transparent image fix.
- Use the browser's built-in `loading="lazy"` instead. Sorry, Safari.
- [x] Size-optimization did not take "device pixel ratio" into account.
- When resizing an image through the CDN, we can't just take the dimensions of the tag in pixels directly -- we need to take zooming into account, otherwise the image ends up blurry.
- Previously, we quickly disabled optimization for the channel avatar in the Channel Page because of this. Now that we know the root-cause, the change was reverted and we now go through the CDN with appropriate sizes. This also improves our Web Vital scores.
- [x] Size-optimization wasn't really implemented for all ChannelThumbnail instances.
- The CDN-optimized size was hardcoded to the largest instance, so small images like sidebar thumbnails are still loading images that are unnecessarily larger.
- There's a little-bit of hardcoding of values from CSS here, but I think it's a ok compromise (not something we change often). It also doesn't need to be exact -- the "device pixel ratio" calculate will ensure it's slightly larger than what we need.
- [x] Set `width` and `height` of `<img>` to improve CLS.
- Addresses Ligthhouse complaints, although technically the shifting was addressed at the `ClaimPreviewTile` level (sub-container dimensions are well defined).
- Notes: the values don't need to be the final CSS-adjusted sizes. It just needs to be in the right aspect ratio to help the browser pre-allocate space to avoid shifts.
- [x] Add option to disable lazy-load Channel Thumbnails
- The guidelines mentioned that items that are already in the viewport should not enable `loading="lazy"`.
- We have a few areas where it doesn't make sense to lazy-load (e.g. thumbnail in Header, channel selector dropdown, publish preview, etc.).
2021-07-05 07:20:40 +02:00
|
|
|
{channelClaim && <ChannelThumbnail xsmall noLazyLoad uri={channelClaim.permanent_url} />}
|
2021-03-30 01:05:18 +02:00
|
|
|
{channel}
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<div className="channel-value">
|
|
|
|
<Icon sectionIcon icon={ICONS.ANONYMOUS} />
|
|
|
|
<i>{__('Anonymous')}</i>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2021-05-15 06:10:28 +02:00
|
|
|
const releaseTimeStr = (time) => {
|
2022-03-29 09:20:55 +02:00
|
|
|
if (time) {
|
|
|
|
try {
|
|
|
|
return new Date(time * 1000).toLocaleString(appLanguage);
|
|
|
|
} catch {
|
|
|
|
return moment(new Date(time * 1000)).format('MMMM Do, YYYY - h:mm a');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return '';
|
|
|
|
}
|
2021-05-15 06:10:28 +02:00
|
|
|
};
|
|
|
|
|
2021-03-30 01:05:18 +02:00
|
|
|
return (
|
|
|
|
<Modal isOpen contentLabel={modalTitle} type="card" onAborted={closeModal}>
|
|
|
|
<Form onSubmit={onConfirmed}>
|
|
|
|
<Card
|
|
|
|
title={modalTitle}
|
|
|
|
body={
|
|
|
|
<>
|
|
|
|
<div className="section">
|
|
|
|
<table className="table table--condensed table--publish-preview">
|
|
|
|
<tbody>
|
|
|
|
{!livestream && !isMarkdownPost && createRow(__('File'), getFilePathName(filePath))}
|
2021-04-14 06:06:11 +02:00
|
|
|
{livestream && remoteFile && createRow(__('Replay'), __('Remote File Selected'))}
|
2022-07-11 16:12:37 +02:00
|
|
|
{livestream && filePath && createRow(__('Replay'), __('Manual Upload'))}
|
2021-03-30 01:05:18 +02:00
|
|
|
{isOptimizeAvail && createRow(__('Transcode'), optimize ? __('Yes') : __('No'))}
|
2021-08-26 16:53:32 +02:00
|
|
|
{createRow(__('Title'), formattedTitle)}
|
2021-03-30 01:05:18 +02:00
|
|
|
{createRow(__('Description'), descriptionValue)}
|
|
|
|
{createRow(__('Channel'), channelValue(channel))}
|
2021-08-26 16:53:32 +02:00
|
|
|
{createRow(__('URL'), formattedUri)}
|
2021-03-30 01:05:18 +02:00
|
|
|
{createRow(__('Deposit'), depositValue)}
|
|
|
|
{createRow(__('Price'), priceValue)}
|
2022-03-29 09:20:55 +02:00
|
|
|
{createRow(__('Language'), language ? getLanguageName(language) : '')}
|
2021-12-16 22:59:13 +01:00
|
|
|
{releaseTimeEdited && createRow(releaseDateText, releaseTimeStr(releaseTimeEdited))}
|
2021-03-30 01:05:18 +02:00
|
|
|
{createRow(__('License'), licenseValue)}
|
|
|
|
{createRow(__('Tags'), tagsValue)}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
</div>
|
|
|
|
{txFee && (
|
|
|
|
<div className="section" aria-label={__('Estimated transaction fee:')}>
|
|
|
|
<b>{__('Est. transaction fee:')}</b>
|
|
|
|
<em>
|
|
|
|
<LbcSymbol postfix={txFee} />
|
|
|
|
</em>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
actions={
|
|
|
|
<>
|
|
|
|
<div className="section__actions">
|
|
|
|
<Button autoFocus button="primary" disabled={publishing} label={confirmBtnText} onClick={onConfirmed} />
|
|
|
|
<Button button="link" label={__('Cancel')} onClick={closeModal} />
|
|
|
|
</div>
|
|
|
|
<p className="help">{__('Once the transaction is sent, it cannot be reversed.')}</p>
|
|
|
|
<FormField
|
|
|
|
type="checkbox"
|
|
|
|
name="sync_toggle"
|
|
|
|
label={__('Skip preview and confirmation')}
|
|
|
|
checked={!enablePublishPreview}
|
|
|
|
onChange={() => setEnablePublishPreview(!enablePublishPreview)}
|
|
|
|
/>
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</Form>
|
|
|
|
</Modal>
|
|
|
|
);
|
|
|
|
};
|
2020-08-13 21:06:43 +02:00
|
|
|
|
|
|
|
export default ModalPublishPreview;
|