wired-elements
Version:
Collection of hand-drawn sketchy web components
227 lines (209 loc) • 6.74 kB
text/typescript
import { WiredBase, BaseCSS, Point } from './wired-base';
import { rectangle } from './wired-lib';
import { css, TemplateResult, html, CSSResultArray } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { WiredProgress } from './wired-progress.js';
import { WiredSlider } from './wired-slider';
import './wired-icon-button.js';
('wired-video')
export class WiredVideo extends WiredBase {
({ type: String }) src = '';
({ type: Boolean }) autoplay = false;
({ type: Boolean }) loop = false;
({ type: Boolean }) muted = false;
({ type: Boolean }) playsinline = false;
() private playing = false;
() private timeDisplay = '';
('wired-progress') private progressBar?: WiredProgress;
('wired-slider') private slider?: WiredSlider;
('video') private video?: HTMLVideoElement;
private resizeObserver?: ResizeObserver;
private windowResizeHandler?: EventListenerOrEventListenerObject;
constructor() {
super();
if ((window as any).ResizeObserver) {
this.resizeObserver = new (window as any).ResizeObserver(() => {
if (this.svg) {
this.wiredRender();
}
});
}
}
static get styles(): CSSResultArray {
return [
BaseCSS,
css`
:host {
display: inline-block;
position: relative;
line-height: 1;
padding: 3px 3px 68px;
--wired-progress-color: var(--wired-video-highlight-color, rgb(51, 103, 214));
--wired-slider-knob-color: var(--wired-video-highlight-color, rgb(51, 103, 214));
}
video {
display: block;
box-sizing: border-box;
max-width: 100%;
max-height: 100%;
}
path {
stroke-width: 1;
}
#controls {
position: absolute;
pointer-events: auto;
left: 0;
bottom: 0;
width: 100%;
box-sizing: border-box;
height: 70px;
}
.layout.horizontal {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding: 5px 10px;
}
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
}
wired-progress {
display: block;
width: 100%;
box-sizing: border-box;
height: 20px;
--wired-progress-label-color: transparent;
--wired-progress-label-background: transparent;
}
wired-icon-button span {
font-size: 16px;
line-height: 16px;
width: 16px;
height: 16px;
padding: 0px;
font-family: sans-serif;
display: inline-block;
}
#timeDisplay {
padding: 0 20px 0 8px;
font-size: 13px;
}
wired-slider {
display: block;
max-width: 200px;
margin: 0 6px 0 auto;
}
`
];
}
render(): TemplateResult {
return html`
<video
.autoplay="${this.autoplay}"
.loop="${this.loop}"
.muted="${this.muted}"
.playsinline="${this.playsinline}"
src="${this.src}"
@play="${() => this.playing = true}"
@pause="${() => this.playing = false}"
@canplay="${this.canPlay}"
@timeupdate="${this.updateTime}">
</video>
<div id="overlay">
<svg></svg>
</div>
<div id="controls">
<wired-progress></wired-progress>
<div class="horizontal layout center">
<wired-icon-button @click="${this.togglePause}">
<span>${this.playing ? '||' : '▶'}</span>
</wired-icon-button>
<div id="timeDisplay">${this.timeDisplay}</div>
<div class="flex">
<wired-slider @change="${this.volumeChange}"></wired-slider>
</div>
<div style="width: 24px; height: 24px;">
<svg viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" focusable="false" style="pointer-events: none; display: block; width: 100%; height: 100%;"><g><path style="stroke: none; fill: currentColor;" d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"></path></g></svg>
</div>
</div>
</div>
`;
}
updated() {
super.updated();
this.attachResizeListener();
}
disconnectedCallback() {
this.detachResizeListener();
}
private attachResizeListener() {
if (this.resizeObserver && this.resizeObserver.observe) {
this.resizeObserver.observe(this);
} else if (!this.windowResizeHandler) {
this.windowResizeHandler = () => this.wiredRender();
window.addEventListener('resize', this.windowResizeHandler, { passive: true });
}
}
private detachResizeListener() {
if (this.resizeObserver && this.resizeObserver.unobserve) {
this.resizeObserver.unobserve(this);
}
if (this.windowResizeHandler) {
window.removeEventListener('resize', this.windowResizeHandler);
}
}
wiredRender() {
super.wiredRender();
if (this.progressBar) {
this.progressBar.wiredRender(true);
}
}
protected canvasSize(): Point {
const s = this.getBoundingClientRect();
return [s.width, s.height];
}
protected draw(svg: SVGSVGElement, size: Point) {
rectangle(svg, 2, 2, size[0] - 4, size[1] - 4, this.seed);
}
private updateTime() {
if (this.video && this.progressBar) {
this.progressBar.value = this.video.duration ? Math.round((this.video.currentTime / this.video.duration) * 100) : 0;
this.timeDisplay = `${this.getTimeDisplay(this.video.currentTime)} / ${this.getTimeDisplay(this.video.duration)}`;
}
}
private getTimeDisplay(time: number) {
const mins = Math.floor(time / 60);
const secs = Math.round(time - (mins * 60));
return `${mins}:${secs}`;
}
private togglePause() {
if (this.video) {
if (this.playing) {
this.video.pause();
} else {
this.video.play();
}
}
}
private volumeChange() {
if (this.video && this.slider) {
this.video.volume = this.slider.value / 100;
}
}
private canPlay() {
if (this.slider && this.video) {
this.slider.value = this.video!.volume * 100;
}
}
}