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;
|
||||
padding-inline-start: 0.5em;
|
||||
padding-inline-end: 0.5em;
|
||||
padding-block-start: 0.25em;
|
||||
padding-block-end: 0.75em;
|
||||
}
|
||||
`
|
||||
|
|
|
@ -4,44 +4,84 @@ import MainCSS from '../assets/styles/main.scss?inline'
|
|||
|
||||
class ImageViewer extends LitElement {
|
||||
static properties = {
|
||||
src: { type: String },
|
||||
details: { type: Object },
|
||||
imgEl: { state: true },
|
||||
figEl: { state: true },
|
||||
captionWidth: { state: true },
|
||||
loading: { state: true }
|
||||
loading: { state: true },
|
||||
title: { state: true },
|
||||
caption : { state: true }
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.src = ''
|
||||
this.details = {}
|
||||
this.imgEl = new Image()
|
||||
this.captionWidth = 'min-content'
|
||||
this.loading = true
|
||||
|
||||
this.title = ''
|
||||
this.caption = ''
|
||||
|
||||
this.figEl = {}
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.figEl = this.shadowRoot.querySelector('figure')
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
|
||||
this.imgEl.addEventListener('load', () => {
|
||||
this.figEl.classList.remove('unloading')
|
||||
|
||||
this.captionWidth = `${this.imgEl.offsetWidth}px`
|
||||
this.loading = false
|
||||
})
|
||||
}
|
||||
|
||||
set src(val) {
|
||||
if (val) {
|
||||
this.imgEl.src = val
|
||||
console.log(this.imgEl)
|
||||
willUpdate(att) {
|
||||
if (att.has('details') && this.details?.media) {
|
||||
this.figEl.classList.add('unloading')
|
||||
|
||||
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() {
|
||||
return html`
|
||||
<figure class="${this.loading ? 'loading' : ''}">
|
||||
<figure class="" @click=${this.fullscreen}>
|
||||
<picture>
|
||||
${this.imgEl}
|
||||
${this.oldEl}
|
||||
</picture>
|
||||
|
||||
<figcaption style="--width: ${this.captionWidth}">
|
||||
Image Description
|
||||
<p class="title">${this.title}</p>
|
||||
${this.caption ? html`<p class="sub">${this.caption}</p>` : ''}
|
||||
</figcaption>
|
||||
</figure>
|
||||
`
|
||||
|
@ -54,10 +94,14 @@ class ImageViewer extends LitElement {
|
|||
display: grid;
|
||||
grid-template-rows: 1fr 4em;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { LitElement, html, css,unsafeCSS } from 'lit'
|
||||
import { Task } from '@lit-labs/task'
|
||||
import { formatSeconds } from '../api/utils'
|
||||
import WaveSurfer from 'wavesurfer.js'
|
||||
|
||||
import Photo from '/images/Meredith Monk (1974) Photo Lauretta Harris.jpg'
|
||||
import MainCSS from '../assets/styles/main.scss?inline'
|
||||
|
@ -31,7 +30,6 @@ class ModularPlayer extends LitElement {
|
|||
super()
|
||||
this.details = {}
|
||||
this.audio = null
|
||||
this.ws = null
|
||||
|
||||
this.track = 0
|
||||
this.position = 0
|
||||
|
@ -44,27 +42,23 @@ class ModularPlayer extends LitElement {
|
|||
_getTrack = new Task(
|
||||
this,
|
||||
async () => {
|
||||
if (this.details.tracks) {
|
||||
this.audio.src = `/media${this.details.media}/${this.details.tracks[this.track]}`
|
||||
} else {
|
||||
this.audio.src = `/media${this.details.media}.mp3`
|
||||
}
|
||||
this.audio.load()
|
||||
if (this.playing && this.audio.paused) {
|
||||
this.audio.play()
|
||||
try {
|
||||
if (this.details) {
|
||||
if (this.details?.tracks) {
|
||||
this.audio.src = `/media${this.details.media}/${this.details.tracks[this.track]}`
|
||||
} else {
|
||||
this.audio.src = `/media${this.details.media}.mp3`
|
||||
}
|
||||
this.audio.load()
|
||||
if (this.playing && this.audio.paused) {
|
||||
this.audio.play()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
},
|
||||
() => [ this.track ]
|
||||
)
|
||||
|
||||
_renderWave = new Task(
|
||||
this,
|
||||
{
|
||||
task: async () => {
|
||||
return await this.initWs(this.details.data)
|
||||
},
|
||||
autoRun: false
|
||||
}
|
||||
() => [ this.track, this.details ]
|
||||
)
|
||||
|
||||
initAudio() {
|
||||
|
@ -97,10 +91,6 @@ class ModularPlayer extends LitElement {
|
|||
this.audio.addEventListener('loadedmetadata', () => {
|
||||
this.position = 0
|
||||
this.duration = this.audio.duration
|
||||
|
||||
if (this.duration > 0) {
|
||||
this._renderWave.run()
|
||||
}
|
||||
})
|
||||
|
||||
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() {
|
||||
if (this.audio.paused) {
|
||||
this.playing = true
|
||||
|
@ -231,7 +187,8 @@ class ModularPlayer extends LitElement {
|
|||
|
||||
render() {
|
||||
return html`
|
||||
<div class="player">
|
||||
${ this.details ?
|
||||
html`<div class="player">
|
||||
${this.details.tracks ? html`
|
||||
<div class="tracklist">
|
||||
<h2>${this.details.title}</h2>
|
||||
|
@ -258,28 +215,30 @@ class ModularPlayer extends LitElement {
|
|||
complete: () => html`
|
||||
<span class="time_pos">${formatSeconds(this.position)}</span>
|
||||
|
||||
${!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}`
|
||||
}) :
|
||||
${this.details?.tracks ?
|
||||
html`<mm-range
|
||||
value=${this.position}
|
||||
max=${this.duration}
|
||||
step="0.1"
|
||||
@input=${this.seekTrack}
|
||||
></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>
|
||||
`
|
||||
`,
|
||||
error: (err) => err
|
||||
})}
|
||||
|
||||
${this.audio}
|
||||
|
@ -304,7 +263,7 @@ class ModularPlayer extends LitElement {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
` : ''}`
|
||||
}
|
||||
|
||||
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
|
||||
|
@ -456,7 +415,6 @@ class ModularPlayer extends LitElement {
|
|||
gap: 1em;
|
||||
align-items: center;
|
||||
|
||||
|
||||
& > mm-range {
|
||||
flex-grow: 1;
|
||||
--track-height: 0.4em;
|
||||
|
@ -472,7 +430,7 @@ class ModularPlayer extends LitElement {
|
|||
& .time_pos,
|
||||
& .time_dur {
|
||||
position: absolute;
|
||||
bottom: var(--padding);
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
& .time_dur {
|
||||
|
@ -522,34 +480,15 @@ class ModularPlayer extends LitElement {
|
|||
}
|
||||
|
||||
.progress {
|
||||
--progress-bar: 0%;
|
||||
--track-height: 0.25em;
|
||||
--track-color: transparent;
|
||||
--thumb-size: 0.75em;
|
||||
--track-progress: #ffffff50;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
inset: 0;
|
||||
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 {
|
||||
|
@ -566,11 +505,17 @@ class ModularPlayer extends LitElement {
|
|||
}
|
||||
|
||||
.waveform {
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset: 0 var(--padding);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
> .wav-img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { LitElement, html, css, unsafeCSS } from 'lit'
|
||||
|
||||
import MainCSS from '../assets/styles/main.scss?inline'
|
||||
import Router from '../api/Router'
|
||||
|
||||
class VerticalCard extends LitElement {
|
||||
static properties = {
|
||||
|
@ -29,7 +30,7 @@ class VerticalCard extends LitElement {
|
|||
return html`
|
||||
<div @click=${this.select} class=${this.selected ? 'selected' : ''}>
|
||||
<picture>
|
||||
<img src="/media/documentaries_interviews/thumbs/girlchild.png">
|
||||
<img src="/media/${Router.route.path}/thumbs/${this.details.media}">
|
||||
</picture>
|
||||
<aside>
|
||||
<p class="title">${this.details?.title}</p>
|
||||
|
|
|
@ -27,8 +27,6 @@ class AudioView extends LitElement {
|
|||
firstUpdated() {
|
||||
this.addEventListener('select-audio', ({ detail }) => {
|
||||
this.selected = { ...detail, media: `${Router.route.path}/${detail.media}` }
|
||||
console.log(this.selected)
|
||||
this._selectAudio.run()
|
||||
})
|
||||
|
||||
this.addEventListener('next-track', () => {
|
||||
|
@ -54,23 +52,11 @@ class AudioView extends LitElement {
|
|||
() => [ 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) {
|
||||
const idx = (this.selected.idx + direction) % this.tracks.length
|
||||
const detail = { ...this.tracks[idx], idx }
|
||||
|
||||
this.selected = { ...detail, media: `${Router.route.path}/${detail.media}` }
|
||||
this._selectAudio.run()
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -90,10 +76,7 @@ class AudioView extends LitElement {
|
|||
|
||||
<div class="player">
|
||||
<div>
|
||||
${this._selectAudio.render({
|
||||
pending: () => html`<mm-loading style="--fill-color: grey"></mm-loading>`,
|
||||
complete: (selected) => html`<mm-audio-player .details=${selected}></mm-audio-player>`
|
||||
})}
|
||||
<mm-audio-player .details=${this.selected}></mm-audio-player>
|
||||
</div>
|
||||
</div>
|
||||
</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/Footer.js'
|
||||
|
@ -7,10 +11,38 @@ import '../components/VerticalCard.js'
|
|||
import '../components/ImageViewer.js'
|
||||
|
||||
class ImageView extends LitElement {
|
||||
static properties = {}
|
||||
static properties = {
|
||||
images: { state: true },
|
||||
selected: { state: true }
|
||||
}
|
||||
|
||||
constructor() {
|
||||
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() {
|
||||
|
@ -18,28 +50,20 @@ class ImageView extends LitElement {
|
|||
<mm-header></mm-header>
|
||||
<main>
|
||||
<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>
|
||||
|
||||
<mm-hscroller>
|
||||
<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-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
||||
<mm-vcard .details=${{'title': 'Image'}}></mm-vcard>
|
||||
${this._getImages.render({ complete: () => this.images.map(i => html`
|
||||
<mm-vcard @click=${this.selectImage} .details=${i} ?selected=${i.title == this.selected?.title}></mm-vcard>
|
||||
`) })}
|
||||
</mm-hscroller>
|
||||
</main>
|
||||
<mm-footer></mm-footer>
|
||||
`
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
static styles = [ css`${unsafeCSS(MainCSS)}`, css`
|
||||
:host {
|
||||
display: grid;
|
||||
grid-template-rows: min-content 1fr min-content;
|
||||
|
@ -52,6 +76,10 @@ class ImageView extends LitElement {
|
|||
--gap: 0.75em;
|
||||
}
|
||||
|
||||
mm-vcard {
|
||||
--color: var(--green-400, lime);
|
||||
}
|
||||
|
||||
main {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
|
@ -63,7 +91,7 @@ class ImageView extends LitElement {
|
|||
inline-margin: auto;
|
||||
height: 100%;
|
||||
}
|
||||
`
|
||||
` ]
|
||||
}
|
||||
|
||||
customElements.define('mm-images', ImageView)
|
||||
|
|
|
@ -41,32 +41,16 @@ class VideoPage extends LitElement {
|
|||
() => [ this.data ]
|
||||
)
|
||||
|
||||
// TODO@mx this might not be necessary
|
||||
_selectVideo = new Task(
|
||||
this,
|
||||
{
|
||||
task: () => {
|
||||
// console.log(this.selected)
|
||||
},
|
||||
args: () => [ this.selected ],
|
||||
}
|
||||
)
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<mm-header></mm-header>
|
||||
|
||||
<main>
|
||||
${this._selectVideo.render({
|
||||
pending: () => html`Loading...`,
|
||||
complete: () => html`
|
||||
<aside>
|
||||
<h2>${this.selected.title}</h2>
|
||||
<p class="detail">${this.selected.detail}</p>
|
||||
</aside>
|
||||
<mm-vplayer media='/media/${this.selected.media}' ?autoplay=${this.selected.autoplay}></mm-vplayer>
|
||||
`
|
||||
})}
|
||||
<aside>
|
||||
<h2>${this.selected.title}</h2>
|
||||
<p class="detail">${this.selected.detail}</p>
|
||||
</aside>
|
||||
<mm-vplayer media='/media/${this.selected.media}' ?autoplay=${this.selected.autoplay}></mm-vplayer>
|
||||
</main>
|
||||
|
||||
<mm-hscroller>
|
||||
|
|
Loading…
Reference in New Issue