[META] Real-time preview: supporting back-end infrastructure

Created on 10 June 2024, 15 days ago
Updated 20 June 2024, 5 days ago

Problem/Motivation

We want to replace the mocked call to render a realtime preview with a real endpoint that renders the page and returns the markup to be displayed. In this first step, performance is a low priority goal. At this early stage it is more important to have this foundation in place on which to build and optimise in the future.

Proposed resolution

  1. βœ… PRECURSOR to Real-time preview of rendered components WITHOUT in-place editing (i.e. sidebar prop editing only):
    use the exact same mechanism the UI is currently using, meaning it won't need to change at all: πŸ“Œ Connect client & server, with zero changes to client (UI): rough working endpoints that mimic the UI's mocks Needs review
  2. πŸ›‘ Real-time preview of rendered components WITHOUT in-place editing (i.e. sidebar prop editing only): ONLY able to click a component and have a form for it appear in the right sidebar, NO ability to in-place edit e.g. the title of a hero component.

    There are relatively few annotation comments required from the backend to enable hooking up the hover/select/drag and drop. The following mocked up markup + some throwaway code were used to verify this can work:

        <body>
          <!-- xb-slot-root -->
          <!-- xb-start-43cd7aa4-0160-4787-a3af-baf44ff17a88 -->
          <div>
            <h1>Hello world.</h1>
          </div>
          <!-- xb-end-43cd7aa4-0160-4787-a3af-baf44ff17a88 -->
    
          <!-- xb-start-fcd2490d-1124-4146-82b6-b1e049ed8026 -->
          <h1>Another component.</h1>
          <p>Ooo 2 elements with no wrapper! Other components should not be able to get inserted above this P and below the H1 ^</p>
          <!-- xb-end-fcd2490d-1124-4146-82b6-b1e049ed8026 -->
    
          <!-- xb-start-05fa13be-8291-4955-aa89-32351f68e776 -->
          <img src="https://placehold.co/600x400" alt="A placceholder image" width="600" height="400" />
          <!-- xb-end-05fa13be-8291-4955-aa89-32351f68e776 -->
    
          <!-- xb-start-1941ffae-f9ed-4ce3-8145-a2c3977ac65b -->
          <div class="row"  style="display: flex;">
              <div class="column" style="width: 60%; background: #c0ffee;">
                <!-- xb-slot-68cafa3e-bfd8-4767-a5cc-c18cce97c236 -->
                <!-- xb-start-bdfce52f-e666-49f0-a57f-dfb8c5c0c75b -->
                <div>
                  <h1>In a slot!</h1>
                </div>
                <!-- xb-end-bdfce52f-e666-49f0-a57f-dfb8c5c0c75b -->
              </div>
              <div class="column"  style="width: 60%; background: #decade;">
                <!-- xb-slot-fe01d628-55ab-4146-9d04-71e5a01ad233 -->
              </div>
          </div>
          <!-- xb-end-1941ffae-f9ed-4ce3-8145-a2c3977ac65b -->
        </body>
    
    1. <!-- xb-slot-* --> used directly inside a slot to indicate that the parent element is a drop target for sorting. The very top level should be xb-slot-root other instances should be a UUID e.g. <!-- xb-slot-fe01d628-55ab-4146-9d04-71e5a01ad233 -->
    2. <!-- xb-start-UUID --> should be immediately before the first element of a component.
    3. <!-- xb-end-UUID --> should be immediately after the last element of a component.

  3. πŸ›‘ Real-time preview of rendered components WITH AFFORDANCES for in-place editing: ONLY able to click a component and have a form for it appear in the right sidebar, with PARTIAL ability to in-place edit: upon hovering component markup marked up for in-place editing we'd be able to highlight in the right sidebar which part of the form corresponds to it.

    Proposal by @jessebaker: #4.

  4. πŸ›‘ Real-time preview of rendered components WITH ACTUAL in-place editing: on top of the above, we'd need to use some input widgets, presumably only text initially (think contenteditable but with restrictions).

    Proposal by @jessebaker: #4.

⚠️ Steps 2–4 would be a lot simpler/more productive if we could leverage @larowlan's AST work (see #13). We're waiting on @larowlan for an update on that front before proceeding further here.

🌱 Plan
Status

Needs review

Component

Page builder

Created by

πŸ‡¬πŸ‡§United Kingdom jessebaker

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @jessebaker
  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Thanks, @jessebaker!

    This will build on top of \Drupal\experience_builder\Plugin\DataType\ComponentTreeHydrated, which was added in https://git.drupalcode.org/project/experience_builder/-/merge_requests/15, and gained end-to-end test coverage in ✨ [MR Only] Edit any component prop, powered by a new FieldForComponentSuggester service, which will power the JS UI Fixed late last week.

  • Assigned to jessebaker
  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Actually, first back to Jesse for

    This makes no affordance for wrapping individual editable pieces or attributes within a component but I don’t think it does anything that would prevent us adding that later. I'm going to continue exploring how we might annotate that.

    Having that exploration done by @jessebaker would allow me to more holistically tackle this MR πŸ˜‡

  • πŸ‡¬πŸ‡§United Kingdom jessebaker

    In order to enable inline editing which I think is the main purpose of the additional annotations we need to

    1. Identify the element whose content is editable
    2. Identify the field that stores the content
    3. Identify the type (plain text vs rich content)

    The following example shows how using data attributes we might go about that. The presence of a data-xb-editable attribute identifies the element. The value of that attribute (combined with the wrapping comment to identify the component) tells us the field and the data-xb-text-content would tell us if, when editing we should show CKEditor or just allow text to typed in.

      <!-- xb-start-fcd2490d-1124-4146-82b6-b1e049ed8026 -->
      <div class="wrapper">
        <div class="content" data-xb-editable="properties.content" data-xb-text-content="rich">
          <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Libero vel ullam repellendus eos aliquam tempore nesciunt. Eveniet quidem veniam debitis quas quasi neque? Beatae suscipit qui, doloribus aliquid debitis illum!</p>
          <p>Natus minus perferendis explicabo provident, in qui sed sapiente, nemo nulla veritatis quae voluptatem numquam eveniet eius est corporis laborum ipsum minima.</p>
        </div>  
      </div>
      <!-- xb-end-fcd2490d-1124-4146-82b6-b1e049ed8026 -->
    

    This next example shows how we might annotate a component so that the UI would be able to highlight a specific element in the preview when hovering over the corresponding field in the contextual panel.

      <!-- xb-start-fcd2490d-1124-4146-82b6-b1e049ed8026 -->
      <div class="wrapper">
          <img data-xb-editable-attr="[{attr: 'src', field: 'properties.img_src'}, {attr: 'alt', field: 'properties.img_alt'}]" src="https://placehold.co/200x30" alt="alt text for image" height="800" width="600" />
      </div>
      <!-- xb-end-fcd2490d-1124-4146-82b6-b1e049ed8026 -->
    

    We can parse the value of data-xb-editable-attr to a JS object and using that mapping, when a user hovers the Image Source or Alternate Text fields in the form, we can draw a border around the <img> in the preview.

    Both examples use data-attributes and I understand that there is going to be a challenge in implementing them and that it might not be technically possible without the developer who makes the SDC adding in certain markup/affordances to the template of their SDC.

    With that said, if that can be solved, then actually replacing the HTML comment annotations (as outlined in the initial issue description) with data- attributes would simplify and optimise things on the front end.

    I'm not sure there is a scenario where it's not possible to add data attributes but it is somehow possible to add HTML comments. But, if that is the case that I suggest (and I've not put AS much thought into this) something like the following example:

      <!-- xb-start-fcd2490d-1124-4146-82b6-b1e049ed8026 -->
      <div class="wrapper">
        <!-- xb-editable-attr-fcd2490d-1124-4146-82b6-b1e049ed8026 src properties.image_src -->
        <!-- xb-editable-attr-fcd2490d-1124-4146-82b6-b1e049ed8026 alt properties.image_alt -->
        <img src="https://placehold.co/200x30" alt="alt text for image" height="800" width="600" />
      </div>
      <!-- xb-end-fcd2490d-1124-4146-82b6-b1e049ed8026 -->
    

    In this example the HTML comment immediately preceding an HTML element is what contains the mapping between the editable attribute and the field that has the value.

  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    The following example shows how using data attributes we might go about that. The presence of a data-xb-editable attribute identifies the element.

    How would that data-xb-editable attribute get added?

    Because product requirement 3. Real-time page preview states:

    As a creator, when I'm editing content, I want to see a real time preview of what I'm editing. Custom component code should not have to be aware that it may be rendered in the page builder.

    You did anticipate this question (the paragraphs below the last <hr>).

    Would it be possible to achieve at least the first example using again only HTML comments? An SDC prop that has a destination an HTML attribute cannot accept HTML comments, but the first example is actually for a DOM text node, so a HTML comment could work? Is that what the last example is referring to?

    But I think that the implication of everything you wrote is that in order to support everything we need for 3. Real-time page preview is the ability to get an AST for any component, because that's the only way to meet the product requirements?

  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    By now, @jessebaker has effectively spelled out what his desired end state is for the markup received by the client side (UI) to achieve the required UX.

    I'd still like @jessebaker to respond to #5 (leaving assigned to him for that). After that, I'll take on breaking this up in individually sequenced issues.

  • πŸ‡¬πŸ‡§United Kingdom jessebaker

    the implication of everything you wrote is that in order to support everything we need for 3. Real-time page preview is the ability to get an AST for any component

    During the initial exploration of this a couple of months back, I believe that was the conclusion we came to. Either AST of the component so we can inject things as needed OR implement an API for SDC developers to "opt-in" to having an improved UX by adding some kind of markup or comments to the SDC's that they make. These could be automatically added to SDC's created with the no-code component editor that we plan on building later.

    Would it be possible to achieve at least the first example using again only HTML comments?

    I'm not actually sure. If the SDC component is just one HTML element and that HTML element's content is the editable piece then yes. However consider the following example where the structure of the SDC is slightly more complex.

      <!-- xb-start-fcd2490d-1124-4146-82b6-b1e049ed8026 -->
      <div class="left">
        <div class="content">
          <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Libero vel ullam repellendus eos aliquam tempore nesciunt. Eveniet quidem veniam debitis quas quasi neque? Beatae suscipit qui, doloribus aliquid debitis illum!</p>
          <p>Natus minus perferendis explicabo provident, in qui sed sapiente, nemo nulla veritatis quae voluptatem numquam eveniet eius est corporis laborum ipsum minima.</p>
        </div>
        <div class="right">
            <img src="..." />
        </div>
      </div>
      <!-- xb-end-fcd2490d-1124-4146-82b6-b1e049ed8026 -->
    

    Assuming the div.content is the editable part we are interested in, is it actually any more feasible to programmatically insert an HTML comment directly above the tag than it is to insert a data- attribute into the tag? If the lift to achieve either option is similar then the data- attribute route has my vote because it's significantly easier to work with in JS.

    On a different note:

    As a creator, when I'm editing content, I want to see a real time preview of what I'm editing. Custom component code should not have to be aware that it may be rendered in the page builder.

    (emphasis mine)

    Perhaps we could interpret this requirement as "Custom component code must work without being aware it may be rendered in the preview but could be enhanced to work better". E.g. it must render, but a developer can add certain extra markup to enhance the user experience. But I'm not sure we should be trying to game the wording!

    TLDR: I also think we need the ability to get an AST for any component to honestly hit that requirement.

  • Assigned to Wim Leers
  • Status changed to Postponed 15 days ago
  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    πŸ‘ Thanks for confirming!

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    Reading this am I right in thinking the comments approach is preferred over the data-attributes?

    Is there a plan for how we would implement this?

    The data-attributes feels more robust but also has the same brittleness as other places we've used that in core (layout builder blocks, layout builder sections, layout builder regions, contextual links, RDF).

    The AST is that the work I have been doing in my spare-time on πŸ“Œ Experiment: Render Twig as React Active that I demoed in 🌱 Meeting notes: May 20th 2024 - Isomorphic rendering Fixed ?

    I had paused it because it felt like it wasn't needed - but are we now saying it will be? If so I could pick it up again.

    One other comment on the data-xb-text-content attribute - this sounds a lot like widgets - which is what was implemented in decoupled layout builder. This would fit well with the Component config entity because we could say 'use this widget' for this prop. We could also make the React component for each widgets swappable too (per this question from nod_)

  • Issue was unassigned.
  • Status changed to Needs review 14 days ago
  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    Is there a plan for how we would implement this?

    HTML comments around components should be easy to achieve. But as described in #4, that won't work for component props (or at least not all possible component props).

    I was thinking of splitting "real-time preview" up in 3 parts:

    1. Real-time preview of rendered components WITHOUT in-place editing (i.e. sidebar prop editing only): ONLY able to click a component and have a form for it appear in the right sidebar, NO ability to in-place edit e.g. the title of a hero component
    2. Real-time preview of rendered components WITH AFFORDANCES for in-place in-place editing: ONLY able to click a component and have a form for it appear in the right sidebar, with PARTIAL ability to in-place edit: upon hovering component markup marked up for in-place editing we'd be able to highlight in the right sidebar which part of the form corresponds to it
    3. Real-time preview of rendered components WITH ACTUAL in-place in-place editing: on top of the above, we'd need to use some input widgets, presumably only text initially (think contenteditable but with restrictions).
    1. The first could be a fairly fast iteration, AFAICT it's technically trivial to achieve.
    2. The second requires an AST and I have no technical details for that in my head at all yet (other than me having grepped vendor/twig/twig for "ast" and having found a number of matches, making me hopeful we can achieve this). This require 1–3 orders of magnitude more work. Once this is done, we may want/need to throw away some work from the first part.

    If you could push forward the AST work that'd mean we might be able to rely on that from day one, which might make things simpler πŸ€“πŸ€ž That being said: I don't understand yet why Twig-to-React is appropriate β€” everything in this issue so far only presumes annotated HTML (components rendered but with additional metadata markup, either HTML comments or attributes), not React. πŸ€”

    The summary of πŸ“Œ Experiment: Render Twig as React Active is empty and its MR has a single commit with no docs β€” perhaps explaining the rationale of where that is going might help make me and others grok the intent? 😊 Or perhaps your point was simply that you've already used the Twig AST, and making it generate just the same markup but with additional attributes would be trivial?

    (The "render as React" and "JSX" pieces in #3448927 that "we" as in @bnjmnm worked on for a PoC were not intended for rendering the components on the XB canvas, but for the Drupal Field Widgets in the sidebar for editing props *for* the components. See https://git.drupalcode.org/project/experience_builder/-/commit/fa2ece58b...)

  • πŸ‡¬πŸ‡§United Kingdom jessebaker

    I think @Wim Leers has done a great job of summarising things. At risk of adding too much redundant noise I'll keep this brief.

    My suggestion to use wrapping HTML comments and not data attributes was based purely on them being much simpler to implement on the Twig side because of the need to move fast on this. Data attributes would be a preferred/superior solution.

    It wouldn't be a huge amount of work to migrate from HTML comments to data attributes should we take a phased approach. We can definitely start with comments and then refactor to attributes later.

    I agree with @Wim Leers that in the context of the preview there is no requirement/desire to start rendering anything with React. Any work on producing an AST in this task would purely be to enable the addition of annotative markup. The preview will be rendered inside an iFrame and I have no intention of trying to get React running inside that iFrame. The React app for the XB UI will exist entirely outside of the iFrame and the data attributes will allow us to interrogate/interact with the preview DOM from outside.

  • Assigned to larowlan
  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    It might be worth setting up a meeting to demo my work - both of you weren't in the isomorphic rendering call. It's all in my private github at the moment because I think it is generic and can be a composer package rather than part of this module.

    tl;dr given a twig component, you get Javascript (not JSX) that has been transpiled to what you get from babel's react preset - so basically a function that takes props as args and uses the jsx runtime to render. It needs a lot more work but I have some tricky test cases working.

    And yes, it does use the Twig token stream - but there's something important to note about Twig - it does not parse HTML - my work combines it with a mastermind/html5 and a custom event subscriber to build an AST of dom components and Twig logic. The twig token stream treats markup as plain text so there is no concept of component hierarchy. i.e it does not see a 'li' as a child of a 'ul'.

    In terms of what's required here - it feels like we want comments before/after a component, comments before/after a prop and components before/after a slot.

    I assume that you're going to rely on a round trip back to Drupal to update previews. My Twig to Javascript w/ jsx runtime experiment was to avoid that.

    Twig doesn't really see the distinction between a prop and a slot, it just sees them as a print statement - _but_ the issue we get into here is things like logic - e.g. sometimes the twig template could be modifying a prop or passing it through a filter.

    So for the comments before/after the component - we can easily do that without much work - we already do that with twig debug (and SDC debug - the emoji thing). The before/after prop/slot thing we will need to drop down to the token stream layer and try to hook into variable nodes.

  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    comments before/after a prop

    "Yes" to the other statements in that paragraph, but "no" to this one. This is the part that is impossible, because any SDC prop targeting a HTML attribute won't be able to use HTML comments: HTML comments aren't allowed inside an attribute!

    That's why the conversation above led to the conclusion we eventually want data- attributes for everything.

    I assume that you're going to rely on a round trip back to Drupal to update previews. My Twig to Javascript w/ jsx runtime experiment was to avoid that.

    I definitely wanted to avoid that.

    _but_ the issue we get into here is things like logic - e.g. sometimes the twig template could be modifying a prop or passing it through a filter.

    That is indeed the issue 😬 SDCs should ideally be written without relying on Twig functions that rely on Drupal data. For example:

    <div>path (as route) not absolute: {{ path('user.register') }}</div>
    <div>url (as route) absolute despite option: {{ url('user.register', {}, {'absolute': false }) }}</div>
    

    Any SDC that has a Twig template like that will unavoidably require a round trip (more so still if the parameter to either of those functions is an SDC prop).

    So for the comments before/after the component - we can easily do that without much work - we already do that with twig debug (and SDC debug - the emoji thing).

    Yep, hence the first part in #10: I could do that without any of the advanced things you're talking about. But we will eventually need all 3 parts, so if you could push that forward, then that'd be fantastic! 🀩

  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    I've implemented a precursor to #10.1 (Real-time preview of rendered components WITHOUT in-place editing (i.e. sidebar prop editing only)) using the exact same mechanism the UI is currently using πŸ˜„

    See it in action at #3455898-3: Connect client & server, with zero changes to client (UI): rough working endpoints that mimic the UI's mocks β†’ .

    I propose to wait for #10.2 + #10.3 until the AST work by @larowlan is ready β€” I think there's plenty of other things to do in the short term.

    (@jessebaker, @lauriii and I had a call 1.5 hour ago with @larowlan but he couldn't make it unfortunately.)

  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί
  • πŸ‡§πŸ‡ͺBelgium Wim Leers Ghent πŸ‡§πŸ‡ͺπŸ‡ͺπŸ‡Ί

    πŸ›‘ to indicate which steps we're waiting to start on until we have more clarity on @larowlan's AST work.

  • πŸ‡¦πŸ‡ΊAustralia larowlan πŸ‡¦πŸ‡ΊπŸ.au GMT+10

    Sorry for missing the meeting, tech and brain failure

    here's a recording of a demo of what I wanted to show https://drive.google.com/file/d/1XFxiMOW2hDrIqXlzdk5K_8bL3RdpO2X6/view

Production build 0.69.0 2024