various updates across components and views
This commit is contained in:
parent
86dbe77be9
commit
97de4f3bbb
|
@ -1,368 +0,0 @@
|
||||||
import { LitElement, css, html, unsafeCSS } from 'lit'
|
|
||||||
import { Task } from '@lit-labs/task'
|
|
||||||
import WaveSurfer from 'wavesurfer.js'
|
|
||||||
import { formatSeconds } from '../api/utils'
|
|
||||||
|
|
||||||
import MainCSS from '../assets/styles/main.scss?inline'
|
|
||||||
|
|
||||||
// temp bg image
|
|
||||||
import Photo from '/images/Meredith Monk (1974) Photo Lauretta HArris.jpg?url'
|
|
||||||
|
|
||||||
// components
|
|
||||||
import './SvgIcon.js'
|
|
||||||
import './RangeSlider.js'
|
|
||||||
|
|
||||||
class AudioPlayer extends LitElement {
|
|
||||||
static properties = {
|
|
||||||
details: { type: Object },
|
|
||||||
data: { state: true },
|
|
||||||
playing: { state: true },
|
|
||||||
volume: { state: true },
|
|
||||||
track: { state: true },
|
|
||||||
audio: { state: true },
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super()
|
|
||||||
this.details = {}
|
|
||||||
this.data = {}
|
|
||||||
this.audio = null
|
|
||||||
this.playing = false
|
|
||||||
this.audioEl = {}
|
|
||||||
|
|
||||||
this.track = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
_getAudio = new Task(
|
|
||||||
this,
|
|
||||||
async () => {
|
|
||||||
if (!this.details.media) return
|
|
||||||
|
|
||||||
if (this.details.tracks) {
|
|
||||||
console.log(this.details.tracks[0])
|
|
||||||
|
|
||||||
if (!this.audio) {
|
|
||||||
this.initAudio()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.audio.load(this.details.tracks[this.track])
|
|
||||||
|
|
||||||
return this.audio
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
if (this.audio.media) {
|
|
||||||
this.audio.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
const wsEl = this.initWs()
|
|
||||||
|
|
||||||
this.audio.load(`/media/${this.details.media}.mp3`, this.details.data)
|
|
||||||
|
|
||||||
return wsEl
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => [ this.details ]
|
|
||||||
)
|
|
||||||
|
|
||||||
initAudio() {
|
|
||||||
this.audio = document.createElement('audio')
|
|
||||||
|
|
||||||
|
|
||||||
this.audio.addEventListener('loadstart', () => {
|
|
||||||
this.loading = true
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.addEventListener('canplay', () => {
|
|
||||||
this.loading = false
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.addEventListener('play', () => {
|
|
||||||
this.playing = true
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.addEventListener('pause', () => {
|
|
||||||
this.playing = false
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.addEventListener('loadedmetadata', () => {
|
|
||||||
this.data = {
|
|
||||||
...this.data,
|
|
||||||
position: 0,
|
|
||||||
duration: this.audio.duration
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.addEventListener('ended', () => {
|
|
||||||
// auto play next track or end if last track
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.addEventListener('timeupdate', () => {
|
|
||||||
this.data = {
|
|
||||||
...this.data,
|
|
||||||
position: this.audio.currentTime
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.addEventListener('seeking', () => {
|
|
||||||
this.data = {
|
|
||||||
...this.data,
|
|
||||||
position: this.audio.currentTime
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
initWs() {
|
|
||||||
const div = document.createElement('div')
|
|
||||||
div.classList.add('waveform')
|
|
||||||
|
|
||||||
this.audio = WaveSurfer.create({
|
|
||||||
container: div,
|
|
||||||
waveColor: '#ffffff50',
|
|
||||||
progressColor: 'white',
|
|
||||||
cursorColor: 'transparent',
|
|
||||||
fillParent: true,
|
|
||||||
barWidth: 4,
|
|
||||||
barGap: 6,
|
|
||||||
barRadius: 5,
|
|
||||||
dragToSeek: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.setVolume(0.6)
|
|
||||||
this.volume = this.audio.getVolume()
|
|
||||||
|
|
||||||
this.audio.on('ready', (duration) => {
|
|
||||||
this.data = {
|
|
||||||
...this.data,
|
|
||||||
position: 0,
|
|
||||||
duration
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.on('play', () => {
|
|
||||||
this.playing = true
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.on('pause', () => {
|
|
||||||
this.playing = false
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.on('interaction', (newTime) => {
|
|
||||||
this.data = {
|
|
||||||
...this.data,
|
|
||||||
position: newTime
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.audio.on('timeupdate', (currentTime) => {
|
|
||||||
this.data = {
|
|
||||||
...this.data,
|
|
||||||
position: currentTime
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return div
|
|
||||||
}
|
|
||||||
|
|
||||||
togglePlay() {
|
|
||||||
this.audio.playPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
skipForward() {
|
|
||||||
this.audio.skip(15)
|
|
||||||
}
|
|
||||||
|
|
||||||
skipBackward() {
|
|
||||||
this.audio.skip(-15)
|
|
||||||
}
|
|
||||||
|
|
||||||
nextTrack() {
|
|
||||||
this.dispatchEvent(new Event('next-track', { bubbles: true, composed: true }))
|
|
||||||
}
|
|
||||||
|
|
||||||
prevTrack() {
|
|
||||||
this.dispatchEvent(new CustomEvent('prev-track', { bubbles: true, composed: true }))
|
|
||||||
}
|
|
||||||
|
|
||||||
volHandler(evt) {
|
|
||||||
if (evt.detail) {
|
|
||||||
this.audio.setVolume(evt.detail.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
progressCalc() {
|
|
||||||
const { position, duration } = this.data
|
|
||||||
let percentage = 0
|
|
||||||
|
|
||||||
if (position && duration) {
|
|
||||||
percentage = (position / duration) * 100
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${percentage}%`
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return html`
|
|
||||||
<img class="bg-img" src=${Photo} />
|
|
||||||
<header>
|
|
||||||
<h2>${this.details.title}</h2>
|
|
||||||
<p class="details">${this.details.details}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
<div class="progress">
|
|
||||||
<div class="bar" style="--progress-bar: ${this.progressCalc()}"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${this._getAudio.render({
|
|
||||||
complete: (div) => div
|
|
||||||
})}
|
|
||||||
|
|
||||||
<span class="time_pos">${formatSeconds(this.data.position)}</span> <span class="time_dur">${formatSeconds(this.data.duration)}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="controls">
|
|
||||||
<div>
|
|
||||||
<mm-icon name="skip-prev" @click=${this.prevTrack} ?disabled=${!this.data.duration || this.getAudio?.status == 1}></mm-icon>
|
|
||||||
<mm-icon name="skip-b15" @click=${this.skipBackward} ?disabled=${!this.data.duration || this.getAudio?.status == 1} class="skip15"></mm-icon>
|
|
||||||
<mm-icon name="${this.playing ? 'pause' : 'play'}" @click=${this.togglePlay} ?disabled=${!this.data.duration || this.getAudio?.status == 1} class=${this.playing ? 'pause' : 'play'}></mm-icon>
|
|
||||||
<mm-icon name="skip-f15" @click=${this.skipForward} ?disabled=${!this.data.duration || this.getAudio?.status == 1} class="skip15"></mm-icon>
|
|
||||||
<mm-icon name="skip-next" @click=${this.nextTrack} ?disabled=${!this.data.duration || this.getAudio?.status == 1}></mm-icon>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mm-range
|
|
||||||
value=${this.volume}
|
|
||||||
@input=${this.volHandler}
|
|
||||||
?disabled=${!this.data.duration || this.getAudio?.status == 1}
|
|
||||||
style="--track-height: 0.4em; --thumb-size: 1.2em; --track-color: #ffffff50"
|
|
||||||
></mm-range>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
|
|
||||||
:host {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
padding: 1em;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 0.7fr 0.7fr 1fr;
|
|
||||||
gap: 1em;
|
|
||||||
opacity: 0;
|
|
||||||
animation: fadeIn 0.125s ease-in forwards;
|
|
||||||
overflow: hidden;
|
|
||||||
border-radius: 0.75em 0 0 0.75em;
|
|
||||||
isolation: isolate;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-img {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
object-fit: cover;
|
|
||||||
object-position: center 25%;
|
|
||||||
opacity: 0.2;
|
|
||||||
mix-blend-mode: overlay;
|
|
||||||
scale: 1.1;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
|
||||||
color: var(--netural-100, white);
|
|
||||||
font-size: 1.5em;
|
|
||||||
line-height: 1.2;
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
header > p {
|
|
||||||
font-size: 1.5em;
|
|
||||||
max-width: 25ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info, .controls {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
--progress-bar: 0%;
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 3;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress > .bar {
|
|
||||||
height: 0.25em;
|
|
||||||
width: var(--progress-bar);
|
|
||||||
background: #ffffff80;
|
|
||||||
border-radius: 100vmax;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time_pos, .time_dur {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: var(--neutral-100, white);
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.time_dur {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
width: 65%;
|
|
||||||
margin-inline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls > div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
margin-block-end: 1em;
|
|
||||||
gap: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
mm-icon {
|
|
||||||
color: white;
|
|
||||||
font-size: 2.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
mm-icon.skip15 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
mm-icon:not(.skip15, .play, .pause) {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
mm-volctrl {
|
|
||||||
display: block;
|
|
||||||
width: 80%;
|
|
||||||
margin-inline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.waveform {
|
|
||||||
position: relative;
|
|
||||||
height: 15em;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
` ]
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define('mm-aplayer', AudioPlayer)
|
|
|
@ -32,6 +32,7 @@ class HorizontalScroller extends LitElement {
|
||||||
scroll-padding-inline: 0.5em;
|
scroll-padding-inline: 0.5em;
|
||||||
padding-inline-start: 0.5em;
|
padding-inline-start: 0.5em;
|
||||||
padding-inline-end: 0.5em;
|
padding-inline-end: 0.5em;
|
||||||
|
padding-block-start: 0.25em;
|
||||||
padding-block-end: 0.75em;
|
padding-block-end: 0.75em;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -4,44 +4,84 @@ import MainCSS from '../assets/styles/main.scss?inline'
|
||||||
|
|
||||||
class ImageViewer extends LitElement {
|
class ImageViewer extends LitElement {
|
||||||
static properties = {
|
static properties = {
|
||||||
src: { type: String },
|
details: { type: Object },
|
||||||
imgEl: { state: true },
|
imgEl: { state: true },
|
||||||
|
figEl: { state: true },
|
||||||
captionWidth: { state: true },
|
captionWidth: { state: true },
|
||||||
loading: { state: true }
|
loading: { state: true },
|
||||||
|
title: { state: true },
|
||||||
|
caption : { state: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.src = ''
|
this.details = {}
|
||||||
this.imgEl = new Image()
|
this.imgEl = new Image()
|
||||||
this.captionWidth = 'min-content'
|
this.captionWidth = 'min-content'
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|
||||||
|
this.title = ''
|
||||||
|
this.caption = ''
|
||||||
|
|
||||||
|
this.figEl = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated() {
|
||||||
|
this.figEl = this.shadowRoot.querySelector('figure')
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback()
|
super.connectedCallback()
|
||||||
|
|
||||||
this.imgEl.addEventListener('load', () => {
|
this.imgEl.addEventListener('load', () => {
|
||||||
|
this.figEl.classList.remove('unloading')
|
||||||
|
|
||||||
this.captionWidth = `${this.imgEl.offsetWidth}px`
|
this.captionWidth = `${this.imgEl.offsetWidth}px`
|
||||||
this.loading = false
|
this.loading = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
set src(val) {
|
willUpdate(att) {
|
||||||
if (val) {
|
if (att.has('details') && this.details?.media) {
|
||||||
this.imgEl.src = val
|
this.figEl.classList.add('unloading')
|
||||||
console.log(this.imgEl)
|
|
||||||
|
this.figEl.addEventListener('transitionend', () => {
|
||||||
|
this.title = this.details.title || ''
|
||||||
|
this.caption = this.details.caption || ''
|
||||||
|
this.imgEl.hidden = false
|
||||||
|
|
||||||
|
this.imgEl.src = this.details.media
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
fullscreen() {
|
||||||
|
if (this.figEl.webkitSupportsFullscreen) {
|
||||||
|
this.figEl.webkitEnterFullscreen()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.fullscreenElement !== null) {
|
||||||
|
document.exitFullscreen()
|
||||||
|
this.figEl.removeAttribute('data-fullscreen')
|
||||||
|
} else {
|
||||||
|
this.figEl.requestFullscreen()
|
||||||
|
this.figEl.setAttribute('data-fullscreen', true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<figure class="${this.loading ? 'loading' : ''}">
|
<figure class="" @click=${this.fullscreen}>
|
||||||
<picture>
|
<picture>
|
||||||
${this.imgEl}
|
${this.imgEl}
|
||||||
|
${this.oldEl}
|
||||||
</picture>
|
</picture>
|
||||||
|
|
||||||
<figcaption style="--width: ${this.captionWidth}">
|
<figcaption style="--width: ${this.captionWidth}">
|
||||||
Image Description
|
<p class="title">${this.title}</p>
|
||||||
|
${this.caption ? html`<p class="sub">${this.caption}</p>` : ''}
|
||||||
</figcaption>
|
</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
`
|
`
|
||||||
|
@ -54,10 +94,14 @@ class ImageViewer extends LitElement {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr 4em;
|
grid-template-rows: 1fr 4em;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: opacity 0.5s ease;
|
transition: opacity 0.125s 0.125s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
figure[data-fullscreen] {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading, .unloading {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { LitElement, html, css,unsafeCSS } from 'lit'
|
import { LitElement, html, css,unsafeCSS } from 'lit'
|
||||||
import { Task } from '@lit-labs/task'
|
import { Task } from '@lit-labs/task'
|
||||||
import { formatSeconds } from '../api/utils'
|
import { formatSeconds } from '../api/utils'
|
||||||
import WaveSurfer from 'wavesurfer.js'
|
|
||||||
|
|
||||||
import Photo from '/images/Meredith Monk (1974) Photo Lauretta Harris.jpg'
|
import Photo from '/images/Meredith Monk (1974) Photo Lauretta Harris.jpg'
|
||||||
import MainCSS from '../assets/styles/main.scss?inline'
|
import MainCSS from '../assets/styles/main.scss?inline'
|
||||||
|
@ -31,7 +30,6 @@ class ModularPlayer extends LitElement {
|
||||||
super()
|
super()
|
||||||
this.details = {}
|
this.details = {}
|
||||||
this.audio = null
|
this.audio = null
|
||||||
this.ws = null
|
|
||||||
|
|
||||||
this.track = 0
|
this.track = 0
|
||||||
this.position = 0
|
this.position = 0
|
||||||
|
@ -44,7 +42,9 @@ class ModularPlayer extends LitElement {
|
||||||
_getTrack = new Task(
|
_getTrack = new Task(
|
||||||
this,
|
this,
|
||||||
async () => {
|
async () => {
|
||||||
if (this.details.tracks) {
|
try {
|
||||||
|
if (this.details) {
|
||||||
|
if (this.details?.tracks) {
|
||||||
this.audio.src = `/media${this.details.media}/${this.details.tracks[this.track]}`
|
this.audio.src = `/media${this.details.media}/${this.details.tracks[this.track]}`
|
||||||
} else {
|
} else {
|
||||||
this.audio.src = `/media${this.details.media}.mp3`
|
this.audio.src = `/media${this.details.media}.mp3`
|
||||||
|
@ -53,18 +53,12 @@ class ModularPlayer extends LitElement {
|
||||||
if (this.playing && this.audio.paused) {
|
if (this.playing && this.audio.paused) {
|
||||||
this.audio.play()
|
this.audio.play()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
() => [ this.track ]
|
|
||||||
)
|
|
||||||
|
|
||||||
_renderWave = new Task(
|
|
||||||
this,
|
|
||||||
{
|
|
||||||
task: async () => {
|
|
||||||
return await this.initWs(this.details.data)
|
|
||||||
},
|
|
||||||
autoRun: false
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => [ this.track, this.details ]
|
||||||
)
|
)
|
||||||
|
|
||||||
initAudio() {
|
initAudio() {
|
||||||
|
@ -97,10 +91,6 @@ class ModularPlayer extends LitElement {
|
||||||
this.audio.addEventListener('loadedmetadata', () => {
|
this.audio.addEventListener('loadedmetadata', () => {
|
||||||
this.position = 0
|
this.position = 0
|
||||||
this.duration = this.audio.duration
|
this.duration = this.audio.duration
|
||||||
|
|
||||||
if (this.duration > 0) {
|
|
||||||
this._renderWave.run()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.audio.addEventListener('ended', () => {
|
this.audio.addEventListener('ended', () => {
|
||||||
|
@ -127,40 +117,6 @@ class ModularPlayer extends LitElement {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
initWs(peakData) {
|
|
||||||
return new Promise((go, no) => {
|
|
||||||
try {
|
|
||||||
const div = document.createElement('div')
|
|
||||||
div.classList.add('waveform')
|
|
||||||
|
|
||||||
this.ws = WaveSurfer.create({
|
|
||||||
container: div,
|
|
||||||
waveColor: '#ffffff50',
|
|
||||||
progressColor: 'white',
|
|
||||||
cursorColor: 'transparent',
|
|
||||||
fillParent: true,
|
|
||||||
normalize: true,
|
|
||||||
barWidth: 4,
|
|
||||||
barGap: 6,
|
|
||||||
barRadius: 5,
|
|
||||||
dragToSeek: true,
|
|
||||||
media: this.audio,
|
|
||||||
peaks: peakData
|
|
||||||
})
|
|
||||||
|
|
||||||
this.ws.on('interaction', (currentTime) => {
|
|
||||||
this.position = currentTime
|
|
||||||
})
|
|
||||||
|
|
||||||
this.ws.on('ready', () => {
|
|
||||||
setTimeout(() => go(div), 500)
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
no(new Error(err))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
togglePlay() {
|
togglePlay() {
|
||||||
if (this.audio.paused) {
|
if (this.audio.paused) {
|
||||||
this.playing = true
|
this.playing = true
|
||||||
|
@ -231,7 +187,8 @@ class ModularPlayer extends LitElement {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<div class="player">
|
${ this.details ?
|
||||||
|
html`<div class="player">
|
||||||
${this.details.tracks ? html`
|
${this.details.tracks ? html`
|
||||||
<div class="tracklist">
|
<div class="tracklist">
|
||||||
<h2>${this.details.title}</h2>
|
<h2>${this.details.title}</h2>
|
||||||
|
@ -258,28 +215,30 @@ class ModularPlayer extends LitElement {
|
||||||
complete: () => html`
|
complete: () => html`
|
||||||
<span class="time_pos">${formatSeconds(this.position)}</span>
|
<span class="time_pos">${formatSeconds(this.position)}</span>
|
||||||
|
|
||||||
${!this.details.tracks ?
|
${this.details?.tracks ?
|
||||||
this._renderWave.render({
|
|
||||||
initial: () => html`<mm-loading class="waveform" nobg></mm-loading>`,
|
|
||||||
pending: () => html`<mm-loading class="waveform" nobg></mm-loading>`,
|
|
||||||
complete: (ws) => html`
|
|
||||||
<div class="progress">
|
|
||||||
<div class="bar" style="--progress-bar: ${this.progressCalc()}">
|
|
||||||
<div class="blob"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${ws}`
|
|
||||||
}) :
|
|
||||||
html`<mm-range
|
html`<mm-range
|
||||||
value=${this.position}
|
value=${this.position}
|
||||||
max=${this.duration}
|
max=${this.duration}
|
||||||
step="0.1"
|
step="0.1"
|
||||||
@input=${this.seekTrack}
|
@input=${this.seekTrack}
|
||||||
></mm-range>`
|
></mm-range>`
|
||||||
|
: html`
|
||||||
|
<div class="waveform">
|
||||||
|
<mm-range
|
||||||
|
value=${this.position}
|
||||||
|
max=${this.duration}
|
||||||
|
step="0.1"
|
||||||
|
@input=${this.seekTrack}
|
||||||
|
class="progress"
|
||||||
|
></mm-range>
|
||||||
|
<img class="wav-img" src="/media${this.details.media}.png">
|
||||||
|
</div>
|
||||||
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
<span class="time_dur">${formatSeconds(this.duration)}</span>
|
<span class="time_dur">${formatSeconds(this.duration)}</span>
|
||||||
`
|
`,
|
||||||
|
error: (err) => err
|
||||||
})}
|
})}
|
||||||
|
|
||||||
${this.audio}
|
${this.audio}
|
||||||
|
@ -304,7 +263,7 @@ class ModularPlayer extends LitElement {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
` : ''}`
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
|
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
|
||||||
|
@ -456,7 +415,6 @@ class ModularPlayer extends LitElement {
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
|
||||||
& > mm-range {
|
& > mm-range {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
--track-height: 0.4em;
|
--track-height: 0.4em;
|
||||||
|
@ -472,7 +430,7 @@ class ModularPlayer extends LitElement {
|
||||||
& .time_pos,
|
& .time_pos,
|
||||||
& .time_dur {
|
& .time_dur {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: var(--padding);
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .time_dur {
|
& .time_dur {
|
||||||
|
@ -522,34 +480,15 @@ class ModularPlayer extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
--progress-bar: 0%;
|
--track-height: 0.25em;
|
||||||
|
--track-color: transparent;
|
||||||
|
--thumb-size: 0.75em;
|
||||||
|
--track-progress: #ffffff50;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
pointer-events: none;
|
|
||||||
padding-inline: var(--padding);
|
|
||||||
|
|
||||||
> .bar {
|
|
||||||
--bar-height: 0.25em;
|
|
||||||
height: var(--bar-height);
|
|
||||||
width: var(--progress-bar);
|
|
||||||
background: #ffffff80;
|
|
||||||
border-radius: 100vmax;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
> .blob {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(var(--bar-height) * -0.5);
|
|
||||||
right: calc(var(--bar-height) * -1);
|
|
||||||
width: 0.5em;
|
|
||||||
height: 0.5em;
|
|
||||||
border-radius: 100vmax;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mm-icon {
|
mm-icon {
|
||||||
|
@ -566,11 +505,17 @@ class ModularPlayer extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
.waveform {
|
.waveform {
|
||||||
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0 var(--padding);
|
inset: 0 var(--padding);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
> .wav-img {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { LitElement, html, css, unsafeCSS } from 'lit'
|
import { LitElement, html, css, unsafeCSS } from 'lit'
|
||||||
|
|
||||||
import MainCSS from '../assets/styles/main.scss?inline'
|
import MainCSS from '../assets/styles/main.scss?inline'
|
||||||
|
import Router from '../api/Router'
|
||||||
|
|
||||||
class VerticalCard extends LitElement {
|
class VerticalCard extends LitElement {
|
||||||
static properties = {
|
static properties = {
|
||||||
|
@ -29,7 +30,7 @@ class VerticalCard extends LitElement {
|
||||||
return html`
|
return html`
|
||||||
<div @click=${this.select} class=${this.selected ? 'selected' : ''}>
|
<div @click=${this.select} class=${this.selected ? 'selected' : ''}>
|
||||||
<picture>
|
<picture>
|
||||||
<img src="/media/documentaries_interviews/thumbs/girlchild.png">
|
<img src="/media/${Router.route.path}/thumbs/${this.details.media}">
|
||||||
</picture>
|
</picture>
|
||||||
<aside>
|
<aside>
|
||||||
<p class="title">${this.details?.title}</p>
|
<p class="title">${this.details?.title}</p>
|
||||||
|
|
|
@ -27,8 +27,6 @@ class AudioView extends LitElement {
|
||||||
firstUpdated() {
|
firstUpdated() {
|
||||||
this.addEventListener('select-audio', ({ detail }) => {
|
this.addEventListener('select-audio', ({ detail }) => {
|
||||||
this.selected = { ...detail, media: `${Router.route.path}/${detail.media}` }
|
this.selected = { ...detail, media: `${Router.route.path}/${detail.media}` }
|
||||||
console.log(this.selected)
|
|
||||||
this._selectAudio.run()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.addEventListener('next-track', () => {
|
this.addEventListener('next-track', () => {
|
||||||
|
@ -54,23 +52,11 @@ class AudioView extends LitElement {
|
||||||
() => [ this.path ]
|
() => [ this.path ]
|
||||||
)
|
)
|
||||||
|
|
||||||
_selectAudio = new Task(
|
|
||||||
this,
|
|
||||||
{
|
|
||||||
task: async ([ selected ]) => {
|
|
||||||
const res = await fetch(`/media${selected.media}.json`)
|
|
||||||
return { ...selected, ...await res.json() }
|
|
||||||
},
|
|
||||||
args: () => [ this.selected ]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
_skipTrack(direction) {
|
_skipTrack(direction) {
|
||||||
const idx = (this.selected.idx + direction) % this.tracks.length
|
const idx = (this.selected.idx + direction) % this.tracks.length
|
||||||
const detail = { ...this.tracks[idx], idx }
|
const detail = { ...this.tracks[idx], idx }
|
||||||
|
|
||||||
this.selected = { ...detail, media: `${Router.route.path}/${detail.media}` }
|
this.selected = { ...detail, media: `${Router.route.path}/${detail.media}` }
|
||||||
this._selectAudio.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -90,10 +76,7 @@ class AudioView extends LitElement {
|
||||||
|
|
||||||
<div class="player">
|
<div class="player">
|
||||||
<div>
|
<div>
|
||||||
${this._selectAudio.render({
|
<mm-audio-player .details=${this.selected}></mm-audio-player>
|
||||||
pending: () => html`<mm-loading style="--fill-color: grey"></mm-loading>`,
|
|
||||||
complete: (selected) => html`<mm-audio-player .details=${selected}></mm-audio-player>`
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import { LitElement, css, html } from "lit";
|
import { LitElement, css, html, unsafeCSS } from 'lit'
|
||||||
|
import { Task } from '@lit-labs/task'
|
||||||
|
import Router from '../api/Router.js'
|
||||||
|
|
||||||
|
import MainCSS from '../assets/styles/main.scss'
|
||||||
|
|
||||||
import '../components/Header.js'
|
import '../components/Header.js'
|
||||||
import '../components/Footer.js'
|
import '../components/Footer.js'
|
||||||
|
@ -7,10 +11,38 @@ import '../components/VerticalCard.js'
|
||||||
import '../components/ImageViewer.js'
|
import '../components/ImageViewer.js'
|
||||||
|
|
||||||
class ImageView extends LitElement {
|
class ImageView extends LitElement {
|
||||||
static properties = {}
|
static properties = {
|
||||||
|
images: { state: true },
|
||||||
|
selected: { state: true }
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
|
this.images = []
|
||||||
|
}
|
||||||
|
|
||||||
|
_getImages = new Task(
|
||||||
|
this,
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/data/images.json')
|
||||||
|
const json = await res.json()
|
||||||
|
|
||||||
|
this.images = json
|
||||||
|
this.selected = { ...this.images[0], media: `/media${Router.route.path}/${this.images[0].media}`}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => []
|
||||||
|
)
|
||||||
|
|
||||||
|
selectImage({ target }) {
|
||||||
|
const { details } = target
|
||||||
|
if (details?.title != this.selected?.title) {
|
||||||
|
this.selected = { ...details, media: `/media${Router.route.path}/${details.media}` }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -18,28 +50,20 @@ class ImageView extends LitElement {
|
||||||
<mm-header></mm-header>
|
<mm-header></mm-header>
|
||||||
<main>
|
<main>
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<mm-imageviewer src="https://picsum.photos/${parseInt((Math.random() * 400) + 300)}/${parseInt((Math.random() * 400) + 400)}"></mm-imageviewer>
|
<mm-imageviewer .details=${this.selected}></mm-imageviewer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mm-hscroller>
|
<mm-hscroller>
|
||||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
${this._getImages.render({ complete: () => this.images.map(i => html`
|
||||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
<mm-vcard @click=${this.selectImage} .details=${i} ?selected=${i.title == this.selected?.title}></mm-vcard>
|
||||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
`) })}
|
||||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
|
||||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
|
||||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
|
||||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
|
||||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
|
||||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
|
||||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
|
||||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
|
||||||
</mm-hscroller>
|
</mm-hscroller>
|
||||||
</main>
|
</main>
|
||||||
<mm-footer></mm-footer>
|
<mm-footer></mm-footer>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
|
||||||
:host {
|
:host {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: min-content 1fr min-content;
|
grid-template-rows: min-content 1fr min-content;
|
||||||
|
@ -52,6 +76,10 @@ class ImageView extends LitElement {
|
||||||
--gap: 0.75em;
|
--gap: 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mm-vcard {
|
||||||
|
--color: var(--green-400, lime);
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr auto;
|
grid-template-rows: 1fr auto;
|
||||||
|
@ -63,7 +91,7 @@ class ImageView extends LitElement {
|
||||||
inline-margin: auto;
|
inline-margin: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
`
|
` ]
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('mm-images', ImageView)
|
customElements.define('mm-images', ImageView)
|
||||||
|
|
|
@ -41,32 +41,16 @@ class VideoPage extends LitElement {
|
||||||
() => [ this.data ]
|
() => [ this.data ]
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO@mx this might not be necessary
|
|
||||||
_selectVideo = new Task(
|
|
||||||
this,
|
|
||||||
{
|
|
||||||
task: () => {
|
|
||||||
// console.log(this.selected)
|
|
||||||
},
|
|
||||||
args: () => [ this.selected ],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<mm-header></mm-header>
|
<mm-header></mm-header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
${this._selectVideo.render({
|
|
||||||
pending: () => html`Loading...`,
|
|
||||||
complete: () => html`
|
|
||||||
<aside>
|
<aside>
|
||||||
<h2>${this.selected.title}</h2>
|
<h2>${this.selected.title}</h2>
|
||||||
<p class="detail">${this.selected.detail}</p>
|
<p class="detail">${this.selected.detail}</p>
|
||||||
</aside>
|
</aside>
|
||||||
<mm-vplayer media='/media/${this.selected.media}' ?autoplay=${this.selected.autoplay}></mm-vplayer>
|
<mm-vplayer media='/media/${this.selected.media}' ?autoplay=${this.selected.autoplay}></mm-vplayer>
|
||||||
`
|
|
||||||
})}
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<mm-hscroller>
|
<mm-hscroller>
|
||||||
|
|
Loading…
Reference in New Issue