Skip to content

Commit f58d0e3

Browse files
authored
Media flow component for media replace (#16200)
* adds a media replace flow component to the image block * fixes bug in chrome where options would be scrolled out of view * removed hiding the URL viewer on popover close
1 parent db52a91 commit f58d0e3

6 files changed

Lines changed: 328 additions & 75 deletions

File tree

‎packages/block-editor/README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,10 @@ _Related_
320320

321321
- <https://github.com/WordPress/gutenberg/blob/master/packages/block-editor/src/components/media-placeholder/README.md>
322322

323+
<a name="MediaReplaceFlow" href="#MediaReplaceFlow">#</a> **MediaReplaceFlow**
324+
325+
Undocumented declaration.
326+
323327
<a name="MediaUpload" href="#MediaUpload">#</a> **MediaUpload**
324328

325329
_Related_

‎packages/block-editor/src/components/index.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export { default as InnerBlocks } from './inner-blocks';
2828
export { default as InspectorAdvancedControls } from './inspector-advanced-controls';
2929
export { default as InspectorControls } from './inspector-controls';
3030
export { default as __experimentalLinkControl } from './link-control';
31+
export { default as MediaReplaceFlow } from './media-replace-flow';
3132
export { default as MediaPlaceholder } from './media-placeholder';
3233
export { default as MediaUpload } from './media-upload';
3334
export { default as MediaUploadCheck } from './media-upload/check';
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { useState, createRef } from '@wordpress/element';
5+
import { __ } from '@wordpress/i18n';
6+
import { speak } from '@wordpress/a11y';
7+
import {
8+
FormFileUpload,
9+
NavigableMenu,
10+
MenuItem,
11+
Toolbar,
12+
Button,
13+
Popover,
14+
withNotices,
15+
} from '@wordpress/components';
16+
import {
17+
LEFT,
18+
RIGHT,
19+
UP,
20+
DOWN,
21+
BACKSPACE,
22+
ENTER,
23+
} from '@wordpress/keycodes';
24+
import { useSelect } from '@wordpress/data';
25+
import { compose } from '@wordpress/compose';
26+
27+
/**
28+
* Internal dependencies
29+
*/
30+
import MediaUpload from '../media-upload';
31+
import MediaUploadCheck from '../media-upload/check';
32+
import LinkEditor from '../url-popover/link-editor';
33+
import LinkViewer from '../url-popover/link-viewer';
34+
35+
const MediaReplaceFlow = (
36+
{
37+
mediaURL,
38+
allowedTypes,
39+
accept,
40+
onSelect,
41+
onSelectURL,
42+
onError,
43+
name = __( 'Replace' ),
44+
}
45+
) => {
46+
const [ showURLInput, setShowURLInput ] = useState( false );
47+
const [ showEditURLInput, setShowEditURLInput ] = useState( false );
48+
const [ mediaURLValue, setMediaURLValue ] = useState( mediaURL );
49+
const [ showMediaReplaceOptions, setShowMediaReplaceOptions ] = useState( false );
50+
const mediaUpload = useSelect( ( select ) => {
51+
return select( 'core/block-editor' ).getSettings().mediaUpload;
52+
} );
53+
const editMediaButtonRef = createRef();
54+
55+
const stopPropagation = ( event ) => {
56+
event.stopPropagation();
57+
};
58+
59+
const stopPropagationRelevantKeys = ( event ) => {
60+
if ( [ LEFT, DOWN, RIGHT, UP, BACKSPACE, ENTER ].indexOf( event.keyCode ) > -1 ) {
61+
// Stop the key event from propagating up to ObserveTyping.startTypingInTextField.
62+
event.stopPropagation();
63+
}
64+
};
65+
66+
const selectMedia = ( media ) => {
67+
onSelect( media );
68+
setMediaURLValue( media.url );
69+
speak( __( 'The media file has been replaced' ) );
70+
};
71+
72+
const selectURL = ( newURL ) => {
73+
onSelectURL( newURL );
74+
setShowEditURLInput( false );
75+
};
76+
77+
const uploadFiles = ( event ) => {
78+
const files = event.target.files;
79+
const setMedia = ( [ media ] ) => {
80+
setShowMediaReplaceOptions( false );
81+
selectMedia( media );
82+
};
83+
mediaUpload( {
84+
allowedTypes,
85+
filesList: files,
86+
onFileChange: setMedia,
87+
onError,
88+
} );
89+
};
90+
91+
const onClose = () => {
92+
editMediaButtonRef.current.focus();
93+
};
94+
95+
const onClickOutside = () => ( setShowMediaReplaceOptions( false ) );
96+
97+
const openOnArrowDown = ( event ) => {
98+
if ( event.keyCode === DOWN ) {
99+
event.preventDefault();
100+
event.stopPropagation();
101+
event.target.click();
102+
}
103+
};
104+
105+
let urlInputUIContent;
106+
if ( showEditURLInput ) {
107+
urlInputUIContent = (
108+
<LinkEditor
109+
onKeyDown={ stopPropagationRelevantKeys }
110+
onKeyPress={ stopPropagation }
111+
value={ mediaURLValue }
112+
isFullWidthInput={ true }
113+
hasInputBorder={ true }
114+
onChangeInputValue={ ( url ) => ( setMediaURLValue( url ) ) }
115+
onSubmit={ ( event ) => {
116+
event.preventDefault();
117+
selectURL( mediaURLValue );
118+
editMediaButtonRef.current.focus();
119+
} }
120+
/>
121+
);
122+
} else {
123+
urlInputUIContent = (
124+
<LinkViewer
125+
isFullWidth={ true }
126+
className="editor-format-toolbar__link-container-content block-editor-format-toolbar__link-container-content"
127+
url={ mediaURLValue }
128+
onEditLinkClick={ () => ( setShowEditURLInput( ! showEditURLInput ) ) }
129+
/>
130+
);
131+
}
132+
133+
return (
134+
<MediaUpload
135+
onSelect={ ( media ) => selectMedia( media ) }
136+
onClose={ () => setShowMediaReplaceOptions( true ) }
137+
allowedTypes={ allowedTypes }
138+
render={ ( { open } ) => (
139+
<Toolbar className={ 'media-replace-flow components-dropdown-menu' }>
140+
<Button
141+
ref={ editMediaButtonRef }
142+
className={ 'components-icon-button components-dropdown-menu__toggle' }
143+
onClick={ () => {
144+
setShowMediaReplaceOptions( ! showMediaReplaceOptions );
145+
} }
146+
onKeyDown={ openOnArrowDown }
147+
>
148+
<span className="components-dropdown-menu__label" > { name } </span>
149+
<span className="components-dropdown-menu__indicator" />
150+
</Button>
151+
{ showMediaReplaceOptions &&
152+
<Popover
153+
onClickOutside={ onClickOutside }
154+
onClose={ onClose }
155+
className={ 'media-replace-flow__options' }
156+
>
157+
<NavigableMenu>
158+
<MenuItem
159+
icon="admin-media"
160+
onClick={ open }
161+
>
162+
{ __( 'Open Media Library' ) }
163+
</MenuItem>
164+
<MediaUploadCheck>
165+
<FormFileUpload
166+
onChange={ uploadFiles }
167+
accept={ accept }
168+
render={ ( { openFileDialog } ) => {
169+
return (
170+
<MenuItem
171+
icon="upload"
172+
onClick={ () => {
173+
openFileDialog();
174+
} }
175+
>
176+
{ __( 'Upload' ) }
177+
</MenuItem>
178+
);
179+
} }
180+
/>
181+
</MediaUploadCheck>
182+
<MenuItem
183+
icon="admin-links"
184+
onClick={ () => ( setShowURLInput( ! showURLInput ) ) }
185+
aria-expanded={ showURLInput }
186+
>
187+
<div> { __( 'Insert from URL' ) } </div>
188+
</MenuItem>
189+
</NavigableMenu>
190+
{ showURLInput && <div className="block-editor-media-flow__url-input">
191+
{ urlInputUIContent }
192+
</div> }
193+
</Popover>
194+
}
195+
</Toolbar>
196+
) }
197+
/>
198+
);
199+
};
200+
201+
export default compose(
202+
withNotices,
203+
)( MediaReplaceFlow );
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
.media-replace-flow .components-dropdown-menu__indicator {
2+
margin-left: 4px;
3+
4+
.components-dropdown-menu.media-flow_toolbar {
5+
.components-dropdown-menu__label {
6+
margin-right: 6px;
7+
margin-left: 2px;
8+
}
9+
}
10+
}
11+
12+
.media-replace-flow__options.components-popover:not(.is-mobile) {
13+
14+
.components-popover__content {
15+
// this is a safari problem that shows scrollbars
16+
// when display:flex and max-width are used together
17+
overflow-x: hidden;
18+
}
19+
20+
.block-editor-media-flow__url-input {
21+
22+
padding: 0 15px 10px 25px;
23+
24+
.components-external-link__icon {
25+
position: absolute;
26+
right: -4px;
27+
bottom: 5px;
28+
margin-right: 2px;
29+
}
30+
31+
input {
32+
max-width: 169px;
33+
border: 1px solid $dark-gray-500;
34+
border-radius: 4px;
35+
}
36+
37+
.block-editor-url-popover__link-viewer-url {
38+
padding-right: 15px;
39+
padding-top: 3px;
40+
max-width: 179px;
41+
position: relative;
42+
margin-right: 0;
43+
}
44+
45+
// Mimic toolbar component styles for the icons in this popover.
46+
.components-icon-button {
47+
padding: 5px;
48+
width: 40px;
49+
height: 40px;
50+
padding-left: 0;
51+
52+
> svg {
53+
padding: 5px;
54+
border-radius: $radius-round-rectangle;
55+
height: 30px;
56+
width: 30px;
57+
}
58+
59+
&:not(:disabled):not([aria-disabled="true"]):not(.is-default):hover {
60+
box-shadow: none;
61+
62+
> svg {
63+
@include formatting-button-style__hover;
64+
}
65+
}
66+
67+
&:not(:disabled):focus {
68+
box-shadow: none;
69+
70+
> svg {
71+
@include formatting-button-style__focus;
72+
}
73+
}
74+
}
75+
}
76+
}

‎packages/block-editor/src/style.scss‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
@import "./components/inserter-with-shortcuts/style.scss";
2525
@import "./components/inserter/style.scss";
2626
@import "./components/inserter-list-item/style.scss";
27+
@import "./components/media-replace-flow/style.scss";
2728
@import "./components/media-placeholder/style.scss";
2829
@import "./components/multi-selection-inspector/style.scss";
2930
@import "./components/panel-color-settings/style.scss";

0 commit comments

Comments
 (0)