Mercurial > code > home > repos > light9
view web/ascoltami/Light9AscoltamiUi.ts @ 2408:7e7874fed2e3
buttons to add panels to the layout
author | drewp@bigasterisk.com |
---|---|
date | Sat, 18 May 2024 21:02:32 -0700 |
parents | 4556eebe5d73 |
children | 06da5db2fafe |
line wrap: on
line source
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`<rdfdb-synced-graph></rdfdb-synced-graph> <link rel="stylesheet" href="../style.css" /> <!-- <h1>ascoltami <a href="metrics">[metrics]</a></h1> --> <div id="grow"> <span> <div id="songList"> <table> ${this.songList.map( (song) => html` <tr class="row ${classMap({ playing: !!(this.song && song.equals(this.song)), })}" > <td><resource-display .uri=${song} noclick></resource-display></td> <td> <button @click=${this.onSelectSong.bind(this, song)}> <span>Select</span> </button> </td> </tr> ` )} </table> </div> </span ><span> <div id="right"> <div> Selected: <resource-display .uri=${this.selectedSong}></resource-display> </div> <div> <button id="playSelected" ?disabled=${this.selectedSong === null} @click=${this.onPlaySelected}>Play selected from start</button> </div> </div> </span> </div> <div class="timeRow"> <div id="timeSlider"></div> <light9-timeline-audio id="overview" .show=${this.show} .song=${this.song} .zoom=${this.overviewZoom}> </light9-timeline-audio> <light9-timeline-audio id="zoomed" .show=${this.show} .song=${this.song} .zoom=${this.zoom}></light9-timeline-audio> <light9-cursor-canvas id="cursor" .viewState=${this.viewState}></light9-cursor-canvas> </div> <div class="commands"> <button id="cmd-stop" @click=${this.onCmdStop} class="playMode ${classMap({ active: !this.isPlaying })}"> <strong>Stop</strong> <div class="key">s</div> </button> <button id="cmd-play" @click=${this.onCmdPlay} class="playMode ${classMap({ active: this.isPlaying })}"> <strong>Play</strong> <div class="key">p</div> </button> <button id="cmd-intro" @click=${this.onCmdIntro}> <strong>Skip intro</strong> <div class="key">i</div> </button> <button id="cmd-post" @click=${this.onCmdPost}> <strong>Skip to Post</strong> <div class="key">t</div> </button> <button id="cmd-go" @click=${this.onCmdGo}> <strong>Go</strong> <div class="key">g</div> <div id="next">${this.nextText}</div> </button> </div>`; } 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); } }