Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image: Adds the block controls for uploading image. #64320

Merged
merged 16 commits into from
Sep 11, 2024

Conversation

vipul0425
Copy link
Contributor

@vipul0425 vipul0425 commented Aug 7, 2024

What?

Fixes #54867

Why?

In small width size the image block placeholder looks broken and it's hard to choose an image from there.

How?

This PR is adding a BlockControl to choose an image which will have all the three options upload, choose from media library or add from a link and will keep the placeholder abstract.

Testing Instructions

  1. Add a columns block or grid block. Choose 6+ columns or grid in a row and add an image block.
  2. Now the choose an image option will appear in BlockControls Toolbar.

Screenshots or screencast

For Large Size
image

For Medium Size
image

For small Size
image

@jasmussen
Copy link
Contributor

Nice! This makes good progress, but doesn't yet fully fix 54867. Quick GIF:

Image

We need only two changes:

  1. Rename "Choose Image" to "Add image" (note the lowercase i)
  2. Don't show the "white material" in mobile.

2 should be possible with CSS. Essentially it should look like this:

Screenshot 2024-08-13 at 13 48 37

That maintains the gray placeholder material rather than the white <Placeholder /> material on-select. You should be able to use the CSS class already present, for this. Something like:

  • Change .wp-block-image.wp-block-image.is-selected .block-editor-media-placeholder to .wp-block-image.wp-block-image.is-selected .block-editor-media-placeholder:not(.is-small)
  • Change .wp-block-image.wp-block-image.is-selected .block-editor-media-placeholder .components-placeholder__illustration to .wp-block-image.wp-block-image.is-selected .block-editor-media-placeholder .components-placeholder__illustration:not(.is-small)`
  • Change .wp-block-image.wp-block-image.is-selected .block-editor-media-placeholder:before to .wp-block-image.wp-block-image.is-selected .block-editor-media-placeholder:not(.is-small):before
  • Change .is-selected>.components-placeholder.has-illustration .components-placeholder__label to .is-selected>.components-placeholder.has-illustration:not(.is-small) .components-placeholder__label

There are probably better places to do this, this was some quick inspector work. Let me know if you'd like me to try and push some code.

@vipul0425
Copy link
Contributor Author

@jasmussen Thanks for the feedback! I'll work on these changes, or if you'd like to make any updates yourself, feel free to do so 😄. Currently, I'm trying to find a workaround for the grid layout, as the current implementation isn't working with grids due to the lack of inner blocks for the columns. I'm experimenting with the number of columns, the minimum width of columns, etc. If you have any better solutions specifically for grids, please let me know.

@vipul0425 vipul0425 marked this pull request as ready for review August 14, 2024 06:17
Copy link

github-actions bot commented Aug 14, 2024

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: vipul0425 <[email protected]>
Co-authored-by: jasmussen <[email protected]>
Co-authored-by: t-hamano <[email protected]>
Co-authored-by: mirka <[email protected]>
Co-authored-by: ciampo <[email protected]>
Co-authored-by: tyxla <[email protected]>
Co-authored-by: richtabor <[email protected]>
Co-authored-by: annezazu <[email protected]>
Co-authored-by: fabiankaegy <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@jasmussen
Copy link
Contributor

Nice. Took the latest for a spin:

status

In the above I added a min-height: 60px; to the placeholder component. The default state is a bit small:

Screenshot 2024-08-14 at 10 16 41

Here's 60px again:

Screenshot 2024-08-14 at 10 14 22

CC: @WordPress/gutenberg-design in case you have opinions on the min-height of this state.

I noticed one glitch. when you insert the image in a narrow column, there's a brief flash of the white placeholder material:
insert

It's a very brief flash, but enough that it's annoying. Since the CSS would never show it, I wonder if it's the resize-observe applying the is-small CSS class with a slight delay? Is there any trick we can do here?

Separately, I noticed that this PR adds a resizeobserver locally to the Image block. That may be fine (perhaps @WordPress/gutenberg-components would know whether this is best done directly in the Placeholder component or if it's good to keep here). I'm asking mainly because the Placeholder component already has one, and it already applies a CSS class. Could we rely on that existing CSS class? It's fine that every placeholder gets the is-small changes you've made here, even if only the Image block makes use of them for now. Make sense?

const [ temporaryURL, setTemporaryURL ] = useState( attributes.blob );

const [ resizeListener, sizes ] = useResizeObserver();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty sure this one is already included in the Placeholder component, so it may be best to make these width changes to that component itself, rather than locally here.

const [ temporaryURL, setTemporaryURL ] = useState( attributes.blob );

const [ resizeListener, sizes ] = useResizeObserver();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we rely on that existing CSS class? It's fine that every placeholder gets the is-small changes you've made here, even if only the Image block makes use of them for now. Make sense?

Technically, yes. But in the long run, I'm thinking it could be cleaner and more flexible if this kind of responsive styling was handled solely by the consumer of Placeholder, using CSS container queries (rather than a resize observer). That way, consumers wouldn't have to depend on a set of arbitrary breakpoints that Placeholder defines.

I'm not intimately familiar with the requirements of this Image block in particular, but on first glance I would say it could make it simpler and more performant. Could be worth trying here. (This is a non-blocking comment, just suggesting in case it hadn't been considered.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mirka, this would be a good change, but since it applies more generally to the placeholder component, should we make the change in this PR or address it in a separate issue? Related: #64288

Copy link
Contributor

@ciampo ciampo Aug 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed with Lena — this seems a good fit for CSS Container Queries

(edit: when I wrote my message, I hadn't seen @vipul0425 's reply, which was posted almost at the same time)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vipul0425 I don't have much insight into the current state of things, but it looks like you were able to achieve what you needed in this PR without resize observers or container queries, so yes it seems like something that can be addressed as a separate issue 👍

@vipul0425
Copy link
Contributor Author

vipul0425 commented Aug 15, 2024

Hi @jasmussen, I have removed the ResizeObserver from this component and refactored the logic using CSS. I also added a min-height for the small variant.

@t-hamano, I have implemented the lockUrlControls logic in the external MediaReplaceFlow, and now we can also replace images. However, my only concern is that in the small size, the "Connected to post meta" text (lockUrlControlsMessage) is also hidden. Do you have any suggestions on where we can display this? I think there should be some visual distinction apart from just the purple image icon that is visible in the toolbar for this.

image image

@jasmussen
Copy link
Contributor

However, my only concern is that in the small size, the "Connected to post meta" text (lockUrlControlsMessage) is also hidden. Do you have any suggestions on where we can display this? I think there should be some visual distinction apart from just the purple image icon that is visible in the toolbar for this.

We should show this in the inspector, if it isn't already. If it's already shown there, it's okay to not show it here, and just show the same gray placeholder material.

Taking a look at the branch again now, thanks for your continued work!

@jasmussen
Copy link
Contributor

Works well!

Screenshot 2024-08-15 at 09 57 42

I'll defer to others for a code review. I also imagine that the brief flash of the white material is hard to fully get rid of. I wonder if—and this can be a separate followup PR—the logic should be inversed: instead of hiding the elaborate white placeholder states in narrow contexts, we show them in the wider contexts? Perhaps the flash would then also be inverted, so this might not be the best solution. But something to potentially try!

Comment on lines 39 to 43
.block-editor-media-placeholder:not(.is-small) {
.components-placeholder__fieldset,
.components-placeholder__label {
display: flex;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.components-* class names are meant to be a private implementation detail and should not be used outside of the components package.

Overrides like the one highlighter make this usage Placeholder component prone to future breakage, should the component update internally (either its classnames, or how its internal layout is organized).

Having said that, I can see that there are more examples of using private classnames and in general applying style overrides in this file, which is a strong indicator that we should completely rethink how the media placeholder is implemented. So, in that sense, my feedback shouldn't considered blocking for this specific PR, but rather a warning about how badly styles are architected for this component and the risks.

It feels like it's trying to bend the Placeholder component beyond its limits. Therefore:

  • we should determine if there are any features that we can add to the Placeholder component so that consumers don't have to resort to style overrides and other hacky practices;
  • otherwise, we should re-implement the component in a way that builds on top of Placeholder . For example, instead of setting display:none to .components-placeholder__illustration, why don't we pass the withIllustration={false} prop?
  • finally, if the designs / feature are diverging too much, we should ask ourselves whether this warrants a new, separate component (in the block library package)

@t-hamano
Copy link
Contributor

@vipul0425 Thanks for the update!

I have implemented the lockUrlControls logic in the external MediaReplaceFlow, and now we can also replace images.

Sorry if my suggestion was unclear.

What I meant is, I don't think we need to add another MediaReplaceFlow component. I mean, I think we can update here and add a prop like name={ ! url ? __( 'Add image' ) : __( 'Replace' ) }. Such an implementation is also done here.

@vipul0425
Copy link
Contributor Author

@t-hamano I tried to integrate that code here, but since it is defined within the Image component, which isn't rendered on the first load, the inspector controls aren't visible. Alternatively, we could define MediaReplace at the root level and reuse it in both the cases. Could you please guide me on whether that approach would work?

@t-hamano
Copy link
Contributor

@vipul0425

How about making the following changes to this PR?

  • Extract the MediaReplaceFlow component from the control component as mediaReplaceFlow
  • Make sure mediaReplaceFlow is always rendered
Diff
diff --git a/packages/block-library/src/image/edit.js b/packages/block-library/src/image/edit.js
index c2b1755670..e867461f00 100644
--- a/packages/block-library/src/image/edit.js
+++ b/packages/block-library/src/image/edit.js
@@ -375,34 +375,6 @@ export function ImageEdit( {
        return (
                <>
                        <figure { ...blockProps }>
-                               { ! ( temporaryURL || url ) && ! lockUrlControls && (
-                                       <BlockControls group="other">
-                                               <MediaReplaceFlow
-                                                       mediaId={ id }
-                                                       mediaURL={ url }
-                                                       allowedTypes={ ALLOWED_MEDIA_TYPES }
-                                                       accept="image/*"
-                                                       name={ __( 'Add image' ) }
-                                                       onSelect={ onSelectImage }
-                                                       onError={ onUploadError }
-                                               >
-                                                       <form className="block-editor-media-flow__url-input has-siblings">
-                                                               <span className="block-editor-media-replace-flow__image-url-label">
-                                                                       { __( 'Insert from URL:' ) }
-                                                               </span>
-
-                                                               <LinkControl
-                                                                       value={ { url } }
-                                                                       settings={ [] }
-                                                                       showSuggestions={ false }
-                                                                       onChange={ ( value ) => {
-                                                                               onSelectURL( value.url );
-                                                                       } }
-                                                               />
-                                                       </form>
-                                               </MediaReplaceFlow>
-                                       </BlockControls>
-                               ) }
                                <Image
                                        temporaryURL={ temporaryURL }
                                        attributes={ attributes }
diff --git a/packages/block-library/src/image/image.js b/packages/block-library/src/image/image.js
index 7dd03a7fb5..775482f0ab 100644
--- a/packages/block-library/src/image/image.js
+++ b/packages/block-library/src/image/image.js
@@ -551,6 +551,27 @@ export default function Image( {
 
        const showBlockControls = showUrlInput || allowCrop || showCoverControls;
 
+       const mediaReplaceFlow = (
+               <>
+                       { console.log(
+                               isSingleSelected && ! isEditingImage && ! lockUrlControls
+                       ) }
+                       { isSingleSelected && ! isEditingImage && ! lockUrlControls && (
+                               <BlockControls group="other">
+                                       <MediaReplaceFlow
+                                               mediaId={ id }
+                                               mediaURL={ url }
+                                               name={ ! url ? __( 'Add image' ) : __( 'Replace' ) }
+                                               allowedTypes={ ALLOWED_MEDIA_TYPES }
+                                               accept="image/*"
+                                               onSelect={ onSelectImage }
+                                               onSelectURL={ onSelectURL }
+                                               onError={ onUploadError }
+                                       />
+                               </BlockControls>
+                       ) }
+               </>
+       );
        const controls = (
                <>
                        { showBlockControls && (
@@ -587,19 +608,6 @@ export default function Image( {
                                        ) }
                                </BlockControls>
                        ) }
-                       { isSingleSelected && ! isEditingImage && ! lockUrlControls && (
-                               <BlockControls group="other">
-                                       <MediaReplaceFlow
-                                               mediaId={ id }
-                                               mediaURL={ url }
-                                               allowedTypes={ ALLOWED_MEDIA_TYPES }
-                                               accept="image/*"
-                                               onSelect={ onSelectImage }
-                                               onSelectURL={ onSelectURL }
-                                               onError={ onUploadError }
-                                       />
-                               </BlockControls>
-                       ) }
                        { isSingleSelected && externalBlob && (
                                <BlockControls>
                                        <ToolbarGroup>
@@ -1001,12 +1009,18 @@ export default function Image( {
        }
 
        if ( ! url && ! temporaryURL ) {
-               // Add all controls if the image attributes are connected.
-               return metadata?.bindings ? controls : sizeControls;
+               return (
+                       <>
+                               { mediaReplaceFlow }
+                               { /* Add all controls if the image attributes are connected. */ }
+                               { metadata?.bindings ? controls : sizeControls }
+                       </>
+               );
        }
 
        return (
                <>
+                       { mediaReplaceFlow }
                        { controls }
                        { img }

@akasunil akasunil added [Type] Enhancement A suggestion for improvement. [Block] Image Affects the Image Block props-bot Adding this label triggers the Props Bot workflow for a PR. labels Aug 23, 2024
@github-actions github-actions bot removed the props-bot Adding this label triggers the Props Bot workflow for a PR. label Aug 23, 2024
@vipul0425
Copy link
Contributor Author

vipul0425 commented Sep 2, 2024

Hi @t-hamano, I've updated the logic as you suggested. I used the reverse logic for the small container to prevent the initial flash of content. However, I noticed that this is causing two e2e tests to fail. If I'm understanding this correctly, this is due to the focus shifting to the image component instead of the Upload button on initial render.

If I remove the isLargeContainer && content condition inside the placeholder, the focus shifts back to the Upload button, but then the buttons also appear on the small container, which we don't want.

Do you have any suggestions? I tried manually focusing on the upload button when the component loads, but that approach didn't seem ideal.

Copy link
Contributor

@t-hamano t-hamano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update!

I'll look into the E2E test failures later, but I've left some feedback for further improvement.

packages/block-library/src/image/editor.scss Outdated Show resolved Hide resolved
packages/block-library/src/image/editor.scss Outdated Show resolved Hide resolved
packages/block-library/src/image/editor.scss Outdated Show resolved Hide resolved
packages/block-library/src/image/edit.js Outdated Show resolved Hide resolved
packages/block-library/src/image/edit.js Outdated Show resolved Hide resolved
Copy link
Contributor

@t-hamano t-hamano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update! I left some additional feedback and I think it's pretty close to being finished 👍

I'll investigate how to solve the E2E testing issue in more detail this weekend.

packages/block-library/src/image/edit.js Outdated Show resolved Hide resolved
packages/block-library/src/image/edit.js Outdated Show resolved Hide resolved
@jasmussen
Copy link
Contributor

@t-hamano apologies for the direct ping, do you still have bandwidth to look at the e2e tests? Otherwise I might be able to find someone else that can help, let me know!

@t-hamano
Copy link
Contributor

@jasmussen Sorry for the late reply.

From what I can see, the E2E test failure is definitely as described in this comment.

That is, after an Image block is inserted, the Upload button should be focused, as shown below:

image

However, in this PR, to prevent flashing the white placeholder in narrow columns like the one below, the placeholder is initially rendered with the illustration and no focusable elements:

357732875-aee02f3f-7d36-430d-ae3b-bd26621a72d0.online-video-cutter.com.mp4

So after the Image block is inserted, the Image block itself gets focus, not the Upload button:

image

We could modify the E2E tests themselves to fit this PR, but that might not be ideal. Because it would mean making an exception to the specification that is common to all blocks: "focus the first focusable element when a block is inserted."

As of now, I have not found an approach to solve this problem, so if anyone knows a good approach on what changes to make to this PR to achieve the following, I would appreciate your help 🙇‍♂️

  • In wide columns, the upload button gets focus when an image block is inserted.
  • In narrow columns, the image block placeholder always renders with an illustration and does not cause a flash.

@jasmussen
Copy link
Contributor

Thanks so much for the clarification.

I wonder: can we extract the little trick that changes the focusing to avoid the brief flash? It's one we can live without, at least initially. Fixing that flash may not be worth extra code complexity, and there might be a different approach to placeholders worth looking at separately.

@t-hamano
Copy link
Contributor

One suggestion would be to revert the illustration changes from this PR, i.e. just add an upload button to the toolbar. Adding an upload button would at least solve the issue of not being able to upload images in narrow columns.

Also, the problem we are trying to solve now will likely be encountered when applying a similar approach to other blocks (Media & Text block, Video block, etc.) in the future.

So maybe we can address this issue as a follow-up to find a more ideal solution.

What do you think?

@jasmussen
Copy link
Contributor

I would hate to lose the fact that the placeholder functions in narrow contexts, it's entirely broken at the moment. What I'm suggesting is we rewind back to maybe this state of the PR (if that helps), where everything seemed to work and the only problem was the brief flash of the full placeholder when inserting from the slash command in a narrow context. The reason being: that insertion is an edge-case, not too common. But the image functioning in narrow contexts is arguably common and would be fantastic to fix.

@t-hamano
Copy link
Contributor

What I'm suggesting is we rewind back to maybe this state of the PR (if that helps), where everything seemed to work and the only problem was the brief flash of the full placeholder when inserting from the slash command in a narrow context.

If we can tolerate this edge case, we can move forward with this PR 👍 As for the flash issue, we can look into an ideal solution in the future.

@t-hamano
Copy link
Contributor

@vipul0425 Could you update this PR to move it forward, i.e. pass the E2E tests?

This means the following changes should be made:

  • Preferentially render a white placeholder instead of an illustrated placeholder. This means changing isLargeContainer to isSmallContainer.
  • Update any conditional statements that use isLargeContainer to use isSmallContainer and invert the condition. This will cause flashing in the narrow columns, but should pass the E2E tests.

Apologies for the frequent changes 🙏

@vipul0425
Copy link
Contributor Author

Thank you so much @t-hamano for your suggestions and for helping me move this forward 🙏. I’ve made the necessary changes, I’ll also try to address fixing the flashing issue alongside the E2E tests. If successful, I’ll create a follow-up PR for the fix.

@t-hamano t-hamano self-requested a review September 11, 2024 01:44
Copy link
Contributor

@t-hamano t-hamano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update! I think everything is working as expected.

One last thing I noticed is that the Enter button for the "Current media URL" field is misaligned:

image

However, I believe this is an issue with the MediaReplaceFlow or LinkControl component itself, and may be addressed in #65158.

packages/block-library/src/image/edit.js Outdated Show resolved Hide resolved
@t-hamano
Copy link
Contributor

It appears to have passed all E2E tests 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Block] Image Affects the Image Block [Type] Enhancement A suggestion for improvement.
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Image Block: MediaPlaceholder / MediaUpload component doesn't addapt to small sizes
7 participants