diff --git a/web/ascoltami/Light9AscoltamiTimeline.ts b/web/ascoltami/Light9AscoltamiTimeline.ts new file mode 100644 --- /dev/null +++ b/web/ascoltami/Light9AscoltamiTimeline.ts @@ -0,0 +1,114 @@ +import { css, html, LitElement, PropertyValueMap } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import Sylvester from "sylvester"; +import { Zoom } from "../light9-timeline-audio"; +import { PlainViewState } from "../Light9CursorCanvas"; +import { getTopGraph } from "../RdfdbSyncedGraph"; +import { show } from "../show_specific"; +import { SyncedGraph } from "../SyncedGraph"; +import { PlayerState } from "./PlayerState"; +export { Light9TimelineAudio } from "../light9-timeline-audio"; +export { Light9CursorCanvas } from "../Light9CursorCanvas"; +export { RdfdbSyncedGraph } from "../RdfdbSyncedGraph"; +export { ResourceDisplay } from "../ResourceDisplay"; + +const $V = Sylvester.Vector.create; + +async function postJson(url: string, jsBody: Object) { + return fetch(url, { + method: "POST", + headers: { "Content-Type": "applcation/json" }, + body: JSON.stringify(jsBody), + }); +} + +@customElement("light9-ascoltami-timeline") +export class Light9AscoltamiTimeline extends LitElement { + static styles = [ + css` + .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%; + } + `, + ]; + graph!: SyncedGraph; + @property() playerState: PlayerState = { + duration: null, + endOfSong: null, + pausedSongTime: null, + playing: null, + song: null, + wallStartTime: null, + }; + @property() playerTime: number = 0; + @state() zoom: Zoom; + @state() overviewZoom: Zoom; + @state() viewState: PlainViewState | null = null; + constructor() { + super(); + getTopGraph().then((g) => { + this.graph = g; + }); + this.zoom = this.overviewZoom = { duration: null, t1: 0, t2: 1 }; + } + protected willUpdate(_changedProperties: PropertyValueMap): void { + super.willUpdate(_changedProperties); + if ((_changedProperties.has("playerState") || _changedProperties.has("playerTime")) && this.playerState !== null) { + const duration = this.playerState.duration; + const t = this.playerTime; + if (duration !== null) { + const timeRow = this.shadowRoot!.querySelector(".timeRow") as HTMLDivElement; + if (timeRow != null) { + this.updateZooms(duration, t, timeRow); + } + } + } + } + + updateZooms(duration: number, t: number, timeRow: HTMLDivElement) { + this.overviewZoom = { duration: duration, t1: 0, t2: duration }; + const t1 = t - 2; + const t2 = t + 20; + this.zoom = { duration: duration, t1, t2 }; + const w = timeRow.offsetWidth; + this.viewState = { + zoomSpec: { t1: () => t1, t2: () => t2 }, + cursor: { t: () => t }, + audioY: () => 0, + audioH: () => 60, + zoomedTimeY: () => 60, + zoomedTimeH: () => 40, + fullZoomX: (sec: number) => (sec / duration) * w, + zoomInX: (sec: number) => ((sec - t1) / (t2 - t1)) * w, + mouse: { pos: () => $V([0, 0]) }, + }; + } + + render() { + const song = this.playerState?.song; + if (!song) return html`(spectrogram)`; + return html` +
+
+ + + +
+ `; + } +}