Skip to content

Commit

Permalink
Media Files Collection (part4: error handling) (#26008)
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

* 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

* renamed event paramter name to easier to understand 'success' boolean, matching the native counterpart

* added cancel and retry bridge methods specific for mediaFiles collection based blocks

* added requestMediaFilesSaveCancelDialog bridge method

* renamed bridge method mediaModelCreatedForFile for more general purpose mediaIdChanged

* Add missing iOS bridge declarations

* added jsdoc like description for methods

* removed unneeded return statement in some bridge methods

* Update packages/react-native-bridge/index.js

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

* Update packages/react-native-bridge/index.js

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

* Update packages/react-native-bridge/index.js

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

* fixed typo, added punctuation

Co-authored-by: eToledo <[email protected]>
Co-authored-by: Joel Dean <[email protected]>
  • Loading branch information
3 people authored Oct 22, 2020
1 parent 3f0c632 commit d10e5d0
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function BlockUpdatingProgress( { url, id } ) {

### mediaFiles

A collection of media IDs that identify the current upload.
A collection of media ID that identifies the current collection of files represented in this media container block.

- Type: `Array`
- Required: Yes
Expand Down Expand Up @@ -97,7 +97,7 @@ The argument of the callback is an object containing the following properties:

### onMediaUploadStateReset

Callback called when the media upload is reset.
Callback called when the media upload is reset

- Type: `Function`
- Required: No
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ 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 const MEDIA_SAVE_MEDIAID_CHANGED = 10;

export class BlockMediaUpdateProgress extends React.Component {
constructor( props ) {
Expand Down Expand Up @@ -118,8 +118,8 @@ export class BlockMediaUpdateProgress extends React.Component {
case MEDIA_SAVE_FINAL_STATE_RESULT:
this.finalSaveResult( payload );
break;
case MEDIA_SAVE_MEDIAMODEL_CREATED:
this.mediaModelCreated( payload );
case MEDIA_SAVE_MEDIAID_CHANGED:
this.mediaIdChanged( payload );
break;
}
}
Expand Down Expand Up @@ -172,15 +172,15 @@ export class BlockMediaUpdateProgress extends React.Component {
}
}

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

Expand Down Expand Up @@ -265,9 +265,17 @@ export class BlockMediaUpdateProgress extends React.Component {
this.state.isUploadInProgress || this.state.isSaveInProgress;
const progress = this.state.progress * 100;
// eslint-disable-next-line @wordpress/i18n-no-collapsible-whitespace
const retryMessage = __(
const retryMessageSave = __(
'Failed to save files.\nPlease tap for options.'
);
// eslint-disable-next-line @wordpress/i18n-no-collapsible-whitespace
const retryMessageUpload = __(
'Failed to upload files.\nPlease tap for options.'
);
let retryMessage = retryMessageSave;
if ( isUploadFailed ) {
retryMessage = retryMessageUpload;
}

return (
<View style={ styles.mediaUploadProgress } pointerEvents="box-none">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface MediaSaveEventEmitter {
void onMediaFileSaveSucceeded(String mediaId, String mediaUrl);
void onMediaFileSaveFailed(String mediaId);
void onMediaCollectionSaveResult(String firstMediaIdInCollection, boolean success);
void onMediaModelCreatedForFile(final String oldId, final String newId, final String oldUrl);
void onMediaIdChanged(final String oldId, final String newId, final String oldUrl);
}

interface ReplaceUnsupportedBlockCallback {
Expand Down Expand Up @@ -175,4 +175,10 @@ void requestMediaFilesEditorLoad(ReplaceMediaFilesEditedBlockCallback replaceMed
ReadableArray mediaFiles,
String blockId
);

void requestMediaFilesFailedRetryDialog(ReadableArray mediaFiles);

void requestMediaFilesUploadCancelDialog(ReadableArray mediaFiles);

void requestMediaFilesSaveCancelDialog(ReadableArray mediaFiles);
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class RNReactNativeGutenbergBridgeModule extends ReactContextBaseJavaModu
public static final String MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_TYPE = "mediaType";
private static final String MAP_KEY_THEME_UPDATE_COLORS = "colors";
private static final String MAP_KEY_THEME_UPDATE_GRADIENTS = "gradients";
public static final String MAP_KEY_MEDIA_FINAL_SAVE_RESULT = "mediaFinalSaveResult";
public static final String MAP_KEY_MEDIA_FINAL_SAVE_RESULT_SUCCESS_VALUE = "success";

private static final String MAP_KEY_IS_PREFERRED_COLOR_SCHEME_DARK = "isPreferredColorSchemeDark";

Expand Down Expand Up @@ -242,6 +242,21 @@ public void requestMediaFilesEditorLoad(ReadableArray mediaFiles, String blockId
replaceBlock(savedMediaFiles, savedBlockId), mediaFiles, blockId);
}

@ReactMethod
public void requestMediaFilesFailedRetryDialog(ReadableArray mediaFiles) {
mGutenbergBridgeJS2Parent.requestMediaFilesFailedRetryDialog(mediaFiles);
}

@ReactMethod
public void requestMediaFilesUploadCancelDialog(ReadableArray mediaFiles) {
mGutenbergBridgeJS2Parent.requestMediaFilesUploadCancelDialog(mediaFiles);
}

@ReactMethod
public void requestMediaFilesSaveCancelDialog(ReadableArray mediaFiles) {
mGutenbergBridgeJS2Parent.requestMediaFilesSaveCancelDialog(mediaFiles);
}

@ReactMethod
public void editorDidEmitLog(String message, int logLevel) {
mGutenbergBridgeJS2Parent.editorDidEmitLog(message, GutenbergBridgeJS2Parent.LogLevel.valueOf(logLevel));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_ID;
import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_NEW_ID;
import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_URL;
import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FINAL_SAVE_RESULT;
import static org.wordpress.mobile.ReactNativeGutenbergBridge.RNReactNativeGutenbergBridgeModule.MAP_KEY_MEDIA_FINAL_SAVE_RESULT_SUCCESS_VALUE;

public class DeferredEventEmitter implements MediaUploadEventEmitter, MediaSaveEventEmitter {
public interface JSEventEmitter {
Expand All @@ -34,7 +34,7 @@ public interface JSEventEmitter {
private static final int MEDIA_SAVE_STATE_FAILED = 7;
private static final int MEDIA_SAVE_STATE_RESET = 8;
private static final int MEDIA_SAVE_FINAL_STATE_RESULT = 9;
private static final int MEDIA_SAVE_MEDIAMODEL_CREATED = 10;
private static final int MEDIA_SAVE_MEDIAID_CHANGED = 10;

private static final String EVENT_NAME_MEDIA_UPLOAD = "mediaUpload";
private static final String EVENT_NAME_MEDIA_SAVE = "mediaSave";
Expand Down Expand Up @@ -128,7 +128,7 @@ private void setMediaSaveResultDataInJS(int state, String mediaId, boolean succe
WritableMap writableMap = new WritableNativeMap();
writableMap.putInt(MAP_KEY_MEDIA_FILE_STATE, state);
writableMap.putString(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_ID, mediaId);
writableMap.putBoolean(MAP_KEY_MEDIA_FINAL_SAVE_RESULT, success);
writableMap.putBoolean(MAP_KEY_MEDIA_FINAL_SAVE_RESULT_SUCCESS_VALUE, success);
writableMap.putDouble(MAP_KEY_MEDIA_FILE_MEDIA_ACTION_PROGRESS, progress);
if (isCriticalMessage(state)) {
queueActionToJS(EVENT_NAME_MEDIA_SAVE, writableMap);
Expand All @@ -140,7 +140,7 @@ private void setMediaSaveResultDataInJS(int state, String mediaId, boolean succe
private boolean isCriticalMessage(int state) {
return state == MEDIA_UPLOAD_STATE_SUCCEEDED || state == MEDIA_UPLOAD_STATE_FAILED
|| state == MEDIA_SAVE_STATE_SUCCEEDED || state == MEDIA_SAVE_STATE_FAILED
|| state == MEDIA_SAVE_MEDIAMODEL_CREATED;
|| state == MEDIA_SAVE_MEDIAID_CHANGED;
}

@Override
Expand Down Expand Up @@ -189,13 +189,13 @@ public void onMediaCollectionSaveResult(String firstMediaIdInCollection, boolean
setMediaSaveResultDataInJS(MEDIA_SAVE_FINAL_STATE_RESULT, firstMediaIdInCollection, success, success ? 1 : 0);
}

@Override public void onMediaModelCreatedForFile(String oldId, String newId, String oldUrl) {
@Override public void onMediaIdChanged(String oldId, String newId, String oldUrl) {
WritableMap writableMap = new WritableNativeMap();
writableMap.putInt(MAP_KEY_MEDIA_FILE_STATE, MEDIA_SAVE_MEDIAMODEL_CREATED);
writableMap.putInt(MAP_KEY_MEDIA_FILE_STATE, MEDIA_SAVE_MEDIAID_CHANGED);
writableMap.putString(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_ID, oldId);
writableMap.putString(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_NEW_ID, newId);
writableMap.putString(MAP_KEY_MEDIA_FILE_UPLOAD_MEDIA_URL, oldUrl);
if (isCriticalMessage(MEDIA_SAVE_MEDIAMODEL_CREATED)) {
if (isCriticalMessage(MEDIA_SAVE_MEDIAID_CHANGED)) {
queueActionToJS(EVENT_NAME_MEDIA_SAVE, writableMap);
} else {
emitOrDrop(EVENT_NAME_MEDIA_SAVE, writableMap);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public class WPAndroidGlueCode {
private OnGutenbergDidSendButtonPressedActionListener mOnGutenbergDidSendButtonPressedActionListener;
private ReplaceUnsupportedBlockCallback mReplaceUnsupportedBlockCallback;
private OnStarterPageTemplatesTooltipShownEventListener mOnStarterPageTemplatesTooltipShownListener;
private OnMediaFilesEditorLoadRequestListener mOnMediaFilesEditorLoadRequestListener;
private OnMediaFilesCollectionBasedBlockEditorListener mOnMediaFilesCollectionBasedBlockEditorListener;
private ReplaceMediaFilesEditedBlockCallback mReplaceMediaFilesEditedBlockCallback;
private boolean mIsEditorMounted;

Expand Down Expand Up @@ -150,8 +150,11 @@ public interface OnMediaLibraryButtonListener {
void onOtherMediaButtonClicked(String mediaSource, boolean allowMultipleSelection);
}

public interface OnMediaFilesEditorLoadRequestListener {
public interface OnMediaFilesCollectionBasedBlockEditorListener {
void onRequestMediaFilesEditorLoad(ArrayList<Object> mediaFiles, String blockId);
void onCancelUploadForMediaCollection(ArrayList<Object> mediaFiles);
void onRetryUploadForMediaCollection(ArrayList<Object> mediaFiles);
void onCancelSaveForMediaCollection(ArrayList<Object> mediaFiles);
}

public interface OnImageFullscreenPreviewListener {
Expand Down Expand Up @@ -424,7 +427,29 @@ public void requestMediaFilesEditorLoad(
String blockId
) {
mReplaceMediaFilesEditedBlockCallback = replaceMediaFilesEditedBlockCallback;
mOnMediaFilesEditorLoadRequestListener.onRequestMediaFilesEditorLoad(mediaFiles.toArrayList(), blockId);
mOnMediaFilesCollectionBasedBlockEditorListener
.onRequestMediaFilesEditorLoad(mediaFiles.toArrayList(), blockId);
}

@Override
public void requestMediaFilesFailedRetryDialog(ReadableArray mediaFiles) {
mOnMediaFilesCollectionBasedBlockEditorListener.onRetryUploadForMediaCollection(
mediaFiles.toArrayList()
);
}

@Override
public void requestMediaFilesUploadCancelDialog(ReadableArray mediaFiles) {
mOnMediaFilesCollectionBasedBlockEditorListener.onCancelUploadForMediaCollection(
mediaFiles.toArrayList()
);
}

@Override
public void requestMediaFilesSaveCancelDialog(ReadableArray mediaFiles) {
mOnMediaFilesCollectionBasedBlockEditorListener.onCancelSaveForMediaCollection(
mediaFiles.toArrayList()
);
}
}, mIsDarkMode);

Expand Down Expand Up @@ -502,7 +527,7 @@ public void attachToContainer(ViewGroup viewGroup,
OnGutenbergDidSendButtonPressedActionListener onGutenbergDidSendButtonPressedActionListener,
AddMentionUtil addMentionUtil,
OnStarterPageTemplatesTooltipShownEventListener onStarterPageTemplatesTooltipListener,
OnMediaFilesEditorLoadRequestListener onMediaFilesEditorLoadRequestListener,
OnMediaFilesCollectionBasedBlockEditorListener onMediaFilesCollectionBasedBlockEditorListener,
boolean isDarkMode) {
MutableContextWrapper contextWrapper = (MutableContextWrapper) mReactRootView.getContext();
contextWrapper.setBaseContext(viewGroup.getContext());
Expand All @@ -520,7 +545,7 @@ public void attachToContainer(ViewGroup viewGroup,
mOnGutenbergDidSendButtonPressedActionListener = onGutenbergDidSendButtonPressedActionListener;
mAddMentionUtil = addMentionUtil;
mOnStarterPageTemplatesTooltipShownListener = onStarterPageTemplatesTooltipListener;
mOnMediaFilesEditorLoadRequestListener = onMediaFilesEditorLoadRequestListener;
mOnMediaFilesCollectionBasedBlockEditorListener = onMediaFilesCollectionBasedBlockEditorListener;

sAddCookiesInterceptor.setOnAuthHeaderRequestedListener(onAuthHeaderRequestedListener);

Expand Down Expand Up @@ -908,8 +933,8 @@ public void mediaCollectionFinalSaveResult(final String blockFirstMediaId, final
mDeferredEventEmitter.onMediaCollectionSaveResult(blockFirstMediaId, success);
}

public void mediaModelCreatedForFile(final String oldId, final String newId, final String oldUrl) {
mDeferredEventEmitter.onMediaModelCreatedForFile(oldId, newId, oldUrl);
public void mediaIdChanged(final String oldId, final String newId, final String oldUrl) {
mDeferredEventEmitter.onMediaIdChanged(oldId, newId, oldUrl);
}

public void replaceUnsupportedBlock(String content, String blockId) {
Expand Down
54 changes: 53 additions & 1 deletion packages/react-native-bridge/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,63 @@ export function setStarterPageTemplatesTooltipShown( tooltipShown ) {
);
}

/**
* Request the host app to show the block for editing its mediaFiles collection
*
* For example, a mediaFiles collection editor can make special handling of visualization
* in this regard.
*
* @param {Array<Map>} mediaFiles the mediaFiles attribute of the block, containing data about each media item.
* @param {string} blockClientId the clientId of the block.
*/
export function requestMediaFilesEditorLoad( mediaFiles, blockClientId ) {
return RNReactNativeGutenbergBridge.requestMediaFilesEditorLoad(
RNReactNativeGutenbergBridge.requestMediaFilesEditorLoad(
mediaFiles,
blockClientId
);
}

/**
* Request the host app to show a retry dialog for mediaFiles arrays which contained items that failed
* to upload
*
* For example, tapping on a failed-media overlay would trigger this request and a "Retry?" dialog
* would be presented to the user
*
* @param {Array<Map>} mediaFiles the mediaFiles attribute of the block, containing data about each media item
*/
export function requestMediaFilesFailedRetryDialog( mediaFiles ) {
RNReactNativeGutenbergBridge.requestMediaFilesFailedRetryDialog(
mediaFiles
);
}

/**
* Request the host app to show a cancel dialog for mediaFiles arrays currently being uploaded
*
* For example, tapping on a block containing mediaFiles that are currently being uplaoded would trigger this request
* and a "Cancel upload?" dialog would be presented to the user.
*
* @param {Array<Map>} mediaFiles the mediaFiles attribute of the block, containing data about each media item
*/
export function requestMediaFilesUploadCancelDialog( mediaFiles ) {
RNReactNativeGutenbergBridge.requestMediaFilesUploadCancelDialog(
mediaFiles
);
}

/**
* Request the host app to show a cancel dialog for mediaFiles arrays currently undergoing a save operation
*
* Save operations on mediaFiles collection could be lengthy so for example, tapping on a mediaFiles-type block
* currently being saved would trigger this request and a "Cancel save?" dialog would be presented to the user
*
* @param {Array<Map>} mediaFiles the mediaFiles attribute of the block, containing data about each media item.
*/
export function requestMediaFilesSaveCancelDialog( mediaFiles ) {
RNReactNativeGutenbergBridge.requestMediaFilesSaveCancelDialog(
mediaFiles
);
}

export default RNReactNativeGutenbergBridge;
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ @interface RCT_EXTERN_MODULE(RNReactNativeGutenbergBridge, NSObject)
RCT_EXTERN_METHOD(requestStarterPageTemplatesTooltipShown:(RCTResponseSenderBlock)callback)
RCT_EXTERN_METHOD(setStarterPageTemplatesTooltipShown:(BOOL)tooltipShown)
RCT_EXTERN_METHOD(requestMediaFilesEditorLoad:(NSArray *)mediaFiles blockId:(NSString *)blockId)
RCT_EXTERN_METHOD(requestMediaFilesFailedRetryDialog:(NSArray *)mediaFiles)
RCT_EXTERN_METHOD(onCancelUploadForMediaCollection:(NSArray *)mediaFiles)
RCT_EXTERN_METHOD(actionButtonPressed:(NSString *)buttonType)
RCT_EXTERN_METHOD(mediaSaveSync)

@end
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,38 @@ public class RNReactNativeGutenbergBridge: RCTEventEmitter {
// }
}

@objc
func requestMediaFilesFailedRetryDialog(_ mediaFilesArray: [AnyObject]) {
// TODO actually implement the delegate call on iOS
let mediaFiles = mediaFilesArray.compactMap { $0 as? String }
// DispatchQueue.main.async {
// self.delegate?.gutenbergDidRequestMediaFilesFailedRetryDialog(mediaFiles: mediaFiles, blockId: blockId)
// }
}

@objc
func requestMediaFilesUploadCancelDialog(_ mediaFilesArray: [AnyObject]) {
// TODO actually implement the delegate call on iOS
let mediaFiles = mediaFilesArray.compactMap { $0 as? String }
// DispatchQueue.main.async {
// self.delegate?.gutenbergDidRequestMediaFilesUploadCancelDialog(mediaFiles: mediaFiles, blockId: blockId)
// }
}

@objc
func requestMediaFilesSaveCancelDialog(_ mediaFilesArray: [AnyObject]) {
// TODO actually implement the delegate call on iOS
let mediaFiles = mediaFilesArray.compactMap { $0 as? String }
// DispatchQueue.main.async {
// self.delegate?.gutenbergDidRequestMediaFilesSaveCancelDialog(mediaFiles: mediaFiles, blockId: blockId)
// }
}

@objc
func mediaSaveSync() {
// TODO: To be implemented
}

@objc
func actionButtonPressed(_ buttonType: String) {
guard let button = Gutenberg.ActionButtonType(rawValue: buttonType) else {
Expand Down Expand Up @@ -330,6 +362,7 @@ extension RNReactNativeGutenbergBridge {
case replaceBlock
case updateCapabilities
case showNotice
case mediaSave
}

public override func supportedEvents() -> [String]! {
Expand Down
Loading

0 comments on commit d10e5d0

Please sign in to comment.