diff --git a/packages/block-editor/src/components/index.native.js b/packages/block-editor/src/components/index.native.js index 45451908a34472..21a9b1114ce5fa 100644 --- a/packages/block-editor/src/components/index.native.js +++ b/packages/block-editor/src/components/index.native.js @@ -45,13 +45,13 @@ export { MEDIA_TYPE_AUDIO, MEDIA_TYPE_ANY, } from './media-upload/constants'; +export { default as MediaUploadProgress } from './media-upload-progress'; export { - default as MediaUploadProgress, MEDIA_UPLOAD_STATE_UPLOADING, MEDIA_UPLOAD_STATE_SUCCEEDED, MEDIA_UPLOAD_STATE_FAILED, MEDIA_UPLOAD_STATE_RESET, -} from './media-upload-progress'; +} from './media-upload-progress/constants'; export { default as BlockMediaUpdateProgress } from './block-media-update-progress'; export { default as URLInput } from './url-input'; export { default as BlockInvalidWarning } from './block-list/block-invalid-warning'; diff --git a/packages/block-editor/src/components/media-upload-progress/constants.js b/packages/block-editor/src/components/media-upload-progress/constants.js new file mode 100644 index 00000000000000..4003cd30e44c53 --- /dev/null +++ b/packages/block-editor/src/components/media-upload-progress/constants.js @@ -0,0 +1,6 @@ +export const MEDIA_UPLOAD_STATE_IDLE = 0; +export const MEDIA_UPLOAD_STATE_UPLOADING = 1; +export const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; +export const MEDIA_UPLOAD_STATE_FAILED = 3; +export const MEDIA_UPLOAD_STATE_RESET = 4; +export const MEDIA_UPLOAD_STATE_PAUSED = 11; diff --git a/packages/block-editor/src/components/media-upload-progress/index.native.js b/packages/block-editor/src/components/media-upload-progress/index.native.js index b64b08eec09d8f..cb5a25d0bb8669 100644 --- a/packages/block-editor/src/components/media-upload-progress/index.native.js +++ b/packages/block-editor/src/components/media-upload-progress/index.native.js @@ -15,23 +15,28 @@ import { subscribeMediaUpload } from '@wordpress/react-native-bridge'; * Internal dependencies */ import styles from './styles.scss'; - -export const MEDIA_UPLOAD_STATE_UPLOADING = 1; -export const MEDIA_UPLOAD_STATE_SUCCEEDED = 2; -export const MEDIA_UPLOAD_STATE_FAILED = 3; -export const MEDIA_UPLOAD_STATE_RESET = 4; +import { + MEDIA_UPLOAD_STATE_IDLE, + MEDIA_UPLOAD_STATE_UPLOADING, + MEDIA_UPLOAD_STATE_SUCCEEDED, + MEDIA_UPLOAD_STATE_FAILED, + MEDIA_UPLOAD_STATE_RESET, + MEDIA_UPLOAD_STATE_PAUSED, +} from './constants'; export class MediaUploadProgress extends Component { constructor( props ) { super( props ); this.state = { + uploadState: MEDIA_UPLOAD_STATE_IDLE, progress: 0, isUploadInProgress: false, isUploadFailed: false, }; this.mediaUpload = this.mediaUpload.bind( this ); + this.getRetryMessage = this.getRetryMessage.bind( this ); } componentDidMount() { @@ -45,7 +50,11 @@ export class MediaUploadProgress extends Component { mediaUpload( payload ) { const { mediaId } = this.props; - if ( payload.mediaId !== mediaId ) { + if ( + payload.mediaId !== mediaId || + ( payload.state === this.state.uploadState && + payload.progress === this.state.progress ) + ) { return; } @@ -56,6 +65,9 @@ export class MediaUploadProgress extends Component { case MEDIA_UPLOAD_STATE_SUCCEEDED: this.finishMediaUploadWithSuccess( payload ); break; + case MEDIA_UPLOAD_STATE_PAUSED: + this.finishMediaUploadWithPause( payload ); + break; case MEDIA_UPLOAD_STATE_FAILED: this.finishMediaUploadWithFailure( payload ); break; @@ -68,6 +80,7 @@ export class MediaUploadProgress extends Component { updateMediaProgress( payload ) { this.setState( { progress: payload.progress, + uploadState: payload.state, isUploadInProgress: true, isUploadFailed: false, } ); @@ -77,21 +90,48 @@ export class MediaUploadProgress extends Component { } finishMediaUploadWithSuccess( payload ) { - this.setState( { isUploadInProgress: false } ); + this.setState( { + uploadState: payload.state, + isUploadInProgress: false, + } ); if ( this.props.onFinishMediaUploadWithSuccess ) { this.props.onFinishMediaUploadWithSuccess( payload ); } } + finishMediaUploadWithPause( payload ) { + if ( ! this.props.enablePausedUploads ) { + this.finishMediaUploadWithFailure( payload ); + return; + } + + this.setState( { + uploadState: payload.state, + isUploadInProgress: true, + isUploadFailed: false, + } ); + if ( this.props.onFinishMediaUploadWithFailure ) { + this.props.onFinishMediaUploadWithFailure( payload ); + } + } + finishMediaUploadWithFailure( payload ) { - this.setState( { isUploadInProgress: false, isUploadFailed: true } ); + this.setState( { + uploadState: payload.state, + isUploadInProgress: false, + isUploadFailed: true, + } ); if ( this.props.onFinishMediaUploadWithFailure ) { this.props.onFinishMediaUploadWithFailure( payload ); } } mediaUploadStateReset( payload ) { - this.setState( { isUploadInProgress: false, isUploadFailed: false } ); + this.setState( { + uploadState: payload.state, + isUploadInProgress: false, + isUploadFailed: false, + } ); if ( this.props.onMediaUploadStateReset ) { this.props.onMediaUploadStateReset( payload ); } @@ -115,15 +155,24 @@ export class MediaUploadProgress extends Component { } } + getRetryMessage() { + if ( + this.state.uploadState === MEDIA_UPLOAD_STATE_PAUSED && + this.props.enablePausedUploads + ) { + return __( 'Waiting for connection' ); + } + + // eslint-disable-next-line @wordpress/i18n-no-collapsible-whitespace + return __( 'Failed to insert media.\nTap for more info.' ); + } + render() { const { renderContent = () => null } = this.props; - const { isUploadInProgress, isUploadFailed } = this.state; + const { isUploadInProgress, isUploadFailed, uploadState } = this.state; const showSpinner = this.state.isUploadInProgress; const progress = this.state.progress * 100; - // eslint-disable-next-line @wordpress/i18n-no-collapsible-whitespace - const retryMessage = __( - 'Failed to insert media.\nTap for more info.' - ); + const retryMessage = this.getRetryMessage(); const progressBarStyle = [ styles.progressBar, @@ -149,6 +198,9 @@ export class MediaUploadProgress extends Component { ) } { renderContent( { + isUploadPaused: + uploadState === MEDIA_UPLOAD_STATE_PAUSED && + this.props.enablePausedUploads, isUploadInProgress, isUploadFailed, retryMessage, diff --git a/packages/block-editor/src/components/media-upload-progress/test/index.native.js b/packages/block-editor/src/components/media-upload-progress/test/index.native.js index 1185c9c35a8682..e5a6b460d94ef5 100644 --- a/packages/block-editor/src/components/media-upload-progress/test/index.native.js +++ b/packages/block-editor/src/components/media-upload-progress/test/index.native.js @@ -14,13 +14,13 @@ import { /** * Internal dependencies */ +import { MediaUploadProgress } from '../'; import { - MediaUploadProgress, MEDIA_UPLOAD_STATE_UPLOADING, MEDIA_UPLOAD_STATE_SUCCEEDED, MEDIA_UPLOAD_STATE_FAILED, MEDIA_UPLOAD_STATE_RESET, -} from '../'; +} from '../constants'; let uploadCallBack; subscribeMediaUpload.mockImplementation( ( callback ) => { diff --git a/packages/block-library/src/image/edit.native.js b/packages/block-library/src/image/edit.native.js index 5c5308af5dfc3c..604fb8a7ce52b2 100644 --- a/packages/block-library/src/image/edit.native.js +++ b/packages/block-library/src/image/edit.native.js @@ -812,6 +812,7 @@ export class ImageEdit extends Component { { ! this.state.isCaptionSelected && getToolbarEditButton( openMediaOptions ) } { + let icon; let iconStyle; switch ( iconType ) { case ICON_TYPE.RETRY: - return ( - - ); + icon = retryIcon || SvgIconRetry; + iconStyle = iconRetryStyles; + break; + case ICON_TYPE.OFFLINE: + icon = offline; + iconStyle = iconOfflineStyles; + break; case ICON_TYPE.PLACEHOLDER: + icon = image; iconStyle = iconPlaceholderStyles; break; case ICON_TYPE.UPLOAD: + icon = image; iconStyle = iconUploadStyles; break; } @@ -130,6 +134,31 @@ const ImageComponent = ( { styles.iconUploadDark ); + const iconOfflineStyles = usePreferredColorSchemeStyle( + styles.iconOffline, + styles.iconOfflineDark + ); + + const retryIconStyles = usePreferredColorSchemeStyle( + styles.retryIcon, + styles.retryIconDark + ); + + const iconRetryStyles = usePreferredColorSchemeStyle( + styles.iconRetry, + styles.iconRetryDark + ); + + const retryContainerStyles = usePreferredColorSchemeStyle( + styles.retryContainer, + styles.retryContainerDark + ); + + const uploadFailedTextStyles = usePreferredColorSchemeStyle( + styles.uploadFailedText, + styles.uploadFailedTextDark + ); + const placeholderStyles = [ usePreferredColorSchemeStyle( styles.imageContainerUpload, @@ -216,9 +245,11 @@ const ImageComponent = ( { > { isSelected && highlightSelected && - ! ( isUploadInProgress || isUploadFailed ) && ( - - ) } + ! ( + isUploadInProgress || + isUploadFailed || + isUploadPaused + ) && } { ! imageData ? ( @@ -239,22 +270,24 @@ const ImageComponent = ( { ) } - { isUploadFailed && retryMessage && ( + { ( isUploadFailed || isUploadPaused ) && retryMessage && ( - { getIcon( ICON_TYPE.RETRY ) } + { isUploadPaused + ? getIcon( ICON_TYPE.OFFLINE ) + : getIcon( ICON_TYPE.RETRY ) } - + { retryMessage } @@ -265,7 +298,11 @@ const ImageComponent = ( { ) } diff --git a/packages/components/src/mobile/image/style.native.scss b/packages/components/src/mobile/image/style.native.scss index f6deb3655f3699..040a8e507667e8 100644 --- a/packages/components/src/mobile/image/style.native.scss +++ b/packages/components/src/mobile/image/style.native.scss @@ -21,10 +21,23 @@ } .retryIcon { - width: 80px; - height: 80px; - justify-content: center; - align-items: center; + background-color: $black; + border-radius: 200px; + padding: 8px; +} + +.retryIconDark { + background-color: $white; +} + +.iconOffline { + fill: $white; + width: 24px; + height: 24px; +} + +.iconOfflineDark { + fill: $black; } .customRetryIcon { @@ -33,9 +46,13 @@ } .iconRetry { - fill: #fff; - width: 100%; - height: 100%; + fill: $white; + width: 24px; + height: 24px; +} + +.iconRetryDark { + fill: $black; } .iconPlaceholder { @@ -90,12 +107,17 @@ } .uploadFailedText { - color: #fff; + color: $black; + font-weight: bold; font-size: 14; margin-top: 5; text-align: center; } +.uploadFailedTextDark { + color: $white; +} + .editContainer { width: 44px; height: 44px; @@ -116,7 +138,7 @@ } .iconCustomise { - fill: #fff; + fill: $white; position: absolute; top: 7px; left: 7px; @@ -124,6 +146,10 @@ .retryContainer { flex: 1; + background-color: "rgba(255, 255, 255, 0.8)"; +} + +.retryContainerDark { background-color: "rgba(0, 0, 0, 0.5)"; } diff --git a/packages/icons/src/library/offline.js b/packages/icons/src/library/offline.js index f0daa1aaeb79ee..444d3667f297e8 100644 --- a/packages/icons/src/library/offline.js +++ b/packages/icons/src/library/offline.js @@ -4,13 +4,7 @@ import { SVG, Path } from '@wordpress/primitives'; const offline = ( - + ## Unreleased +- [**] Image block media uploads display a custom error message when there is no internet connection [#56937] ## 1.110.0 - [*] [internal] Move InserterButton from components package to block-editor package [#56494]