Overview

Provide an SDC component to render a responsive image that can be included by other components. It should not be visible as an available component in the editor, but should be usable by component authors in their own SDCs. To demonstrate this, additionally provide a basic SDC card component example in one of the testing modules.

Proposed resolution

Create an Image SDC that is hidden from the UI, that can be used as such:

<article class="{{ class ?? 'card' }}">
  <header>
    <h2>{{ heading }}</h2>
  </header>

  {% include 'experience_builder:image' with image|merge({
    loading,
    sizes,
    class: 'card--image',
    attributes: create_attribute({
      'data-testid': 'card-component-image',
    })
  }|filter((value) => value is not null)) only %}

  <div class="content">
    <p>{{ content }}</p>
  </div>
  <footer>{{ footer }}</footer>
</article>

The component is hidden from the UI by adding `noUI: true` to it's component.yml file

$schema: https://git.drupalcode.org/project/drupal/-/raw/HEAD/core/assets/schemas/v1/metadata.schema.json
name: Image
noUi: true
props:
.
.

Screenshots

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

justafish created an issue. See original summary.

justafish’s picture

Issue summary: View changes
effulgentsia’s picture

Issue tags: +beta blocker

@lauriii and I discussed this and decided to make it a beta blocker. Every design system includes components with images, so providing the XB recommended way to do that is important to include in the beta.

penyaskito’s picture

This is (slightly) related to #3513563: [later phase] [META] Component slot restrictions ("which?") + limits ("how many?") per discussion with @lauriii in slack. We want to restrict an SDC on the root canvas instead of a concrete slot, but the mechanism could be similar or even the same API. For now it's fine to hard-code it.

lauriii’s picture

I actually don't think this is exactly the same as what's being worked on there. What we want to do here is prevent exposing the component to the UI at all. Something along the lines of `hide_from_ui: true` property in the SDC schema. The difference is that this is the component indicating it should be hidden from the UI, vs #3513563: [later phase] [META] Component slot restrictions ("which?") + limits ("how many?") where it would most likely be the canvas indicating what it can accept.

effulgentsia’s picture

Something along the lines of `hide_from_ui: true` property in the SDC schema

Field types and Views plugins can have a no_ui key in their plugin definition, so we can continue with that pattern here, just in the SDC YAML instead of in a PHP attribute.

effulgentsia’s picture

Probably worth opening a core issue to propose adding the no_ui key to SDC definitions. We don't need to hold this issue up on that actually landing in core, but it would be good to get some initial feedback on it from SDC system maintainers.

lauriii’s picture

penyaskito’s picture

Assigned: Unassigned » penyaskito

I'll work on the backend infra.

penyaskito’s picture

I see two alternatives:

  1. Add a check for ComponentMetadataRequirementsChecker::check for noUI == TRUE and don't create the component config entity. Profit.
  2. Add no_ui to Component config entity

I'm tempted for 2, even if potentially more complex, because:

  • We can track versions when the noUI component changes.
  • We can add dependencies/be dependant on.
  • I suppose this might be a thing at some point for code components. Where as a site admin I cannot delete a code component because it's in use in some old page, but I want to prevent editors to add it to new pages.

penyaskito’s picture

Status: Active » Needs review

Before moving further would love thoughts on #10 + the MR direction.

penyaskito’s picture

Assigned: penyaskito » larowlan
penyaskito’s picture

re: #10: Another reason for #2. When/if we migrate from LB, we might want people to use/edit pages with layouts, but we might not want people to add layouts to new pages. Those would be no_ui components.

larowlan’s picture

Assigned: larowlan » Unassigned

Looks good to me

penyaskito’s picture

Assigned: Unassigned » penyaskito
Status: Needs review » Needs work
wim leers’s picture

#10:

I suppose this might be a thing at some point for code components. Where as a site admin I cannot delete a code component because it's in use in some old page, but I want to prevent editors to add it to new pages.

This is already possible: the Component config entity's status needs to change to FALSE.

We can track versions when the noUI component changes.

What is the purpose of this? 🤔 A Component config entity that keeps getting updated but is not available for anybody to instantiate … what value is there in/problem does it solve to then have a log of all changes that were made to that component?

We can add dependencies/be dependant on.

This is definitely true! And that is where the prior point becomes valuable too, I suspect? 🤔


I am still not convinced we need #10.2 aka Add no_ui to Component config entity. For the dependency reason above, the first option in #10 is also not good.

Unless I'm missing something, a hybrid actually makes more sense:

  1. Add a check for ComponentMetadataRequirementsChecker::check for no_ui == TRUE and don't DO create the component config entity. Profit. But set status = FALSE.
  2. Add no_ui to Component config entity

That means zero new infrastructure, zero config schema changes, but it achieves everything outlined in #10? 🤓🤞

penyaskito’s picture

I suppose this might be a thing at some point for code components. Where as a site admin I cannot delete a code component because it's in use in some old page, but I want to prevent editors to add it to new pages.

This is already possible: the Component config entity's status needs to change to FALSE.

Maybe I'm wrong, but that would not render the component on existing pages where it already existed, right? As a content manager, I'd like to be able to not affect existing pages, but still block editors from adding them again.

See also #14 where this might be even more relevant.


For the record, I don't know if/where we are using this but we are already preventing components to be added (config is not created) when their category is "Elements".
wim leers’s picture

Maybe I'm wrong, but that would not render the component on existing pages where it already existed, right?

You're wrong :)

Component's status only controls whether those components are available for content creators to instantiate. Disabling a Component does not and MUST NOT prevent existing instances from working. In fact, that is exactly why Component config entities were introduced: to use (config) dependency management to guarantee that all existing component trees will continue to work (unless of course you're going to start hacking code and/or bypassing validation).

See the original issue that introduced the Component config entity for more detail if you like: #3444417: "Developer-created components": mark which SDCs should be exposed in XB.

EDIT: The docs at docs/config-management.md cover this — I should've done that rather than typing the above equivalent from memory 😅:

- the `status`: `true` conveys it is available for XB Content Creators, `false` conveys it once was available, but not
  anymore (either because it was explicitly disabled by the Site Builder, or because the underlying SDC was marked as
  "obsolete"). Existing content can then continue to use disabled `Component`s (in other words: nothing breaks), while
  new content must use the most current Site Builder-curated list of `Component`s.
penyaskito’s picture

#19 Sure, there's no way we could manually disable SDCs as we can now if what you said wasn't true 🤦🏽‍♂️

Repurposed the MR with that info. Sadly the SDC metadata noUi attribute depends on the core change, we cannot really leap ahead unless we decided to re-parse the metadata (which we don't want).

penyaskito’s picture

Assigned: penyaskito » wim leers
Status: Needs work » Needs review

This should be ready. We cannot leap ahead of core though. So they will be visible before #3535958: Allow SDCs to be marked to be excluded from UI is released though.

penyaskito’s picture

As I wasn't explicit and I see now we didn't discuss this: I'd expect we land !1307, and a different MR for the actual component in this very same issue.

penyaskito’s picture

I asked @larowlan if there's any way the core MR could be backported to 11.2.x, and he clarified that as a new feature it's a no-go.
Per previous conversations, we should just hardcode the component name for now until we have a release and can require 11.3.x.
I didn't do this yet as I don't know the name of the component (image? responsive_image?)

justafish changed the visibility of the branch 1.x to hidden.

  • wim leers committed 06150f72 on 1.x authored by penyaskito
    Issue #3535453 by penyaskito, wim leers: Respect Drupal 11.3's upcoming...
wim leers’s picture

Assigned: wim leers » justafish
Status: Needs review » Needs work
Issue tags: +Needs followup

TIL #3535958: Allow SDCs to be marked to be excluded from UI happened!

RTBC'd the "pure infra" MR https://git.drupalcode.org/project/experience_builder/-/merge_requests/1307 — not the other MR.

Needs follow-up issue to remove the work-around once XB requires >=11.3.

Back to Needs work and to Sally for the other MR on this issue :) Thanks, @penyaskito!

wim leers’s picture

Did an initial review of the other MR, and raised 2 major concerns 😅

wim leers’s picture

Also, the issue summary is very outdated, because it says

See https://www.drupal.org/project/experience_builder/issues/3532718 for relevant upcoming changes to how srcset is generated, however some of the work here can be started before that's completed e.g. creating a "hidden" component, the card component, and a skeleton for the new image component

and I believe that the thing that concerns me most is something worth calling out in the issue summary.

#3532718: Improve the front-end DX of `<img srcset>` landed 9 days ago.

wim leers’s picture

@justafish and I met about this MR — we both agrees that https://git.drupalcode.org/project/experience_builder/-/merge_requests/1... is problematic.

The reason that code is in there, is because @justafish understood from talking to @lauriii and from this example in the issue summary:

{{ include('image', {
  src: './gracie.jpg',
  alt: 'Gracie is awesome',
  class: 'max-content',
  fill: true,
  sizes: '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw'
})}}

… that:

  • the default image inside an SDC (the examples.0 in the metadata) must ALSO be able to generate a srcset
  • and on top of that, any local image (so: outside of this SDC) must ALSO be able to generate a srcset

Those 2 bits are why this (in-progress) MR is copying local files (both inside the SDC and outside) to some location in public://: because otherwise no derivative for it can be generated, since \Drupal\image\Controller\ImageStyleDownloadController::deliver() does NOT support "shipped files" (aka files shipped as part of a module or theme), even though the logic in \Drupal\image\Entity\ImageStyle::buildUri() suggests it does (see the else branch).

(For debugging: install XB, then navigate to /sites/default/files/styles/xb_parametrized_width--640//core/tests/fixtures/files/image-1.png.webp to generate a 640px-wide derivative of core/tests/fixtures/files/image-1.png. Put a breakpoint in the aforementioned methods and observe: it'll try to use core as the scheme.)

This is why @justafish resorted to copying shipped files into public:// — because core's ImageStyleDownloadController does not support generating derivatives for shipped files.

But precisely that is the very worrying piece here: a Twig extension writing to the file system as a side effect. 😱

This opens a can of worms:

  1. What if the default image is updated (its contents are changed but its filename is not): that should then be reflected too in the public:// copy. How do we efficiently detect such a change?
  2. What about multiple SDCs with the same filename? Multiple extensions with the same SDC name? In core, the File entity does the deduplicating of identically named files; here we would become responsible. A naming scheme is conceivable (public://sdc-default-images/<extension name>/<SDC name>/<filename>) to avoid that, but it's not great.
  3. What about the extra disk space consumption? Wouldn't it be better to just use the actual shipped files instead?
  4. et cetera

Conclusion

  1. Value? IMHO the value of generating a srcset for a shipped default image in an SDC is questionable — it is unlikely to be enormous anyway. @justafish understood @lauriii to want this, but based on the issue summary, I'm not confident that is the case. Doubly so because we don't currently do that in HEAD either and this was jointly agreed in #3515646: Add automated <img srcset> generation as a reasonable trade-off.
    EDIT: I found the original comment covering this.
  2. If there is sufficient value in that in Lauri's eyes, then the correct (and maintainable) solution would be:
    1. Rewrite the relative-to-the-SDC URLs to relative-to-Drupal-root URLs, aka exactly what \Drupal\experience_builder\PropSource\DefaultRelativeUrlPropSource + \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\SingleDirectoryComponent::rewriteExampleUrl() do in HEAD.
    2. Subclass core's ImageStyleDownloadController and add support for shipped files.
  3. 99% of the value is NOT in making the "shipped images in an SDC have srcset derivatives" bit work, but in what the issue title says: Create an Image SDC that can be included by other SDCs. So: if @lauriii thinks this should happen despite all of the above concerns, then it'll need to be in a non-beta blocking follow-up.

Assigning to @lauriii to get feedback on this; meanwhile @justafish will push ahead with everything else.

lauriii’s picture

It's fine to open a follow-up for generating a srcset for shipped images. Sorry, I should have made it clear that it's critical that shipped images work when using the image component, but we don't have to add the full support for that here. Once we have the follow-up, we can figure out how to prioritize that. In the meanwhile, we should just passthrough those images.

I think we need two follow-up issues where we should cover this for both SDC and code components:

  1. Generate srcset for shipped images
  2. Generate srcset for remote images from allowed 3rd parties
wim leers’s picture

Assigned: lauriii » justafish
Issue tags: +Needs followup

#34: perfect, that sounds completely sensible 😊 — thanks!

penyaskito’s picture

Version: 0.x-dev » 1.x-dev
isholgueras’s picture

I've left some comments in the MR

wim leers’s picture

Issue tags: +Needs screenshots

Can we get some screenshots of uses of the new noUi SDC experience_builder:image (both preview + its component instance form) in the issue summary? 🙏

While reviewing, found some concerns, most importantly tests/modules/xb_test_sdc/xb_test_sdc.install.

justafish’s picture

Status: Needs work » Needs review
wim leers’s picture

Status: Needs review » Needs work

Feedback on the MR + there's still 3 "Needs …" tags. 😅

justafish’s picture

Issue summary: View changes
StatusFileSize
new939.42 KB
justafish’s picture

Issue summary: View changes
StatusFileSize
new1.24 MB
justafish’s picture

Issue summary: View changes
justafish’s picture

justafish’s picture

Issue summary: View changes
justafish’s picture

Issue summary: View changes
justafish’s picture

Assigned: justafish » Unassigned
Status: Needs work » Needs review
penyaskito’s picture

Assigned: Unassigned » justafish
Status: Needs review » Needs work
Issue tags: +Needs followup

I reviewed and fixed my findings, but would need clarification on the Playwright changes. Otherwise this looks ready to me.


I'm not sure if the follow-up from #34 is still needed, re-tagging just to be cautious. The infra follow-up for core 11.3 exists already at #3537695: [11.3.0-and-up] Remove hardcoded Image SDC from `\Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\SingleDirectoryComponent::componentMeetsRequirements`

wim leers’s picture

Thanks for the improvements here, this MR looks much more solid now! 🤩 🙏

In addition to @penyaskito's questions, I also posted a few to the MR — again about documenting the rationale to ensure maintainability (the commits of the past 24 hours made that a lot better — these are the last bits!).

But there's also one bit of missing test coverage (the Twig extension is updated by this MR but tests/src/Unit/Twig/XbTwigExtensionFiltersTest.php did not gain new test cases)


I'm not sure if the follow-up from #34 is still needed,

Yes, I think we still need 2 follow-up issues for these 2 things Lauri requested:

I think we need two follow-up issues where we should cover this for both SDC and code components:

  1. Generate srcset for shipped images
  2. Generate srcset for remote images from allowed 3rd parties
justafish’s picture

Assigned: justafish » Unassigned
Status: Needs work » Needs review

https://www.drupal.org/project/experience_builder/issues/3538858 created for generating srcsets for remote images

I haven't created a follow up for shipped images as we can already do that 😊

justafish’s picture

Issue tags: -Needs followup
wim leers’s picture

Thanks for creating #3538858: `ParametrizedImageStyle`: Generate srcset for remote images from allowed 3rd parties! Posted a few questions there. Doing (final! 🤞) review pass here :)

wim leers’s picture

Assigned: wim leers » justafish
Status: Needs review » Reviewed & tested by the community
Issue tags: +Needs followup
Related issues: +#3539038: DX: update `PropShapeToFieldInstanceTest` to not test all SDCs, but only those that meet XB's requirements

I haven't created a follow up for shipped images as we can already do that 😊

No, we don't support that. We're copying a shipped image *to* a stream wrapper to test stream wrappers — quoting xb_test_sdc_install():

  $source = $module_path . '/components/card/balloons.png';
  $destination = 'public://balloons.png';
  $directory = 'public://';
  $file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
  $copied_file = $file_system->copy($source, $destination, FileExists::Replace);

See https://git.drupalcode.org/project/experience_builder/-/merge_requests/1... + https://git.drupalcode.org/project/experience_builder/-/merge_requests/1... they literally show that no srcset is generated for sdc.xb_test_sdc.card (shipped image inside SDC) nor sdc.xb_test_sdc.card-with-local-image (shipped image outside an SDC) — the test expectation make this very clear. 😊


Finished review.

Issue created for something surfaced here but not caused here: #3539038: DX: update `PropShapeToFieldInstanceTest` to not test all SDCs, but only those that meet XB's requirements.

Only fixed some nits, but also found some missing test coverage, and that revealed a bug. @justafish, please confirm that you agree with the changes I made for that — see the 2 commits referenced here: https://git.drupalcode.org/project/experience_builder/-/merge_requests/1...

And thanks for teaching me https://git.drupalcode.org/project/experience_builder/-/merge_requests/1... 😃

justafish’s picture

Wim is correct, we discussed this and though we both remember it working we can't actually reproduce it so have decided it was a Folie à deux 😆

Follow up created here https://www.drupal.org/project/experience_builder/issues/3539062

wim leers’s picture

🤣

Thanks!

wim leers’s picture

Status: Reviewed & tested by the community » Fixed

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

wim leers’s picture

FYI:

  1. We overlooked something here: card requires a publicly resolvable image URI, but card-with-stream-wrapper-image specifically does NOT accept that, but a stream wrapper URI! See https://git.drupalcode.org/project/experience_builder/-/merge_requests/1....

    (This went unnoticed because json-schema-definitions://experience_builder.module/image-uri's regex was too loose.)

  2. On top of that, this MR landed the expectation that card-with-stream-wrapper-image renders an invalid src:
    https://git.drupalcode.org/project/experience_builder/-/merge_requests/1...

Both of these will be fixed as part of #3530351: Decouple image+video (URI) shape matching from specific image+video file types/extensions.

wim leers’s picture

Extracted #59 into its own issue because it made the other MR unwieldy: #3543783: `card-with-stream-wrapper-image` test SDC generates an invalid `<img src>`.

wim leers’s picture