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

Theme architecture proposal #42

Merged
merged 6 commits into from
Oct 17, 2021
Merged

Theme architecture proposal #42

merged 6 commits into from
Oct 17, 2021

Conversation

heff
Copy link
Collaborator

@heff heff commented Jul 29, 2021

I was thinking through how we might host and load themes, and I landed on using custom elements to do it.

Custom elements:

  • Have a mechanism for template variables (slots)
  • Have a global registry, accessible to the shadow and light doms on the page (customElements.define). This would be hard to recreate otherwise.
  • Have a mechanism for knowing when the template is loaded (customElements.whenDefined)

Hoping to get feedback.

I see two ways they might be used.

1. Used directly in the HTML

<media-theme-netflix>
  <video slot="media">
</media-theme-netflix>

2. Access the template string to clone into the shadow dom of a wrapping player

<my-player theme="netflix">
  <video slot="media">
</my-player>

Video player constructor

constructor() {
    const shadow = this.attachShadow({ mode: 'open' });

    customElements.whenDefined(themeName).then(()=>{
      const theme = customElements.get(themeName);
      const template = themeEl.template;
      shadow.innerHTML = template;
    });
}

@vercel
Copy link

vercel bot commented Jul 29, 2021

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/mux/media-chrome/J2FjGjWfiPwtW8QQBntmp1jcgrNG
✅ Preview: https://media-chrome-git-themes-mux.vercel.app


<media-controller>

<slot name="media" slot="media"></slot>
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the thing that feels nuckin futs. Chaining slots. But it works!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Whoa.

So from the outside, an application does this:

<media-theme-netflix>
  <video slot="media"></video>
</media-theme-netflix>

That "media" slot gets passed through into the "media" slot for media-controller. Pretty nuckin futs

@heff
Copy link
Collaborator Author

heff commented Aug 20, 2021

@dylanjha @mmcc @cjpillsbury Speak up if you have an opinion on this, otherwise I'm gonna move forward with it.

Copy link
Collaborator

@dylanjha dylanjha left a comment

Choose a reason for hiding this comment

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

I'm in favor of this approach with <media-theme-xxx> elements. Easy to understand what's going on and each of the <media-theme-xxx> themes provide a great example.

The one question I have is:

  • What about in this Netflix example when the theme contains other variables like videoTitle? -- Would the media-theme-netflix specify other attributes like <media-theme-netflix video-title="Breaking Bad" video-episode="Episode 2">


<media-controller>

<slot name="media" slot="media"></slot>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Whoa.

So from the outside, an application does this:

<media-theme-netflix>
  <video slot="media"></video>
</media-theme-netflix>

That "media" slot gets passed through into the "media" slot for media-controller. Pretty nuckin futs

@heff
Copy link
Collaborator Author

heff commented Sep 1, 2021

What about in this Netflix example when the theme contains other variables like videoTitle

Good call that this needs to be clear as part of this.

Slots are the first answer for me. Themes may have lots of use cases for embedding custom content, and using attributes might feel overloaded. Slots are a custom element's answer to content "variables". And compared to attributes, updates to the slot contents are automatically handled.

<media-theme-abc>
  <video slot="media"></video>
  <span slot="media-title">My great title</span>
</media-theme-abc>

A second answer for things like title specifically is potentially defining media metadata elements inside a media element. I think I mentioned this in another issue somewhere.

<media-theme-abc>
  <video slot="media">
    <media-metadata-title>My great title</media-metadata-title>
  </video>
</media-theme-abc>

With that, if you swap the video, for example as part of playlist, the metadata comes/goes with it. Doesn't even need to be a real custom element, I think. A downside is handling changes to those elements. Might require yet more mutation observers. But upside is it's compatible with the native audio/video elements, which hide that content by default but still make it available to javascript.

So it might end up being both. For example, the <media-title> UI element may have a media-title="My title" attribute that can be set by <media-controller> (the title being read from the <media-metadata-title> element). And the also you can have a slot within the theme that is child content and overrides the attribute value, <media-title><slot name="media-title"></slot></media-title> which overrides the attribute value.

Does that make sense? Any thoughts or ideas on that?

@dylanjha
Copy link
Collaborator

dylanjha commented Sep 3, 2021

@heff I love using slots for this.

I'm on board with the idea that any particular "theme" like <media-theme-netflix> can define a few of it's own slots. That approach makes a lot of sense.

Copy link
Collaborator

@cjpillsbury cjpillsbury left a comment

Choose a reason for hiding this comment

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

mostly nits.

document[fullscreenApi.exit]();

// Shadow root throws an error for this function
// this.getRootNode()[fullscreenApi.exit]();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we know why we were using this.getRootNode() here originally? will this work for iframe embeds? Noticed we were using getRootNode() in several places. If this is safe, should we be changing it in other locations as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It seems like it's gonna be context specific, depending on if we need the shadowRoot when that's the element's context and if it supports the needed APIs.

</media-theme-netflix>

</body>
</html>
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: missing newline for EOF

@@ -0,0 +1,18 @@
<html>
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: We should probably keep "examples" separate from "source"

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

👍

@@ -0,0 +1,164 @@
import MediaTheme from './media-theme.js';
Copy link
Collaborator

Choose a reason for hiding this comment

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

Fine for now, but there are several inconsistencies with the current browser-based netflix player. Should make followup issue(s) to clean up. Also may want to consider splitting out the part that sizes this to full width from the rest of the theme.


class MediaThemeNetflix extends MediaTheme {
constructor(options={}) {
options = Object.assign({
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: no need to mutate options (may cause unexpected side effects for implementors). consider instead simply inline object spread, a la:

super(template, { /* allow ...defaultOptions, */ ...options });

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

How is options getting mutated? Either way I'm good with the spread.

constructor(template=``, options={}) {
super();

options = Object.assign({
Copy link
Collaborator

Choose a reason for hiding this comment

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

see comment ☝️

let media = this.querySelector(':scope > [slot=media]');

// Chaining media slots for media templates
if (media.nodeName == 'SLOT') media = media.assignedElements({flatten:true})[0];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Fine for now. May be some weird edge cases that are missed, but we can file followup issues if/when they show up and are a priority.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Such a pessimist. :) It's like you've had to work in the browser before or something.

@heff
Copy link
Collaborator Author

heff commented Oct 17, 2021

Cleaned up and finally merging in.

@dylanjha
Copy link
Collaborator

yooooo 🙌🏼 🎉

@dylanjha dylanjha deleted the themes branch October 18, 2021 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants