-
Notifications
You must be signed in to change notification settings - Fork 82
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
Changes from 2 commits
b93c82c
5d9773c
0996772
d9a7318
5173fd0
6baf09b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,7 +86,10 @@ class MediaController extends MediaContainer { | |
super[fullscreenApi.enter](); | ||
}, | ||
MEDIA_EXIT_FULLSCREEN_REQUEST: () => { | ||
this.getRootNode()[fullscreenApi.exit](); | ||
document[fullscreenApi.exit](); | ||
|
||
// Shadow root throws an error for this function | ||
// this.getRootNode()[fullscreenApi.exit](); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we know why we were using There was a problem hiding this comment. Choose a reason for hiding this commentThe 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_ENTER_PIP_REQUEST: () => { | ||
const docOrRoot = this.getRootNode(); | ||
|
@@ -409,7 +412,8 @@ function propagateMediaState(nodeList, stateName, val) { | |
} | ||
|
||
// Make sure custom els are ready | ||
if (childName.includes('-') && !window.customElements.get(childName)) { | ||
// Even if customElements.get returns something, it may not be ready! | ||
if (childName.includes('-')) { | ||
window.customElements.whenDefined(childName).then(setAndPropagate); | ||
} else { | ||
setAndPropagate(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<html> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: We should probably keep "examples" separate from "source" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
<head> | ||
<script type="module" src="../index.js"></script> | ||
<script type="module" src="./media-theme-netflix.js"></script> | ||
</head> | ||
<body> | ||
|
||
<h2>Example: Using the custom element directly</h2> | ||
|
||
<media-theme-netflix> | ||
<video | ||
slot="media" | ||
src="https://stream.mux.com/DS00Spx1CV902MCtPj5WknGlR102V5HFkDe/high.mp4" | ||
></video> | ||
</media-theme-netflix> | ||
|
||
</body> | ||
</html> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: missing newline for EOF |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
import MediaTheme from './media-theme.js'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
||
const template = ` | ||
<style> | ||
body { | ||
font-family: "Helvetica Neue", sans-serif; | ||
} | ||
|
||
media-controller { | ||
width: 100%; | ||
/* Keep the buttons from overflowing */ | ||
min-width: 420px; | ||
height: 720px; | ||
|
||
--media-range-thumb-background: rgba(255,0,0, 1); | ||
--media-range-track-height: 4px; | ||
--media-range-track-transition: height .2s ease; | ||
--media-range-track-background: #555; | ||
--media-range-bar-color: rgb(229, 9, 20); | ||
|
||
--media-button-icon-width: 50px; | ||
--media-button-icon-height: 50px; | ||
} | ||
|
||
media-time-range { | ||
width: 100%; | ||
height: 25px; | ||
margin-bottom: 10px; | ||
--media-range-thumb-height: 20px; | ||
--media-range-thumb-width: 20px; | ||
--media-range-thumb-border-radius: 20px; | ||
--media-time-buffered-color: #777; | ||
} | ||
|
||
media-time-range:hover { | ||
--media-range-track-height: 9px; | ||
} | ||
|
||
media-control-bar { | ||
background: none; | ||
justify-content: space-between; | ||
-webkit-box-align: center; | ||
-ms-align-items: center; | ||
align-items: center; | ||
display: flex; | ||
-webkit-box-pack: justify; | ||
flex-wrap: nowrap; | ||
} | ||
|
||
media-control-bar > * { | ||
background: none; | ||
display: flex; | ||
flex: 0 1 auto; | ||
width: 60px; | ||
min-width: 60px; | ||
height: 80px; | ||
padding-bottom: 20px; | ||
margin: 0 3px; | ||
|
||
--media-button-icon-transform: scale(1.2); | ||
--media-button-icon-transition: transform .2s ease; | ||
} | ||
|
||
/* For some reason media-control-bar > *:hover doesn't work... | ||
while media-control-bar > *:focus-within does. | ||
And also media-control-bar > *:not(:hover) | ||
Really annoying. Need to submit a bug. */ | ||
media-control-bar > *:not(:hover) { | ||
--media-button-icon-transform: scale(1); | ||
--media-button-icon-transition: transform .2s ease; | ||
} | ||
|
||
media-play-button, | ||
media-seek-backward-button, | ||
media-seek-forward-button, | ||
media-mute-button, | ||
media-fullscreen-button { | ||
height: 80px; | ||
} | ||
|
||
media-fullscreen-button { | ||
margin-right: 10px; | ||
} | ||
|
||
media-control-bar > *:focus, | ||
media-control-bar > *:focus-within { | ||
outline: 0; | ||
} | ||
|
||
media-volume-range { | ||
width: 100px; | ||
} | ||
|
||
.videoTitle { | ||
flex-grow: 1; | ||
height: 80px; | ||
line-height: 64px; | ||
vertical-align: middle; | ||
overflow: hidden; | ||
padding: 0 10px; | ||
min-width: 0; | ||
} | ||
|
||
.videoTitleText { | ||
width: 100%; | ||
vertical-align: middle; | ||
text-overflow: ellipsis; | ||
overflow: hidden; | ||
white-space: nowrap; | ||
} | ||
|
||
.videoTitleText h4 { | ||
margin: 0; | ||
display: inline-block; | ||
white-space: nowrap; | ||
} | ||
|
||
.videoTitle span { | ||
display: inline; | ||
font-weight: 200; | ||
margin-left: 5px; | ||
} | ||
|
||
</style> | ||
|
||
<media-controller> | ||
|
||
<slot name="media" slot="media"></slot> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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-time-range></media-time-range> | ||
<media-control-bar> | ||
<media-play-button></media-play-button> | ||
<media-seek-backward-button></media-seek-backward-button> | ||
<media-seek-forward-button></media-seek-forward-button> | ||
<media-mute-button></media-mute-button> | ||
<media-volume-range></media-volume-range> | ||
<div class="videoTitle"> | ||
<div class="videoTitleText"> | ||
<h4>My Title</h4><span>P2:E4 Episode 4</span> | ||
</div> | ||
</div> | ||
<media-fullscreen-button> | ||
<svg slot="enter" viewBox="0 0 28 28"><g transform="translate(2, 6)"><polygon points="8 0 6 0 5.04614258 0 0 0 0 5 2 5 2 2 8 2"></polygon><polygon transform="translate(4, 13.5) scale(1, -1) translate(-4, -13.5) " points="8 11 6 11 5.04614258 11 0 11 0 16 2 16 2 13 8 13"></polygon><polygon transform="translate(20, 2.5) scale(-1, 1) translate(-20, -2.5) " points="24 0 22 0 21.0461426 0 16 0 16 5 18 5 18 2 24 2"></polygon><polygon transform="translate(20, 13.5) scale(-1, -1) translate(-20, -13.5) " points="24 11 22 11 21.0461426 11 16 11 16 16 18 16 18 13 24 13"></polygon></g></svg> | ||
<svg slot="exit" viewBox="0 0 28 28"><g transform="translate(3, 6)"><polygon transform="translate(19.000000, 3.000000) scale(-1, 1) translate(-19.000000, -3.000000) " points="22 0 20 0 20 4 16 4 16 6 22 6"></polygon><polygon transform="translate(19.000000, 13.000000) scale(-1, -1) translate(-19.000000, -13.000000) " points="22 10 20 10 20 14 16 14 16 16 22 16"></polygon><polygon points="6 0 4 0 4 4 0 4 0 6 6 6"></polygon><polygon transform="translate(3.000000, 13.000000) scale(1, -1) translate(-3.000000, -13.000000) " points="6 10 4 10 4 14 0 14 0 16 6 16"></polygon></g></svg> | ||
</media-fullscreen-button> | ||
</media-control-bar> | ||
</media-controller> | ||
`; | ||
|
||
class MediaThemeNetflix extends MediaTheme { | ||
constructor(options={}) { | ||
options = Object.assign({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: no need to mutate super(template, { /* allow ...defaultOptions, */ ...options }); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is |
||
// allow options | ||
}, options); | ||
|
||
super(template, options); | ||
} | ||
} | ||
|
||
if (!customElements.get('media-theme-netflix')) { | ||
customElements.define('media-theme-netflix', MediaThemeNetflix); | ||
} | ||
|
||
export default MediaThemeNetflix; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
|
||
class MediaTheme extends HTMLElement { | ||
constructor(template=``, options={}) { | ||
super(); | ||
|
||
options = Object.assign({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see comment ☝️ |
||
// Default options | ||
}, options); | ||
|
||
// Expose the template publicaly for other uses | ||
this.template = template; | ||
|
||
// Not sure if this is best practice or if we should just | ||
// innerHTML the template string in the shadow dom | ||
const templateEl = document.createElement('template'); | ||
templateEl.innerHTML = template; | ||
|
||
// Clone the template in the shadow dom | ||
const shadow = this.attachShadow({ mode: 'open' }); | ||
shadow.appendChild(templateEl.content.cloneNode(true)); | ||
|
||
// Expose the media controller if API access is needed | ||
this.mediaController = shadow.querySelector('media-controller'); | ||
} | ||
} | ||
|
||
if (!customElements.get('media-theme')) { | ||
customElements.define('media-theme', MediaTheme); | ||
} | ||
|
||
export default MediaTheme; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.