Thumbnail fixes #6860

Merged
saltrafael merged 1 commit from thumbnail_fixes into master 2021-08-25 02:28:23 +02:00
9 changed files with 97 additions and 109 deletions

View file

@ -22,6 +22,8 @@ import SUPPORTED_LANGUAGES from 'constants/supported_languages';
import WalletSpendableBalanceHelp from 'component/walletSpendableBalanceHelp';
import { SIMPLE_SITE } from 'config';
import { sortLanguageMap } from 'util/default-languages';
import ThumbnailBrokenImage from 'component/selectThumbnail/thumbnail-broken.png';
import Gerbil from 'component/channelThumbnail/gerbil.png';
const LANG_NONE = 'none';
@ -52,7 +54,7 @@ type Props = {
onDone: () => void,
openModal: (
id: string,
{ onUpdate: (string) => void, assetName: string, helpText: string, currentValue: string, title: string }
{ onUpdate: (string, boolean) => void, assetName: string, helpText: string, currentValue: string, title: string }
) => void,
uri: string,
disabled: boolean,
@ -90,7 +92,9 @@ function ChannelForm(props: Props) {
} = props;
const [nameError, setNameError] = React.useState(undefined);
const [bidError, setBidError] = React.useState('');
const [isUpload, setIsUpload] = React.useState({ cover: false, thumbnail: false });
const [coverError, setCoverError] = React.useState(false);
const [thumbError, setThumbError] = React.useState(false);
const { claim_id: claimId } = claim || {};
const [params, setParams]: [any, (any) => void] = React.useState(getChannelParams());
const { channelName } = parseURI(uri);
@ -112,10 +116,12 @@ function ChannelForm(props: Props) {
creatingChannel ||
updatingChannel ||
nameError ||
thumbError ||
coverError ||
bidError ||
(isNewChannel && !params.name)
);
}, [isClaimingInitialRewards, creatingChannel, updatingChannel, nameError, bidError, isNewChannel, params]);
}, [isClaimingInitialRewards, creatingChannel, updatingChannel, nameError, thumbError, coverError, bidError, isNewChannel, params.name]);
function getChannelParams() {
// fill this in with sdk data
@ -195,12 +201,16 @@ function ChannelForm(props: Props) {
setParams({ ...params, languages: langs });
}
function handleThumbnailChange(thumbnailUrl: string) {
function handleThumbnailChange(thumbnailUrl: string, uploadSelected: boolean) {
setParams({ ...params, thumbnailUrl });
setIsUpload({ ...isUpload, thumbnail: uploadSelected });
setThumbError(false);
}
function handleCoverChange(coverUrl: string) {
function handleCoverChange(coverUrl: string, uploadSelected: boolean) {
setParams({ ...params, coverUrl });
setIsUpload({ ...isUpload, cover: uploadSelected });
setCoverError(false);
}
function handleSubmit() {
@ -225,6 +235,9 @@ function ChannelForm(props: Props) {
if (errorMsg && errorMsg.includes(LIMIT_ERR_PARTIAL_MSG)) {
errorMsg = __('Transaction limit reached. Try reducing the Description length.');
}
if ((!isUpload.thumbnail && thumbError) || (!isUpload.cover && coverError)) {
errorMsg = __('Invalid %error_type%', { error_type: (thumbError && 'thumbnail') || (coverError && 'cover image') });
}
React.useEffect(() => {
let nameError;
@ -247,6 +260,17 @@ function ChannelForm(props: Props) {
}
}, [hasClaimedInitialRewards, claimInitialRewards]);
const coverSrc = coverError ? ThumbnailBrokenImage : params.coverUrl;
let thumbnailPreview;
if (!params.thumbnailUrl) {
thumbnailPreview = Gerbil;
} else if (thumbError) {
thumbnailPreview = ThumbnailBrokenImage;
} else {
thumbnailPreview = params.thumbnailUrl;
}
// TODO clear and bail after submit
return (
<>
@ -258,7 +282,7 @@ function ChannelForm(props: Props) {
title={__('Cover')}
onClick={() =>
openModal(MODALS.IMAGE_UPLOAD, {
onUpdate: (coverUrl) => handleCoverChange(coverUrl),
onUpdate: (coverUrl, isUpload) => handleCoverChange(coverUrl, isUpload),
title: __('Edit Cover Image'),
helpText: __('(6.25:1)'),
assetName: __('Cover Image'),
@ -270,11 +294,16 @@ function ChannelForm(props: Props) {
/>
</div>
{params.coverUrl &&
(coverError ? (
(coverError && isUpload.cover ? (
<div className="channel-cover__custom--waiting">{__('This will be visible in a few minutes.')}</div>
) : (
<img className="channel-cover__custom" src={params.coverUrl} onError={() => setCoverError(true)} />
))}
<img
className="channel-cover__custom"
src={coverSrc}
onError={() => setCoverError(true)}
/>
)
)}
<div className="channel__primary-info">
<div className="channel__edit-thumb">
<Button
@ -282,7 +311,7 @@ function ChannelForm(props: Props) {
title={__('Edit')}
onClick={() =>
openModal(MODALS.IMAGE_UPLOAD, {
onUpdate: (v) => handleThumbnailChange(v),
onUpdate: (thumbnailUrl, isUpload) => handleThumbnailChange(thumbnailUrl, isUpload),
title: __('Edit Thumbnail Image'),
helpText: __('(1:1)'),
assetName: __('Thumbnail'),
@ -296,9 +325,11 @@ function ChannelForm(props: Props) {
<ChannelThumbnail
className="channel__thumbnail--channel-page"
uri={uri}
thumbnailPreview={params.thumbnailUrl}
thumbnailPreview={thumbnailPreview}
allowGifs
showDelayedMessage
showDelayedMessage={isUpload.thumbnail}
setThumbError={(v) => setThumbError(v)}
thumbError={thumbError}
/>
<h1 className="channel__title">
{params.title || (channelName && '@' + channelName) || (params.name && '@' + params.name)}

View file

@ -23,6 +23,10 @@ type Props = {
showDelayedMessage?: boolean,
noLazyLoad?: boolean,
hideStakedIndicator?: boolean,
xsmall?: boolean,
noOptimization?: boolean,
setThumbError: (boolean) => void,
thumbError: boolean,
};
function ChannelThumbnail(props: Props) {
@ -41,15 +45,15 @@ function ChannelThumbnail(props: Props) {
showDelayedMessage = false,
noLazyLoad,
hideStakedIndicator = false,
setThumbError,
} = props;
const [thumbError, setThumbError] = React.useState(false);
const shouldResolve = claim === undefined;
const thumbnail = rawThumbnail && rawThumbnail.trim().replace(/^http:\/\//i, 'https://');
const thumbnailPreview = rawThumbnailPreview && rawThumbnailPreview.trim().replace(/^http:\/\//i, 'https://');
const channelThumbnail = thumbnail || thumbnailPreview;
const defaultAvater = AVATAR_DEFAULT || Gerbil;
const channelThumbnail = thumbnailPreview || thumbnail || defaultAvater;
const isGif = channelThumbnail && channelThumbnail.endsWith('gif');
const showThumb = (!obscure && !!thumbnail) || thumbnailPreview;
const defaultAvater = AVATAR_DEFAULT || Gerbil;
// Generate a random color class based on the first letter of the channel name
const { channelName } = parseURI(uri);
@ -75,6 +79,7 @@ function ChannelThumbnail(props: Props) {
</FreezeframeWrapper>
);
}
return (
<div
className={classnames('channel-thumbnail', className, {
@ -84,30 +89,17 @@ function ChannelThumbnail(props: Props) {
'channel-thumbnail--resolving': isResolving,
})}
>
{!showThumb && (
{showDelayedMessage ? (
<div className="channel-thumbnail--waiting">{__('This will be visible in a few minutes.')}</div>
) : (
<OptimizedImage
alt={__('Channel profile picture')}
className="channel-thumbnail__default"
src={!thumbError && channelThumbnail ? channelThumbnail : defaultAvater}
className={!channelThumbnail ? 'channel-thumbnail__default' : 'channel-thumbnail__custom'}
src={channelThumbnail}
loading={noLazyLoad ? undefined : 'lazy'}
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
onError={() => setThumbError(true)}
/>
)}
{showThumb && (
<>
{showDelayedMessage && thumbError ? (
<div className="chanel-thumbnail--waiting">{__('This will be visible in a few minutes.')}</div>
) : (
<OptimizedImage
alt={__('Channel profile picture')}
className="channel-thumbnail__custom"
src={!thumbError && channelThumbnail ? channelThumbnail : defaultAvater}
loading={noLazyLoad ? undefined : 'lazy'}
onError={() => setThumbError(true)} // if thumb fails (including due to https replace, show gerbil.
/>
)}
</>
)}
{!hideStakedIndicator && <ChannelStakedIndicator uri={uri} claim={claim} />}
</div>
);

View file

@ -7,7 +7,7 @@ import { FormField } from 'component/common/form';
type Props = {
type: string,
currentPath?: ?string,
onFileChosen: WebFile => void,
onFileChosen: (WebFile) => void,
label?: string,
placeholder?: string,
accept?: string,
@ -19,17 +19,6 @@ type Props = {
class FileSelector extends React.PureComponent<Props> {
static defaultProps = {
autoFocus: false,
};
componentDidUpdate(prevProps: Props) {
// If the form has just been cleared,
// clear the file input
if (prevProps.currentPath && !this.props.currentPath) {
this.fileInput.current.value = null;
}
}
static defaultProps = {
type: 'file',
};
@ -39,6 +28,7 @@ class FileSelector extends React.PureComponent<Props> {
super();
this.fileInput = React.createRef();
this.handleFileInputSelection = this.handleFileInputSelection.bind(this);
this.handleDirectoryInputSelection = this.handleDirectoryInputSelection.bind(this);
this.fileInputButton = this.fileInputButton.bind(this);
}
@ -53,6 +43,7 @@ class FileSelector extends React.PureComponent<Props> {
if (this.props.onFileChosen) {
this.props.onFileChosen(file);
}
this.fileInput.current.value = null; // clear the file input
};
handleDirectoryInputSelection = () => {

View file

@ -1,5 +1,4 @@
// @flow
import React from 'react';
import FileSelector from 'component/common/file-selector';
import { SPEECH_URLS } from 'lbry-redux';
@ -8,7 +7,6 @@ import Button from 'component/button';
import Card from 'component/common/card';
import { generateThumbnailName } from 'util/generate-thumbnail-name';
import usePersistedState from 'effects/use-persisted-state';
import classnames from 'classnames';
const accept = '.png, .jpg, .jpeg, .gif';
const SPEECH_READY = 'READY';
@ -17,7 +15,7 @@ const SPEECH_UPLOADING = 'UPLOADING';
type Props = {
assetName: string,
currentValue: ?string,
onUpdate: (string) => void,
onUpdate: (string, boolean) => void,
recommended: string,
title: string,
onDone?: () => void,
@ -25,27 +23,14 @@ type Props = {
};
function SelectAsset(props: Props) {
const { onUpdate, onDone, assetName, recommended, title, inline } = props;
const { onUpdate, onDone, assetName, currentValue, recommended, title, inline } = props;
const [pathSelected, setPathSelected] = React.useState('');
const [fileSelected, setFileSelected] = React.useState<any>(null);
const [uploadStatus, setUploadStatus] = React.useState(SPEECH_READY);
const [useUrl, setUseUrl] = usePersistedState('thumbnail-upload:mode', false);
const [url, setUrl] = React.useState('');
const [url, setUrl] = React.useState(currentValue);
const [error, setError] = React.useState();
React.useEffect(() => {
if (pathSelected && fileSelected) {
doUploadAsset();
}
}, [pathSelected, fileSelected]);
function handleToggleMode(useUrl) {
setPathSelected('');
setFileSelected(null);
setUrl('');
setUseUrl(useUrl);
}
function doUploadAsset() {
const uploadError = (error = '') => {
setError(error);
@ -53,7 +38,7 @@ function SelectAsset(props: Props) {
const onSuccess = (thumbnailUrl) => {
setUploadStatus(SPEECH_READY);
onUpdate(thumbnailUrl);
onUpdate(thumbnailUrl, !useUrl);
if (onDone) {
onDone();
@ -93,30 +78,6 @@ function SelectAsset(props: Props) {
}
const formBody = (
<>
<div className={'section__header--actions'}>
<div>
<Button
button="alt"
className={classnames('button-toggle', {
'button-toggle--active': useUrl, // disable on upload status
})}
label={__('URL')}
onClick={() => {
handleToggleMode(true);
}}
/>
<Button
button="alt"
className={classnames('button-toggle', {
'button-toggle--active': !useUrl, // disable on upload status
})}
label={__('Upload')}
onClick={() => {
handleToggleMode(false);
}}
/>
</div>
</div>
<fieldset-section>
{error && <div className="error__text">{error}</div>}
{useUrl ? (
@ -129,7 +90,7 @@ function SelectAsset(props: Props) {
value={url}
onChange={(e) => {
setUrl(e.target.value);
onUpdate(e.target.value);
onUpdate(e.target.value, !useUrl);
}}
/>
) : (
@ -151,6 +112,25 @@ function SelectAsset(props: Props) {
/>
)}
</fieldset-section>
<div className="section__actions">
{onDone && (
<Button
button="primary"
type="submit"
label={__('Done')}
disabled={!useUrl && ((uploadStatus === SPEECH_UPLOADING) || !pathSelected || !fileSelected)}
onClick={() => doUploadAsset()}
/>
)}
<FormField
name="toggle-upload"
type="checkbox"
label={__('Use a URL')}
checked={useUrl}
onChange={() => setUseUrl(!useUrl)}
/>
</div>
</>
);

View file

@ -93,9 +93,7 @@ class SelectThumbnail extends React.PureComponent<Props> {
style={{ display: 'none' }}
src={thumbnailSrc}
alt={__('Thumbnail Preview')}
onError={(e) => {
updatePublishForm({ thumbnailError: true });
}}
onError={() => updatePublishForm({ thumbnailError: true })}
/>
</div>
<div className="column__item">
@ -130,12 +128,7 @@ class SelectThumbnail extends React.PureComponent<Props> {
)}
{status === THUMBNAIL_STATUSES.COMPLETE && thumbnail && (
<div className="column column--space-between">
<div
className="column__item thumbnail-preview"
// style={{ backgroundImage: `url(${thumbnail})` }}
>
{__('This will be visible in a few minutes.')}
</div>
<div className="column__item thumbnail-preview" style={{ backgroundImage: `url(${thumbnail})` }} />
<div className="column__item">
<p>{__('Upload complete.')}</p>
<div className="section__actions">

View file

@ -13,13 +13,16 @@ type Props = {
class ModalConfirmThumbnailUpload extends React.PureComponent<Props> {
upload() {
const { upload, updatePublishForm, closeModal, file } = this.props;
upload(file);
updatePublishForm({ thumbnailPath: file.path });
closeModal();
if (file) {
upload(file);
updatePublishForm({ thumbnailPath: file.path });
closeModal();
}
}
render() {
const { closeModal, file } = this.props;
const filePath = file && (file.path || file.name);
return (
<Modal
@ -33,7 +36,7 @@ class ModalConfirmThumbnailUpload extends React.PureComponent<Props> {
>
<label>{__('Are you sure you want to upload this thumbnail to %domain%', { domain: DOMAIN })}?</label>
<blockquote>{file.path || file.name}</blockquote>
<blockquote>{filePath}</blockquote>
</Modal>
);
}

View file

@ -2,10 +2,8 @@ import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app';
import ModalImageUpload from './view';
const perform = dispatch => () => ({
closeModal: () => {
dispatch(doHideModal());
},
const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()),
});
export default connect(null, perform)(ModalImageUpload);

View file

@ -8,7 +8,7 @@ type Props = {
currentValue: string,
title: string,
helpText: string,
onUpdate: string => void,
onUpdate: (string, boolean) => void,
assetName: string,
};
@ -18,7 +18,7 @@ function ModalImageUpload(props: Props) {
return (
<Modal isOpen type="card" onAborted={closeModal} contentLabel={title}>
<SelectAsset
onUpdate={v => onUpdate(v)}
onUpdate={(a, b) => onUpdate(a, b)}
currentValue={currentValue}
assetName={assetName}
recommended={helpText}

View file

@ -84,7 +84,7 @@ $actions-z-index: 2;
margin-right: var(--spacing-xs);
}
.chanel-thumbnail--waiting {
.channel-thumbnail--waiting {
background-color: var(--color-gray-5);
border-radius: var(--border-radius);
padding-top: 4rem;