mirror of
https://code.castopod.org/adaures/castopod
synced 2025-04-19 04:51:17 +00:00
139 lines
3.2 KiB
TypeScript
139 lines
3.2 KiB
TypeScript
import { css, html, LitElement, TemplateResult } from "lit";
|
|
import {
|
|
customElement,
|
|
property,
|
|
queryAssignedElements,
|
|
} from "lit/decorators.js";
|
|
import { styleMap } from "lit/directives/style-map.js";
|
|
|
|
enum VideoFormats {
|
|
Landscape = "landscape",
|
|
Portrait = "portrait",
|
|
Squared = "squared",
|
|
}
|
|
|
|
const formatMap = {
|
|
[VideoFormats.Landscape]: "16/9",
|
|
[VideoFormats.Portrait]: "9/16",
|
|
[VideoFormats.Squared]: "1/1",
|
|
};
|
|
|
|
@customElement("video-clip-previewer")
|
|
export class VideoClipPreviewer extends LitElement {
|
|
@queryAssignedElements({ slot: "preview_image", flatten: true })
|
|
_image!: NodeListOf<HTMLImageElement>;
|
|
|
|
@property()
|
|
title = "";
|
|
|
|
@property()
|
|
format: VideoFormats = VideoFormats.Portrait;
|
|
|
|
@property()
|
|
theme = "172 100% 17%";
|
|
|
|
@property({ type: Number })
|
|
duration!: number;
|
|
|
|
@property({ attribute: false })
|
|
_previewImage!: HTMLImageElement;
|
|
|
|
protected firstUpdated(): void {
|
|
this._previewImage = this._image[0].cloneNode(true) as HTMLImageElement;
|
|
this._previewImage.classList.add("preview-bg");
|
|
}
|
|
|
|
private secondsToHHMMSS(seconds: number) {
|
|
// Adapted from https://stackoverflow.com/a/34841026
|
|
const h = Math.floor(seconds / 3600);
|
|
const min = Math.floor(seconds / 60) % 60;
|
|
const s = seconds % 60;
|
|
|
|
return [h, min, s]
|
|
.map((v) => (v < 10 ? "0" + v : v))
|
|
.filter((v, i) => v !== "00" || i > 0)
|
|
.join(":");
|
|
}
|
|
|
|
static styles = css`
|
|
.metadata {
|
|
position: absolute;
|
|
top: 1rem;
|
|
left: 1.5rem;
|
|
color: #ffffff;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.title {
|
|
font-family: "Kumbh Sans";
|
|
font-weight: 900;
|
|
font-size: 1.5rem;
|
|
text-shadow: 2px 3px 5px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
.duration {
|
|
font-family: "Inter";
|
|
font-weight: 600;
|
|
}
|
|
|
|
.preview-bg {
|
|
position: absolute;
|
|
background-color: red;
|
|
width: 100%;
|
|
object-fit: cover;
|
|
filter: blur(30px);
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.video-background {
|
|
position: relative;
|
|
display: grid;
|
|
justify-items: center;
|
|
align-items: center;
|
|
background-color: black;
|
|
width: 100%;
|
|
aspect-ratio: 16 / 9;
|
|
border-radius: 0.75rem 0.75rem 0 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.video-format {
|
|
z-index: 10;
|
|
display: grid;
|
|
align-items: center;
|
|
justify-items: center;
|
|
height: 100%;
|
|
border: 4px solid hsl(0 0% 100% / 0.5);
|
|
transition: 300ms ease-in-out aspect-ratio;
|
|
}
|
|
|
|
::slotted(img) {
|
|
border-radius: 0.5rem;
|
|
box-shadow:
|
|
0 4px 6px -1px rgb(0 0 0 / 0.1),
|
|
0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
}
|
|
`;
|
|
|
|
render(): TemplateResult<1> {
|
|
const styles = {
|
|
aspectRatio: formatMap[this.format],
|
|
backgroundColor: `hsl(${this.theme})`,
|
|
};
|
|
|
|
return html`<div class="video-background">
|
|
${this._previewImage}
|
|
<div class="video-format" style=${styleMap(styles)}>
|
|
<div class="metadata">
|
|
<span class="title">${this.title}</span>
|
|
<time datetime="PT${this.duration}S" class="duration"
|
|
>${this.secondsToHHMMSS(Math.floor(this.duration))}</time
|
|
>
|
|
</div>
|
|
<slot name="preview_image"></slot>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
}
|