import debug from "debug"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; import { NamedNode } from "n3"; import Sylvester from "sylvester"; import { Zoom } from "../light9-timeline-audio"; import { PlainViewState } from "../Light9CursorCanvas"; import { getTopGraph } from "../RdfdbSyncedGraph"; import { SyncedGraph } from "../SyncedGraph"; import { TimingUpdate } from "./main"; import { showRoot } from "../show_specific"; export { Light9TimelineAudio } from "../light9-timeline-audio"; export { Light9CursorCanvas } from "../Light9CursorCanvas"; export { RdfdbSyncedGraph } from "../RdfdbSyncedGraph"; export { ResourceDisplay } from "../ResourceDisplay"; const $V = Sylvester.Vector.create; debug.enable("*"); const log = debug("asco"); function byId(id: string): HTMLElement { return document.getElementById(id)!; } async function postJson(url: string, jsBody: Object) { return fetch(url, { method: "POST", headers: { "Content-Type": "applcation/json" }, body: JSON.stringify(jsBody), }); } @customElement("light9-ascoltami-ui") export class Light9AscoltamiUi extends LitElement { graph!: SyncedGraph; times!: { intro: number; post: number }; @property() nextText: string = ""; @property() isPlaying: boolean = false; @property() show: NamedNode | null = null; @property() song: NamedNode | null = null; @property() selectedSong: NamedNode | null = null; @property() currentDuration: number = 0; @property() zoom: Zoom; @property() overviewZoom: Zoom; @property() viewState: PlainViewState | null = null; static styles = [ css` :host { display: flex; flex-direction: column; } .timeRow { margin: 14px; position: relative; } #overview { height: 60px; } #zoomed { margin-top: 40px; height: 80px; } #cursor { position: absolute; left: 0; top: 0; width: 100%; height: 100%; } #grow { flex: 1 1 auto; display: flex; } #grow > span { display: flex; position: relative; width: 50%; } #playSelected { height: 100px; } #songList { overflow-y: scroll; position: absolute; left: 0; top: 0; right: 0; bottom: 0; } #songList .row { width: 60%; min-height: 40px; text-align: left; position: relative; } #songList .row:nth-child(even) { background: #333; } #songList .row:nth-child(odd) { background: #444; } #songList button { min-height: 40px; margin-bottom: 10px; } #songList .row.playing { box-shadow: 0 0 30px red; background-color: #de5050; } `, ]; render() { return html`
${this.songList.map( (song) => html` ` )}
`; } onSelectSong(song: NamedNode, ev: MouseEvent) { if (this.selectedSong && song.equals(this.selectedSong)) { this.selectedSong = null; } else { this.selectedSong = song; } } async onPlaySelected(ev: Event) { if (!this.selectedSong) { return; } await fetch("../service/ascoltami/song", { method: "POST", body: this.selectedSong.value }); } onCmdStop(ev?: MouseEvent): void { postJson("../service/ascoltami/time", { pause: true }); } onCmdPlay(ev?: MouseEvent): void { postJson("../service/ascoltami/time", { resume: true }); } onCmdIntro(ev?: MouseEvent): void { postJson("../service/ascoltami/time", { t: this.times.intro, resume: true }); } onCmdPost(ev?: MouseEvent): void { postJson("../service/ascoltami/time", { t: this.currentDuration - this.times.post, resume: true, }); } onCmdGo(ev?: MouseEvent): void { postJson("../service/ascoltami/go", {}); } bindKeys() { document.addEventListener("keypress", (ev) => { if (ev.which == 115) { this.onCmdStop(); return false; } if (ev.which == 112) { this.onCmdPlay(); return false; } if (ev.which == 105) { this.onCmdIntro(); return false; } if (ev.which == 116) { this.onCmdPost(); return false; } if (ev.key == "g") { this.onCmdGo(); return false; } return true; }); } async musicSetup() { // shoveled over from the vanillajs version const config = await (await fetch("../service/ascoltami/config")).json(); this.show = new NamedNode(config.show); this.times = config.times; document.title = document.title.replace("{{host}}", config.host); try { const h1 = document.querySelector("h1")!; h1.innerText = h1.innerText.replace("{{host}}", config.host); } catch (e) {} (window as any).finishOldStyleSetup(this.times, this.onOldStyleUpdate.bind(this)); } onOldStyleUpdate(data: TimingUpdate) { this.nextText = data.next; this.isPlaying = data.playing; this.currentDuration = data.duration; this.song = new NamedNode(data.song); this.overviewZoom = { duration: data.duration, t1: 0, t2: data.duration }; const t1 = data.t - 2, t2 = data.t + 20; this.zoom = { duration: data.duration, t1, t2 }; const timeRow = this.shadowRoot!.querySelector(".timeRow") as HTMLDivElement; const w = timeRow.offsetWidth; this.viewState = { zoomSpec: { t1: () => t1, t2: () => t2 }, cursor: { t: () => data.t }, audioY: () => 0, audioH: () => 60, zoomedTimeY: () => 60, zoomedTimeH: () => 40, fullZoomX: (sec: number) => (sec / data.duration) * w, zoomInX: (sec: number) => ((sec - t1) / (t2 - t1)) * w, mouse: { pos: () => $V([0, 0]) }, }; } @property() songList: NamedNode[] = []; constructor() { super(); this.bindKeys(); this.zoom = this.overviewZoom = { duration: null, t1: 0, t2: 1 }; getTopGraph().then((g) => { this.graph = g; this.musicSetup(); // async this.graph.runHandler(this.graphChanged.bind(this), "loadsongs"); }); } graphChanged() { this.songList = []; try { const playList = this.graph.uriValue( // this.graph.Uri(showRoot), this.graph.Uri(":playList") ); log(playList); this.songList = this.graph.items(playList) as NamedNode[]; } catch (e) { log("no playlist yet"); } log(this.songList.length); } }