mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(monorepo): migrate prototype code to organized package structure
Migrate all prototype code from vjs10-prototype repository into the monorepo structure following established dependency hierarchy and package organization patterns. Core packages migrated: - @vjs-10/media-store: Complete media store with nanostores integration - @vjs-10/playback-engine: HLS.js playback engine implementation - @vjs-10/media: Core media state owner and interfaces HTML packages migrated: - @vjs-10/html-icons: Web component icons with proper base classes - @vjs-10/html-media-elements: Context provider integration - @vjs-10/html: Main HTML UI components, skins, and utilities React packages migrated: - @vjs-10/react-icons: React icon components - @vjs-10/react-media-elements: Video component with state integration - @vjs-10/react-media-store: MediaProvider and React hooks - @vjs-10/react: Main React UI components and skins Changes made: - Updated all imports to use monorepo package names (@vjs-10/*) - Distributed dependencies to specific packages that use them - Preserved all prototype functionality and component structure - Created proper index files for all packages - Fixed workspace protocol usage for npm compatibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
fc5a0e46fd
commit
4b472ec49c
@@ -0,0 +1,11 @@
|
||||
import { toConnectedMediaMuteButton } from "../connected/media-mute-button";
|
||||
import { MediaMuteButton as BaseMediaMuteButton } from "../ui/media-mute-button";
|
||||
const MediaMuteButton = toConnectedMediaMuteButton(BaseMediaMuteButton);
|
||||
|
||||
// NOTE: In this architecture it will be important to decouple component class definitions from their registration in the CustomElementsRegistry. (CJP)
|
||||
if (!globalThis.customElements.get('media-mute-button')) {
|
||||
globalThis.customElements.define('media-mute-button', MediaMuteButton);
|
||||
}
|
||||
|
||||
export { MediaMuteButton };
|
||||
export default MediaMuteButton;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { toConnectedMediaPlayButton } from "../connected/media-play-button";
|
||||
import { MediaPlayButton as BaseMediaPlayButton } from "../ui/media-play-button";
|
||||
const MediaPlayButton = toConnectedMediaPlayButton(BaseMediaPlayButton);
|
||||
|
||||
// NOTE: In this architecture it will be important to decouple component class definitions from their registration in the CustomElementsRegistry. (CJP)
|
||||
if (!globalThis.customElements.get('media-play-button')) {
|
||||
globalThis.customElements.define('media-play-button', MediaPlayButton);
|
||||
}
|
||||
|
||||
export { MediaPlayButton };
|
||||
export default MediaPlayButton;
|
||||
@@ -0,0 +1,58 @@
|
||||
import { ConsumerMixin } from '@open-wc/context-protocol';
|
||||
|
||||
// NOTE: This should be fairly generic code that could itself be abstracted into configuration for a more generic factory implementation. (CJP)
|
||||
export const toConnectedMediaMuteButton = (BaseClass = HTMLElement) => {
|
||||
return class MediaMuteButton extends ConsumerMixin(BaseClass) {
|
||||
static get observedAttributes(): string[] {
|
||||
return [
|
||||
// @ts-ignore
|
||||
...(super.observedAttributes ?? []),
|
||||
'mediapaused',
|
||||
];
|
||||
}
|
||||
|
||||
#mediaStore: any;
|
||||
|
||||
contexts = {
|
||||
mediaStore: (mediaStore: any) => {
|
||||
this.#mediaStore = mediaStore;
|
||||
|
||||
this.#mediaStore.subscribeKeys(
|
||||
['mediaVolumeLevel', 'mediaMuted'],
|
||||
({ mediaVolumeLevel, mediaMuted }: any) => {
|
||||
/** @ts-ignore */
|
||||
this.mediaVolumeLevel = mediaVolumeLevel;
|
||||
/** @ts-ignore */
|
||||
this.mediaMuted = mediaMuted;
|
||||
this.setAttribute('data-volume-level', mediaVolumeLevel);
|
||||
this.toggleAttribute('data-muted', mediaMuted);
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback?.();
|
||||
this.addEventListener('mediamuterequest', this);
|
||||
this.addEventListener('mediaunmuterequest', this);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback?.();
|
||||
this.removeEventListener('mediamuterequest', this);
|
||||
this.removeEventListener('mediaunmuterequest', this);
|
||||
}
|
||||
|
||||
handleEvent(event: CustomEvent) {
|
||||
/** @ts-ignore */
|
||||
super.handleEvent?.(event);
|
||||
if (
|
||||
this.#mediaStore &&
|
||||
['mediamuterequest', 'mediaunmuterequest'].includes(event.type)
|
||||
) {
|
||||
const { type, detail } = event;
|
||||
this.#mediaStore.dispatch({ type, detail });
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import { ConsumerMixin } from '@open-wc/context-protocol';
|
||||
|
||||
// NOTE: This should be fairly generic code that could itself be abstracted into configuration for a more generic factory implementation. (CJP)
|
||||
export const toConnectedMediaPlayButton = (BaseClass = HTMLElement) => {
|
||||
return class MediaPlayButton extends ConsumerMixin(BaseClass) {
|
||||
static get observedAttributes(): string[] {
|
||||
return [
|
||||
// @ts-ignore
|
||||
...(super.observedAttributes ?? []),
|
||||
'mediapaused',
|
||||
];
|
||||
}
|
||||
|
||||
#mediaStore: any;
|
||||
|
||||
contexts = {
|
||||
mediaStore: (mediaStore: any) => {
|
||||
this.#mediaStore = mediaStore;
|
||||
|
||||
this.#mediaStore.subscribeKeys(
|
||||
['mediaPaused'],
|
||||
({ mediaPaused }: any) => {
|
||||
// NOTE: We may want to assume setting properties instead of attributes here to leave things generic for
|
||||
// complex values. That allows implementors to decide what to do if/when the property is set.
|
||||
// Using `this.toggleAttribute('mediapaused', mediaPaused);` should also work if that's preferred.
|
||||
/** @ts-ignore */
|
||||
this.mediaPaused = mediaPaused;
|
||||
this.toggleAttribute('data-paused', mediaPaused);
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback?.();
|
||||
this.addEventListener('mediaplayrequest', this);
|
||||
this.addEventListener('mediapauserequest', this);
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback?.();
|
||||
this.removeEventListener('mediaplayrequest', this);
|
||||
this.removeEventListener('mediapauserequest', this);
|
||||
}
|
||||
|
||||
handleEvent(event: CustomEvent) {
|
||||
/** @ts-ignore */
|
||||
super.handleEvent?.(event);
|
||||
if (
|
||||
this.#mediaStore &&
|
||||
['mediaplayrequest', 'mediapauserequest'].includes(event.type)
|
||||
) {
|
||||
const { type, detail } = event;
|
||||
this.#mediaStore.dispatch({ type, detail });
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import { namedNodeMapToObject } from '../../utils/element-utils.js';
|
||||
|
||||
export function getTemplateHTML(
|
||||
this: typeof MediaChromeButton,
|
||||
_attrs: Record<string, string>,
|
||||
_props: Record<string, any> = {}
|
||||
) {
|
||||
return /* html */ `
|
||||
<style>
|
||||
/*
|
||||
NOTE: Even though primitives should aim to be "unstyled" in their core definitions, we should
|
||||
still add pointer-events, as this defines functionality. (CJP)
|
||||
*/
|
||||
:host {
|
||||
pointer-events: auto;
|
||||
}
|
||||
</style>
|
||||
<slot>
|
||||
</slot>
|
||||
`;
|
||||
}
|
||||
|
||||
export class MediaChromeButton extends HTMLElement {
|
||||
static shadowRootOptions = {
|
||||
mode: 'open' as ShadowRootMode,
|
||||
};
|
||||
static getTemplateHTML = getTemplateHTML;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (!this.shadowRoot) {
|
||||
// Set up the Shadow DOM if not using Declarative Shadow DOM.
|
||||
this.attachShadow((this.constructor as typeof MediaChromeButton).shadowRootOptions);
|
||||
|
||||
const attrs = namedNodeMapToObject(this.attributes);
|
||||
const html = (this.constructor as typeof MediaChromeButton).getTemplateHTML(attrs);
|
||||
// From MDN: setHTMLUnsafe should be used instead of ShadowRoot.innerHTML
|
||||
// when a string of HTML may contain declarative shadow roots.
|
||||
const shadowRoot = this.shadowRoot as unknown as ShadowRoot;
|
||||
shadowRoot.setHTMLUnsafe ? shadowRoot.setHTMLUnsafe(html) : (shadowRoot.innerHTML = html);
|
||||
}
|
||||
|
||||
this.addEventListener('click', this.handleClick);
|
||||
}
|
||||
handleClick() {}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
import { MediaChromeButton } from './media-chrome-button';
|
||||
|
||||
export class MediaMuteButton extends MediaChromeButton {
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['mediavolumelevel', 'mediamuted'];
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
const type = this.mediaMuted ? 'mediaunmuterequest' : 'mediamuterequest';
|
||||
this.dispatchEvent(new CustomEvent(type));
|
||||
}
|
||||
|
||||
get mediaMuted() {
|
||||
return this.hasAttribute('mediamuted');
|
||||
}
|
||||
|
||||
set mediaMuted(value: boolean) {
|
||||
this.toggleAttribute('mediamuted', !!value);
|
||||
}
|
||||
|
||||
get mediaVolumeLevel() {
|
||||
return this.getAttribute('mediavolumelevel');
|
||||
}
|
||||
|
||||
set mediaVolumeLevel(value: string | null | undefined) {
|
||||
if (value == null) {
|
||||
this.removeAttribute('mediavolumelevel');
|
||||
} else {
|
||||
this.setAttribute('mediavolumelevel', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
import { MediaChromeButton } from './media-chrome-button';
|
||||
|
||||
export class MediaPlayButton extends MediaChromeButton {
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['mediapaused'];
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
const type = this.mediaPaused ? 'mediaplayrequest' : 'mediapauserequest';
|
||||
this.dispatchEvent(new CustomEvent(type));
|
||||
}
|
||||
|
||||
get mediaPaused() {
|
||||
return this.hasAttribute('mediapaused');
|
||||
}
|
||||
|
||||
set mediaPaused(value: boolean) {
|
||||
this.toggleAttribute('mediapaused', !!value);
|
||||
}
|
||||
}
|
||||
@@ -1,216 +1,9 @@
|
||||
export * from '@vjs-10/html-icons';
|
||||
export * from '@vjs-10/html-media-elements';
|
||||
export * from '@vjs-10/html-media-store';
|
||||
export * as MediaProvider from './media-provider.js';
|
||||
export * as MediaThemeDefault from './skins/media-skin-default.js';
|
||||
|
||||
export interface PlayerOptions {
|
||||
controls?: boolean;
|
||||
autoplay?: boolean;
|
||||
preload?: 'none' | 'metadata' | 'auto';
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
|
||||
export class VjsPlayer extends HTMLElement {
|
||||
private mediaElement: HTMLElement;
|
||||
private controlBar: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: 'open' });
|
||||
|
||||
this.mediaElement = document.createElement('vjs-media');
|
||||
this.controlBar = this.createControlBar();
|
||||
}
|
||||
|
||||
static get observedAttributes() {
|
||||
return ['src', 'controls', 'autoplay', 'preload', 'width', 'height'];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
||||
if (oldValue === newValue) return;
|
||||
|
||||
switch (name) {
|
||||
case 'src':
|
||||
case 'controls':
|
||||
case 'autoplay':
|
||||
case 'preload':
|
||||
this.mediaElement.setAttribute(name, newValue);
|
||||
break;
|
||||
case 'width':
|
||||
case 'height':
|
||||
this.updateDimensions();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private render() {
|
||||
if (!this.shadowRoot) return;
|
||||
|
||||
const styles = `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
background: #000;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.vjs-player-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.vjs-control-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(transparent, rgba(0,0,0,0.7));
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
:host(:hover) .vjs-control-bar {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vjs-play-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.vjs-progress-bar {
|
||||
flex: 1;
|
||||
height: 4px;
|
||||
background: rgba(255,255,255,0.3);
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vjs-progress-fill {
|
||||
height: 100%;
|
||||
background: #ff0000;
|
||||
border-radius: 2px;
|
||||
width: 0%;
|
||||
transition: width 0.1s ease;
|
||||
}
|
||||
|
||||
.vjs-volume-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
const template = `
|
||||
<div class="vjs-player-container">
|
||||
${this.mediaElement.outerHTML}
|
||||
${this.getAttribute('controls') !== null ? this.controlBar.outerHTML : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.shadowRoot.innerHTML = styles + template;
|
||||
|
||||
const mediaEl = this.shadowRoot.querySelector('vjs-media');
|
||||
const controlBarEl = this.shadowRoot.querySelector('.vjs-control-bar');
|
||||
|
||||
if (mediaEl) {
|
||||
this.mediaElement = mediaEl as HTMLElement;
|
||||
}
|
||||
if (controlBarEl) {
|
||||
this.controlBar = controlBarEl as HTMLElement;
|
||||
}
|
||||
}
|
||||
|
||||
private createControlBar(): HTMLElement {
|
||||
const controlBar = document.createElement('div');
|
||||
controlBar.className = 'vjs-control-bar';
|
||||
|
||||
controlBar.innerHTML = `
|
||||
<button class="vjs-play-button">
|
||||
<vjs-icon name="play" size="20"></vjs-icon>
|
||||
</button>
|
||||
<div class="vjs-progress-bar">
|
||||
<div class="vjs-progress-fill"></div>
|
||||
</div>
|
||||
<button class="vjs-volume-button">
|
||||
<vjs-icon name="volumeUp" size="20"></vjs-icon>
|
||||
</button>
|
||||
`;
|
||||
|
||||
return controlBar;
|
||||
}
|
||||
|
||||
private setupEventListeners() {
|
||||
const playButton = this.shadowRoot?.querySelector('.vjs-play-button');
|
||||
const progressBar = this.shadowRoot?.querySelector('.vjs-progress-bar');
|
||||
const volumeButton = this.shadowRoot?.querySelector('.vjs-volume-button');
|
||||
|
||||
playButton?.addEventListener('click', () => {
|
||||
if (this.mediaElement && 'paused' in this.mediaElement) {
|
||||
if ((this.mediaElement as any).paused) {
|
||||
(this.mediaElement as any).play();
|
||||
} else {
|
||||
(this.mediaElement as any).pause();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.mediaElement?.addEventListener('vjs-play', () => {
|
||||
const icon = playButton?.querySelector('vjs-icon');
|
||||
if (icon) {
|
||||
(icon as any).name = 'pause';
|
||||
}
|
||||
});
|
||||
|
||||
this.mediaElement?.addEventListener('vjs-pause', () => {
|
||||
const icon = playButton?.querySelector('vjs-icon');
|
||||
if (icon) {
|
||||
(icon as any).name = 'play';
|
||||
}
|
||||
});
|
||||
|
||||
this.mediaElement?.addEventListener('vjs-timeupdate', (event: CustomEvent) => {
|
||||
const progressFill = this.shadowRoot?.querySelector('.vjs-progress-fill') as HTMLElement;
|
||||
if (progressFill && this.mediaElement && 'duration' in this.mediaElement) {
|
||||
const duration = (this.mediaElement as any).duration;
|
||||
const currentTime = event.detail.currentTime;
|
||||
const percentage = duration ? (currentTime / duration) * 100 : 0;
|
||||
progressFill.style.width = `${percentage}%`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateDimensions() {
|
||||
const width = this.getAttribute('width');
|
||||
const height = this.getAttribute('height');
|
||||
|
||||
if (width) {
|
||||
this.style.width = width.includes('px') ? width : `${width}px`;
|
||||
}
|
||||
if (height) {
|
||||
this.style.height = height.includes('px') ? height : `${height}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!customElements.get('vjs-player')) {
|
||||
customElements.define('vjs-player', VjsPlayer);
|
||||
export function defineVjsPlayer() {
|
||||
/** @TODO - Reimplement me (at least as a POC) (CJP) */
|
||||
// defineVideoProvider();
|
||||
// defineVideoDefaultSkin();
|
||||
// <video> is native, no need to define
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { ConsumerMixin } from '@open-wc/context-protocol';
|
||||
|
||||
export function getTemplateHTML() {
|
||||
return /* html */`
|
||||
<slot name="media"></slot>
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
export class MediaContainer extends ConsumerMixin(HTMLElement) {
|
||||
static shadowRootOptions = { mode: 'open' as ShadowRootMode };
|
||||
static getTemplateHTML = getTemplateHTML;
|
||||
|
||||
#mediaStore: any;
|
||||
#mediaSlot: HTMLSlotElement;
|
||||
|
||||
contexts = {
|
||||
mediaStore: (mediaStore: any) => {
|
||||
this.#mediaStore = mediaStore;
|
||||
this.#handleMediaSlotChange();
|
||||
},
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (!this.shadowRoot) {
|
||||
this.attachShadow((this.constructor as typeof MediaContainer).shadowRootOptions);
|
||||
this.shadowRoot!.innerHTML = (this.constructor as typeof MediaContainer).getTemplateHTML();
|
||||
}
|
||||
|
||||
this.#mediaSlot = this.shadowRoot!.querySelector('slot[name=media]') as HTMLSlotElement;
|
||||
this.#mediaSlot.addEventListener('slotchange', this.#handleMediaSlotChange);
|
||||
}
|
||||
|
||||
#handleMediaSlotChange = () => {
|
||||
const media = this.#mediaSlot.assignedElements({ flatten: true })[0];
|
||||
this.#mediaStore.dispatch({ type: 'mediaelementchangerequest', detail: media });
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define('media-container', MediaContainer);
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ProviderMixin } from '@open-wc/context-protocol';
|
||||
import { createMediaStore } from '@vjs-10/media-store';
|
||||
|
||||
export class MediaProvider extends ProviderMixin(HTMLElement) {
|
||||
contexts = {
|
||||
mediaStore: () => {
|
||||
return createMediaStore();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define('media-provider', MediaProvider);
|
||||
@@ -0,0 +1,34 @@
|
||||
export function getTemplateHTML() {
|
||||
return /* html */ `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
media-container {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
export class MediaSkin extends HTMLElement {
|
||||
static shadowRootOptions = { mode: 'open' as ShadowRootMode };
|
||||
static getTemplateHTML = getTemplateHTML;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (!this.shadowRoot) {
|
||||
this.attachShadow((this.constructor as typeof MediaSkin).shadowRootOptions);
|
||||
this.shadowRoot!.innerHTML = (this.constructor as typeof MediaSkin).getTemplateHTML();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!customElements.get('media-skin')) {
|
||||
customElements.define('media-skin', MediaSkin);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { MediaSkin } from '../media-skin.js';
|
||||
|
||||
import '../media-container.js';
|
||||
import '../components/connected-with-defaults/media-play-button.js';
|
||||
import '../components/connected-with-defaults/media-mute-button.js';
|
||||
import '../icons/index.js';
|
||||
|
||||
export function getTemplateHTML() {
|
||||
return /* html */ `
|
||||
${MediaSkin.getTemplateHTML()}
|
||||
<style>
|
||||
/** @TODO: Improve/Polish CSS Here */
|
||||
/* Media Container UI/Styles */
|
||||
media-container {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
/* NOTE: Setting color here for generic inheritance, including SVG fill: currentColor defaults (CJP) */
|
||||
color: rgb(238 238 238);
|
||||
}
|
||||
|
||||
media-container > [slot=media] {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Media Container UI Overlay Styling */
|
||||
media-container > .overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: start;
|
||||
/* pointer-events: none; */
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* Generic Media Button Styling */
|
||||
.button {
|
||||
border: none;
|
||||
background: rgb(20 20 30 / .7);
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
color: rgb(238 238 238);
|
||||
}
|
||||
|
||||
.button .icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Media Play Button UI/Styles */
|
||||
media-play-button:not([data-paused]) .pause-icon,
|
||||
media-play-button[data-paused] .play-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* One way to define the "default visible" icon (CJP) */
|
||||
media-mute-button:not([data-volume-level]) .volume-low-icon,
|
||||
media-mute-button[data-volume-level=high] .volume-high-icon,
|
||||
media-mute-button[data-volume-level=low] .volume-low-icon,
|
||||
media-mute-button[data-volume-level=medium] .volume-low-icon,
|
||||
media-mute-button[data-volume-level=off] .volume-off-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Media Control Bar UI/Styles */
|
||||
.control-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
||||
<media-container>
|
||||
<slot name="media" slot="media"></slot>
|
||||
<div class="overlay">
|
||||
<div class="spacer"></div>
|
||||
<div class="control-bar">
|
||||
<!-- NOTE: We can decide if we further want to provide a further, "themed" media-play-button that comes with baked in default styles and icons. (CJP) -->
|
||||
<media-play-button class="button">
|
||||
<media-play-icon class="icon play-icon"></media-play-icon>
|
||||
<media-pause-icon class="icon pause-icon"></media-pause-icon>
|
||||
</media-play-button>
|
||||
<media-mute-button class="button">
|
||||
<media-volume-high-icon class="icon volume-high-icon"></media-volume-high-icon>
|
||||
<media-volume-low-icon class="icon volume-low-icon"></media-volume-low-icon>
|
||||
<media-volume-off-icon class="icon volume-off-icon"></media-volume-off-icon>
|
||||
</media-mute-button>
|
||||
</div>
|
||||
<div>
|
||||
</media-container>
|
||||
`;
|
||||
}
|
||||
|
||||
export class MediaSkinDefault extends MediaSkin {
|
||||
static getTemplateHTML = getTemplateHTML;
|
||||
}
|
||||
|
||||
customElements.define('media-skin-default', MediaSkinDefault);
|
||||
@@ -0,0 +1,7 @@
|
||||
export function namedNodeMapToObject(namedNodeMap: NamedNodeMap) {
|
||||
const obj: Record<string, string> = {};
|
||||
for (const attr of namedNodeMap) {
|
||||
obj[attr.name] = attr.value;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
Reference in New Issue
Block a user