Skip to content

Commit

Permalink
Mobile Stories block (part2: on done) (#25771)
Browse files Browse the repository at this point in the history
* added mobile StoryUpdateProgress component and bridge code to send/receive save progress

* updated WPAndroid bridge DeferredEventEmitter to handle Story save events separately

* changed all Save event interface methods to use String ids instead of int, and removed serverMediaId params as these don't apply while saving locally

* redefined upload/save state constants

* added onStorySaveResult handling to bridge, and renamed STORY_SAVE_STATE_* events to MEDIA_SAVE_STATE_* where appropriate

* checking for matches of mediaId in  mediaFiles while saving to send save progress updates

* added mediaModelCreated() method to the bridge, so a new ID can be assigned to a mediaFile in a Story block

* mediaId should always be a string in mediaFiles so, converting to avoid strict comparison to fail

* removed commented code

* updated documentation

* added missing implementation of method storySaveSync() in demo app

* fixed prettier warning

* Update packages/block-editor/src/components/story-update-progress/README.md

Co-authored-by: Joel Dean <[email protected]>

* Update packages/block-editor/src/components/story-update-progress/README.md

Co-authored-by: Joel Dean <[email protected]>

* Mobile Stories block (part3: refactor / rename) (#26005)

* renames for generic media files collection block and BlockMediaUpdateProgres

* referencing the right props method in finishMediaSaveWithFailure

* mistaken renames of  parameters in bridge methods

* renamed more abstract/generic method names requestMediaFilesEditorLoad

* removed extra whtie space

* renamed argument type

Co-authored-by: Joel Dean <[email protected]>
  • Loading branch information
mzorz and jd-alexander authored Oct 14, 2020
1 parent 71e5d6d commit 66c15e3
Show file tree
Hide file tree
Showing 13 changed files with 595 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
BlockMediaUpdateProgress
===================

`BlockMediaUpdateProgress` shows a progress bar while the media files associated with a media-containing block are being saved first and uploaded later

## Usage

Usage example

```jsx
import { ImageBackground, Text, View } from 'react-native';
import {
BlockMediaUpdateProgress,
} from '@wordpress/block-editor';

function BlockUpdatingProgress( { url, id } ) {
return (
<BlockMediaUpdateProgress
mediaId={ id }
renderContent={ ( { isSaveFailed, retryMessage } ) => {
return (
<ImageBackground
resizeMethod="scale"
source={ { uri: url } }
>
{ isSaveFailed &&
<View>
<Text>{ retryMessage }</Text>
</View>
}
</ImageBackground>
);
} }
/>
);
}
```

## Props

### mediaFiles

A collection of media IDs that identify the current story upload.

- Type: `Array`
- Required: Yes
- Platform: Mobile

### renderContent

Content to be rendered along with the progress bar, usually the thumbnail of the media being uploaded.

- Type: `React components`
- Required: Yes
- Platform: Mobile

It passes an object containing the following properties:

`{ isUploadInProgress, isUploadFailed, isSaveInProgress, isSaveFailed, retryMessage }`

### onUpdateMediaProgress

Callback called when the progress of the upload is updated.

- Type: `Function`
- Required: No
- Platform: Mobile

The argument of the callback is an object containing the following properties:

`{ mediaId, mediaUrl, progress, state }`

### onFinishMediaUploadWithSuccess

Callback called when the media file has been uploaded successfully.

- Type: `Function`
- Required: No
- Platform: Mobile

The argument of the callback is an object containing the following properties:

`{ mediaId, mediaServerId, mediaUrl, progress, state }`

### onFinishMediaUploadWithFailure

Callback called when the media file couldn't be uploaded.

- Type: `Function`
- Required: No
- Platform: Mobile

The argument of the callback is an object containing the following properties:

`{ mediaId, progress, state }`


### onMediaUploadStateReset

Callback called when the media upload is reset.

- Type: `Function`
- Required: No
- Platform: Mobile
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
/**
* External dependencies
*/
import React from 'react';
import { View } from 'react-native';

/**
* WordPress dependencies
*/
import { Spinner } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import {
subscribeMediaUpload,
subscribeMediaSave,
} 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;

export const MEDIA_SAVE_STATE_SAVING = 5;
export const MEDIA_SAVE_STATE_SUCCEEDED = 6;
export const MEDIA_SAVE_STATE_FAILED = 7;
export const MEDIA_SAVE_STATE_RESET = 8;
export const MEDIA_SAVE_FINAL_STATE_RESULT = 9;
export const MEDIA_SAVE_MEDIAMODEL_CREATED = 10;

export class BlockMediaUpdateProgress extends React.Component {
constructor( props ) {
super( props );

this.state = {
progress: 0,
isSaveInProgress: false,
isSaveFailed: false,
isUploadInProgress: false,
isUploadFailed: false,
};

this.mediaUpload = this.mediaUpload.bind( this );
this.mediaSave = this.mediaSave.bind( this );
}

componentDidMount() {
this.addMediaUploadListener();
this.addMediaSaveListener();
}

componentWillUnmount() {
this.removeMediaUploadListener();
this.removeMediaSaveListener();
}

mediaIdContainedInMediaFiles( mediaId, mediaFiles ) {
if ( mediaId !== undefined && mediaFiles !== undefined ) {
for ( let i = 0; i < this.props.mediaFiles.length; i++ ) {
if ( mediaFiles[ i ].id === mediaId.toString() ) {
return true;
}
}
}
return false;
}

mediaUpload( payload ) {
const { mediaFiles } = this.props;

if (
this.mediaIdContainedInMediaFiles( payload.mediaId, mediaFiles ) ===
false
) {
return;
}

switch ( payload.state ) {
case MEDIA_UPLOAD_STATE_UPLOADING:
this.updateMediaUploadProgress( payload );
break;
case MEDIA_UPLOAD_STATE_SUCCEEDED:
this.finishMediaUploadWithSuccess( payload );
break;
case MEDIA_UPLOAD_STATE_FAILED:
this.finishMediaUploadWithFailure( payload );
break;
case MEDIA_UPLOAD_STATE_RESET:
this.mediaUploadStateReset( payload );
break;
}
}

mediaSave( payload ) {
const { mediaFiles } = this.props;

if (
this.mediaIdContainedInMediaFiles( payload.mediaId, mediaFiles ) ===
false
) {
return;
}

switch ( payload.state ) {
case MEDIA_SAVE_STATE_SAVING:
this.updateMediaSaveProgress( payload );
break;
case MEDIA_SAVE_STATE_SUCCEEDED:
this.finishMediaSaveWithSuccess( payload );
break;
case MEDIA_SAVE_STATE_FAILED:
this.finishMediaSaveWithFailure( payload );
break;
case MEDIA_SAVE_STATE_RESET:
this.mediaSaveStateReset( payload );
break;
case MEDIA_SAVE_FINAL_STATE_RESULT:
this.finalSaveResult( payload );
break;
case MEDIA_SAVE_MEDIAMODEL_CREATED:
this.mediaModelCreated( payload );
break;
}
}

// ---- Block media save actions
updateMediaSaveProgress( payload ) {
this.setState( {
progress: payload.progress,
isUploadInProgress: false,
isUploadFailed: false,
isSaveInProgress: true,
isSaveFailed: false,
} );
if ( this.props.onUpdateMediaSaveProgress ) {
this.props.onUpdateMediaSaveProgress( payload );
}
}

finishMediaSaveWithSuccess( payload ) {
this.setState( { isSaveInProgress: false } );
if ( this.props.onFinishMediaSaveWithSuccess ) {
this.props.onFinishMediaSaveWithSuccess( payload );
}
}

finishMediaSaveWithFailure( payload ) {
this.setState( { isSaveInProgress: false, isSaveFailed: true } );
if ( this.props.onFinishMediaSaveWithFailure ) {
this.props.onFinishMediaSaveWithFailure( payload );
}
}

mediaSaveStateReset( payload ) {
this.setState( { isUploadInProgress: false, isUploadFailed: false } );
if ( this.props.onMediaSaveStateReset ) {
this.props.onMediaSaveStateReset( payload );
}
}

finalSaveResult( payload ) {
this.setState( {
progress: payload.progress,
isUploadInProgress: false,
isUploadFailed: false,
isSaveInProgress: false,
isSaveFailed: ! payload.success,
} );
if ( this.props.onFinalSaveResult ) {
this.props.onFinalSaveResult( payload );
}
}

mediaModelCreated( payload ) {
this.setState( {
isUploadInProgress: false,
isUploadFailed: false,
isSaveInProgress: false,
isSaveFailed: false,
} );
if ( this.props.onMediaModelCreated ) {
this.props.onMediaModelCreated( payload );
}
}

// ---- Block media upload actions
updateMediaUploadProgress( payload ) {
this.setState( {
progress: payload.progress,
isUploadInProgress: true,
isUploadFailed: false,
isSaveInProgress: false,
isSaveFailed: false,
} );
if ( this.props.onUpdateMediaUploadProgress ) {
this.props.onUpdateMediaUploadProgress( payload );
}
}

finishMediaUploadWithSuccess( payload ) {
this.setState( { isUploadInProgress: false, isSaveInProgress: false } );
if ( this.props.onFinishMediaUploadWithSuccess ) {
this.props.onFinishMediaUploadWithSuccess( payload );
}
}

finishMediaUploadWithFailure( payload ) {
this.setState( { isUploadInProgress: false, isUploadFailed: true } );
if ( this.props.onFinishMediaUploadWithFailure ) {
this.props.onFinishMediaUploadWithFailure( payload );
}
}

mediaUploadStateReset( payload ) {
this.setState( { isUploadInProgress: false, isUploadFailed: false } );
if ( this.props.onMediaUploadStateReset ) {
this.props.onMediaUploadStateReset( payload );
}
}

addMediaUploadListener() {
//if we already have a subscription not worth doing it again
if ( this.subscriptionParentMediaUpload ) {
return;
}
this.subscriptionParentMediaUpload = subscribeMediaUpload(
( payload ) => {
this.mediaUpload( payload );
}
);
}

removeMediaUploadListener() {
if ( this.subscriptionParentMediaUpload ) {
this.subscriptionParentMediaUpload.remove();
}
}

addMediaSaveListener() {
//if we already have a subscription not worth doing it again
if ( this.subscriptionParentMediaSave ) {
return;
}
this.subscriptionParentMediaSave = subscribeMediaSave( ( payload ) => {
this.mediaSave( payload );
} );
}

removeMediaSaveListener() {
if ( this.subscriptionParentMediaSave ) {
this.subscriptionParentMediaSave.remove();
}
}

render() {
const { renderContent = () => null } = this.props;
const {
isUploadInProgress,
isUploadFailed,
isSaveInProgress,
isSaveFailed,
} = this.state;
const showSpinner =
this.state.isUploadInProgress || this.state.isSaveInProgress;
const progress = this.state.progress * 100;
// eslint-disable-next-line @wordpress/i18n-no-collapsible-whitespace
const retryMessage = __(
'Failed to save files.\nPlease tap for options.'
);

return (
<View style={ styles.mediaUploadProgress } pointerEvents="box-none">
{ showSpinner && (
<View style={ styles.progressBar }>
<Spinner progress={ progress } />
</View>
) }
{ renderContent( {
isUploadInProgress,
isUploadFailed,
isSaveInProgress,
isSaveFailed,
retryMessage,
} ) }
</View>
);
}
}

export default BlockMediaUpdateProgress;
Loading

0 comments on commit 66c15e3

Please sign in to comment.