radiostasis/src/Playlist.ts

148 lines
4.8 KiB
TypeScript

class Playlist {
private readonly queueContainer: HTMLElement;
private readonly queueTab: HTMLElement;
private readonly overlay: HTMLElement;
private readonly queueList: HTMLOListElement;
private readonly queueInitialHeight: string;
private readonly queueExpandedHeight = 'calc(100% - 6ex)';
// event handlers
private changedHandlers: Array<() => void> = [];
private playEpisodeHandler: ((episode: Episode) => void) | null = null;
// the actual episode queue
private readonly episodes: Array<[Episode, HTMLLIElement]> = [];
private readonly episodeHash: Set<string> = new Set<string>();
constructor() {
this.queueContainer = getOrThrow(
document.getElementById('queue-container')
);
this.queueInitialHeight = getComputedStyle(this.queueContainer).height;
this.queueTab = getOrThrow(
this.queueContainer.getElementsByTagName('h2').item(0)
);
this.queueList = getOrThrow(
this.queueContainer.getElementsByTagName('ol').item(0)
);
this.overlay = getOrThrow(document.getElementById('overlay'));
this.queueTab.addEventListener('click', () => this.toggleQueueUI());
this.overlay.addEventListener('click', () => this.toggleQueueUI());
}
public addPlaylistChangedHandler(handler: () => void): void {
this.changedHandlers.push(handler);
}
public setPlayEpisodeHandler(handler: (episode: Episode) => void): void {
this.playEpisodeHandler = handler;
}
public hasNextEpisode(): boolean {
return this.episodes.length > 0;
}
public nextEpisode(): Episode | null {
if (this.episodes.length > 0) {
return this.episodes[0][0];
}
return null;
}
public pushEpisode(episode: Episode): void {
this.pushEpisodes([episode]);
}
public pushEpisodes(episodes: Array<Episode>): void {
for (const episode of episodes) {
if (this.isQueued(episode)) this.removeEpisode(episode, true);
const litem = this.createQueueListItem(episode);
this.episodes.push([episode, litem]);
this.episodeHash.add(episode.id);
this.queueList.appendChild(litem);
}
this.playlistChanged();
}
public unshiftEpisodes(episodes: Array<Episode>): void {
for (let i = episodes.length - 1; i >= 0; i--) {
const episode = episodes[i];
if (this.isQueued(episode)) this.removeEpisode(episode, true);
const litem = this.createQueueListItem(episode);
this.episodes.unshift([episode, litem]);
this.episodeHash.add(episode.id);
this.queueList.prepend(litem);
}
this.playlistChanged();
}
public removeEpisode(episode: Episode, ignoreChanged = false): void {
if (this.isQueued(episode)) {
const idx = this.episodes.findIndex((e) => e[0].id == episode.id);
const deleted = this.episodes.splice(idx, 1);
this.episodeHash.delete(episode.id);
deleted[0][1].remove();
}
if (!ignoreChanged) this.playlistChanged();
}
public isQueued(episode: Episode): boolean {
return this.episodeHash.has(episode.id);
}
public triggerPlaylistChanged(): void {
this.playlistChanged();
}
private playlistChanged(): void {
for (const handler of this.changedHandlers) {
handler();
}
}
private toggleQueueUI(): void {
if (this.queueContainer.style.height !== this.queueExpandedHeight) {
this.queueContainer.style.height = this.queueExpandedHeight;
this.overlay.style.backgroundColor = 'rgba(255, 255, 255, 0.75)';
this.overlay.style.backdropFilter = 'blur(5px)';
this.overlay.style.pointerEvents = 'auto';
} else {
this.queueContainer.style.height = this.queueInitialHeight;
this.overlay.style.backgroundColor = 'rgba(255, 255, 255, 0)';
this.overlay.style.backdropFilter = 'none';
this.overlay.style.pointerEvents = 'none';
}
}
private createQueueListItem(episode: Episode): HTMLLIElement {
const item = document.createElement('li');
item.classList.add('episode');
item.title = episode.title;
const label = document.createElement('label');
label.innerHTML = episode.title;
const sspan = document.createElement('span');
sspan.innerHTML = episode.series.title;
const aside = document.createElement('aside');
aside.appendChild(sspan);
const controls = document.createElement('div');
controls.classList.add('controls');
const playBtn = document.createElement('a');
playBtn.href = '#';
playBtn.innerHTML = 'Play Now';
playBtn.addEventListener('click', () => {
if (this.playEpisodeHandler) this.playEpisodeHandler(episode);
});
const remBtn = document.createElement('a');
remBtn.href = '#';
remBtn.innerHTML = 'Remove';
remBtn.addEventListener('click', () => this.removeEpisode(episode));
controls.appendChild(playBtn);
controls.appendChild(remBtn);
item.appendChild(label);
item.appendChild(aside);
item.appendChild(controls);
return item;
}
}