radiostasis/src/Radiostasis.ts

153 lines
4.9 KiB
TypeScript

class Radiostasis {
// audio player
private readonly player: Player;
private readonly playlist: Playlist;
// ui element
private readonly main: HTMLElement;
private lastSearch: string | null = null;
private debouncer: number | null = null;
constructor() {
this.playlist = new Playlist();
this.player = new Player(this.playlist);
this.main = getOrThrow(document.getElementsByTagName('main').item(0));
// set up htmx event handlers
document.addEventListener('htmx:historyRestore', () =>
this.wireLoadedFragment()
);
document
.getElementsByTagName('main')
.item(0)
?.addEventListener('htmx:afterSwap', () => this.wireLoadedFragment());
// set up playlist changed handler
this.playlist.addPlaylistChangedHandler(() => this.episodeStateChanged());
}
public initialize(): void {
const path =
location.pathname == '/'
? '/partial/home.html'
: `/partial/${location.pathname}.html`;
htmx.ajax('GET', path, 'main');
}
private wireLoadedFragment(): void {
// episode play and queue buttons
const episodes = this.main.getElementsByClassName('episode');
for (let i = 0; i < episodes.length; i++) {
const el = <HTMLElement>episodes.item(i);
const episode: Episode = {
id: `${el.dataset.sslug ?? ''}/${el.dataset.slug ?? ''}`,
slug: el.dataset.slug ?? '',
title: el.getAttribute('title') ?? '',
file: el.dataset.file ?? '',
length: el.dataset.length ?? '',
size: parseInt(el.dataset.size ?? '0'),
series: {
slug: el.dataset.sslug ?? '',
title: el.dataset.series ?? '',
cover: el.dataset.cover ?? '',
},
};
// store this model for later use
el.dataset.ejson = JSON.stringify(episode);
// play button
el.getElementsByTagName('a')
.item(0)
?.addEventListener('click', (e) => {
if (this.player.currentEpisode()?.id !== episode.id) {
this.player.playEpisode(episode);
}
e.preventDefault();
});
// queue button
el.getElementsByTagName('a')
.item(1)
?.addEventListener('click', (e) => {
if (!this.player.currentEpisode()) {
// if nothing is playing, first queued item gets loaded
// into the player but does not autoplay
this.player.playEpisode(episode, true);
} else if (this.player.currentEpisode()?.id !== episode.id) {
if (this.playlist.isQueued(episode)) {
this.playlist.removeEpisode(episode);
} else {
this.playlist.pushEpisode(episode);
}
}
e.preventDefault();
});
// trigger the playlist update function to mark queued episodes
this.episodeStateChanged();
}
// series filter input
const filter = <HTMLInputElement>(
this.main.getElementsByClassName('filter').item(0)
);
if (filter) {
if (this.lastSearch) {
filter.value = this.lastSearch;
this.lastSearch = null;
}
const allSeries = this.main.getElementsByTagName('section');
filter.addEventListener('input', () => {
if (this.debouncer) clearTimeout(this.debouncer);
this.debouncer = setTimeout(() => {
this.lastSearch = filter.value.toLowerCase();
const terms = this.lastSearch.split(' ');
for (let i = 0; i < allSeries.length; i++) {
const series = allSeries.item(i);
if (!series || !series.dataset.filter) continue;
let match = true;
for (const term of terms) {
if (term.length > 0 && series.dataset.filter.indexOf(term) < 0) {
match = false;
break;
}
}
if (match) series.classList.remove('no-match');
else series.classList.add('no-match');
}
}, 499);
});
}
}
private episodeStateChanged(): void {
const episodes = this.main.getElementsByClassName('episode');
for (let i = 0; i < episodes.length; i++) {
const el = <HTMLElement>episodes.item(i);
if (!el || !el.dataset.ejson) continue;
const episode = <Episode>JSON.parse(el.dataset.ejson);
if (this.player.currentEpisode()?.id === episode.id) {
el.classList.add('playing');
getOrThrow(el.getElementsByTagName('a').item(0)).innerHTML = 'Playing';
} else {
el.classList.remove('playing');
getOrThrow(el.getElementsByTagName('a').item(0)).innerHTML =
'Play Episode';
}
const queueBtn = getOrThrow(el.getElementsByTagName('a').item(1));
if (this.playlist.isQueued(episode)) {
queueBtn.classList.add('queued');
queueBtn.innerHTML = 'Remove from Queue';
} else {
queueBtn.classList.remove('queued');
queueBtn.innerHTML = 'Queue Episode';
}
}
}
}