Skip to content

Commit

Permalink
Mobile stories block (#17140)
Browse files Browse the repository at this point in the history
* basic scaffolding for the Story block on mobile editor

* added flex alignment and actual mediaFiles attribute rendering

* applying styles to stories block view

* removed unused alignment

* added StoryEditingButton and showing it when the story block is selected

* connecting StoryEditingButton with bridge's requestStoryCreatorLoad

* updated requestStoryCreatorLoad call to pass block's mediaFiles and clientId over the bridge

* added new component StoryUpdateProgress

* Revert "added new component StoryUpdateProgress", not belonging to this branch

This reverts commit 86fb956.

* fixed lint warnings

* changed the no longer existing color dark-gray-500 removed in WordPress/gutenberg@162bc50 with gray-700

* added package-lock.json

* Mobile Stories block (part 2): introduce StoryUpdateProgress (#17222)

* Revert "Revert "added new component StoryUpdateProgress", not belonging to this branch"

This reverts commit 34349b1.

* added mediaSave statuses listeners definitions

* moved StoryEdit to a React.Component class and implemented StoryUpdateProgress overlay

* added onStorySaveResult handling to Story block

* edit mode: replacing urls by id for saving process

* added onMediaModelCreated() callback so we can re-assign the mediaID to the mediaFiles attribute of a Story block once such a mediaModel is created

* update the mediaFile id and URL of a given story frame when finished uploading succesfully

* removed commented imports

* added explicit TODO comments to make sure to follow up on them for error handling

* make sure to call mediaUploadSync and storySaveSync if any of the mediaFiles contained in this block is not a remote url - also call onRemoveBlockCheckUpload() action under the same conditions in componentWillUnmount()

* Mobile Stories block (part 3): rename using BlockMediaUpdateProgress (#17456)

* using BlockMediaUpdateProgress

* method rename

* updated (automatically modified) package-lock.json

* removed package-lock.json

* moved condition check for http or https to helper method isUrlRemote()

* calling mediaUploadSync and mediaSaveSync regardless of ids, we always will want to receive updates on all 3 stages

* fixed spelling

* Mobile Stories block (part 4): error handling (#17458)

* Revert "Revert "added new component StoryUpdateProgress", not belonging to this branch"

This reverts commit 34349b1.

* added mediaSave statuses listeners definitions

* moved StoryEdit to a React.Component class and implemented StoryUpdateProgress overlay

* added onStorySaveResult handling to Story block

* edit mode: replacing urls by id for saving process

* added onMediaModelCreated() callback so we can re-assign the mediaID to the mediaFiles attribute of a Story block once such a mediaModel is created

* update the mediaFile id and URL of a given story frame when finished uploading succesfully

* removed commented imports

* added explicit TODO comments to make sure to follow up on them for error handling

* make sure to call mediaUploadSync and storySaveSync if any of the mediaFiles contained in this block is not a remote url - also call onRemoveBlockCheckUpload() action under the same conditions in componentWillUnmount()

* using BlockMediaUpdateProgress

* method rename

* updated story block to represent error state

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

* using a deep copy of mediaFiles when replacing ids and mediaUrl, given mediaFiles attribute needs to be replaced again and cannot be modified in place as per React conventions

* added requestMediaFilesSaveCancelDialog bridge method

* changed props name onMediaModelCreated to more generic onMediaIdChanged

* removed commented code

* replaced for loops with map

* Mobile Stories block (part 5): add empty placeholder (#17539)

* Revert "Revert "added new component StoryUpdateProgress", not belonging to this branch"

This reverts commit 34349b1.

* added mediaSave statuses listeners definitions

* moved StoryEdit to a React.Component class and implemented StoryUpdateProgress overlay

* added onStorySaveResult handling to Story block

* edit mode: replacing urls by id for saving process

* added onMediaModelCreated() callback so we can re-assign the mediaID to the mediaFiles attribute of a Story block once such a mediaModel is created

* update the mediaFile id and URL of a given story frame when finished uploading succesfully

* removed commented imports

* added explicit TODO comments to make sure to follow up on them for error handling

* make sure to call mediaUploadSync and storySaveSync if any of the mediaFiles contained in this block is not a remote url - also call onRemoveBlockCheckUpload() action under the same conditions in componentWillUnmount()

* using BlockMediaUpdateProgress

* method rename

* updated story block to represent error state

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

* using a deep copy of mediaFiles when replacing ids and mediaUrl, given mediaFiles attribute needs to be replaced again and cannot be modified in place as per React conventions

* added requestMediaFilesSaveCancelDialog bridge method

* changed props name onMediaModelCreated to more generic onMediaIdChanged

* removed commented code

* added MediaPlaceholder, wrapped in a pointerEvents=none View so we can handle media picking from the StoryComposer

* replaced for loops with map

* importing defined const MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO instead of re-defining

* made placeholder container look as per any other media placeholder, reservign the Story style container for populated Story blocks

* Mobile Stories block (part 6): rewrote StoryEdit class as a function (#17549)

* Revert "Revert "added new component StoryUpdateProgress", not belonging to this branch"

This reverts commit 34349b1.

* added mediaSave statuses listeners definitions

* moved StoryEdit to a React.Component class and implemented StoryUpdateProgress overlay

* added onStorySaveResult handling to Story block

* edit mode: replacing urls by id for saving process

* added onMediaModelCreated() callback so we can re-assign the mediaID to the mediaFiles attribute of a Story block once such a mediaModel is created

* update the mediaFile id and URL of a given story frame when finished uploading succesfully

* removed commented imports

* added explicit TODO comments to make sure to follow up on them for error handling

* make sure to call mediaUploadSync and storySaveSync if any of the mediaFiles contained in this block is not a remote url - also call onRemoveBlockCheckUpload() action under the same conditions in componentWillUnmount()

* using BlockMediaUpdateProgress

* method rename

* updated story block to represent error state

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

* using a deep copy of mediaFiles when replacing ids and mediaUrl, given mediaFiles attribute needs to be replaced again and cannot be modified in place as per React conventions

* added requestMediaFilesSaveCancelDialog bridge method

* changed props name onMediaModelCreated to more generic onMediaIdChanged

* removed commented code

* added MediaPlaceholder, wrapped in a pointerEvents=none View so we can handle media picking from the StoryComposer

* replaced for loops with map

* rewrote StoryEdit class as a function

* removed unused imports

* removed unused function

* story block: set inserter support to false for web, but keep it for mobile (#17690)

* renamed params to avoid name shadowing
  • Loading branch information
mzorz authored Nov 10, 2020
1 parent 2b46fb8 commit 898c8d7
Show file tree
Hide file tree
Showing 8 changed files with 388 additions and 4 deletions.
7 changes: 6 additions & 1 deletion extensions/blocks/contact-info/edit.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ const TEMPLATE = [ [ 'jetpack/email' ], [ 'jetpack/phone' ], [ 'jetpack/address'
const ContactInfoEdit = () => {
return (
<View style={ styles.jetpackContactInfoBlock }>
<InnerBlocks allowedBlocks={ ALLOWED_BLOCKS } templateLock={ false } templateInsertUpdatesSelection={ false } template={ TEMPLATE } />
<InnerBlocks
allowedBlocks={ ALLOWED_BLOCKS }
templateLock={ false }
templateInsertUpdatesSelection={ false }
template={ TEMPLATE }
/>
</View>
);
};
Expand Down
240 changes: 240 additions & 0 deletions extensions/blocks/story/edit.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
/**
* External dependencies
*/
import { Text, View, TouchableWithoutFeedback } from 'react-native';
/**
* WordPress dependencies
*/
import { Image } from '@wordpress/components';
import {
BlockIcon,
MediaPlaceholder,
BlockMediaUpdateProgress,
MEDIA_TYPE_IMAGE,
MEDIA_TYPE_VIDEO,
} from '@wordpress/block-editor';
import { __, sprintf } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';
import { getProtocol } from '@wordpress/url';
import {
requestMediaFilesFailedRetryDialog,
requestMediaFilesSaveCancelDialog,
requestMediaFilesUploadCancelDialog,
mediaUploadSync,
mediaSaveSync,
requestMediaFilesEditorLoad,
} from '@wordpress/react-native-bridge';

/**
* Internal dependencies
*/
import { icon } from '.';
import styles from './editor.scss';
import StoryEditingButton from './story-editing-button';

const StoryEdit = ( { attributes, isSelected, clientId, setAttributes, onFocus } ) => {
const { mediaFiles } = attributes;
const hasContent = !! mediaFiles.length;

// setup state vars
const [ isUploadInProgress, setUploadInProgress ] = useState( false );

const [ isSaveInProgress, setSaveInProgress ] = useState( false );

const [ didUploadFail, setUploadFail ] = useState( false );

const [ didSaveFail, setSaveFail ] = useState( false );

// sync with local media store
useEffect( mediaUploadSync, [] );

useEffect( mediaSaveSync, [] );

function onEditButtonTapped() {
// let's open the Story Creator and load this block in there
requestMediaFilesEditorLoad( mediaFiles, clientId );
}

// upload state handling methods
function updateMediaUploadProgress( payload ) {
if ( payload.mediaUrl ) {
setAttributes( { url: payload.mediaUrl } );
}
if ( ! isUploadInProgress ) {
setUploadInProgress( true );
}
}

function finishMediaUploadWithSuccess( payload ) {
// find the mediaFiles item that needs to change via its id, and apply the new URL
const updatedMediaFiles = replaceNewIdInMediaFilesByOldId(
payload.mediaId,
payload.mediaServerId,
payload.mediaUrl
);
setAttributes( { mediaFiles: updatedMediaFiles } );
setUploadInProgress( false );
}

function finishMediaUploadWithFailure( payload ) {
// should anything be done on media upload failure, do it here
setUploadInProgress( false );
setUploadFail( true );
}

function mediaUploadStateReset() {
setUploadInProgress( false );
}

// save state handling methods
function updateMediaSaveProgress( payload ) {
if ( payload.mediaUrl ) {
setAttributes( { url: payload.mediaUrl } );
}
if ( ! isSaveInProgress ) {
setSaveInProgress( true );
}
}

function replaceMediaUrlInMediaFilesById( mediaId, mediaUrl ) {
if ( mediaId !== undefined ) {
const newMediaFiles = mediaFiles.map( mediaFile => {
if ( mediaFile.id === mediaId.toString() ) {
// we need to deep copy because attributes can't be modified in-place
return { ...mediaFile, url: mediaUrl, link: mediaUrl };
}
return { ...mediaFile };
} );
return newMediaFiles;
}
return mediaFiles;
}

function replaceNewIdInMediaFilesByOldId( oldId, mediaId, mediaUrl ) {
if ( mediaId !== undefined ) {
const newMediaFiles = mediaFiles.map( mediaFile => {
if ( mediaFile.id === oldId.toString() ) {
// we need to deep copy because attributes can't be modified in-place
return { ...mediaFile, id: mediaId, url: mediaUrl, link: mediaUrl };
}
return { ...mediaFile };
} );
return newMediaFiles;
}
return mediaFiles;
}

function finishMediaSaveWithSuccess( payload ) {
// find the mediaFiles item that needs to change via its id, and apply the new URL
const updatedMediaFiles = replaceMediaUrlInMediaFilesById( payload.mediaId, payload.mediaUrl );
setAttributes( { mediaFiles: updatedMediaFiles } );
setSaveInProgress( false );
}

function finishMediaSaveWithFailure( payload ) {
// should anything be done on save failure on one single item in the media collection, do it here
setSaveInProgress( false );
}

function mediaSaveStateReset() {
setSaveInProgress( false );
}

function onStorySaveResult( payload ) {
// when story result ends up in failure, the failed overlay will be set in BlockMediaUpdateProgress
setSaveInProgress( false );
setSaveFail( ! payload.success );
}

function onMediaIdChanged( payload ) {
const updatedMediaFiles = replaceNewIdInMediaFilesByOldId(
payload.mediaId,
payload.newId,
payload.mediaUrl
);
setAttributes( { mediaFiles: updatedMediaFiles } );
setSaveInProgress( false );
}

function onStoryPressed() {
if ( isUploadInProgress ) {
// issue cancellation for all media files involved
requestMediaFilesUploadCancelDialog( mediaFiles );
} else if ( isSaveInProgress ) {
requestMediaFilesSaveCancelDialog( mediaFiles );
} else if ( didUploadFail ) {
requestMediaFilesFailedRetryDialog( mediaFiles );
} else {
// open the editor
onEditButtonTapped();
}
}

const mediaPlaceholder = (
// TODO this we are wrapping in a pointerEvents=none because we don't want to
// trigger the ADD MEDIA bottom sheet just yet, but only give the placedholder the right appearance.
<View pointerEvents="none" style={ styles[ 'content-placeholder' ] }>
<MediaPlaceholder
icon={ <BlockIcon icon={ icon } /> }
labels={ {
title: __( 'Story' ),
instructions: __( 'ADD MEDIA' ),
} }
allowedTypes={ [ MEDIA_TYPE_IMAGE, MEDIA_TYPE_VIDEO ] }
onFocus={ onFocus }
/>
</View>
);

return (
<TouchableWithoutFeedback
accessible={ ! isSelected }
onPress={ onStoryPressed }
disabled={ ! isSelected }
>
<View style={ styles[ 'content-placeholder' ] }>
{ ! hasContent && mediaPlaceholder }
{ hasContent && (
<View style={ styles[ 'wp-story-container' ] }>
{ ! isUploadInProgress && ! isSaveInProgress && isSelected && (
<StoryEditingButton onEditButtonTapped={ onEditButtonTapped } />
) }
<BlockMediaUpdateProgress
coverUrl={ mediaFiles[ 0 ].url } // just select the first one // TODO see how to handle video
mediaFiles={ mediaFiles }
onUpdateMediaUploadProgress={ updateMediaUploadProgress }
onFinishMediaUploadWithSuccess={ finishMediaUploadWithSuccess }
onFinishMediaUploadWithFailure={ finishMediaUploadWithFailure }
onMediaUploadStateReset={ mediaUploadStateReset }
onUpdateMediaSaveProgress={ updateMediaSaveProgress }
onFinishMediaSaveWithSuccess={ finishMediaSaveWithSuccess }
onFinishMediaSaveWithFailure={ finishMediaSaveWithFailure }
onMediaSaveStateReset={ mediaSaveStateReset }
onFinalSaveResult={ onStorySaveResult }
onMediaIdChanged={ onMediaIdChanged }
renderContent={ ( {
isUploadProgressing,
isUploadFailed,
isSaveProgressing,
isSaveFailed,
retryMessage,
} ) => {
return (
<Image
isUploadFailed={ isUploadFailed || isSaveFailed }
isUploadInProgress={ isUploadProgressing || isSaveProgressing }
retryMessage={ retryMessage }
url={ mediaFiles[ 0 ].url } // just select the first one // TODO see how to handle video
style={ styles[ 'wp-story-image' ] }
/>
);
} }
/>
</View>
) }
</View>
</TouchableWithoutFeedback>
);
};

export default StoryEdit;
97 changes: 97 additions & 0 deletions extensions/blocks/story/editor.native.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

@import './player/variables.scss';

.wp-story-container {
height: 320px;
width: 180px;
margin-left: auto;
margin-right: auto;
position: relative;
list-style: none;
padding: 0;
z-index: 1;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);

.wp-story-wrapper {
display: block;
position: absolute;
height: auto;
bottom: 0;
top: 0;
left: 0;
right: 0;
z-index: -1;
border-radius: 15px;
background-color: $wp-story-background-color;
}

.wp-story-slide {
display: flex;
height: 100%;
width: 100%;

figure {
align-items: center;
display: flex;
height: 100%;
width: 100%;
justify-content: center;
margin: 0;
position: relative;
overflow: hidden;
object-fit: contain;
}

}

.wp-story-image, .wp-story-video {
display: block;
height: auto;
width: auto;
max-height: 100%;
max-width: 100%;
margin: 0;
border: 0;

&.wp-story-crop-wide {
max-width: revert;
}

&.wp-story-crop-narrow {
max-height: revert;
}
}

}

.editContainer {
width: 44px;
height: 44px;
position: absolute;
top: 0;
right: 0;
z-index: 2;
}

.edit {
width: 30px;
height: 30px;
background-color: $gray-dark;
border-radius: 22px;
position: absolute;
top: 5px;
right: 5px;
}

.iconCustomize {
fill: #fff;
position: absolute;
top: 7px;
left: 7px;
}

.content-placeholder {
flex: 1;
}
10 changes: 10 additions & 0 deletions extensions/blocks/story/icon-customize.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* WordPress dependencies
*/
import { Path, SVG } from '@wordpress/components';

export default (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<Path d="M2 6c0-1.505.78-3.08 2-4 0 .845.69 2 2 2 1.657 0 3 1.343 3 3 0 .386-.08.752-.212 1.09.74.594 1.476 1.19 2.19 1.81L8.9 11.98c-.62-.716-1.214-1.454-1.807-2.192C6.753 9.92 6.387 10 6 10c-2.21 0-4-1.79-4-4zm12.152 6.848l1.34-1.34c.607.304 1.283.492 2.008.492 2.485 0 4.5-2.015 4.5-4.5 0-.725-.188-1.4-.493-2.007L18 9l-2-2 3.507-3.507C18.9 3.188 18.225 3 17.5 3 15.015 3 13 5.015 13 7.5c0 .725.188 1.4.493 2.007L3 20l2 2 6.848-6.848c1.885 1.928 3.874 3.753 5.977 5.45l1.425 1.148 1.5-1.5-1.15-1.425c-1.695-2.103-3.52-4.092-5.448-5.977z" />
</SVG>
);
5 changes: 3 additions & 2 deletions extensions/blocks/story/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/**
* External dependencies
* WordPress dependencies
*/
import { __, _x } from '@wordpress/i18n';
import { Platform } from '@wordpress/element';

/**
* Internal dependencies
Expand Down Expand Up @@ -57,7 +58,7 @@ export const settings = {
attributes,
supports: {
html: false,
inserter: false,
inserter: Platform.OS !== 'web', // false for web, true for mobile
},
icon: {
src: icon,
Expand Down
Loading

0 comments on commit 898c8d7

Please sign in to comment.