diff --git a/src/components/AudioPlayer.js b/src/components/AudioPlayer.js deleted file mode 100644 index de2c8ea..0000000 --- a/src/components/AudioPlayer.js +++ /dev/null @@ -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` - -
-

${this.details.title}

-

${this.details.details}

-
- -
-
-
-
- - ${this._getAudio.render({ - complete: (div) => div - })} - - ${formatSeconds(this.data.position)} ${formatSeconds(this.data.duration)} -
- -
-
- - - - - -
- - -
- ` - } - - 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) diff --git a/src/components/HorizontalScroller.js b/src/components/HorizontalScroller.js index 6a1daff..a02e13f 100644 --- a/src/components/HorizontalScroller.js +++ b/src/components/HorizontalScroller.js @@ -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; } ` diff --git a/src/components/ImageViewer.js b/src/components/ImageViewer.js index cca8ba3..e68c0ca 100644 --- a/src/components/ImageViewer.js +++ b/src/components/ImageViewer.js @@ -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` -
+
${this.imgEl} + ${this.oldEl}
- Image Description +

${this.title}

+ ${this.caption ? html`

${this.caption}

` : ''}
` @@ -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; } diff --git a/src/components/ModularPlayer.js b/src/components/ModularPlayer.js index 31b454c..d3a821d 100644 --- a/src/components/ModularPlayer.js +++ b/src/components/ModularPlayer.js @@ -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` -
+${ this.details ? + html`
${this.details.tracks ? html`

${this.details.title}

@@ -258,28 +215,30 @@ class ModularPlayer extends LitElement { complete: () => html` ${formatSeconds(this.position)} - ${!this.details.tracks ? - this._renderWave.render({ - initial: () => html``, - pending: () => html``, - complete: (ws) => html` -
-
-
-
-
- ${ws}` - }) : + ${this.details?.tracks ? html`` + : html` +
+ + +
+ ` } ${formatSeconds(this.duration)} - ` + `, + error: (err) => err })} ${this.audio} @@ -304,7 +263,7 @@ class ModularPlayer extends LitElement {
- ` + ` : ''}` } 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 { diff --git a/src/components/VerticalCard.js b/src/components/VerticalCard.js index ba45d9e..7771f1d 100644 --- a/src/components/VerticalCard.js +++ b/src/components/VerticalCard.js @@ -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`
- +