comparison light9/ascoltami/Light9AscoltamiUi.ts @ 2135:197a4ec877a7

acso relayout so only the song list scrolls
author drewp@bigasterisk.com
date Mon, 06 Jun 2022 01:15:17 +0000
parents 1dbbf0db3036
children b39f6c363163
comparison
equal deleted inserted replaced
2134:40475b0ee0ba 2135:197a4ec877a7
7 import { Zoom } from "../web/light9-timeline-audio"; 7 import { Zoom } from "../web/light9-timeline-audio";
8 import { PlainViewState } from "../web/Light9CursorCanvas"; 8 import { PlainViewState } from "../web/Light9CursorCanvas";
9 import { getTopGraph } from "../web/RdfdbSyncedGraph"; 9 import { getTopGraph } from "../web/RdfdbSyncedGraph";
10 import { SyncedGraph } from "../web/SyncedGraph"; 10 import { SyncedGraph } from "../web/SyncedGraph";
11 import { TimingUpdate } from "./main"; 11 import { TimingUpdate } from "./main";
12 import { ResourceDisplay } from '../web/ResourceDisplay';
13 export { Light9TimelineAudio } from "../web/light9-timeline-audio"; 12 export { Light9TimelineAudio } from "../web/light9-timeline-audio";
14 export { Light9CursorCanvas } from "../web/Light9CursorCanvas"; 13 export { Light9CursorCanvas } from "../web/Light9CursorCanvas";
15 export { RdfdbSyncedGraph } from "../web/RdfdbSyncedGraph"; 14 export { RdfdbSyncedGraph } from "../web/RdfdbSyncedGraph";
16 export { ResourceDisplay } from "../web/ResourceDisplay"; 15 export { ResourceDisplay } from "../web/ResourceDisplay";
17 const $V = Sylvester.Vector.create; 16 const $V = Sylvester.Vector.create;
35 times!: { intro: number; post: number }; 34 times!: { intro: number; post: number };
36 @property() nextText: string = ""; 35 @property() nextText: string = "";
37 @property() isPlaying: boolean = false; 36 @property() isPlaying: boolean = false;
38 @property() show: NamedNode | null = null; 37 @property() show: NamedNode | null = null;
39 @property() song: NamedNode | null = null; 38 @property() song: NamedNode | null = null;
40 @property() requestedSong: NamedNode | null = null; 39 @property() selectedSong: NamedNode | null = null;
41 @property() currentDuration: number = 0; 40 @property() currentDuration: number = 0;
42 @property() zoom: Zoom; 41 @property() zoom: Zoom;
43 @property() overviewZoom: Zoom; 42 @property() overviewZoom: Zoom;
44 @property() viewState: PlainViewState | null = null; 43 @property() viewState: PlainViewState | null = null;
45 static styles = [ 44 static styles = [
46 css` 45 css`
46 :host {
47 display: flex;
48 flex-direction: column;
49 }
47 .timeRow { 50 .timeRow {
48 margin: 14px; 51 margin: 14px;
49 position: relative; 52 position: relative;
50 } 53 }
51 #overview { 54 #overview {
60 left: 0; 63 left: 0;
61 top: 0; 64 top: 0;
62 width: 100%; 65 width: 100%;
63 height: 100%; 66 height: 100%;
64 } 67 }
68 #grow {
69
70 flex: 1 1 auto;
71 display: flex;
72
73 }
74 #grow > span {
75 display: flex;
76 position: relative;
77 width:50%;
78 }
79 #playSelected {
80 height:100px;
81 }
82 #songList {
83 overflow-y: scroll;
84 position: absolute;
85 left:0;top:0;right:0;bottom:0;
86 }
87 #songList .row {
88 width: 60%;
89 min-height: 40px;
90 text-align: left;
91 position: relative;
92 }
93 #songList .row:nth-child(even) {background: #333;}
94 #songList .row:nth-child(odd) {background: #444;}
95 #songList button {
96 min-height: 40px;
97 margin-bottom: 10px;
98 }
99 #songList .row.playing {
100 box-shadow: 0 0 30px red;
101 background-color: #de5050;
102 }
65 `, 103 `,
66 ]; 104 ];
67 render() { 105 render() {
68 return html`<rdfdb-synced-graph></rdfdb-synced-graph> 106 return html`<rdfdb-synced-graph></rdfdb-synced-graph>
69 107
70 <link rel="stylesheet" href="./style.css" /> 108 <link rel="stylesheet" href="./style.css" />
71 109
72 <!-- <h1>ascoltami <a href="metrics">[metrics]</a></h1> --> 110 <!-- <h1>ascoltami <a href="metrics">[metrics]</a></h1> -->
73 <!-- <div> 111
74 songlist: 112 <div id="grow">
75 ${this.songList.map((song) => html` 113 <span>
76 <div>song: <resource-display .uri=${song}></resource-display> 114 <div id="songList">
77 </div> 115 <table>
78 `)} 116 ${this.songList.map(
79 </div> --> 117 (song) => html`
80 <div class="timeRow"> 118 <tr
81 <div id="timeSlider"></div> 119 class="row ${classMap({
82 <light9-timeline-audio id="overview" .show=${this.show} .song=${this.song} .zoom=${this.overviewZoom}> 120 playing: !!(this.song && song.equals(this.song)),
83 </light9-timeline-audio> 121 })}"
84 <light9-timeline-audio id="zoomed" .show=${this.show} .song=${this.song} .zoom=${this.zoom}></light9-timeline-audio> 122 >
85 <light9-cursor-canvas id="cursor" .viewState=${this.viewState}></light9-cursor-canvas> 123 <td><resource-display .uri=${song}></resource-display></td>
86 </div> 124 <td>
87 125 <button @click=${this.onSelectSong.bind(this, song)}>
88 <div class="commands"> 126 <span>Select</span>
89 <button id="cmd-stop" @click=${this.onCmdStop} class="playMode ${classMap({ active: !this.isPlaying })}"> 127 </button>
90 <strong>Stop</strong> 128 </td>
91 <div class="key">s</div> 129 </tr>
92 </button> 130 `
93 <button id="cmd-play" @click=${this.onCmdPlay} class="playMode ${classMap({ active: this.isPlaying })}"> 131 )}
94 <strong>Play</strong> 132 </table>
95 <div class="key">p</div> 133 </div>
96 </button> 134 </span><span>
97 <button id="cmd-intro" @click=${this.onCmdIntro}> 135
98 <strong>Skip intro</strong> 136 <div id="right">
99 <div class="key">i</div> 137 <div>
100 </button> 138 Selected:
101 <button id="cmd-post" @click=${this.onCmdPost}> 139 <resource-display .uri=${this.selectedSong}></resource-display>
102 <strong>Skip to Post</strong> 140 </div>
103 <div class="key">t</div> 141 <div>
104 </button> 142 <button id="playSelected"
105 <button id="cmd-go" @click=${this.onCmdGo}> 143 ?disabled=${this.selectedSong === null}
106 <strong>Go</strong> 144 @click=${this.onPlaySelected}
107 <div class="key">g</div> 145 >
108 <div id="next">${this.nextText}</div> 146 Play selected from start
109 </button> 147 </button>
110 </div>`; 148 </div>
149 </div>
150 </span>
151 </div>
152
153
154 <div class="timeRow">
155 <div id="timeSlider"></div>
156 <light9-timeline-audio
157 id="overview"
158 .show=${this.show}
159 .song=${this.song}
160 .zoom=${this.overviewZoom}
161 >
162 </light9-timeline-audio>
163 <light9-timeline-audio
164 id="zoomed"
165 .show=${this.show}
166 .song=${this.song}
167 .zoom=${this.zoom}
168 ></light9-timeline-audio>
169 <light9-cursor-canvas
170 id="cursor"
171 .viewState=${this.viewState}
172 ></light9-cursor-canvas>
173 </div>
174
175 <div class="commands">
176 <button
177 id="cmd-stop"
178 @click=${this.onCmdStop}
179 class="playMode ${classMap({ active: !this.isPlaying })}"
180 >
181 <strong>Stop</strong>
182 <div class="key">s</div>
183 </button>
184 <button
185 id="cmd-play"
186 @click=${this.onCmdPlay}
187 class="playMode ${classMap({ active: this.isPlaying })}"
188 >
189 <strong>Play</strong>
190 <div class="key">p</div>
191 </button>
192 <button id="cmd-intro" @click=${this.onCmdIntro}>
193 <strong>Skip intro</strong>
194 <div class="key">i</div>
195 </button>
196 <button id="cmd-post" @click=${this.onCmdPost}>
197 <strong>Skip to Post</strong>
198 <div class="key">t</div>
199 </button>
200 <button id="cmd-go" @click=${this.onCmdGo}>
201 <strong>Go</strong>
202 <div class="key">g</div>
203 <div id="next">${this.nextText}</div>
204 </button>
205 </div>`;
206 }
207
208 onSelectSong(song: NamedNode, ev: MouseEvent) {
209 if (this.selectedSong && song.equals(this.selectedSong)) {
210 this.selectedSong = null;
211 } else {
212 this.selectedSong = song;
213 }
214 }
215 async onPlaySelected(ev: Event) {
216 if (!this.selectedSong) {
217 return;
218 }
219 await fetch("api/song", { method: "POST", body: this.selectedSong.value });
111 } 220 }
112 221
113 onCmdStop(ev?: MouseEvent): void { 222 onCmdStop(ev?: MouseEvent): void {
114 postJson("api/time", { pause: true }); 223 postJson("api/time", { pause: true });
115 } 224 }
118 } 227 }
119 onCmdIntro(ev?: MouseEvent): void { 228 onCmdIntro(ev?: MouseEvent): void {
120 postJson("api/time", { t: this.times.intro, resume: true }); 229 postJson("api/time", { t: this.times.intro, resume: true });
121 } 230 }
122 onCmdPost(ev?: MouseEvent): void { 231 onCmdPost(ev?: MouseEvent): void {
123 postJson("api/time", { t: this.currentDuration - this.times.post, resume: true }); 232 postJson("api/time", {
233 t: this.currentDuration - this.times.post,
234 resume: true,
235 });
124 } 236 }
125 onCmdGo(ev?: MouseEvent): void { 237 onCmdGo(ev?: MouseEvent): void {
126 postJson("api/go", {}); 238 postJson("api/go", {});
127 } 239 }
128 240
160 this.times = config.times; 272 this.times = config.times;
161 document.title = document.title.replace("{{host}}", config.host); 273 document.title = document.title.replace("{{host}}", config.host);
162 try { 274 try {
163 const h1 = document.querySelector("h1")!; 275 const h1 = document.querySelector("h1")!;
164 h1.innerText = h1.innerText.replace("{{host}}", config.host); 276 h1.innerText = h1.innerText.replace("{{host}}", config.host);
165 } catch (e) { 277 } catch (e) {}
166
167 }
168 byId("nav").innerText = navigator.userAgent; 278 byId("nav").innerText = navigator.userAgent;
169 var updateFreq = navigator.userAgent.indexOf("Linux") != -1 ? 10 : 2; 279 var updateFreq = navigator.userAgent.indexOf("Linux") != -1 ? 10 : 2;
170 if (navigator.userAgent.match(/Windows NT/)) { 280 if (navigator.userAgent.match(/Windows NT/)) {
171 // helper laptop 281 // helper laptop
172 updateFreq = 10; 282 updateFreq = 10;
173 } 283 }
174 byId("updateReq").innerText = "" + updateFreq; 284 byId("updateReq").innerText = "" + updateFreq;
175 285
176 (window as any).finishOldStyleSetup(this.times, updateFreq, (data: TimingUpdate) => { 286 (window as any).finishOldStyleSetup(
177 this.nextText = data.next; 287 this.times,
178 this.isPlaying = data.playing; 288 updateFreq,
179 this.currentDuration = data.duration; 289 this.onOldStyleUpdate.bind(this)
180 this.song = new NamedNode(data.song); 290 );
181 this.overviewZoom = { duration: data.duration, t1: 0, t2: data.duration }; 291 }
182 const t1 = data.t - 2, 292
183 t2 = data.t + 20; 293 onOldStyleUpdate(data: TimingUpdate) {
184 this.zoom = { duration: data.duration, t1, t2 }; 294 this.nextText = data.next;
185 const timeRow = this.shadowRoot!.querySelector(".timeRow") as HTMLDivElement; 295 this.isPlaying = data.playing;
186 const w = timeRow.offsetWidth; 296 this.currentDuration = data.duration;
187 this.viewState = { 297 this.song = new NamedNode(data.song);
188 zoomSpec: { t1: () => t1, t2: () => t2 }, 298 this.overviewZoom = { duration: data.duration, t1: 0, t2: data.duration };
189 cursor: { t: () => data.t }, 299 const t1 = data.t - 2,
190 audioY: () => 0, 300 t2 = data.t + 20;
191 audioH: () => 60, 301 this.zoom = { duration: data.duration, t1, t2 };
192 zoomedTimeY: () => 60, 302 const timeRow = this.shadowRoot!.querySelector(
193 zoomedTimeH: () => 40, 303 ".timeRow"
194 fullZoomX: (sec: number) => (sec / data.duration) * w, 304 ) as HTMLDivElement;
195 zoomInX: (sec: number) => ((sec - t1) / (t2 - t1)) * w, 305 const w = timeRow.offsetWidth;
196 mouse: { pos: () => $V([0, 0]) }, 306 this.viewState = {
197 }; 307 zoomSpec: { t1: () => t1, t2: () => t2 },
198 }); 308 cursor: { t: () => data.t },
199 } 309 audioY: () => 0,
200 @property() songList: NamedNode[] = [] 310 audioH: () => 60,
311 zoomedTimeY: () => 60,
312 zoomedTimeH: () => 40,
313 fullZoomX: (sec: number) => (sec / data.duration) * w,
314 zoomInX: (sec: number) => ((sec - t1) / (t2 - t1)) * w,
315 mouse: { pos: () => $V([0, 0]) },
316 };
317 }
318
319 @property() songList: NamedNode[] = [];
201 constructor() { 320 constructor() {
202 super(); 321 super();
203 this.bindKeys(); 322 this.bindKeys();
204 this.zoom = this.overviewZoom = { duration: null, t1: 0, t2: 1 }; 323 this.zoom = this.overviewZoom = { duration: null, t1: 0, t2: 1 };
205 324
206 getTopGraph().then((g) => { 325 getTopGraph().then((g) => {
207 this.graph = g; 326 this.graph = g;
208 this.musicSetup(); // async 327 this.musicSetup(); // async
209 this.graph.runHandler(this.graphChanged.bind(this), 'loadsongs'); 328 this.graph.runHandler(this.graphChanged.bind(this), "loadsongs");
210 }); 329 });
211 } 330 }
212 graphChanged() { 331 graphChanged() {
213 this.songList = [] 332 this.songList = [];
214 try { 333 try {
215 const playList = this.graph.uriValue(// 334 const playList = this.graph.uriValue(
216 this.graph.Uri('http://light9.bigasterisk.com/show/dance2022'), 335 //
217 this.graph.Uri(':playList')); 336 this.graph.Uri("http://light9.bigasterisk.com/show/dance2022"),
218 log(playList) 337 this.graph.Uri(":playList")
338 );
339 log(playList);
219 this.songList = this.graph.items(playList) as NamedNode[]; 340 this.songList = this.graph.items(playList) as NamedNode[];
220 } catch (e) { 341 } catch (e) {
221 log('no playlist yet') 342 log("no playlist yet");
222 } 343 }
223 log(this.songList.length); 344 log(this.songList.length);
224
225 } 345 }
226 } 346 }