From c0f0848c74ef959fa90d150ae3cf93259c3fd08e Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Tue, 12 Apr 2016 14:20:48 +0200 Subject: [PATCH] Squashed 'libs/editor/' changes from 7be0b57..72933eb 72933eb Merge pull request #343 from wordpress-mobile/feature/290-optimistic-upload-progress 24a391e Merge branch 'develop' into feature/290-optimistic-upload-progress 96eebb0 Merge commit '52910dd0664cf2031a6a1c78f3cb1b170a4eeff9' into issue/editor-p-to-div-update fe5df26 Merge branch 'develop' into issue/editor-p-to-div-update 31f8844 Use optimistic progress updates for retried media uploads 8726f0d Turn off automated 'optimistic' progress updates for failed media 8a535e7 Reset progress to 0% when media uploads fail 299e9e9 Moved progress setting for media retries to the ZSSEditor 98dbf21 Send 'optimistic' automatic progress updates until 15% for media uploads f673a1a Don't update media upload progress bar beyond 90% abfa1fe Use setProgressOnMedia in EditorFragment 2f058ac s/imageUri/mediaUri/ 65913c8 Added a media option to the demo app simulating an image upload over a slow connection 3e61e04 Replaced the setProgressOnImage/Video methods with the more generic setProgressOnMedia 159585b Merge branch 'develop' into feature/290-optimistic-upload-progress 06e6da8 Merge pull request #3931 from wordpress-mobile/issue/fix-placeholder-title-in-french e5c8bc9 Updating to gradle 2.0 bdfee87 Updating to rc3 3ea4753 Escape quotes on translated strings when calling execJavaScriptFromString 6861416 Move progress bar initialization inside the insertLocal methods ac2575e Merge commit 'c5b98a88c01551e8dbf44366072607d364ec01dd' into develop d6f1313 Updating the rest of the modules to buildToolsVersion 23.0.3 4361ec9 Merge commit '4c9324cf1eee00b66c76e0d5a917c86e1293a845' into develop 04edf05 Add missing classes for images inserted from media library - also fix a bug with undefined alt text f83361a Add missing attributes for uploaded images 1c031dd update to android-gradle-2.0.0-rc1 ef46180 Merge commit 'c6efe0a9190244d40e64300efc9cca56ae5acd5c' into develop 964aee7 Merge branch 'develop' into issue/120editor-initial-focus f78081f Merge commit '9b0b5fab24db9f435b278ad0b9e77d5895135700' into develop 16ca267 Merge pull request #3897 from wordpress-mobile/issue/260editor-clear-failed-images-on-upload 53c5e25 Null check getParentRangeOfFocusedNode/setRange in onMutationObserved - in case editor is not in focus a023df0 Show the software keyboard once the DOM has loaded 1eb8e1e Focus on the title field when opening posts 76e050d New EditorFragment method to removeAllFailedMediaUploads() 3e91458 New JS function ZSSEditor.removeAllFailedMediaUploads b4e2d18 Merge pull request #3896 from wordpress-mobile/issue/300editor-broken-images-after-upload-2 9ae4bbc remove debug logs 7d792ab Use remoteurl in the link wrapper 180d3ad Merge branch 'develop' into issue/300editor-broken-images-after-upload-2 4a38ef6 Merge branch 'develop' into issue/297editor-backspace-media 4f2f7cb update to com.android.tools.build:gradle:2.0.0-beta7 d826f94 Merge branch 'develop' into issue/297editor-backspace-media dccdc76 Merge commit '8db246f15ce6f4d2c7f7f7ec51c68b87e9a66c2f' into develop fee3247 Changed MutationObserver handling to check if the WebView supports it, rather than rely on API levels 8812a3d Refactor: grouped mutation observation methods together a6d25a8 Refactored DOM element mutation listening, delegating everything to one trackNodeForMutation method a6a0236 Changed MutationObserver behavior to track individual media nodes instead of each contenteditable div 2340c5c Moved failed media methods to the generic media method group 63e0210 Parse for failed media when returning from HTML to visual mode 26159ed Track DOMNodeRemoved events when parsing for failed media 284411a Fixed a variable name error in ZSSEditor.removeImage 403b61c On API<19, use DOMNodeRemoved events to track media deletions (instead of the unsupported MutationObserver used for newer APIs) 9652b4c Merge branch 'develop' into issue/297editor-backspace-media 49a5029 Merge pull request #3804 from wordpress-mobile/issue/enable-editor-debug-mode 848a974 Consume KEYCODE_VOLUME_UP event when debug print is called 764c6ee broken retries 17efb8f Merge branch 'issue/enable-editor-debug-mode' into issue/300editor-broken-images-after-upload-2 44da980 use a remoteUrl attribute to avoid seeing broken image if download failed baec233 remove debug action bar button and log raw html when volume up button is pressed 329ebf3 fix function call errors bf4771c Add back image swapping onError 6bc0d4b fix wordpress-mobile/WordPress-Editor-Android#300: Retry download onError after an upload 0aef278 add missing comment 0f369d0 Updated gradle to 2.0.0-beta6 7269fbb Fixes an issue where manually deleting uploading/failed media will cause the caret to disappear f2d0a44 Notify native through a callback whenever uploading/failed media are manually deleted 0ccc419 catch a common JS exception 42f3a10 Merge branch 'develop' into issue/288editor-log-js-errors-in-crashlytics 60a7ec7 fix wordpress-mobile/WordPress-Editor-Android#288: new EditorWebViewAbstract.ErrorListener used to forward JS errors to Crashlytics cc87834 Keep the format bar disabled on rotation when the title field is in focus 7039d9d Rely on ZSSEditor to flag uploads as completed in native-side checks 88d5ae1 Added null checking for MediaType onMediaUploadFailed 6a6081b Wait until the ZSSEditor has replaced the local media with remote before marking it as completed 2e8d1f5 Strip any trailing   when returning the title git-subtree-dir: libs/editor git-subtree-split: 72933eb1e06055b5852f011ad4980c47e24d578f --- .../android/editor/EditorFragment.java | 21 +-- .../example/EditorExampleActivity.java | 63 ++++++- example/src/main/res/values/strings.xml | 1 + .../editor-common/assets/ZSSRichTextEditor.js | 156 +++++++++++------- 4 files changed, 162 insertions(+), 79 deletions(-) diff --git a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java index 266e2ed4e066..f4fc4537925c 100755 --- a/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java +++ b/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java @@ -793,12 +793,10 @@ public void run() { String posterUrl = Utils.escapeQuotes(StringUtils.notNullStr(mediaFile.getThumbnailURL())); mWebView.execJavaScriptFromString("ZSSEditor.insertLocalVideo(" + id + ", '" + posterUrl + "');"); - mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnVideo(" + id + ", " + 0 + ");"); mUploadingMedia.put(id, MediaType.VIDEO); } else { mWebView.execJavaScriptFromString("ZSSEditor.insertLocalImage(" + id + ", '" + safeMediaUrl + "');"); - mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnImage(" + id + ", " + 0 + ");"); mUploadingMedia.put(id, MediaType.IMAGE); } } @@ -901,13 +899,8 @@ public void onMediaUploadProgress(final String mediaId, final float progress) { @Override public void run() { String progressString = String.format(Locale.US, "%.1f", progress); - if (mediaType.equals(MediaType.IMAGE)) { - mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnImage(" + mediaId + ", " + - progressString + ");"); - } else if (mediaType.equals(MediaType.VIDEO)) { - mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnVideo(" + mediaId + ", " + - progressString + ");"); - } + mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnMedia(" + mediaId + ", " + + progressString + ");"); } }); } @@ -972,9 +965,9 @@ public void run() { // Set title and content placeholder text mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_title').setPlaceholderText('" + - mTitlePlaceholder + "');"); + Utils.escapeQuotes(mTitlePlaceholder) + "');"); mWebView.execJavaScriptFromString("ZSSEditor.getField('zss_field_content').setPlaceholderText('" + - mContentPlaceholder + "');"); + Utils.escapeQuotes(mContentPlaceholder) + "');"); // Load title and content into ZSSEditor updateVisualEditorFields(); @@ -982,7 +975,7 @@ public void run() { // If there are images that are still in progress (because the editor exited before they completed), // set them to failed, so the user can restart them (otherwise they will stay stuck in 'uploading' mode) mWebView.execJavaScriptFromString("ZSSEditor.markAllUploadingMediaAsFailed('" - + getString(R.string.tap_to_try_again) + "');"); + + Utils.escapeQuotes(getString(R.string.tap_to_try_again)) + "');"); // Update the list of failed media uploads mWebView.execJavaScriptFromString("ZSSEditor.getFailedMedia();"); @@ -1124,14 +1117,10 @@ public void run() { case IMAGE: mWebView.execJavaScriptFromString("ZSSEditor.unmarkImageUploadFailed(" + mediaId + ");"); - mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnImage(" + mediaId + ", " - + 0 + ");"); break; case VIDEO: mWebView.execJavaScriptFromString("ZSSEditor.unmarkVideoUploadFailed(" + mediaId + ");"); - mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnVideo(" + mediaId + ", " - + 0 + ");"); } mFailedMediaIds.remove(mediaId); mUploadingMedia.put(mediaId, mediaType); diff --git a/example/src/main/java/org/wordpress/example/EditorExampleActivity.java b/example/src/main/java/org/wordpress/example/EditorExampleActivity.java index e9525daad480..f2cc1aa98144 100644 --- a/example/src/main/java/org/wordpress/example/EditorExampleActivity.java +++ b/example/src/main/java/org/wordpress/example/EditorExampleActivity.java @@ -34,6 +34,7 @@ public class EditorExampleActivity extends AppCompatActivity implements EditorFr public static final int ADD_MEDIA_ACTIVITY_REQUEST_CODE = 1111; public static final int ADD_MEDIA_FAIL_ACTIVITY_REQUEST_CODE = 1112; + public static final int ADD_MEDIA_SLOW_NETWORK_REQUEST_CODE = 1113; public static final String MEDIA_REMOTE_ID_SAMPLE = "123"; @@ -41,6 +42,7 @@ public class EditorExampleActivity extends AppCompatActivity implements EditorFr private static final int SELECT_IMAGE_FAIL_MENU_POSITION = 1; private static final int SELECT_VIDEO_MENU_POSITION = 2; private static final int SELECT_VIDEO_FAIL_MENU_POSITION = 3; + private static final int SELECT_IMAGE_SLOW_MENU_POSITION = 4; private EditorFragmentAbstract mEditorFragment; @@ -85,6 +87,7 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMen menu.add(0, SELECT_IMAGE_FAIL_MENU_POSITION, 0, getString(R.string.select_image_fail)); menu.add(0, SELECT_VIDEO_MENU_POSITION, 0, getString(R.string.select_video)); menu.add(0, SELECT_VIDEO_FAIL_MENU_POSITION, 0, getString(R.string.select_video_fail)); + menu.add(0, SELECT_IMAGE_SLOW_MENU_POSITION, 0, getString(R.string.select_image_slow_network)); } @Override @@ -120,6 +123,13 @@ public boolean onContextItemSelected(MenuItem item) { startActivityForResult(intent, ADD_MEDIA_FAIL_ACTIVITY_REQUEST_CODE); return true; + case SELECT_IMAGE_SLOW_MENU_POSITION: + intent.setType("image/*"); + intent.setAction(Intent.ACTION_GET_CONTENT); + intent = Intent.createChooser(intent, getString(R.string.select_image_slow_network)); + + startActivityForResult(intent, ADD_MEDIA_SLOW_NETWORK_REQUEST_CODE); + return true; default: return false; } @@ -133,27 +143,35 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { return; } - Uri imageUri = data.getData(); + Uri mediaUri = data.getData(); MediaFile mediaFile = new MediaFile(); String mediaId = String.valueOf(System.currentTimeMillis()); mediaFile.setMediaId(mediaId); - mediaFile.setVideo(imageUri.toString().contains("video")); + mediaFile.setVideo(mediaUri.toString().contains("video")); switch (requestCode) { case ADD_MEDIA_ACTIVITY_REQUEST_CODE: - mEditorFragment.appendMediaFile(mediaFile, imageUri.toString(), null); + mEditorFragment.appendMediaFile(mediaFile, mediaUri.toString(), null); if (mEditorFragment instanceof EditorMediaUploadListener) { - simulateFileUpload(mediaId, imageUri.toString()); + simulateFileUpload(mediaId, mediaUri.toString()); } break; case ADD_MEDIA_FAIL_ACTIVITY_REQUEST_CODE: - mEditorFragment.appendMediaFile(mediaFile, imageUri.toString(), null); + mEditorFragment.appendMediaFile(mediaFile, mediaUri.toString(), null); + + if (mEditorFragment instanceof EditorMediaUploadListener) { + simulateFileUploadFail(mediaId, mediaUri.toString()); + } + break; + case ADD_MEDIA_SLOW_NETWORK_REQUEST_CODE: + mEditorFragment.appendMediaFile(mediaFile, mediaUri.toString(), null); if (mEditorFragment instanceof EditorMediaUploadListener) { - simulateFileUploadFail(mediaId, imageUri.toString()); + simulateSlowFileUpload(mediaId, mediaUri.toString()); } + break; } } @@ -280,4 +298,37 @@ public void run() { thread.start(); } + + private void simulateSlowFileUpload(final String mediaId, final String mediaUrl) { + Thread thread = new Thread() { + @Override + public void run() { + try { + sleep(5000); + float count = (float) 0.1; + while (count < 1.1) { + sleep(2000); + + ((EditorMediaUploadListener) mEditorFragment).onMediaUploadProgress(mediaId, count); + + count += 0.1; + } + + MediaFile mediaFile = new MediaFile(); + mediaFile.setMediaId(MEDIA_REMOTE_ID_SAMPLE); + mediaFile.setFileURL(mediaUrl); + + ((EditorMediaUploadListener) mEditorFragment).onMediaUploadSucceeded(mediaId, mediaFile); + + if (mFailedUploads.containsKey(mediaId)) { + mFailedUploads.remove(mediaId); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + + thread.start(); + } } diff --git a/example/src/main/res/values/strings.xml b/example/src/main/res/values/strings.xml index bfffb0de8fb7..c8ac8b7dd3da 100644 --- a/example/src/main/res/values/strings.xml +++ b/example/src/main/res/values/strings.xml @@ -17,4 +17,5 @@ Select an image (failure demo) Select a video Select a video (failure demo) + Select an image (slow network demo) diff --git a/libs/editor-common/assets/ZSSRichTextEditor.js b/libs/editor-common/assets/ZSSRichTextEditor.js index 22da90e05350..8b9bd271b726 100755 --- a/libs/editor-common/assets/ZSSRichTextEditor.js +++ b/libs/editor-common/assets/ZSSRichTextEditor.js @@ -992,6 +992,24 @@ ZSSEditor.extractMediaIdentifier = function(node) { return ""; }; +ZSSEditor.getMediaNodeWithIdentifier = function(mediaNodeIdentifier) { + var imageNode = ZSSEditor.getImageNodeWithIdentifier(mediaNodeIdentifier); + if (imageNode.length > 0) { + return imageNode; + } else { + return ZSSEditor.getVideoNodeWithIdentifier(mediaNodeIdentifier); + } +}; + +ZSSEditor.getMediaProgressNodeWithIdentifier = function(mediaNodeIdentifier) { + var imageProgressNode = ZSSEditor.getImageProgressNodeWithIdentifier(mediaNodeIdentifier); + if (imageProgressNode.length > 0) { + return imageProgressNode; + } else { + return ZSSEditor.getVideoProgressNodeWithIdentifier(mediaNodeIdentifier); + } +}; + ZSSEditor.getMediaContainerNodeWithIdentifier = function(mediaNodeIdentifier) { var imageContainerNode = ZSSEditor.getImageContainerNodeWithIdentifier(mediaNodeIdentifier); if (imageContainerNode.length > 0) { @@ -1001,6 +1019,61 @@ ZSSEditor.getMediaContainerNodeWithIdentifier = function(mediaNodeIdentifier) { } }; +/** + * @brief Update the progress indicator for the media item identified with the value in progress. + * + * @param mediaNodeIdentifier This is a unique ID provided by the caller. + * @param progress A value between 0 and 1 indicating the progress on the media upload. + */ +ZSSEditor.setProgressOnMedia = function(mediaNodeIdentifier, progress) { + var mediaNode = this.getMediaNodeWithIdentifier(mediaNodeIdentifier); + var mediaProgressNode = this.getMediaProgressNodeWithIdentifier(mediaNodeIdentifier); + + // Don't allow the progress bar to move backward + if (mediaNode.length == 0 || mediaProgressNode.length == 0 || mediaProgressNode.attr("value") > progress) { + return; + } + + if (progress == 0) { + mediaNode.addClass("uploading"); + } + + // Revert to non-compatibility image container once image upload has begun. This centers the overlays on the image + // (instead of the screen), while still circumventing the small container bug the compat class was added to fix + if (progress > 0) { + this.getMediaContainerNodeWithIdentifier(mediaNodeIdentifier).removeClass("compat"); + } + + // Sometimes the progress bar can be stuck at 100% for a long time while further processing happens + // From a UX perspective, it's better to just keep the progress bars at 90% until the upload is really complete + // and the progress bar is removed entirely + if (progress > 0.9) { + return; + } + + mediaProgressNode.attr("value", progress); +}; + +ZSSEditor.setupOptimisticProgressUpdate = function(mediaNodeIdentifier, nCall) { + setTimeout(ZSSEditor.sendOptimisticProgressUpdate, nCall * 100, mediaNodeIdentifier, nCall); +}; + +ZSSEditor.sendOptimisticProgressUpdate = function(mediaNodeIdentifier, nCall) { + if (nCall > 15) { + return; + } + + var mediaNode = ZSSEditor.getMediaNodeWithIdentifier(mediaNodeIdentifier); + + // Don't send progress updates to failed media + if (mediaNode.length != 0 && mediaNode[0].classList.contains("failed")) { + return; + } + + ZSSEditor.setProgressOnMedia(mediaNodeIdentifier, nCall / 100); + ZSSEditor.setupOptimisticProgressUpdate(mediaNodeIdentifier, nCall + 1); +}; + ZSSEditor.sendMediaRemovedCallback = function(mediaNodeIdentifier) { var arguments = ['id=' + encodeURIComponent(mediaNodeIdentifier)]; var joinedArguments = arguments.join(defaultCallbackSeparator); @@ -1128,6 +1201,12 @@ ZSSEditor.insertLocalImage = function(imageNodeIdentifier, localImageUrl) { ZSSEditor.trackNodeForMutation(this.getImageContainerNodeWithIdentifier(imageNodeIdentifier)); + this.setProgressOnMedia(imageNodeIdentifier, 0); + + if (nativeState.androidApiLevel > 18) { + setTimeout(ZSSEditor.setupOptimisticProgressUpdate, 300, imageNodeIdentifier, 1); + } + this.sendEnabledStyles(); }; @@ -1225,34 +1304,6 @@ ZSSEditor.tryToReload = function (image, imageNode, imageNodeIdentifier, remoteI setTimeout(ZSSEditor.reloadImage, nCall * 500, image, imageNode, imageNodeIdentifier, remoteImageId, nCall); } -/** - * @brief Update the progress indicator for the image identified with the value in progress. - * - * @param imageNodeIdentifier This is a unique ID provided by the caller. - * @param progress A value between 0 and 1 indicating the progress on the image. - */ -ZSSEditor.setProgressOnImage = function(imageNodeIdentifier, progress) { - var imageNode = this.getImageNodeWithIdentifier(imageNodeIdentifier); - if (imageNode.length == 0){ - return; - } - if (progress < 1){ - imageNode.addClass("uploading"); - } - - // Revert to non-compatibility image container once image upload has begun. This centers the overlays on the image - // (instead of the screen), while still circumventing the small container bug the compat class was added to fix - if (progress > 0) { - this.getImageContainerNodeWithIdentifier(imageNodeIdentifier).removeClass("compat"); - } - - var imageProgressNode = this.getImageProgressNodeWithIdentifier(imageNodeIdentifier); - if (imageProgressNode.length == 0){ - return; - } - imageProgressNode.attr("value",progress); -}; - /** * @brief Notifies that the image upload as finished * @@ -1330,6 +1381,7 @@ ZSSEditor.markImageUploadFailed = function(imageNodeIdentifier, message) { var imageProgressNode = this.getImageProgressNodeWithIdentifier(imageNodeIdentifier); if (imageProgressNode.length != 0){ imageProgressNode.addClass('failed'); + imageProgressNode.attr("value", 0); } // Delete the compatibility overlay if present @@ -1360,6 +1412,12 @@ ZSSEditor.unmarkImageUploadFailed = function(imageNodeIdentifier) { // Display the compatibility overlay again if present imageContainerNode.find("span.upload-overlay").removeClass("failed"); + + this.setProgressOnMedia(imageNodeIdentifier, 0); + + if (nativeState.androidApiLevel > 18) { + setTimeout(ZSSEditor.setupOptimisticProgressUpdate, 300, imageNodeIdentifier, 1); + } }; /** @@ -1459,6 +1517,12 @@ ZSSEditor.insertLocalVideo = function(videoNodeIdentifier, posterURL) { ZSSEditor.trackNodeForMutation(this.getVideoContainerNodeWithIdentifier(videoNodeIdentifier)); + this.setProgressOnMedia(videoNodeIdentifier, 0); + + if (nativeState.androidApiLevel > 18) { + setTimeout(ZSSEditor.setupOptimisticProgressUpdate, 300, videoNodeIdentifier, 1); + } + this.sendEnabledStyles(); }; @@ -1523,35 +1587,6 @@ ZSSEditor.replaceLocalVideoWithRemoteVideo = function(videoNodeIdentifier, remot setTimeout(function() { thisObj.sendVideoReplacedCallback(videoNodeIdentifier);}, 500); }; -/** - * @brief Update the progress indicator for the Video identified with the value in progress. - * - * @param VideoNodeIdentifier This is a unique ID provided by the caller. - * @param progress A value between 0 and 1 indicating the progress on the Video. - */ -ZSSEditor.setProgressOnVideo = function(videoNodeIdentifier, progress) { - var videoNode = this.getVideoNodeWithIdentifier(videoNodeIdentifier); - if (videoNode.length == 0){ - return; - } - if (progress < 1){ - videoNode.addClass("uploading"); - } - - // Revert to non-compatibility video container once video upload has begun. This centers the overlays on the - // placeholder image (instead of the screen), while still circumventing the small container bug the compat class - // was added to fix - if (progress > 0) { - this.getVideoContainerNodeWithIdentifier(videoNodeIdentifier).removeClass("compat"); - } - - var videoProgressNode = this.getVideoProgressNodeWithIdentifier(videoNodeIdentifier); - if (videoProgressNode.length == 0){ - return; - } - videoProgressNode.attr("value",progress); -}; - /** * @brief Callbacks to native that the video upload as finished and the local url was replaced by the remote url * @@ -1611,6 +1646,7 @@ ZSSEditor.markVideoUploadFailed = function(videoNodeIdentifier, message) { var videoProgressNode = this.getVideoProgressNodeWithIdentifier(videoNodeIdentifier); if (videoProgressNode.length != 0){ videoProgressNode.addClass('failed'); + videoProgressNode.attr("value", 0); } // Delete the compatibility overlay if present @@ -1641,6 +1677,12 @@ ZSSEditor.unmarkVideoUploadFailed = function(videoNodeIdentifier) { // Display the compatibility overlay again if present videoContainerNode.find("span.upload-overlay").removeClass("failed"); + + this.setProgressOnMedia(videoNodeIdentifier, 0); + + if (nativeState.androidApiLevel > 18) { + setTimeout(ZSSEditor.setupOptimisticProgressUpdate, 300, videoNodeIdentifier, 1); + } }; /**